summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.classpath1
-rw-r--r--.gitignore12
-rw-r--r--.gitmodules8
-rw-r--r--.tx/config34
-rw-r--r--AndroidManifest.xml138
-rw-r--r--CHANGELOG.md8
-rw-r--r--CONTRIBUTORS6
-rw-r--r--assets/about.html4
-rw-r--r--build.gradle56
-rw-r--r--pom.xml485
-rw-r--r--proguard.cfg15
-rw-r--r--project.properties2
-rw-r--r--res/layout-large/feedlist.xml3
-rw-r--r--res/layout/feeditemlist.xml3
-rw-r--r--res/layout/feedlist.xml4
-rw-r--r--res/layout/listview_activity.xml12
-rw-r--r--res/layout/miroguide_category.xml10
-rw-r--r--res/layout/organize_queue.xml2
-rw-r--r--res/menu/main.xml46
-rw-r--r--res/values-az/strings.xml231
-rw-r--r--res/values-ca/strings.xml27
-rw-r--r--res/values-cs-rCZ/strings.xml231
-rw-r--r--res/values-da/strings.xml9
-rw-r--r--res/values-de/strings.xml8
-rw-r--r--res/values-es/strings.xml13
-rw-r--r--res/values-fr/strings.xml9
-rw-r--r--res/values-it-rIT/strings.xml21
-rw-r--r--res/values-land/styles.xml2
-rw-r--r--res/values-pt/strings.xml231
-rw-r--r--res/values-ro-rRO/strings.xml9
-rw-r--r--res/values-ru/strings.xml99
-rw-r--r--res/values-uk-rUA/strings.xml29
-rw-r--r--res/values-zh-rCN/strings.xml39
-rw-r--r--res/values/strings.xml5
-rw-r--r--res/values/styles.xml26
-rw-r--r--res/xml/searchable.xml7
-rw-r--r--settings.gradle1
-rw-r--r--src/de/danoeh/antennapod/PodcastApp.java3
-rw-r--r--src/de/danoeh/antennapod/activity/AboutActivity.java4
-rw-r--r--src/de/danoeh/antennapod/activity/AddFeedActivity.java28
-rw-r--r--src/de/danoeh/antennapod/activity/AudioplayerActivity.java12
-rw-r--r--src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java11
-rw-r--r--src/de/danoeh/antennapod/activity/DownloadActivity.java451
-rw-r--r--src/de/danoeh/antennapod/activity/DownloadLogActivity.java76
-rw-r--r--src/de/danoeh/antennapod/activity/FeedInfoActivity.java104
-rw-r--r--src/de/danoeh/antennapod/activity/FeedItemlistActivity.java312
-rw-r--r--src/de/danoeh/antennapod/activity/FlattrAuthActivity.java9
-rw-r--r--src/de/danoeh/antennapod/activity/ItemviewActivity.java129
-rw-r--r--src/de/danoeh/antennapod/activity/MainActivity.java62
-rw-r--r--src/de/danoeh/antennapod/activity/MediaplayerActivity.java19
-rw-r--r--src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java13
-rw-r--r--src/de/danoeh/antennapod/activity/MiroGuideChannelViewActivity.java309
-rw-r--r--src/de/danoeh/antennapod/activity/MiroGuideMainActivity.java277
-rw-r--r--src/de/danoeh/antennapod/activity/MiroGuideSearchActivity.java126
-rw-r--r--src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java47
-rw-r--r--src/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java219
-rw-r--r--src/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java5
-rw-r--r--src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java5
-rw-r--r--src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java112
-rw-r--r--src/de/danoeh/antennapod/activity/PlaybackHistoryActivity.java18
-rw-r--r--src/de/danoeh/antennapod/activity/PreferenceActivity.java689
-rw-r--r--src/de/danoeh/antennapod/activity/SearchActivity.java332
-rw-r--r--src/de/danoeh/antennapod/activity/StorageErrorActivity.java5
-rw-r--r--src/de/danoeh/antennapod/activity/VideoplayerActivity.java7
-rw-r--r--src/de/danoeh/antennapod/adapter/DownloadLogAdapter.java21
-rw-r--r--src/de/danoeh/antennapod/adapter/DownloadlistAdapter.java39
-rw-r--r--src/de/danoeh/antennapod/adapter/ExternalEpisodesListAdapter.java31
-rw-r--r--src/de/danoeh/antennapod/adapter/FeedlistAdapter.java94
-rw-r--r--src/de/danoeh/antennapod/adapter/InternalFeedItemlistAdapter.java11
-rw-r--r--src/de/danoeh/antennapod/asynctask/DownloadStatus.java198
-rw-r--r--src/de/danoeh/antennapod/asynctask/FeedRemover.java15
-rw-r--r--src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java5
-rw-r--r--src/de/danoeh/antennapod/feed/EventDistributor.java2
-rw-r--r--src/de/danoeh/antennapod/feed/Feed.java679
-rw-r--r--src/de/danoeh/antennapod/feed/FeedImage.java2
-rw-r--r--src/de/danoeh/antennapod/feed/FeedItem.java531
-rw-r--r--src/de/danoeh/antennapod/feed/FeedManager.java2014
-rw-r--r--src/de/danoeh/antennapod/feed/FeedMedia.java724
-rw-r--r--src/de/danoeh/antennapod/feed/FeedSearcher.java253
-rw-r--r--src/de/danoeh/antennapod/feed/SearchResult.java3
-rw-r--r--src/de/danoeh/antennapod/fragment/CoverFragment.java5
-rw-r--r--src/de/danoeh/antennapod/fragment/EpisodesFragment.java115
-rw-r--r--src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java5
-rw-r--r--src/de/danoeh/antennapod/fragment/FeedlistFragment.java479
-rw-r--r--src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java849
-rw-r--r--src/de/danoeh/antennapod/fragment/ItemlistFragment.java151
-rw-r--r--src/de/danoeh/antennapod/fragment/MiroGuideChannellistFragment.java5
-rw-r--r--src/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java90
-rw-r--r--src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java2
-rw-r--r--src/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java4
-rw-r--r--src/de/danoeh/antennapod/receiver/FeedUpdateReceiver.java4
-rw-r--r--src/de/danoeh/antennapod/service/PlaybackService.java1681
-rw-r--r--src/de/danoeh/antennapod/service/download/DownloadRequest.java177
-rw-r--r--src/de/danoeh/antennapod/service/download/DownloadService.java1772
-rw-r--r--src/de/danoeh/antennapod/service/download/DownloadStatus.java181
-rw-r--r--src/de/danoeh/antennapod/service/download/Downloader.java51
-rw-r--r--src/de/danoeh/antennapod/service/download/HttpDownloader.java57
-rw-r--r--src/de/danoeh/antennapod/storage/DBReader.java755
-rw-r--r--src/de/danoeh/antennapod/storage/DBTasks.java731
-rw-r--r--src/de/danoeh/antennapod/storage/DBWriter.java727
-rw-r--r--src/de/danoeh/antennapod/storage/DownloadRequester.java566
-rw-r--r--src/de/danoeh/antennapod/storage/FeedItemStatistics.java42
-rw-r--r--src/de/danoeh/antennapod/storage/FeedSearcher.java57
-rw-r--r--src/de/danoeh/antennapod/storage/PodDBAdapter.java1823
-rw-r--r--src/de/danoeh/antennapod/util/ConnectionTester.java6
-rw-r--r--src/de/danoeh/antennapod/util/DownloadError.java86
-rw-r--r--src/de/danoeh/antennapod/util/QueueAccess.java93
-rw-r--r--src/de/danoeh/antennapod/util/ShownotesProvider.java17
-rw-r--r--src/de/danoeh/antennapod/util/UndoBarController.java14
-rw-r--r--src/de/danoeh/antennapod/util/comparator/DownloadStatusComparator.java2
-rw-r--r--src/de/danoeh/antennapod/util/id3reader/ChapterReader.java36
-rw-r--r--src/de/danoeh/antennapod/util/id3reader/ID3Reader.java89
-rw-r--r--src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java5
-rw-r--r--src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java32
-rw-r--r--src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java15
-rw-r--r--src/de/danoeh/antennapod/util/playback/ExternalMedia.java10
-rw-r--r--src/de/danoeh/antennapod/util/playback/Playable.java513
-rw-r--r--src/de/danoeh/antennapod/util/playback/PlaybackController.java1375
-rw-r--r--src/instrumentationTest/de/test/antennapod/AntennaPodTestRunner.java18
-rw-r--r--src/instrumentationTest/de/test/antennapod/service/download/HttpDownloaderTest.java144
-rw-r--r--src/instrumentationTest/de/test/antennapod/storage/DBReaderTest.java11
-rw-r--r--src/instrumentationTest/de/test/antennapod/storage/DBTasksTest.java9
-rw-r--r--src/instrumentationTest/de/test/antennapod/storage/DBWriterTest.java442
-rw-r--r--src/instrumentationTest/de/test/antennapod/syndication/handler/FeedHandlerTest.java (renamed from tests/src/de/danoeh/antennapod/test/FeedHandlerTest.java)26
-rw-r--r--src/instrumentationTest/de/test/antennapod/syndication/handler/TestFeeds.java (renamed from tests/src/de/danoeh/antennapod/test/TestFeeds.java)2
-rw-r--r--src/instrumentationTest/de/test/antennapod/util/FilenameGeneratorTest.java (renamed from tests/src/de/danoeh/antennapod/test/FilenameGeneratorTest.java)6
m---------submodules/ActionBarSherlock0
m---------submodules/ViewPagerIndicator0
-rw-r--r--tests/.classpath1
-rw-r--r--tests/src/de/danoeh/antennapod/test/HttpDownloaderTest.java70
-rw-r--r--tests/src/de/danoeh/antennapod/test/TestDownloads.java36
131 files changed, 12986 insertions, 10253 deletions
diff --git a/.classpath b/.classpath
index 3f9691c5d..3a0c88fe4 100644
--- a/.classpath
+++ b/.classpath
@@ -4,5 +4,6 @@
<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
+ <classpathentry kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>
diff --git a/.gitignore b/.gitignore
index c3d0e23ef..cbf04e1bd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,9 +12,12 @@
bin/
gen/
target/
+build/
# Local configuration file (sdk path, etc)
local.properties
+gradle.properties
+.gradle
build.xml
# Backup files
@@ -23,6 +26,15 @@ build.xml
#eclipse project files
.metadata
.settings
+#IntelliJ project files
+.idea
+*.iml
+gen-external-apklibs
+out
+#transifex downloads
+changelog
+description
+
# other
*.odg#
proguard
diff --git a/.gitmodules b/.gitmodules
index cd43a243e..17dac28b0 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,9 +1,3 @@
-[submodule "submodules/ActionBarSherlock"]
- path = submodules/ActionBarSherlock
- url = git://github.com/JakeWharton/ActionBarSherlock.git
-[submodule "submodules/ViewPagerIndicator"]
- path = submodules/ViewPagerIndicator
- url = git://github.com/JakeWharton/Android-ViewPagerIndicator.git
[submodule "submodules/dslv"]
path = submodules/dslv
- url = git://github.com/bauerca/drag-sort-listview.git
+ url = git://github.com/danieloeh/drag-sort-listview.git
diff --git a/.tx/config b/.tx/config
new file mode 100644
index 000000000..3a05b8725
--- /dev/null
+++ b/.tx/config
@@ -0,0 +1,34 @@
+[main]
+host = https://www.transifex.com
+
+[antennapod.english]
+source_file = res/values/strings.xml
+source_lang = en
+trans.az = res/values-az/strings.xml
+trans.ca = res/values-ca/strings.xml
+trans.cs_CZ = res/values-cs-rCZ/strings.xml
+trans.da = res/values-da/strings.xml
+trans.de = res/values-de/strings.xml
+trans.es = res/values-es/strings.xml
+trans.es_ES = res/values-es-rES/strings.xml
+trans.fr = res/values-fr/strings.xml
+trans.it_IT = res/values-it-rIT/strings.xml
+trans.pt = res/values-pt/strings.xml
+trans.pt_BR = res/values-pt-rBR/strings.xml
+trans.ro_RO = res/values-ro-rRO/strings.xml
+trans.ru = res/values-ru/strings.xml
+trans.ru-RU = res/values-ru/strings.xml
+trans.ru_RU = res/values-ru/strings.xml
+trans.uk_UA = res/values-uk-rUA/strings.xml
+trans.zh_CN = res/values-zh-rCN/strings.xml
+
+[antennapod.description]
+file_filter = description/<lang>.txt
+source_file = description/en.txt
+source_lang = en
+
+[antennapod.changelog]
+file_filter = changelog/<lang>.md
+source_file = CHANGELOG.md
+source_lang = en
+
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index fae6b862d..ed441e090 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.danoeh.antennapod"
- android:versionCode="30"
- android:versionName="0.9.7.3" >
+ android:versionCode="31"
+ android:versionName="0.9.7.4" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-sdk
android:minSdkVersion="10"
- android:targetSdkVersion="17" />
+ android:targetSdkVersion="18" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@@ -40,15 +40,16 @@
android:name=".activity.MainActivity"
android:configChanges="keyboardHidden|orientation"
android:label="@string/app_name" >
+ <meta-data
+ android:name="android.app.default_searchable"
+ android:value="de.danoeh.antennapod.activity.SearchActivity" />
+ <meta-data
+ android:name="android.app.searchable"
+ android:resource="@xml/searchable" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
-
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
-
- <meta-data
- android:name="android.app.default_searchable"
- android:value=".activity.SearchActivity" />
</activity>
<activity
android:name="de.danoeh.antennapod.activity.AddFeedActivity"
@@ -56,6 +57,37 @@
android:label="@string/add_new_feed_label"
android:windowSoftInputMode="adjustResize" >
<intent-filter>
+ <action android:name="android.intent.action.VIEW"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.BROWSABLE"/>
+ <data android:scheme="http"/>
+ <data android:host="*"/>
+ <data android:pathPattern=".*\\.xml"/>
+ <data android:pathPattern=".*\\.rss"/>
+ </intent-filter>
+
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.BROWSABLE"/>
+ <data android:scheme="http"/>
+ <data android:host="feeds.feedburner.com"/>
+ <data android:host="feedproxy.google.com"/>
+ <data android:host="feeds2.feedburner.com"/>
+ <data android:host="feedsproxy.google.com"/>
+ </intent-filter>
+
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.BROWSABLE"/>
+ <data android:scheme="http"/>
+ <data android:mimeType="text/xml"/>
+ <data android:mimeType="application/rss+xml"/>
+ <data android:mimeType="application/atom+xml"/>
+ <data android:mimeType="application/xml"/>
+ </intent-filter>
+ <intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
@@ -68,7 +100,10 @@
android:configChanges="orientation|screenSize" >
<meta-data
android:name="android.app.default_searchable"
- android:value=".activity.SearchActivity" />
+ android:value="de.danoeh.antennapod.activity.SearchActivity" />
+ <meta-data
+ android:name="android.app.searchable"
+ android:resource="@xml/searchable" />
</activity>
<activity
android:name="de.danoeh.antennapod.activity.ItemviewActivity"
@@ -192,67 +227,9 @@
<data
android:host="*"
- android:pathPattern=".*\\.xml"
- android:scheme="https" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.VIEW" />
-
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.BROWSABLE" />
-
- <data
- android:host="*"
- android:pathPattern=".*\\.xml"
- android:scheme="http" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.VIEW" />
-
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.BROWSABLE" />
-
- <data
- android:pathPattern=".*\\.xml"
- android:scheme="file" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.VIEW" />
-
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.BROWSABLE" />
-
- <data android:mimeType="application/xml" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.VIEW" />
-
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.BROWSABLE" />
-
- <data android:mimeType="text/xml" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.VIEW" />
-
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.BROWSABLE" />
-
- <data
- android:host="*"
- android:pathPattern=".*\\.opml"
- android:scheme="https" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.VIEW" />
-
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.BROWSABLE" />
-
- <data
- android:host="*"
+ android:mimeType="*/*"
android:pathPattern=".*\\.opml"
- android:scheme="http" />
+ android:scheme="file" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
@@ -262,17 +239,9 @@
<data
android:host="*"
- android:mimeType="*/*"
android:pathPattern=".*\\.opml"
- android:scheme="file" />
- </intent-filter>
- <intent-filter>
- <action android:name="android.intent.action.VIEW" />
-
- <category android:name="android.intent.category.DEFAULT" />
- <category android:name="android.intent.category.BROWSABLE" />
-
- <data android:mimeType="text/x-opml" />
+ android:scheme="file"
+ android:mimeType="text/x-opml" />
</intent-filter>
</activity>
<activity
@@ -291,16 +260,16 @@
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
- <meta-data
- android:name="android.app.default_searchable"
- android:value=".activity.SearchActivity" />
</activity>
<activity
android:name=".activity.MiroGuideMainActivity"
android:label="@string/miro_guide_label" >
<meta-data
android:name="android.app.default_searchable"
- android:value=".activity.MiroGuideSearchActivity" />
+ android:value="de.danoeh.antennapod.activity.MiroGuideSearchActivity" />
+ <meta-data
+ android:name="android.app.searchable"
+ android:resource="@xml/miroguide_searchable" />
</activity>
<activity
android:name=".activity.MiroGuideSearchActivity"
@@ -313,9 +282,6 @@
<meta-data
android:name="android.app.searchable"
android:resource="@xml/miroguide_searchable" />
- <meta-data
- android:name="android.app.default_searchable"
- android:value=".activity.MiroGuideSearchActivity" />
</activity>
<activity
android:name=".activity.MiroGuideCategoryActivity"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5c2d376d1..8e9b28fb2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,14 @@
Change Log
==========
+Version 0.9.7.4
+---------------
+* Episode cache size can now be set to unlimited
+* Removing an episode in the queue via sliding can now be undone
+* Added support for Links in MP3 chapters
+* Added Czech(Czech Republic), Azerbaijani and Portuguese translations
+* Several bugfixes and improvements
+
Version 0.9.7.3
---------------
* Bluetooth devices now display metadata during playback (requires AVRCP 1.3 or higher)
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index 887bea94d..2f51914fe 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -7,6 +7,7 @@ ortylp
LatinSuD
wseemann
hzulla
+andrewgaul
Translations:
@@ -20,4 +21,7 @@ Spanish: frandavid100, Fitoschido, dvd1985
Spanish (Spain): e2jk, dvd1985, frandavid100
Ukrainian (Ukraine): zhenya97, older
French: lacouture, e2jk
-Italian (Italy): m.chinni \ No newline at end of file
+Italian (Italy): m.chinni
+Czech(Czech Republic): elich
+Azerbaijani: phoenixar
+Portuguese: smarquespt \ No newline at end of file
diff --git a/assets/about.html b/assets/about.html
index e0c972682..84d94e39c 100644
--- a/assets/about.html
+++ b/assets/about.html
@@ -40,7 +40,7 @@
<body>
<div id="header" align="center">
<img src="logo.png" alt="Logo" width="100px" height="100px"/>
- <p>AntennaPod, Version 0.9.7.3</p>
+ <p>AntennaPod, Version 0.9.7.4</p>
<p>Copyright © 2012 Daniel Oeh</p>
<p>Licensed under the MIT License <a href="LICENSE.html">(View)</a></p>
</div>
@@ -48,7 +48,7 @@
<h2>ActionBarSherlock <a href="http://actionbarsherlock.com" title="Link">(Link)</a></h2>
by Jake Wharton, licensed under the Apache 2.0 license
- <h2>Android-ViewPagerIndicator <a href="http://viewpagerindicator.com">(Link)</a></h2>
+ <h2>NineOldAndroids <a href="http://nineoldandroids.com">(Link)</a></h2>
by Jake Wharton, licensed under the Apache 2.0 license
<h2>Apache Commons <a href="http://commons.apache.org/">(Link)</a></h2>
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 000000000..c2167a74e
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,56 @@
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:0.5.6'
+ }
+}
+apply plugin: 'android'
+
+repositories {
+ mavenCentral()
+}
+dependencies {
+ compile 'com.android.support:appcompat-v7:18.0.+'
+ compile 'org.apache.commons:commons-lang3:3.1'
+ compile ('org.shredzone.flattr4j:flattr4j-core:2.4') {
+ exclude group: 'org.apache.httpcomponents', module: 'httpcore'
+ exclude group: 'org.apache.httpcomponents', module: 'httpclient'
+ exclude group: 'org.json', module: 'json'
+ }
+ compile 'commons-io:commons-io:2.4'
+ compile 'com.nineoldandroids:library:2.4.0'
+ compile project(':submodules:dslv:library')
+ compile files('libs/presto_client-0.8.5.jar')
+}
+
+android {
+ compileSdkVersion 18
+ buildToolsVersion "17"
+
+ defaultConfig {
+ minSdkVersion 10
+ targetSdkVersion 18
+ testPackageName "de.test.antennapod"
+ testInstrumentationRunner "instrumentationTest.de.test.antennapod.AntennaPodTestRunner"
+ }
+
+ sourceSets {
+ main {
+ manifest.srcFile 'AndroidManifest.xml'
+ java.srcDirs = ['src']
+ resources.srcDirs = ['src']
+ aidl.srcDirs = ['src']
+ renderscript.srcDirs = ['src']
+ res.srcDirs = ['res']
+ assets.srcDirs = ['assets']
+ }
+ }
+
+ buildTypes {
+ debug {
+ packageNameSuffix ".debug"
+ }
+ }
+}
diff --git a/pom.xml b/pom.xml
index 5e574d898..c14e309cf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,75 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>de.danoeh</groupId>
- <artifactId>antennapod</artifactId>
- <packaging>apk</packaging>
- <version>0.9.7.3</version>
- <name>AntennaPod</name>
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>de.danoeh</groupId>
+ <artifactId>antennapod</artifactId>
+ <packaging>apk</packaging>
+ <version>0.9.7.4</version>
+ <name>AntennaPod</name>
-
- <dependencies>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-lang3</artifactId>
- <version>3.1</version>
- </dependency>
- <dependency>
- <groupId>org.shredzone.flattr4j</groupId>
- <artifactId>flattr4j-core</artifactId>
- <version>2.4</version>
- <scope>compile</scope>
- <exclusions>
- <exclusion>
- <groupId>org.apache.httpcomponents</groupId>
- <artifactId>httpcore</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.apache.httpcomponents</groupId>
- <artifactId>httpclient</artifactId>
- </exclusion>
- <exclusion>
- <groupId>org.json</groupId>
- <artifactId>json</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
- <dependency>
- <groupId>com.google.android</groupId>
- <artifactId>android</artifactId>
- <scope>provided</scope>
- <version>4.1.1.4</version>
- </dependency>
- <dependency>
- <groupId>com.actionbarsherlock</groupId>
- <artifactId>library</artifactId>
- <version>4.1.0</version>
- <type>apklib</type>
- </dependency>
- <dependency>
- <groupId>com.viewpagerindicator</groupId>
- <artifactId>library</artifactId>
- <version>2.3.1</version>
- <type>apklib</type>
- </dependency>
- <dependency>
- <groupId>com.google.android</groupId>
- <artifactId>annotations</artifactId>
- <version>4.1.1.4</version>
- </dependency>
- <dependency>
- <groupId>commons-io</groupId>
- <artifactId>commons-io</artifactId>
- <version>2.4</version>
- </dependency>
- <dependency>
- <groupId>com.mobeta.android.dslv</groupId>
- <artifactId>drag-sort-listview</artifactId>
- <version>0.6.1-SNAPSHOT</version>
- <type>apklib</type>
- </dependency>
+ <dependencies>
+ <dependency>
+ <groupId>android.support</groupId>
+ <artifactId>compatibility-v4</artifactId>
+ <version>18</version>
+ </dependency>
+ <dependency>
+ <groupId>android.support</groupId>
+ <artifactId>compatibility-v7-appcompat</artifactId>
+ <version>18</version>
+ <type>apklib</type>
+ </dependency>
+ <dependency>
+ <groupId>android.support</groupId>
+ <artifactId>compatibility-v7-appcompat</artifactId>
+ <version>18</version>
+ <type>jar</type>
+ </dependency>
+ <dependency>
+ <groupId>com.google.android</groupId>
+ <artifactId>android-test</artifactId>
+ <version>2.2.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <version>3.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.shredzone.flattr4j</groupId>
+ <artifactId>flattr4j-core</artifactId>
+ <version>2.7</version>
+ <scope>compile</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpcore</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.json</groupId>
+ <artifactId>json</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>com.google.android</groupId>
+ <artifactId>android</artifactId>
+ <scope>provided</scope>
+ <version>4.1.1.4</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.android</groupId>
+ <artifactId>annotations</artifactId>
+ <version>4.1.1.4</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>2.4</version>
+ </dependency>
+ <dependency>
+ <groupId>com.mobeta.android.dslv</groupId>
+ <artifactId>drag-sort-listview</artifactId>
+ <version>0.6.1-SNAPSHOT</version>
+ <type>apklib</type>
+ </dependency>
+ <dependency>
+ <groupId>com.nineoldandroids</groupId>
+ <artifactId>library</artifactId>
+ <version>2.4.0</version>
+ </dependency>
<dependency>
<groupId>com.aocate</groupId>
<artifactId>presto_client</artifactId>
@@ -78,172 +92,177 @@
<scope>system</scope>
<systemPath>${project.basedir}/libs/presto_client-0.8.5.jar</systemPath>
</dependency>
- </dependencies>
+ </dependencies>
- <build>
- <sourceDirectory>src</sourceDirectory>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- <version>2.3.2</version>
- <configuration>
- <source>1.6</source>
- <target>1.6</target>
- </configuration>
- </plugin>
- <plugin>
- <groupId>com.jayway.maven.plugins.android.generation2</groupId>
- <artifactId>android-maven-plugin</artifactId>
- <version>3.5.0</version>
- <configuration>
- <sdk>
- <path>${env.ANDROID_HOME}</path>
- <platform>17</platform>
- </sdk>
- <manifest>
- <debuggable>true</debuggable>
- </manifest>
- </configuration>
- <extensions>true</extensions>
- <executions>
- <execution>
- <id>alignApk</id>
- <phase>package</phase>
- <goals>
- <goal>zipalign</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
+ <build>
+ <sourceDirectory>src</sourceDirectory>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.3.2</version>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>com.jayway.maven.plugins.android.generation2</groupId>
+ <artifactId>android-maven-plugin</artifactId>
+ <version>3.6.1</version>
+ <configuration>
+ <sdk>
+ <path>${env.ANDROID_HOME}</path>
+ <platform>18</platform>
+ </sdk>
+ <manifest>
+ <debuggable>true</debuggable>
+ </manifest>
+ </configuration>
+ <extensions>true</extensions>
+ <executions>
+ <execution>
+ <id>alignApk</id>
+ <phase>package</phase>
+ <goals>
+ <goal>zipalign</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
- <profiles>
- <profile>
- <id>development</id>
- <!-- using this since activeByDefault does not work well with multiple
- profiles -->
- <activation>
- <property>
- <name>environment</name>
- <value>!production</value>
- </property>
- </activation>
- <properties>
- <deployment.stage>In Development</deployment.stage>
- </properties>
- </profile>
- <profile>
- <id>production</id>
- <properties>
- <deployment.stage>In Production</deployment.stage>
- </properties>
- </profile>
- <profile>
- <id>release</id>
- <!-- via this activation the profile is automatically used when the release
- is done with the maven release plugin -->
- <activation>
- <property>
- <name>performRelease</name>
- <value>true</value>
- </property>
- </activation>
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-jarsigner-plugin</artifactId>
- <executions>
- <execution>
- <id>signing</id>
- <goals>
- <goal>sign</goal>
- <goal>verify</goal>
- </goals>
- <phase>package</phase>
- <inherited>true</inherited>
- <configuration>
- <removeExistingSignatures>true</removeExistingSignatures>
- <archiveDirectory />
- <includes>
- <include>${project.build.directory}/${project.artifactId}-${project.version}.apk</include>
- </includes>
- <keystore>${sign.keystore}</keystore>
- <alias>${sign.alias}</alias>
- <storepass>${sign.storepass}</storepass>
- <keypass>${sign.keypass}</keypass>
- <verbose>true</verbose>
- </configuration>
- </execution>
- </executions>
- </plugin>
- <!-- the signed apk then needs to be zipaligned and we activate proguard
- and we run the manifest update -->
- <plugin>
- <groupId>com.jayway.maven.plugins.android.generation2</groupId>
- <artifactId>android-maven-plugin</artifactId>
- <inherited>true</inherited>
- <configuration>
- <sign>
- <debug>false</debug>
- </sign>
- <zipalign>
- <skip>false</skip>
- <verbose>true</verbose>
- <inputApk>${project.build.directory}/${project.artifactId}-${project.version}.apk</inputApk>
- <outputApk>${project.build.directory}/${project.artifactId}-${project.version}-signed-aligned.apk
- </outputApk>
- </zipalign>
- <manifest>
- <debuggable>false</debuggable>
- <versionCodeAutoIncrement>true</versionCodeAutoIncrement>
- </manifest>
- <proguard>
- <skip>true</skip>
- </proguard>
- </configuration>
- <executions>
- <execution>
- <id>alignApk</id>
- <phase>package</phase>
- <goals>
- <goal>zipalign</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- <plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>build-helper-maven-plugin</artifactId>
- <configuration>
- <artifacts>
- <artifact>
- <file>${project.build.directory}/${project.artifactId}-${project.version}-signed-aligned.apk</file>
- <type>apk</type>
- <classifier>signed-aligned</classifier>
- </artifact>
- <artifact>
- <file>${project.build.directory}/proguard/mapping.txt</file>
- <type>map</type>
- <classifier>release</classifier>
- </artifact>
- </artifacts>
- </configuration>
- <executions>
- <execution>
- <id>attach-signed-aligned</id>
- <phase>package</phase>
- <goals>
- <goal>attach-artifact</goal>
- </goals>
- </execution>
- </executions>
- </plugin>
- </plugins>
- </build>
- </profile>
- </profiles>
+ <profiles>
+ <profile>
+ <id>development</id>
+ <!-- using this since activeByDefault does not work well with multiple
+ profiles -->
+ <activation>
+ <property>
+ <name>environment</name>
+ <value>!production</value>
+ </property>
+ </activation>
+ <properties>
+ <deployment.stage>In Development</deployment.stage>
+ </properties>
+ </profile>
+ <profile>
+ <id>production</id>
+ <properties>
+ <deployment.stage>In Production</deployment.stage>
+ </properties>
+ </profile>
+ <profile>
+ <id>release</id>
+ <!-- via this activation the profile is automatically used when the release
+ is done with the maven release plugin -->
+ <activation>
+ <property>
+ <name>performRelease</name>
+ <value>true</value>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jarsigner-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>signing</id>
+ <goals>
+ <goal>sign</goal>
+ <goal>verify</goal>
+ </goals>
+ <phase>package</phase>
+ <inherited>true</inherited>
+ <configuration>
+ <removeExistingSignatures>true</removeExistingSignatures>
+ <archiveDirectory />
+ <includes>
+ <include>${project.build.directory}/${project.artifactId}-${project.version}.apk</include>
+ </includes>
+ <keystore>${sign.keystore}</keystore>
+ <alias>${sign.alias}</alias>
+ <storepass>${sign.storepass}</storepass>
+ <keypass>${sign.keypass}</keypass>
+ <verbose>true</verbose>
+ <arguments>
+ <argument>-sigalg</argument><argument>MD5withRSA</argument>
+ <argument>-digestalg</argument><argument>SHA1</argument>
+ </arguments>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <!-- the signed apk then needs to be zipaligned and we activate proguard
+ and we run the manifest update -->
+ <plugin>
+ <groupId>com.jayway.maven.plugins.android.generation2</groupId>
+ <artifactId>android-maven-plugin</artifactId>
+ <inherited>true</inherited>
+ <configuration>
+ <sign>
+ <debug>false</debug>
+ </sign>
+ <zipalign>
+ <skip>false</skip>
+ <verbose>true</verbose>
+ <inputApk>${project.build.directory}/${project.artifactId}-${project.version}.apk</inputApk>
+ <outputApk>${project.build.directory}/${project.artifactId}-${project.version}-signed-aligned.apk
+ </outputApk>
+ </zipalign>
+ <manifest>
+ <debuggable>false</debuggable>
+ <versionCodeAutoIncrement>false</versionCodeAutoIncrement>
+ </manifest>
+ <proguard>
+ <skip>false</skip>
+ <config>proguard.cfg</config>
+ </proguard>
+ </configuration>
+ <executions>
+ <execution>
+ <id>alignApk</id>
+ <phase>package</phase>
+ <goals>
+ <goal>zipalign</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <configuration>
+ <artifacts>
+ <artifact>
+ <file>${project.build.directory}/${project.artifactId}-${project.version}-signed-aligned.apk</file>
+ <type>apk</type>
+ <classifier>signed-aligned</classifier>
+ </artifact>
+ <artifact>
+ <file>${project.build.directory}/proguard/mapping.txt</file>
+ <type>map</type>
+ <classifier>release</classifier>
+ </artifact>
+ </artifacts>
+ </configuration>
+ <executions>
+ <execution>
+ <id>attach-signed-aligned</id>
+ <phase>package</phase>
+ <goals>
+ <goal>attach-artifact</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
</project>
diff --git a/proguard.cfg b/proguard.cfg
index 795bbbb9c..489af0536 100644
--- a/proguard.cfg
+++ b/proguard.cfg
@@ -8,6 +8,11 @@
-optimizations !code/simplification/arithmetic
-keepattributes *Annotation*
+#-libraryjars libs/android-support-v4.jar
+#-libraryjars libs/commons-lang3-3.1.jar
+#-libraryjars libs/flattr4j-core-2.4.jar
+#-libraryjars libs/commons-io-2.4.jar
+
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
@@ -47,10 +52,12 @@
public static <fields>;
}
--keep class android.support.v4.app.** { *; }
--keep interface android.support.v4.app.** { *; }
--keep class com.actionbarsherlock.** { *; }
--keep interface com.actionbarsherlock.** { *; }
+-keep class android.support.v4.** { *; }
+-keep interface android.support.v4.** { *; }
+-keep class android.support.v7.** { *; }
+-keep interface android.support.v7.** { *; }
+-dontwarn android.support.v4.**
+-dontwarn android.support.v7.**
-keepattributes *Annotation*
diff --git a/project.properties b/project.properties
index 2706f89b9..75f295e31 100644
--- a/project.properties
+++ b/project.properties
@@ -9,7 +9,7 @@
# Project target.
proguard.config=proguard.cfg
-target=android-17
+target=android-18
android.library.reference.1=submodules/ActionBarSherlock/library
android.library.reference.2=submodules/ViewPagerIndicator/library
android.library.reference.3=submodules/dslv/library
diff --git a/res/layout-large/feedlist.xml b/res/layout-large/feedlist.xml
index 3e8664245..49261b88b 100644
--- a/res/layout-large/feedlist.xml
+++ b/res/layout-large/feedlist.xml
@@ -19,7 +19,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
- android:gravity="center"
- android:text="@string/no_feeds_label" />
+ android:gravity="center"/>
</LinearLayout> \ No newline at end of file
diff --git a/res/layout/feeditemlist.xml b/res/layout/feeditemlist.xml
index c2c51ba63..932cfb051 100644
--- a/res/layout/feeditemlist.xml
+++ b/res/layout/feeditemlist.xml
@@ -16,7 +16,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
- android:gravity="center"
- android:text="@string/no_items_label" />
+ android:gravity="center"/>
</LinearLayout> \ No newline at end of file
diff --git a/res/layout/feedlist.xml b/res/layout/feedlist.xml
index b88e2eb6e..0557d8aea 100644
--- a/res/layout/feedlist.xml
+++ b/res/layout/feedlist.xml
@@ -15,7 +15,5 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
- android:layout_gravity="center"
- android:text="@string/no_feeds_label" />
-
+ android:layout_gravity="center"/>
</LinearLayout> \ No newline at end of file
diff --git a/res/layout/listview_activity.xml b/res/layout/listview_activity.xml
new file mode 100644
index 000000000..b276f506c
--- /dev/null
+++ b/res/layout/listview_activity.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <ListView
+ android:id="@+id/listview"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+</LinearLayout> \ No newline at end of file
diff --git a/res/layout/miroguide_category.xml b/res/layout/miroguide_category.xml
index 1ab614050..b6f9f9418 100644
--- a/res/layout/miroguide_category.xml
+++ b/res/layout/miroguide_category.xml
@@ -5,17 +5,15 @@
android:layout_height="match_parent"
android:orientation="vertical" >
- <com.viewpagerindicator.TabPageIndicator
- android:id="@+id/tabs"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- />
-
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1">
+ <android.support.v4.view.PagerTabStrip
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top" />
</android.support.v4.view.ViewPager>
</LinearLayout>
diff --git a/res/layout/organize_queue.xml b/res/layout/organize_queue.xml
index 62b2e980c..3982529a2 100644
--- a/res/layout/organize_queue.xml
+++ b/res/layout/organize_queue.xml
@@ -1,5 +1,5 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:dslv="http://schemas.android.com/apk/res/de.danoeh.antennapod"
+ xmlns:dslv="http://schemas.android.com/apk/res-auto"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
diff --git a/res/menu/main.xml b/res/menu/main.xml
index 9232a95aa..0b1b3cbcb 100644
--- a/res/menu/main.xml
+++ b/res/menu/main.xml
@@ -1,17 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:id="@+id/all_feed_refresh" android:title="@string/refresh_label" android:icon="?attr/navigation_refresh" android:showAsAction="ifRoom|collapseActionView">
- </item><item
+ <item
+ android:id="@+id/all_feed_refresh"
+ android:title="@string/refresh_label"
+ android:icon="?attr/navigation_refresh"
+ android:showAsAction="ifRoom|collapseActionView">
+ </item>
+ <item
android:id="@+id/add_feed"
android:title="@string/add_feed_label"
android:icon="?attr/content_new"
android:showAsAction="ifRoom|collapseActionView">
</item>
-
-
-
- <item android:id="@id/search_item" android:icon="?attr/action_search" android:title="@string/search_label" android:showAsAction="ifRoom|collapseActionView"></item><item android:id="@+id/show_player" android:title="@string/show_player_label" android:icon="@drawable/av_play" android:showAsAction="collapseActionView"></item><item android:id="@+id/show_playback_history" android:title="@string/playback_history_label" android:showAsAction="collapseActionView"></item><item android:id="@+id/show_downloads" android:title="@string/downloads_label" android:icon="@drawable/av_download" android:showAsAction="collapseActionView">
- </item><item android:id="@+id/show_preferences" android:title="@string/settings_label" android:icon="?attr/action_settings" android:showAsAction="collapseActionView"></item>
-
-
+
+
+ <item
+ android:id="@id/search_item"
+ android:icon="?attr/action_search"
+ android:title="@string/search_label"
+ android:showAsAction="ifRoom|collapseActionView"
+ android:actionViewClass="android.support.v7.widget.SearchView"/>
+ <item
+ android:id="@+id/show_player"
+ android:title="@string/show_player_label"
+ android:icon="@drawable/av_play"
+ android:showAsAction="collapseActionView"/>
+ <item
+ android:id="@+id/show_playback_history"
+ android:title="@string/playback_history_label"
+ android:showAsAction="collapseActionView"/>
+ <item
+ android:id="@+id/show_downloads"
+ android:title="@string/downloads_label"
+ android:icon="@drawable/av_download"
+ android:showAsAction="collapseActionView">
+ </item>
+ <item
+ android:id="@+id/show_preferences"
+ android:title="@string/settings_label"
+ android:icon="?attr/action_settings"
+ android:showAsAction="collapseActionView"/>
+
+
</menu>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
new file mode 100644
index 000000000..7047a2df2
--- /dev/null
+++ b/res/values-az/strings.xml
@@ -0,0 +1,231 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--Activitiy titles-->
+ <string name="app_name">AntennaPod</string>
+ <string name="feeds_label">Kanallar</string>
+ <string name="podcasts_label">PODKASTLAR</string>
+ <string name="episodes_label">EPIZODLAR</string>
+ <string name="new_label">Yeni</string>
+ <string name="waiting_list_label">Gözləmədə</string>
+ <string name="settings_label">Parametrlər</string>
+ <string name="add_new_feed_label">Yeni kanal əlavə et</string>
+ <string name="downloads_label">Yükləmələr</string>
+ <string name="cancel_download_label">Yükləməyi ləğv et</string>
+ <string name="download_log_label">Yükləmə jurnalı</string>
+ <string name="playback_history_label">Oynatma tarixiçəsi</string>
+ <!--Webview actions-->
+ <string name="open_in_browser_label">Brauzerdə aç</string>
+ <string name="copy_url_label">URLı kopiyala</string>
+ <string name="share_url_label">URLı paylaş</string>
+ <string name="copied_url_msg">URL buferə köçürüldü</string>
+ <!--Playback history-->
+ <string name="clear_history_label">Tarixiçəni sildir</string>
+ <!--Other-->
+ <string name="confirm_label">Oldu</string>
+ <string name="cancel_label">Ləğv et</string>
+ <string name="author_label">Müəlif</string>
+ <string name="language_label">Dil</string>
+ <string name="cover_label">Üz:</string>
+ <string name="error_label">Xəta</string>
+ <string name="error_msg_prefix">Xəta baş verdi:</string>
+ <string name="refresh_label">Təzələ</string>
+ <string name="external_storage_error_msg">Heç bir yaddaş cihazı tapılmadı.</string>
+ <string name="chapters_label">Fəsillər</string>
+ <string name="shownotes_label">Təsvir</string>
+ <string name="most_recent_prefix">Ən yeni epizod:\u0020</string>
+ <string name="episodes_suffix">\u0020epizod</string>
+ <string name="published_prefix">Nəşr edən:\u0020</string>
+ <string name="length_prefix">Müddət:\u0020</string>
+ <string name="size_prefix">Ölçü:\u0020</string>
+ <string name="processing_label">Hazırlaşma</string>
+ <string name="loading_label">Yükləmə...</string>
+ <string name="image_of_prefix">Şəkil:\u0020</string>
+ <!--'Add Feed' Activity labels-->
+ <string name="feedurl_label">Kanalın URLı</string>
+ <string name="txtvfeedurl_label">Kanalın URLını yaz:</string>
+ <!--Actions on feeds-->
+ <string name="mark_all_read_label">Hamısını oxunmuş kimi işarələ</string>
+ <string name="show_info_label">Məlumatı göstər</string>
+ <string name="remove_feed_label">Kanlı sil</string>
+ <string name="share_link_label">Web-səhifəyi paylaş</string>
+ <string name="share_source_label">Kanalı paylaş</string>
+ <string name="feed_delete_confirmation_msg">Bütün kanallar və epizodlar silinəçək.</string>
+ <!--actions on feeditems-->
+ <string name="download_label">Yüklə</string>
+ <string name="play_label">Oynat</string>
+ <string name="pause_label">Pauza</string>
+ <string name="stream_label">İnternetən yayimla</string>
+ <string name="remove_label">Sil</string>
+ <string name="mark_read_label">Oxumuş kimi işarələ</string>
+ <string name="mark_unread_label">Oxunmamış kimi işarələ</string>
+ <string name="add_to_queue_label">Növbəyə əlavə et</string>
+ <string name="remove_from_queue_label">Növbədən sil</string>
+ <string name="visit_website_label">Web-səhifəsini aç</string>
+ <string name="support_label">Flattrla</string>
+ <string name="enqueue_all_new">Hamsını növbəyə əlavə et</string>
+ <string name="download_all">Hamısını yüklə</string>
+ <string name="skip_episode_label">Epizodu burax</string>
+ <!--Download messages and labels-->
+ <string name="download_successful">Yükləmə uğurlu keçdi</string>
+ <string name="download_failed">Yükləmə uğursuzdur</string>
+ <string name="download_pending">Yükləmə gözlənir</string>
+ <string name="download_running">Yükləmə gedir</string>
+ <string name="download_error_device_not_found">Yaddaş cihazı tapılmadı</string>
+ <string name="download_error_insufficient_space">Yaddaş çatmır</string>
+ <string name="download_error_file_error">Fayl xətası</string>
+ <string name="download_error_http_data_error">HTTP protokolnun xətası</string>
+ <string name="download_error_error_unknown">Naməlum xəta</string>
+ <string name="download_error_parser_exception">Parserin xətası</string>
+ <string name="download_error_unsupported_type">Naməlum kanal növü</string>
+ <string name="download_error_connection_error">Əlaqə xətasi</string>
+ <string name="download_error_unknown_host">Naməlum xost</string>
+ <string name="cancel_all_downloads_label">Yükləmələrin hamısını ləğv et</string>
+ <string name="download_cancelled_msg">Yükləmə ləğv olundu</string>
+ <string name="download_report_title">Yükləmə başa çatdı</string>
+ <string name="download_error_malformed_url">Yanlış URL</string>
+ <string name="download_error_io_error">IO xətasi</string>
+ <string name="download_error_request_error">Tələbin xətası</string>
+ <string name="downloads_left">\u0020yükləmə galdı</string>
+ <string name="download_notification_title">Podkast məlumatların yüklənişi</string>
+ <string name="download_report_content">%1$d yükləmə uğurludur, %2$d uğursuzdur</string>
+ <string name="download_log_title_unknown">Naməlum başliğ</string>
+ <string name="download_type_feed">Kanal</string>
+ <string name="download_type_media">Mediya fayl</string>
+ <string name="download_type_image">Şəkil</string>
+ <string name="download_request_error_dialog_message_prefix">Fayl yükləmə xətası:\u0020</string>
+ <!--Mediaplayer messages-->
+ <string name="player_error_msg">Xəta!</string>
+ <string name="player_stopped_msg">Heç nə oynadılmır</string>
+ <string name="player_preparing_msg">Hazırlanır</string>
+ <string name="player_ready_msg">Hazır</string>
+ <string name="player_seeking_msg">Axtarış</string>
+ <string name="playback_error_server_died">Server iştəmir</string>
+ <string name="playback_error_unknown">Naməlum xəta</string>
+ <string name="no_media_playing_label">Heç nə oynadılmır</string>
+ <string name="position_default_label">00:00:00</string>
+ <string name="player_buffering_msg">Buferləşmə</string>
+ <string name="playbackservice_notification_title">Podkast oynadılır</string>
+ <string name="playbackservice_notification_content">Daha çox info üçün bura bas</string>
+ <!--Navigation-->
+ <string name="show_download_log">Jurnalı göstər</string>
+ <string name="show_player_label">Pleyeri göstər</string>
+ <!--Queue operations-->
+ <string name="clear_queue_label">Növbəyi sil</string>
+ <string name="organize_queue_label">Növbələ düzənlə</string>
+ <string name="undo">Qaytar</string>
+ <string name="removed_from_queue">Element silindi</string>
+ <!--Flattr-->
+ <string name="flattr_auth_label">Flattra gir</string>
+ <string name="flattr_auth_explanation">Girmə prosesini başlamaq üçün düyməyi basın. Flattrın giriş səhifəsinə aparılacağsınız.</string>
+ <string name="authenticate_label">Gir</string>
+ <string name="return_home_label">Baş ekrana dön</string>
+ <string name="flattr_auth_success">Giriş uğurludur! İndi tətbiqlədən Flattrla istifadə edə bilərsiniz.</string>
+ <string name="no_flattr_token_title">Heç bir Flattr tokeni tapılmadı</string>
+ <string name="no_flattr_token_msg">Olsun ki sizin Flattr hesabınız AntennaPod\'a qoşulmadı. Yenə Flattra girin ya da podkastın səhifəsinə keçin.</string>
+ <string name="authenticate_now_label">Gir</string>
+ <string name="action_forbidden_title">Əməliyyat qadağan olundu</string>
+ <string name="action_forbidden_msg">Bu əməliyyat üçün AntennaPod\'un icazəsi yoxdur. AntennaPod\'un keçid tokenin ləğv olunması bunun səbəbi ola bilər. Yenə Flattra girin ya da podkastın səhifəsinə keçin.</string>
+ <string name="access_revoked_title">Keçid ləğv olundu</string>
+ <string name="access_revoked_info">AntennaPod\'un keçid tokeni uğurlu ləğv olundu.</string>
+ <string name="flattr_click_success">Flattrma uğurludur</string>
+ <string name="flattring_label">Flattrləmə</string>
+ <!--Empty list labels-->
+ <string name="no_items_label">Siyahıda heç nə yoxdur</string>
+ <string name="no_feeds_label">Hələ heç bir kanala yazilmadınız</string>
+ <!--Preferences-->
+ <string name="other_pref">Başqa</string>
+ <string name="about_pref">Proqram haqqinda</string>
+ <string name="queue_label">Növbə</string>
+ <string name="pref_pauseOnHeadsetDisconnect_sum">Qulaqliqı ayiranda oynatma dayanacağ</string>
+ <string name="pref_followQueue_sum">Oynatma başa çatanda növbədə irəlidəki epizodu oynat</string>
+ <string name="playback_pref">Oynatma</string>
+ <string name="network_pref">Şəbəkə</string>
+ <string name="pref_autoUpdateIntervall_title">Təzələmə intervali</string>
+ <string name="pref_autoUpdateIntervall_sum">Kanalın avtomatik təzələməsinin intervalını seç ya da keçir onu</string>
+ <string name="pref_downloadMediaOnWifiOnly_sum">Təkçə Wi-Fi vasitəsiilə yüklə</string>
+ <string name="pref_followQueue_title">Fasiləsiz oynatma</string>
+ <string name="pref_downloadMediaOnWifiOnly_title">Wi-Fi vasitəsiilə yükləmə</string>
+ <string name="pref_pauseOnHeadsetDisconnect_title">Qulaqliqı ayır</string>
+ <string name="pref_mobileUpdate_title">Mobil şəbəbkə vasitəsiilə təzələmə</string>
+ <string name="pref_mobileUpdate_sum">Mobil şəbəbkə vasitəsiilə təzələməyə icazə vermək</string>
+ <string name="refreshing_label">Təzələmə</string>
+ <string name="flattr_settings_label">Flattr parametrləri</string>
+ <string name="pref_flattr_auth_title">Flattra gir</string>
+ <string name="pref_flattr_auth_sum">Flattr\'la istifadə etmək üçün, öz Flattr hesabınıza girin</string>
+ <string name="pref_flattr_this_app_title">Bu proqramı flattrla</string>
+ <string name="pref_flattr_this_app_sum">Flattr vasitəsiilə AntennaPodun inkişafını dəstək edin. Sağolun!</string>
+ <string name="pref_revokeAccess_title">Keçidi ləğv ət</string>
+ <string name="pref_revokeAccess_sum">Flattr hesabına keçidi ləğv et</string>
+ <string name="pref_display_only_episodes_title">Təkçə epizodları göstər</string>
+ <string name="pref_display_only_episodes_sum">Təkçə daxilində epizod olan elementləri göstər</string>
+ <string name="user_interface_label">İnterfeys</string>
+ <string name="pref_set_theme_title">Görüşü seç</string>
+ <string name="pref_set_theme_sum">AntennaPod\'un görüşünü dəyişdir</string>
+ <string name="pref_automatic_download_title">Avtomatik yükləmə</string>
+ <string name="pref_automatic_download_sum">Epizodların avtomatik yüklənişinin konfiqurasiyanı dəyiş</string>
+ <string name="pref_autodl_wifi_filter_title">Wi-Fi filtr</string>
+ <string name="pref_autodl_wifi_filter_sum">Seçilən Wi-Fi səbəkələr vasitəsiilə avtomatik yükləməyi icazə ver</string>
+ <string name="pref_episode_cache_title">Epizod keşi</string>
+ <string name="pref_theme_title_light">Ağ</string>
+ <string name="pref_theme_title_dark">Qara</string>
+ <string name="pref_episode_cache_unlimited">Hədsiz</string>
+ <string name="pref_update_interval_hours_plural">saat</string>
+ <string name="pref_update_interval_hours_singular">saat</string>
+ <string name="pref_update_interval_hours_manual">Əl ilə</string>
+ <!--Search-->
+ <string name="search_hint">Kanalları və ya epizodları axtar</string>
+ <string name="found_in_shownotes_label">Təsvirlərdə tapıldı</string>
+ <string name="found_in_chapters_label">Fəsillərdə tapıldı</string>
+ <string name="search_status_searching">Axtarış...</string>
+ <string name="search_status_no_results">Heç nə tapılmadı</string>
+ <string name="search_results_label">Axtarışın nəticələri</string>
+ <string name="search_term_label">Axtarılan:\u0020</string>
+ <string name="search_label">Axtar</string>
+ <string name="found_in_title_label">Başlığda tapıldı</string>
+ <!--OPML import and export-->
+ <string name="opml_import_txtv_button_lable">Ya da OPML faylı idxal edə bilərsiniz. OPML fayl vasitəsiilə siz öz podkastlarınızı başqa podcast menecerə köcürə bilərsiniz:</string>
+ <string name="opml_import_explanation">OPML faylın idxalı üçün onu aşağıdakı qovluqa yerləşdirin və idxal prosesini başlamaq üçün düyməyi basın.</string>
+ <string name="start_import_label">İdxalı başla</string>
+ <string name="opml_import_label">OPML idxalı</string>
+ <string name="opml_directory_error">XƏTA!</string>
+ <string name="reading_opml_label">OPML faylın oxunması</string>
+ <string name="opml_reader_error">OPML faylını oxuyanda xəta baş verdi:</string>
+ <string name="opml_import_error_dir_empty">İdxal qovliqu boşdur.</string>
+ <string name="select_all_label">Hamısını seç</string>
+ <string name="deselect_all_label">Seçimi ləğv et</string>
+ <string name="choose_file_to_import_label">İdxal üçün fayl seç</string>
+ <string name="opml_export_label">OPML ixraçı</string>
+ <string name="exporting_label">İxrac...</string>
+ <string name="export_error_label">İxracın xətası</string>
+ <string name="opml_export_success_title">OPML ixracı uğurlu keçdi</string>
+ <string name="opml_export_success_sum">OPML fayl:\u0020 yazılıb</string>
+ <!--Sleep timer-->
+ <string name="set_sleeptimer_label">Yuxu taymerini qoy</string>
+ <string name="disable_sleeptimer_label">Yuxu taymerini keçir</string>
+ <string name="enter_time_here_label">Vaxtı yaz</string>
+ <string name="sleep_timer_label">Yuxu taymeri</string>
+ <string name="time_left_label">Vaxt galdı:\u0020</string>
+ <string name="time_dialog_invalid_input">Yanlış yazi. Vaxt təkçə rəqmlərlə yazılır</string>
+ <!--Miro Guide-->
+ <string name="loading_categories_label">Kategoriya yüklənişi...</string>
+ <string name="browse_miroguide_label">MiroGuide\'da bax</string>
+ <string name="txtv_browse_miroguide_label">Ya da MiroGuide\'da bax:</string>
+ <string name="miro_guide_label">MiroGuide</string>
+ <string name="miro_search_hint">MiroGuide\'da axtar</string>
+ <string name="popular_label">Populyar</string>
+ <string name="best_rating_label">Ən reytinqli</string>
+ <string name="add_feed_label">Kanalı əlavə et</string>
+ <string name="miro_feed_added">Kanal əlavə olundu</string>
+ <!--Directory chooser-->
+ <string name="selected_folder_label">Seçilən qovluq:</string>
+ <string name="create_folder_label">Qovluqu yarat</string>
+ <string name="choose_data_directory">Məlumat qovluqunu seç</string>
+ <string name="create_folder_msg">\"%1$s\" adlı qovluq yaradılsınmı?</string>
+ <string name="create_folder_success">Yeni qovluq yaradıldı</string>
+ <string name="create_folder_error_no_write_access">Bu qovluqa yazıla bilinmer</string>
+ <string name="create_folder_error_already_exists">Qovluq artiq var</string>
+ <string name="create_folder_error">Qovluq yaradılmadı</string>
+ <string name="folder_not_empty_dialog_title">Qovluq boş deyil</string>
+ <string name="folder_not_empty_dialog_msg">Seçilən qovluq boş deyil. Mediya yükləmələr və başka fayllar bu qovluqa yazılacaqlar. Necə olsa davam olsunmu?</string>
+ <string name="set_to_default_folder">Başlanğıc qovluqu seç</string>
+</resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index eb15eea29..df2c3b719 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -32,6 +32,7 @@
<string name="external_storage_error_msg">L\'emmagatzemament extern no està disponible. Assegureu-vos que està muntat per què l\'aplicació funcioni correctament.</string>
<string name="chapters_label">Capítols</string>
<string name="shownotes_label">Notes del programa</string>
+ <string name="most_recent_prefix">Episodi més recent: \u0020</string>
<string name="episodes_suffix">\u0020episodis</string>
<string name="published_prefix">Publicat:\u0020</string>
<string name="length_prefix">Durada:\u0020</string>
@@ -50,10 +51,10 @@
<string name="share_source_label">Comparteix l\'enllaç del canal</string>
<string name="feed_delete_confirmation_msg">Confirmeu que, efectivament, voleu suprimir aquest canal i tots els episodis que us n\'heu baixat.</string>
<!--actions on feeditems-->
- <string name="download_label">Baixades</string>
+ <string name="download_label">Baixa</string>
<string name="play_label">Reprodueix</string>
<string name="pause_label">Pausa</string>
- <string name="stream_label">Flux</string>
+ <string name="stream_label">Reprodueix sense baixar</string>
<string name="remove_label">Suprimeix</string>
<string name="mark_read_label">Marca com a llegit</string>
<string name="mark_unread_label">Marca com a pendent</string>
@@ -111,6 +112,8 @@
<!--Queue operations-->
<string name="clear_queue_label">Buida la cua</string>
<string name="organize_queue_label">Ordena la cua</string>
+ <string name="undo">Desfés</string>
+ <string name="removed_from_queue">Ítem esborrat</string>
<!--Flattr-->
<string name="flattr_auth_label">Inici de sessió a Flattr</string>
<string name="flattr_auth_explanation">Premeu el botó per iniciar el procés d\'autenticació. Quan s\'obri la pantalla d\'inici de sessió de Flattr al vostre navegador, introduïu les vostres credencials i concediu a AntennaPod els permisos de compartir mitjançant Flattr. En finalitzar el procés, tornareu automàticament a aquesta pantalla.</string>
@@ -133,18 +136,18 @@
<string name="other_pref">Altres</string>
<string name="about_pref">Quant a</string>
<string name="queue_label">Cua</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">Pausa la reproducció en desconnectar els auriculars</string>
+ <string name="pref_pauseOnHeadsetDisconnect_sum">Pausa la reproducció en desconnectar els auriculars.</string>
<string name="pref_followQueue_sum">Salta al següent element de la cua en acabar la reproducció</string>
<string name="playback_pref">Reproducció</string>
<string name="network_pref">Xarxa</string>
<string name="pref_autoUpdateIntervall_title">Interval d\'actualització</string>
- <string name="pref_autoUpdateIntervall_sum">Especifiqueu l\'interval en què els canals s\'actualitzen de forma automàtica, o deshabiliteu la funcionalitat</string>
+ <string name="pref_autoUpdateIntervall_sum">Especifiqueu l\'interval en què els canals s\'actualitzen de forma automàtica, o deshabiliteu la funcionalitat.</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Només baixa fitxers a través d\'una xarxa sense fils</string>
<string name="pref_followQueue_title">Reproducció continuada</string>
<string name="pref_downloadMediaOnWifiOnly_title">Baixa a través de xarxes sense fils</string>
<string name="pref_pauseOnHeadsetDisconnect_title">Desconnexió d\'auriculars</string>
<string name="pref_mobileUpdate_title">Actualitzacions sobre xarxes mòbils</string>
- <string name="pref_mobileUpdate_sum">Permet actualitzacions a través de xarxes mòbils</string>
+ <string name="pref_mobileUpdate_sum">Permet actualitzacions a través de xarxes mòbils.</string>
<string name="refreshing_label">S\'està actualitzant</string>
<string name="flattr_settings_label">Configuració de Flattr</string>
<string name="pref_flattr_auth_title">Inici de sessió Flattr</string>
@@ -152,17 +155,23 @@
<string name="pref_flattr_this_app_title">Compartiu aquesta aplicació amb Flattr</string>
<string name="pref_flattr_this_app_sum">Doneu suport al desenvolupament d\'AntennaPod compartint l\'aplicació a través de Flattr. Gràcies!</string>
<string name="pref_revokeAccess_title">Revoca l\'accés</string>
- <string name="pref_revokeAccess_sum">Revoca el permís d\'accés d\'aquesta aplicació al vostre compte Flattr.</string>
+ <string name="pref_revokeAccess_sum">Revoqueu el permís d\'accés d\'aquesta aplicació al vostre compte Flattr.</string>
<string name="pref_display_only_episodes_title">Mostra només episodis</string>
<string name="pref_display_only_episodes_sum">Mostra només els elements que tenen algun episodi.</string>
<string name="user_interface_label">Interfície d\'usuari</string>
- <string name="pref_set_theme_title">Seleccioneu un tema</string>
+ <string name="pref_set_theme_title">Selecció de tema</string>
<string name="pref_set_theme_sum">Canvieu l\'aparença d\'AntennaPod.</string>
<string name="pref_automatic_download_title">Baixada automàtica</string>
- <string name="pref_automatic_download_sum">Configura la baixada automàtica d\'episodis.</string>
+ <string name="pref_automatic_download_sum">Configureu la baixada automàtica d\'episodis.</string>
<string name="pref_autodl_wifi_filter_title">Activa el filtre de la xarxa sense fils</string>
<string name="pref_autodl_wifi_filter_sum">Permet les baixades automàtiques només per a les xarxes sense fils seleccionades.</string>
<string name="pref_episode_cache_title">Memòria d\'episodis</string>
+ <string name="pref_theme_title_light">Clar</string>
+ <string name="pref_theme_title_dark">Fosc</string>
+ <string name="pref_episode_cache_unlimited">Sense límits</string>
+ <string name="pref_update_interval_hours_plural">hores</string>
+ <string name="pref_update_interval_hours_singular">hora</string>
+ <string name="pref_update_interval_hours_manual">Manual</string>
<!--Search-->
<string name="search_hint">Cerca canals o episodis</string>
<string name="found_in_shownotes_label">Trobat a notes del programa</string>
@@ -175,7 +184,7 @@
<string name="found_in_title_label">Trobat al títol</string>
<!--OPML import and export-->
<string name="opml_import_txtv_button_lable">També podeu importar fitxers OPML. Els fitxers OPML us permeten moure els podcasts d\'una aplicació a una altra:</string>
- <string name="opml_import_explanation">Per importar un fitxer OPML, l\'heu d\'ubicar al següent directori i prémer el botó de sota per iniciar el procés. </string>
+ <string name="opml_import_explanation">Per importar un fitxer OPML, ubiqueu-lo al següent directori i premeu el botó de sota per iniciar el procés. </string>
<string name="start_import_label">Inicia la importació</string>
<string name="opml_import_label">Importació OPML</string>
<string name="opml_directory_error">Error!</string>
diff --git a/res/values-cs-rCZ/strings.xml b/res/values-cs-rCZ/strings.xml
new file mode 100644
index 000000000..84db0c7f6
--- /dev/null
+++ b/res/values-cs-rCZ/strings.xml
@@ -0,0 +1,231 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--Activitiy titles-->
+ <string name="app_name">AntennaPod</string>
+ <string name="feeds_label">Zdroje</string>
+ <string name="podcasts_label">PODCASTY</string>
+ <string name="episodes_label">EPIZODY</string>
+ <string name="new_label">Nový</string>
+ <string name="waiting_list_label">Seznam nepřečtených</string>
+ <string name="settings_label">Nastavení</string>
+ <string name="add_new_feed_label">Přidat nový zdroj</string>
+ <string name="downloads_label">Stahování</string>
+ <string name="cancel_download_label">Zrušit stahování</string>
+ <string name="download_log_label">Historie stáhnování</string>
+ <string name="playback_history_label">Historie přehrávání</string>
+ <!--Webview actions-->
+ <string name="open_in_browser_label">Otevřít v prohlížeči</string>
+ <string name="copy_url_label">Kopírovat URL</string>
+ <string name="share_url_label">Sdílet URL</string>
+ <string name="copied_url_msg">URL zkopírováno do schránky.</string>
+ <!--Playback history-->
+ <string name="clear_history_label">Vymazat historii</string>
+ <!--Other-->
+ <string name="confirm_label">Potvrdit</string>
+ <string name="cancel_label">Zrušit</string>
+ <string name="author_label">Autor</string>
+ <string name="language_label">Jazyk</string>
+ <string name="cover_label">Obal</string>
+ <string name="error_label">Chyba</string>
+ <string name="error_msg_prefix">Nastala chyba:</string>
+ <string name="refresh_label">Obnovit</string>
+ <string name="external_storage_error_msg">Není dostupné žádné externí uložiště. Pro správnou funkci aplikace se prosím ujistěte, že je připojeno externí úložiště.</string>
+ <string name="chapters_label">Kapitoly</string>
+ <string name="shownotes_label">Poznámky</string>
+ <string name="most_recent_prefix">Poslední epizoda:\u0020</string>
+ <string name="episodes_suffix">\u0020epizod</string>
+ <string name="published_prefix">Publikováno:\u0020</string>
+ <string name="length_prefix">Délka:\u0020</string>
+ <string name="size_prefix">Velikost:\u0020</string>
+ <string name="processing_label">Zpracovávám</string>
+ <string name="loading_label">Načítám...</string>
+ <string name="image_of_prefix">Obrázek:\u0020</string>
+ <!--'Add Feed' Activity labels-->
+ <string name="feedurl_label">URL zdroje</string>
+ <string name="txtvfeedurl_label">Zadejte URL zdroje:</string>
+ <!--Actions on feeds-->
+ <string name="mark_all_read_label">Označit vše jako přečtené</string>
+ <string name="show_info_label">Informace o zdroji</string>
+ <string name="remove_feed_label">Odstranit zdroj</string>
+ <string name="share_link_label">Sdílet odkaz</string>
+ <string name="share_source_label">Sdílet adresu zdroje</string>
+ <string name="feed_delete_confirmation_msg">Prosím potvrďte, že chcete smazat tento zdroj včetně všech stažených epizod.</string>
+ <!--actions on feeditems-->
+ <string name="download_label">Stáhnout</string>
+ <string name="play_label">Přehrát</string>
+ <string name="pause_label">Pozastavit</string>
+ <string name="stream_label">Streamovat</string>
+ <string name="remove_label">Odstranit</string>
+ <string name="mark_read_label">Označit jako přečtené</string>
+ <string name="mark_unread_label">Označit jako nepřečtené</string>
+ <string name="add_to_queue_label">Přidat do fronty</string>
+ <string name="remove_from_queue_label">Odebrat z fronty</string>
+ <string name="visit_website_label">Navštívit stránku</string>
+ <string name="support_label">Flattr</string>
+ <string name="enqueue_all_new">Vše do fronty</string>
+ <string name="download_all">Stáhnout vše</string>
+ <string name="skip_episode_label">Přeskočit epizodu</string>
+ <!--Download messages and labels-->
+ <string name="download_successful">Stahování dokončeno</string>
+ <string name="download_failed">Stahování selhalo</string>
+ <string name="download_pending">Čekající na stažení</string>
+ <string name="download_running">Probíhající stahování</string>
+ <string name="download_error_device_not_found">Úložné zařízení nenalezeno</string>
+ <string name="download_error_insufficient_space">Nedostatek volného místa</string>
+ <string name="download_error_file_error">Souborová chyba</string>
+ <string name="download_error_http_data_error">HTTP chyba</string>
+ <string name="download_error_error_unknown">Neznámá chyba</string>
+ <string name="download_error_parser_exception">Výjimka parseru</string>
+ <string name="download_error_unsupported_type">Nepodporovaný typ zdroje</string>
+ <string name="download_error_connection_error">Chyba spojení</string>
+ <string name="download_error_unknown_host">Neznámý host</string>
+ <string name="cancel_all_downloads_label">Zrušit všechna stahování</string>
+ <string name="download_cancelled_msg">Stahování zrušeno</string>
+ <string name="download_report_title">Všechna stahování dokončena</string>
+ <string name="download_error_malformed_url">Chybné URL</string>
+ <string name="download_error_io_error">IO chyba</string>
+ <string name="download_error_request_error">Chyba požadavku</string>
+ <string name="downloads_left">\u0020Stahování zbývá</string>
+ <string name="download_notification_title">Stahuji podcast data</string>
+ <string name="download_report_content">%1$d úspěšných stahování, %2$d selhalo</string>
+ <string name="download_log_title_unknown">Neznámý název</string>
+ <string name="download_type_feed">Zdroj</string>
+ <string name="download_type_media">Soubor</string>
+ <string name="download_type_image">Obrázek</string>
+ <string name="download_request_error_dialog_message_prefix">Nastala chyba při pokusu o stažení souboru:\u0020</string>
+ <!--Mediaplayer messages-->
+ <string name="player_error_msg">Chyba!</string>
+ <string name="player_stopped_msg">Žádné probíhající přehrávání</string>
+ <string name="player_preparing_msg">Připravuji</string>
+ <string name="player_ready_msg">Připraven</string>
+ <string name="player_seeking_msg">Přetáčím</string>
+ <string name="playback_error_server_died">Server nereaguje</string>
+ <string name="playback_error_unknown">Neznámá chyba</string>
+ <string name="no_media_playing_label">Žádné probíhající přehrávání</string>
+ <string name="position_default_label">00:00:00</string>
+ <string name="player_buffering_msg">Načítání</string>
+ <string name="playbackservice_notification_title">Přehrávaný podcast</string>
+ <string name="playbackservice_notification_content">Stiskni zde pro více informací</string>
+ <!--Navigation-->
+ <string name="show_download_log">Historie stahování</string>
+ <string name="show_player_label">Přehrávač</string>
+ <!--Queue operations-->
+ <string name="clear_queue_label">Vyprázdnit frontu</string>
+ <string name="organize_queue_label">Přeuspořádat frontu</string>
+ <string name="undo">Zpět</string>
+ <string name="removed_from_queue">Položka odebrána</string>
+ <!--Flattr-->
+ <string name="flattr_auth_label">Flattr přihlášení</string>
+ <string name="flattr_auth_explanation">Stiskněte následující tlačítko pro spuštění autentizačního procesu. Budete přesměrováni na přihlašovací obrazovku flattru a vyzváni k potvrzení udělení práv pro použití flattru aplikací AntennaPod. Po udělení práv se automaticky vrátíte na tuto obrazovku.</string>
+ <string name="authenticate_label">Přihlásit</string>
+ <string name="return_home_label">Návrat domů</string>
+ <string name="flattr_auth_success">Úspěšně přihlášen. Nyní můžete využít flattru přímo v aplikaci. </string>
+ <string name="no_flattr_token_title">Nenalezen Flattr token</string>
+ <string name="no_flattr_token_msg">Váš flattr učet není napojen do AntenaPodu. Můžete buďto napojit váš flattr účet do AntennaPodu a využít flattru přímo v aplikaci a nebo použít flattr přímo na webových stránkách zdroje v prohlížeči. </string>
+ <string name="authenticate_now_label">Přihlásit</string>
+ <string name="action_forbidden_title">Akce zakázána</string>
+ <string name="action_forbidden_msg">AntennaPod nemá oprávnění pro tuto akci. Důvodem může být revokování přístupového tokenu AntennaPodu k vašemu účtu. Přístup můžete obnovit nebo využít prohlížeče k návštěvě stránky zdroje.</string>
+ <string name="access_revoked_title">Přístup revokován</string>
+ <string name="access_revoked_info">Úspěšně revokován přístup AntennPodu k vašemu účtu. Pro dokončení tohoto procesu je ještě zapotřebí na stránkách flattru odebrat z vašeho účtu AntennaPod ze seznamu povolených aplikací.</string>
+ <string name="flattr_click_success">Úspěšně flattrováno!</string>
+ <string name="flattring_label">Flattruji</string>
+ <!--Empty list labels-->
+ <string name="no_items_label">Žádné položky v seznamu.</string>
+ <string name="no_feeds_label">Zatím nebyly přidány žádné zdroje.</string>
+ <!--Preferences-->
+ <string name="other_pref">Ostatní</string>
+ <string name="about_pref">O aplikaci</string>
+ <string name="queue_label">Fronta</string>
+ <string name="pref_pauseOnHeadsetDisconnect_sum">Při odpojení sluchátek automaticky pozastavit přehrávání.</string>
+ <string name="pref_followQueue_sum">Po přehrání položky z fronty přejít automaticky na další.</string>
+ <string name="playback_pref">Přehrávání</string>
+ <string name="network_pref">Síť</string>
+ <string name="pref_autoUpdateIntervall_title">Interval aktualizace zdrojů</string>
+ <string name="pref_autoUpdateIntervall_sum">Udává interval, ve kterém se zdroje automaticky aktualizují nebo tuto funkci deaktivuje.</string>
+ <string name="pref_downloadMediaOnWifiOnly_sum">Stahovat soubory pouze pomocí WiFi</string>
+ <string name="pref_followQueue_title">Kontinuální přehrávání</string>
+ <string name="pref_downloadMediaOnWifiOnly_title">WiFi stahování</string>
+ <string name="pref_pauseOnHeadsetDisconnect_title">Odpojení sluchátek</string>
+ <string name="pref_mobileUpdate_title">Mobilní aktualizace</string>
+ <string name="pref_mobileUpdate_sum">Povolit aktualizace pomocí mobilního připojení.</string>
+ <string name="refreshing_label">Obnovuji</string>
+ <string name="flattr_settings_label">Nastavení Flattr</string>
+ <string name="pref_flattr_auth_title">Flattr přihlášení</string>
+ <string name="pref_flattr_auth_sum">Přihlásit se k flattr účtu a umožnit flattrování přímo z aplikace.</string>
+ <string name="pref_flattr_this_app_title">Flattrovat tuto aplikaci</string>
+ <string name="pref_flattr_this_app_sum">Podpořit vývoj AntennaPodu na flatteru. Děkujeme!</string>
+ <string name="pref_revokeAccess_title">Odebrat přístup</string>
+ <string name="pref_revokeAccess_sum">Odebere aplikaci přístupová práva k vašemu flattr účtu.</string>
+ <string name="pref_display_only_episodes_title">Zobrazit pouze epizody</string>
+ <string name="pref_display_only_episodes_sum">Zobrazit pouze položky obsahující epizody.</string>
+ <string name="user_interface_label">Uživatelské rozhraní</string>
+ <string name="pref_set_theme_title">Vybrat motiv</string>
+ <string name="pref_set_theme_sum">Změnit vzhled AntennaPod.</string>
+ <string name="pref_automatic_download_title">Automatické stahování</string>
+ <string name="pref_automatic_download_sum">Nastavení automatického stahování epizod.</string>
+ <string name="pref_autodl_wifi_filter_title">Zapnout Wi-Fi filtr</string>
+ <string name="pref_autodl_wifi_filter_sum">Povolit automatické stahování pouze pomocí vybraných Wi-Fi sítí.</string>
+ <string name="pref_episode_cache_title">Historie epizod</string>
+ <string name="pref_theme_title_light">Světlý</string>
+ <string name="pref_theme_title_dark">Tmavý</string>
+ <string name="pref_episode_cache_unlimited">Bez omezení</string>
+ <string name="pref_update_interval_hours_plural">hodin</string>
+ <string name="pref_update_interval_hours_singular">hodina</string>
+ <string name="pref_update_interval_hours_manual">Ručně</string>
+ <!--Search-->
+ <string name="search_hint">Hledat zdroje a epizody</string>
+ <string name="found_in_shownotes_label">Nalezeno v poznámkách k show</string>
+ <string name="found_in_chapters_label">Nalezeno v kapitolách</string>
+ <string name="search_status_searching">Vyhledávám...</string>
+ <string name="search_status_no_results">Žádné výsledky</string>
+ <string name="search_results_label">Výsledky vyhledávání</string>
+ <string name="search_term_label">Vyhledáváno:\u0020</string>
+ <string name="search_label">Vyhledat</string>
+ <string name="found_in_title_label">Nalezeno v názvu</string>
+ <!--OPML import and export-->
+ <string name="opml_import_txtv_button_lable">Můžete také importovat OPML soubor. OPML soubory umožňují přenést vaše podcasty z jednoho podcast manažera do jiného:</string>
+ <string name="opml_import_explanation">Pro import OPML souboru je třeba ho nejdříve umístit do následujícího adresáře a poté pro zahájení procesu importu stisknout tlačítko. </string>
+ <string name="start_import_label">Importovat</string>
+ <string name="opml_import_label">OPML import</string>
+ <string name="opml_directory_error">CHYBA!</string>
+ <string name="reading_opml_label">Načítání OPML souboru</string>
+ <string name="opml_reader_error">Nastala chyba při čtení OPML souboru:</string>
+ <string name="opml_import_error_dir_empty">Adresář importu je prázdný.</string>
+ <string name="select_all_label">Označit vše</string>
+ <string name="deselect_all_label">Zrušit výběr</string>
+ <string name="choose_file_to_import_label">Vyberte soubor k importování</string>
+ <string name="opml_export_label">OPML export</string>
+ <string name="exporting_label">Exportuji...</string>
+ <string name="export_error_label">Chyba exportu</string>
+ <string name="opml_export_success_title">OPML export byl úspěšný.</string>
+ <string name="opml_export_success_sum">OPML soubor byl zapsán do:\u0020</string>
+ <!--Sleep timer-->
+ <string name="set_sleeptimer_label">Nastavit časovač vypnutí</string>
+ <string name="disable_sleeptimer_label">Deaktivovat časovač vypnutí</string>
+ <string name="enter_time_here_label">Zadejte čas</string>
+ <string name="sleep_timer_label">Časovač vypnutí</string>
+ <string name="time_left_label">Zbývá času:\u0020</string>
+ <string name="time_dialog_invalid_input">Neplatný vstup, musí být zadáno celé číslo</string>
+ <!--Miro Guide-->
+ <string name="loading_categories_label">Načítám kategorie...</string>
+ <string name="browse_miroguide_label">Procházet Miro Guide</string>
+ <string name="txtv_browse_miroguide_label">Nebo procházet Miro Guide:</string>
+ <string name="miro_guide_label">Miro Guide</string>
+ <string name="miro_search_hint">Vyhledávat v Miro Guide</string>
+ <string name="popular_label">Populární</string>
+ <string name="best_rating_label">Nejlépe hodnocené</string>
+ <string name="add_feed_label">Přidat zdroj</string>
+ <string name="miro_feed_added">Přidávám zdroj</string>
+ <!--Directory chooser-->
+ <string name="selected_folder_label">Vybraný adresář:</string>
+ <string name="create_folder_label">Vytvořit adresář</string>
+ <string name="choose_data_directory">Vybrat umístění dat</string>
+ <string name="create_folder_msg">Vytvořit adresář \"%1$s\"?</string>
+ <string name="create_folder_success">Nový adresář vytvořen</string>
+ <string name="create_folder_error_no_write_access">Nelze zapisovat do adresáře</string>
+ <string name="create_folder_error_already_exists">Adresář již existuje</string>
+ <string name="create_folder_error">Nelze vytvořit adresář</string>
+ <string name="folder_not_empty_dialog_title">Adresář není prázdný</string>
+ <string name="folder_not_empty_dialog_msg">Vybraný adresář není prázdný. Stažená media a ostatní soubory budou umístěny přímo do tohoto adresáře. Přesto pokračovat?</string>
+ <string name="set_to_default_folder">Vybrat hlavní adresář</string>
+</resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 2e98a6e63..ae57da607 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -32,6 +32,7 @@
<string name="external_storage_error_msg">Ingen ekstern harddisk er tilgængelig. Vær venlig at sørge for at den eksterne hukommelse er monteret så app\'en kan fungere korrekt.</string>
<string name="chapters_label">Kapitler</string>
<string name="shownotes_label">Afsnitsnoter</string>
+ <string name="most_recent_prefix">Seneste episoder:\u0020</string>
<string name="episodes_suffix">\u0020episoder</string>
<string name="published_prefix">Offentliggjort:\u0020</string>
<string name="length_prefix">Længde:\u0020</string>
@@ -111,6 +112,8 @@
<!--Queue operations-->
<string name="clear_queue_label">Fjern kø</string>
<string name="organize_queue_label">Arranger kø</string>
+ <string name="undo">Fortryd</string>
+ <string name="removed_from_queue">Emne slettet</string>
<!--Flattr-->
<string name="flattr_auth_label">Flattr log ind</string>
<string name="flattr_auth_explanation">Tryk på knappen nedenfor for at starte godkendelsesprocessen. Du vil blive ført til flattr log ind siden i din browser og bedt om at give AntennaPod tilladelse til at flattr emner. Efter at du har givet tilladelsen vil du automatisk vende tilbage til denne side.</string>
@@ -163,6 +166,12 @@
<string name="pref_autodl_wifi_filter_title">Sæt Wi-Fi filter til</string>
<string name="pref_autodl_wifi_filter_sum">Tillad kun automatisk download for de valgte Wi-Fi netværk</string>
<string name="pref_episode_cache_title">Episode cache</string>
+ <string name="pref_theme_title_light">Lys</string>
+ <string name="pref_theme_title_dark">Mørk</string>
+ <string name="pref_episode_cache_unlimited">Uendelig</string>
+ <string name="pref_update_interval_hours_plural">timer</string>
+ <string name="pref_update_interval_hours_singular">time</string>
+ <string name="pref_update_interval_hours_manual">Manuelt</string>
<!--Search-->
<string name="search_hint">Søg efter feeds eller episoder</string>
<string name="found_in_shownotes_label">Funder i showets noter</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index be723effe..3afd65052 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -112,6 +112,8 @@
<!--Queue operations-->
<string name="clear_queue_label">Abspielliste leeren</string>
<string name="organize_queue_label">Abspielliste organisieren</string>
+ <string name="undo">Rückgängig</string>
+ <string name="removed_from_queue">Element entfernt</string>
<!--Flattr-->
<string name="flattr_auth_label">Flattr Anmeldung</string>
<string name="flattr_auth_explanation">Drücke den Button unten um den Authentifizierungsprozess zu starten. Du wirst dann zur Flattr-Anmeldeseite weitergeleitet, wo du gefragt wirst, AntennaPod die Erlaubnis zu geben, Dinge zu flattrn. Nachdem du die Erlaubnis erteilt hast, kehrst du automatisch zu diesem Bildschirm zurück.</string>
@@ -164,6 +166,12 @@
<string name="pref_autodl_wifi_filter_title">W-LAN-Filter aktivieren</string>
<string name="pref_autodl_wifi_filter_sum">Erlaube das automatische Herunterladen nur in ausgewählten W-LAN Netzwerken.</string>
<string name="pref_episode_cache_title">Episodenspeicher</string>
+ <string name="pref_theme_title_light">Hell</string>
+ <string name="pref_theme_title_dark">Dunkel</string>
+ <string name="pref_episode_cache_unlimited">Unbegrenzt</string>
+ <string name="pref_update_interval_hours_plural">Stunden</string>
+ <string name="pref_update_interval_hours_singular">Stunde</string>
+ <string name="pref_update_interval_hours_manual">Manuell</string>
<!--Search-->
<string name="search_hint">Suche nach Feeds oder Episoden</string>
<string name="found_in_shownotes_label">In Sendungsnotizen gefunden</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 09ad122f5..0afdd0d1e 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -32,8 +32,9 @@
<string name="external_storage_error_msg">No se encuentra un almacenamiento externo. Asegúrese de que su almacenamiento externo esté montado para que la aplicación funcione correctamente.</string>
<string name="chapters_label">Capítulos</string>
<string name="shownotes_label">Notas del programa</string>
+ <string name="most_recent_prefix">Episodio más reciente:\u0020</string>
<string name="episodes_suffix">\u0020episodios</string>
- <string name="published_prefix">Publicado:\u0020</string>
+ <string name="published_prefix">Publicado el:\u0020</string>
<string name="length_prefix">Duración:\u0020</string>
<string name="size_prefix">Tamaño:\u0020</string>
<string name="processing_label">Procesando</string>
@@ -41,7 +42,7 @@
<string name="image_of_prefix">Imagen de:\u0020</string>
<!--'Add Feed' Activity labels-->
<string name="feedurl_label">URL del canal</string>
- <string name="txtvfeedurl_label">Escriba aquí la URL del canal</string>
+ <string name="txtvfeedurl_label">Escriba aquí la URL del canal:</string>
<!--Actions on feeds-->
<string name="mark_all_read_label">Marcar todo como leído</string>
<string name="show_info_label">Información del programa</string>
@@ -111,6 +112,8 @@
<!--Queue operations-->
<string name="clear_queue_label">Limpiar la cola</string>
<string name="organize_queue_label">Organizar cola</string>
+ <string name="undo">Deshacer</string>
+ <string name="removed_from_queue">Artículo eliminado</string>
<!--Flattr-->
<string name="flattr_auth_label">Identificarse en Flattr</string>
<string name="flattr_auth_explanation">Pulse el botón inferior para comenzar la autenticación. Su navegador abrirá la pantalla de identificación de Flattr y le preguntará si quiere conceder permiso a AntennaPod para valorar cosas. Tras concederlo, volverá a esta pantalla automáticamente.</string>
@@ -163,6 +166,12 @@
<string name="pref_autodl_wifi_filter_title">Activar el filtro WiFi</string>
<string name="pref_autodl_wifi_filter_sum">Permitir la descarga automática sólo para las redes WiFi marcadas.</string>
<string name="pref_episode_cache_title">Caché de episodios</string>
+ <string name="pref_theme_title_light">Claro</string>
+ <string name="pref_theme_title_dark">Oscuro</string>
+ <string name="pref_episode_cache_unlimited">Ilimitado</string>
+ <string name="pref_update_interval_hours_plural">horas</string>
+ <string name="pref_update_interval_hours_singular">hora</string>
+ <string name="pref_update_interval_hours_manual">Manual</string>
<!--Search-->
<string name="search_hint">Buscar canales o episodios</string>
<string name="found_in_shownotes_label">Encontrado en las notas del programa</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 02fb68388..5589b59f3 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -32,6 +32,7 @@
<string name="external_storage_error_msg">Aucun stockage externe n\'est disponible. Merci de connecter un volume de stockage externe pour que l\'application puisse fonctionner correctement.</string>
<string name="chapters_label">Chapitres</string>
<string name="shownotes_label">Notes d\'épisode</string>
+ <string name="most_recent_prefix">Episode le plus récent :\u0020</string>
<string name="episodes_suffix">\u0020épisodes</string>
<string name="published_prefix">Publié :\u0020</string>
<string name="length_prefix">Durée :\u0020</string>
@@ -111,6 +112,8 @@
<!--Queue operations-->
<string name="clear_queue_label">Effacer la liste</string>
<string name="organize_queue_label">Ordre de lecture</string>
+ <string name="undo">Annuler</string>
+ <string name="removed_from_queue">Élément retiré</string>
<!--Flattr-->
<string name="flattr_auth_label">Connecter à Flattr</string>
<string name="flattr_auth_explanation">Appuyez sur le bouton ci-dessous pour vous authentifier. Vous serez envoyés à l\'écran de connexion Flattr dans le navigateur, et il vous sera demandé de donner à AntennaPod la permission de flattr. Une fois ceci fait, vous reviendrez automatiquement à cet écran.</string>
@@ -163,6 +166,12 @@
<string name="pref_autodl_wifi_filter_title">Activer le filtre Wi-Fi</string>
<string name="pref_autodl_wifi_filter_sum">Autoriser le téléchargement automatique uniquement sur les réseaux Wi-Fi sélectionnés.</string>
<string name="pref_episode_cache_title">Épisodes stockés localement</string>
+ <string name="pref_theme_title_light">Clair</string>
+ <string name="pref_theme_title_dark">Sombre</string>
+ <string name="pref_episode_cache_unlimited">Illimité</string>
+ <string name="pref_update_interval_hours_plural">heures</string>
+ <string name="pref_update_interval_hours_singular">heure</string>
+ <string name="pref_update_interval_hours_manual">Manuel</string>
<!--Search-->
<string name="search_hint">Chercher des flux ou épisodes</string>
<string name="found_in_shownotes_label">Trouvé dans les notes</string>
diff --git a/res/values-it-rIT/strings.xml b/res/values-it-rIT/strings.xml
index 96e0b0ca1..0fe45c2df 100644
--- a/res/values-it-rIT/strings.xml
+++ b/res/values-it-rIT/strings.xml
@@ -3,7 +3,7 @@
<!--Activitiy titles-->
<string name="app_name">AntennaPod</string>
<string name="feeds_label">Feed</string>
- <string name="podcasts_label">PODCASTS</string>
+ <string name="podcasts_label">PODCAST</string>
<string name="episodes_label">EPISODI</string>
<string name="new_label">Nuovo</string>
<string name="waiting_list_label">Lista d\'attesa</string>
@@ -32,6 +32,7 @@
<string name="external_storage_error_msg">Non risulta disponibile lo spazio di archiviazione esterno. Assicurati che lo spazio di archiviazione sia montato per permettere all\'applicazione di funzionare correttamente.</string>
<string name="chapters_label">Capitoli</string>
<string name="shownotes_label">Note dell\'episodio</string>
+ <string name="most_recent_prefix">Episodi Recenti:\u0020</string>
<string name="episodes_suffix">\u0020episodi</string>
<string name="published_prefix">Pubblicato:\u0020</string>
<string name="length_prefix">Durata:\u0020</string>
@@ -85,7 +86,7 @@
<string name="download_error_io_error">IO Error</string>
<string name="download_error_request_error">Request error</string>
<string name="downloads_left">\u0020Download rimasti</string>
- <string name="download_notification_title">Download dati dei podcast in corso</string>
+ <string name="download_notification_title">Download dati podcast in corso</string>
<string name="download_report_content">%1$d download con successo, %2$d ko</string>
<string name="download_log_title_unknown">Titolo sconosciuto</string>
<string name="download_type_feed">Feed</string>
@@ -111,6 +112,8 @@
<!--Queue operations-->
<string name="clear_queue_label">Svuota la coda</string>
<string name="organize_queue_label">Riordina la coda</string>
+ <string name="undo">Undo</string>
+ <string name="removed_from_queue">Oggetto rimosso</string>
<!--Flattr-->
<string name="flattr_auth_label">Flattr sign-in</string>
<string name="flattr_auth_explanation">Premi il tasto seguente per iniziare il processo di autenticazione. Sarai trasferito alla pagina di login di flattr sul tuo browser e ti sarà richiesto di garantire ad AntennaPod il permesso di effettuare microdonazioni. Dopo la tua autorizzazione, sarai riportato alla seguente schermata in modo automatico.</string>
@@ -137,13 +140,13 @@
<string name="pref_followQueue_sum">Passa al prossimo episodio in coda quanto si completa una riproduzione</string>
<string name="playback_pref">Riproduzione</string>
<string name="network_pref">Rete</string>
- <string name="pref_autoUpdateIntervall_title">Intervallo di aggiornamento</string>
+ <string name="pref_autoUpdateIntervall_title">Intervallo di update</string>
<string name="pref_autoUpdateIntervall_sum">Specifica un intervallo per l\'aggiornamento automatico dei feed o disabilitalo</string>
<string name="pref_downloadMediaOnWifiOnly_sum">Abilita il download dei media solo tramite WiFi</string>
<string name="pref_followQueue_title">Playback continuo</string>
<string name="pref_downloadMediaOnWifiOnly_title">Download dei media su WiFi</string>
<string name="pref_pauseOnHeadsetDisconnect_title">Disconnessione cuffie</string>
- <string name="pref_mobileUpdate_title">Aggiornamenti su rete mobile</string>
+ <string name="pref_mobileUpdate_title">Update su rete mobile</string>
<string name="pref_mobileUpdate_sum">Permetti gli aggiornamenti tramite connessione dati mobile</string>
<string name="refreshing_label">Aggiornamento</string>
<string name="flattr_settings_label">Impostazioni Flattr</string>
@@ -163,6 +166,12 @@
<string name="pref_autodl_wifi_filter_title">Abilita il filtro Wi-Fi</string>
<string name="pref_autodl_wifi_filter_sum">Abilita il download automatico solo per alcune reti Wi-Fi selezionate.</string>
<string name="pref_episode_cache_title">Cache degli episodi</string>
+ <string name="pref_theme_title_light">Light</string>
+ <string name="pref_theme_title_dark">Dark</string>
+ <string name="pref_episode_cache_unlimited">Illimitato</string>
+ <string name="pref_update_interval_hours_plural">ore</string>
+ <string name="pref_update_interval_hours_singular">ora</string>
+ <string name="pref_update_interval_hours_manual">Manuale</string>
<!--Search-->
<string name="search_hint">Ricerca per Feed o Episodi</string>
<string name="found_in_shownotes_label">Trovato nelle note dell\'episodio</string>
@@ -191,9 +200,9 @@
<string name="opml_export_success_title">Esportazione OPML avvenuta con successo.</string>
<string name="opml_export_success_sum">Il file .opml è stato scritto su:\u0020</string>
<!--Sleep timer-->
- <string name="set_sleeptimer_label">Imposta il timer di spegnimento</string>
+ <string name="set_sleeptimer_label">Imposta timer</string>
<string name="disable_sleeptimer_label">Disabilita il timer di spegnimento</string>
- <string name="enter_time_here_label">Inserisci il tempo</string>
+ <string name="enter_time_here_label">Tempo di spegnimento</string>
<string name="sleep_timer_label">Timer di spegnimento</string>
<string name="time_left_label">Tempo residuo:\u0020</string>
<string name="time_dialog_invalid_input">Input non valido, il campo deve essere un numero intero.</string>
diff --git a/res/values-land/styles.xml b/res/values-land/styles.xml
index 877ac6412..d964ef3d4 100644
--- a/res/values-land/styles.xml
+++ b/res/values-land/styles.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
- <style name="Theme.MediaPlayer" parent="@style/Theme.Sherlock.Light.ForceOverflow">
+ <style name="Theme.MediaPlayer" parent="@style/Theme.AppCompat.Light">
<item name="android:windowActionBarOverlay">true</item>
</style>
</resources> \ No newline at end of file
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
new file mode 100644
index 000000000..8b9383979
--- /dev/null
+++ b/res/values-pt/strings.xml
@@ -0,0 +1,231 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources>
+ <!--Activitiy titles-->
+ <string name="app_name">AntennaPod</string>
+ <string name="feeds_label">Fontes</string>
+ <string name="podcasts_label">Podcasts</string>
+ <string name="episodes_label">Episódios</string>
+ <string name="new_label">Novo</string>
+ <string name="waiting_list_label">Lista de espera</string>
+ <string name="settings_label">Definições</string>
+ <string name="add_new_feed_label">Adicionar fonte</string>
+ <string name="downloads_label">Transferências</string>
+ <string name="cancel_download_label">Cancelar transferência</string>
+ <string name="download_log_label">Registo de transferências</string>
+ <string name="playback_history_label">Histórico de reprodução</string>
+ <!--Webview actions-->
+ <string name="open_in_browser_label">Abrir no navegador</string>
+ <string name="copy_url_label">Copiar URL</string>
+ <string name="share_url_label">Partilhar URL</string>
+ <string name="copied_url_msg">URL copiado para a área de transferência.</string>
+ <!--Playback history-->
+ <string name="clear_history_label">Limpar histórico</string>
+ <!--Other-->
+ <string name="confirm_label">Confirmar</string>
+ <string name="cancel_label">Cancelar</string>
+ <string name="author_label">Autor</string>
+ <string name="language_label">Idioma</string>
+ <string name="cover_label">Imagem</string>
+ <string name="error_label">Erro</string>
+ <string name="error_msg_prefix">Ocorreu um erro:</string>
+ <string name="refresh_label">Atualizar</string>
+ <string name="external_storage_error_msg">Não existe um cartão SD. Certifique-se que inseriu o cartão corretamente.</string>
+ <string name="chapters_label">Capítulos</string>
+ <string name="shownotes_label">Notas</string>
+ <string name="most_recent_prefix">Episódio mais recente:\u0020</string>
+ <string name="episodes_suffix">\u0020episódios</string>
+ <string name="published_prefix">Publicado:\u0020</string>
+ <string name="length_prefix">Duração:\u0020</string>
+ <string name="size_prefix">Tamanho:\u0020</string>
+ <string name="processing_label">A processar...</string>
+ <string name="loading_label">A carregar...</string>
+ <string name="image_of_prefix">Imagem de:\u0020</string>
+ <!--'Add Feed' Activity labels-->
+ <string name="feedurl_label">URL da fonte</string>
+ <string name="txtvfeedurl_label">Introduza o URL da fonte:</string>
+ <!--Actions on feeds-->
+ <string name="mark_all_read_label">Marcar tudo como lido</string>
+ <string name="show_info_label">Mostrar informações</string>
+ <string name="remove_feed_label">Remover fonte</string>
+ <string name="share_link_label">Partilhar ligação do sítio web</string>
+ <string name="share_source_label">Partilhar ligação da fonte</string>
+ <string name="feed_delete_confirmation_msg">Confirme a eliminação desta fonte e de todos os episódios a ela petencentes.</string>
+ <!--actions on feeditems-->
+ <string name="download_label">Transferir</string>
+ <string name="play_label">Reproduzir</string>
+ <string name="pause_label">Pausa</string>
+ <string name="stream_label">Emitir</string>
+ <string name="remove_label">Remover</string>
+ <string name="mark_read_label">Marcar como lido</string>
+ <string name="mark_unread_label">Marcar como novo</string>
+ <string name="add_to_queue_label">Adicionar à fila</string>
+ <string name="remove_from_queue_label">Remover da fila</string>
+ <string name="visit_website_label">Aceder ao sítio web</string>
+ <string name="support_label">Flattr</string>
+ <string name="enqueue_all_new">Colocar tudo na fila</string>
+ <string name="download_all">Transferir tudo</string>
+ <string name="skip_episode_label">Ignorar episódio</string>
+ <!--Download messages and labels-->
+ <string name="download_successful">Transferência terminada</string>
+ <string name="download_failed">Erro ao transferir</string>
+ <string name="download_pending">Transferência pendente</string>
+ <string name="download_running">Transferência atual</string>
+ <string name="download_error_device_not_found">Cartão SD não encontrado</string>
+ <string name="download_error_insufficient_space">Espaço insuficiente</string>
+ <string name="download_error_file_error">Erro no ficheiro</string>
+ <string name="download_error_http_data_error">Erro HTTP</string>
+ <string name="download_error_error_unknown">Erro desconhecido</string>
+ <string name="download_error_parser_exception">Exceção do processador</string>
+ <string name="download_error_unsupported_type">Fonte não suportada</string>
+ <string name="download_error_connection_error">Erro de ligação</string>
+ <string name="download_error_unknown_host">Servidor desconhecido</string>
+ <string name="cancel_all_downloads_label">Cancelar transferências</string>
+ <string name="download_cancelled_msg">Transferência cancelada</string>
+ <string name="download_report_title">Transferências terminadas</string>
+ <string name="download_error_malformed_url">URL inválido</string>
+ <string name="download_error_io_error">Erro I/O</string>
+ <string name="download_error_request_error">Erro de pedido</string>
+ <string name="downloads_left">\u0020Transferências em falta</string>
+ <string name="download_notification_title">A transferir dados...</string>
+ <string name="download_report_content">%1$d transferências efetuadas, %2$d falhadas</string>
+ <string name="download_log_title_unknown">Título desconhecido</string>
+ <string name="download_type_feed">Fonte</string>
+ <string name="download_type_media">Ficheiro multimédia</string>
+ <string name="download_type_image">Imagem</string>
+ <string name="download_request_error_dialog_message_prefix">Ocorreu um erro ao transferir o ficheiro:\u0020</string>
+ <!--Mediaplayer messages-->
+ <string name="player_error_msg">Erro!</string>
+ <string name="player_stopped_msg">Nada em reprodução</string>
+ <string name="player_preparing_msg">A preparar</string>
+ <string name="player_ready_msg">Pronto</string>
+ <string name="player_seeking_msg">A procurar</string>
+ <string name="playback_error_server_died">Erro de servidor</string>
+ <string name="playback_error_unknown">Erro desconhecido</string>
+ <string name="no_media_playing_label">Nada em reprodução</string>
+ <string name="position_default_label">00:00:00</string>
+ <string name="player_buffering_msg">A processar...</string>
+ <string name="playbackservice_notification_title">Reproduzir podcast</string>
+ <string name="playbackservice_notification_content">Clique para mais informações</string>
+ <!--Navigation-->
+ <string name="show_download_log">Mostrar registo</string>
+ <string name="show_player_label">Mostrar reprodutor</string>
+ <!--Queue operations-->
+ <string name="clear_queue_label">Limpar fila</string>
+ <string name="organize_queue_label">Organizar fila</string>
+ <string name="undo">Anular</string>
+ <string name="removed_from_queue">Item removido</string>
+ <!--Flattr-->
+ <string name="flattr_auth_label">Sessão Flattr</string>
+ <string name="flattr_auth_explanation">Prima o botão abaixo para iniciar a autenticação. O seu navegador web abrirá o ecrã da sessão flattr e ser-lhe-á solicitada a permissão para o AntennaPod efetuar as alterações. Após ser dada a permissão, voltará novamente a este ecrã.</string>
+ <string name="authenticate_label">Autenticar</string>
+ <string name="return_home_label">Voltar ao ecrã</string>
+ <string name="flattr_auth_success">Autenticação efetuada! Já pode fazer o flattr com a aplicação.</string>
+ <string name="no_flattr_token_title">Token flattr não encontrado</string>
+ <string name="no_flattr_token_msg">Parece que a sua conta flattr não está vinculada ao AntennaPod. Pode vincular a sua conta ao AntennaPod ou aceder ao sítio web para fazer o flattr.</string>
+ <string name="authenticate_now_label">Autenticar</string>
+ <string name="action_forbidden_title">Ação negada</string>
+ <string name="action_forbidden_msg">O AntennaPod não possui as permissões para esta ação. É possível que o token de acesso ao flattr via AntennaPod tenha sido revogado. Pode efetuar nova autenticação ou aceder ao sítio web do item.</string>
+ <string name="access_revoked_title">Acesso revogado</string>
+ <string name="access_revoked_info">Você revogou o token de acesso do AntennaPod à sua conta. Para concluir o processo, tem que remover esta aplicação da lista de aplicações presentes nas definições de conta no sítio web do flattr.</string>
+ <string name="flattr_click_success">Flattered com sucesso!</string>
+ <string name="flattring_label">Flattring</string>
+ <!--Empty list labels-->
+ <string name="no_items_label">Não existem itens na lista.</string>
+ <string name="no_feeds_label">Ainda não possui quaisquer fontes.</string>
+ <!--Preferences-->
+ <string name="other_pref">Outras</string>
+ <string name="about_pref">Sobre</string>
+ <string name="queue_label">Fila</string>
+ <string name="pref_pauseOnHeadsetDisconnect_sum">Parar reprodução ao remover os auscultadores</string>
+ <string name="pref_followQueue_sum">Ir para a faixa seguinte ao terminar a reprodução</string>
+ <string name="playback_pref">Reprodução</string>
+ <string name="network_pref">Rede</string>
+ <string name="pref_autoUpdateIntervall_title">Intervalo entre atualizações</string>
+ <string name="pref_autoUpdateIntervall_sum">Indique o intervalo de tempo entre as atualizações de fontes ou desative a opção</string>
+ <string name="pref_downloadMediaOnWifiOnly_sum">Apenas transferir pelas redes sem fios</string>
+ <string name="pref_followQueue_title">Reprodução contínua</string>
+ <string name="pref_downloadMediaOnWifiOnly_title">Transferência Wi-Fi</string>
+ <string name="pref_pauseOnHeadsetDisconnect_title">Auscultadores removidos</string>
+ <string name="pref_mobileUpdate_title">Atualizações móveis</string>
+ <string name="pref_mobileUpdate_sum">Permitir atualizações através da rede de dados</string>
+ <string name="refreshing_label">A atualizar</string>
+ <string name="flattr_settings_label">Definições flattr</string>
+ <string name="pref_flattr_auth_title">Sessão flattr</string>
+ <string name="pref_flattr_auth_sum">Inicie sessão na sua conta flattr para fazer o flattr através do AntennaPod.</string>
+ <string name="pref_flattr_this_app_title">Flattr desta aplicação</string>
+ <string name="pref_flattr_this_app_sum">Ajude no desenvolvimento do AntennaPod através do Flattr. Obrigado!</string>
+ <string name="pref_revokeAccess_title">Revogar acesso</string>
+ <string name="pref_revokeAccess_sum">Revogar permissões de acesso da aplicação à sua conta flattr.</string>
+ <string name="pref_display_only_episodes_title">Mostrar apenas episódios</string>
+ <string name="pref_display_only_episodes_sum">Apenas mostrar itens que possuam episódios.</string>
+ <string name="user_interface_label">Interface</string>
+ <string name="pref_set_theme_title">Escolha o tema</string>
+ <string name="pref_set_theme_sum">Mudar o aspeto do AntennaPod.</string>
+ <string name="pref_automatic_download_title">Transferência automática</string>
+ <string name="pref_automatic_download_sum">Configure a transferência automática dos episódios.</string>
+ <string name="pref_autodl_wifi_filter_title">Ativar filtro Wi-Fi</string>
+ <string name="pref_autodl_wifi_filter_sum">Apenas permitir transferências automáticas através de redes sem fios.</string>
+ <string name="pref_episode_cache_title">Cache de episódios</string>
+ <string name="pref_theme_title_light">Claro</string>
+ <string name="pref_theme_title_dark">Escuro</string>
+ <string name="pref_episode_cache_unlimited">Sem limite</string>
+ <string name="pref_update_interval_hours_plural">horas</string>
+ <string name="pref_update_interval_hours_singular">hora</string>
+ <string name="pref_update_interval_hours_manual">Manual</string>
+ <!--Search-->
+ <string name="search_hint">Procurar fontes ou episódios</string>
+ <string name="found_in_shownotes_label">Encontrado nas notas</string>
+ <string name="found_in_chapters_label">Encontrado nos capítulos</string>
+ <string name="search_status_searching">A procurar...</string>
+ <string name="search_status_no_results">Nenhum resultado</string>
+ <string name="search_results_label">Resultados da procura</string>
+ <string name="search_term_label">Você procurou:\u0020</string>
+ <string name="search_label">Procura</string>
+ <string name="found_in_title_label">Encontrado no título</string>
+ <!--OPML import and export-->
+ <string name="opml_import_txtv_button_lable">Também pode importar ficheiros OPML. Estes ficheiros permitem-lhe mover os seus podcasts entre aplicações.</string>
+ <string name="opml_import_explanation">Para importar um ficheiro OPML, tem que o colocar neste diretório e premir o botão abaixo para iniciar o processo.</string>
+ <string name="start_import_label">Iniciar importação</string>
+ <string name="opml_import_label">Importação OPML</string>
+ <string name="opml_directory_error">Erro!</string>
+ <string name="reading_opml_label">A ler OPML</string>
+ <string name="opml_reader_error">Ocorreu um erro ao ler o ficheiro OPML:</string>
+ <string name="opml_import_error_dir_empty">O diretório de importação está vazio.</string>
+ <string name="select_all_label">Marcar tudo</string>
+ <string name="deselect_all_label">Desmarcar tudo</string>
+ <string name="choose_file_to_import_label">Escolha o ficheiro a importar</string>
+ <string name="opml_export_label">Exportação OPML</string>
+ <string name="exporting_label">A exportar...</string>
+ <string name="export_error_label">Erro de exportação</string>
+ <string name="opml_export_success_title">Exportação efetuada.</string>
+ <string name="opml_export_success_sum">O ficheiro .opml foi gravado em:\u0020</string>
+ <!--Sleep timer-->
+ <string name="set_sleeptimer_label">Definir temporizador</string>
+ <string name="disable_sleeptimer_label">Desativar temporizador</string>
+ <string name="enter_time_here_label">Introduza o tempo</string>
+ <string name="sleep_timer_label">Temporizador</string>
+ <string name="time_left_label">Tempo restante:\u0020</string>
+ <string name="time_dialog_invalid_input">Valor inválido. Tem que ser um inteiro.</string>
+ <!--Miro Guide-->
+ <string name="loading_categories_label">A carregar categorias...</string>
+ <string name="browse_miroguide_label">Explorar o guia Miro</string>
+ <string name="txtv_browse_miroguide_label">Ou explore o guia Miro:</string>
+ <string name="miro_guide_label">Guia Miro</string>
+ <string name="miro_search_hint">Procurar no guia Miro</string>
+ <string name="popular_label">Popular</string>
+ <string name="best_rating_label">Melhor avaliados</string>
+ <string name="add_feed_label">Adicionar fonte</string>
+ <string name="miro_feed_added">A fonte está a ser adicionada</string>
+ <!--Directory chooser-->
+ <string name="selected_folder_label">Diretório escolhido:</string>
+ <string name="create_folder_label">Criar diretório</string>
+ <string name="choose_data_directory">Escolha o diretório</string>
+ <string name="create_folder_msg">Criar um diretório com o nome \"%1$s\"?</string>
+ <string name="create_folder_success">Novo diretório criado</string>
+ <string name="create_folder_error_no_write_access">Não é possível gravar neste diretório</string>
+ <string name="create_folder_error_already_exists">O diretório já existe</string>
+ <string name="create_folder_error">Não é possível criar o diretório</string>
+ <string name="folder_not_empty_dialog_title">Diretório não vazio</string>
+ <string name="folder_not_empty_dialog_msg">O diretório escolhido não está vazio. As transferências serão colocadas neste diretório. Continuar?</string>
+ <string name="set_to_default_folder">Escolha a pasta pré-definida</string>
+</resources>
diff --git a/res/values-ro-rRO/strings.xml b/res/values-ro-rRO/strings.xml
index d0bc9fdec..c1320964a 100644
--- a/res/values-ro-rRO/strings.xml
+++ b/res/values-ro-rRO/strings.xml
@@ -32,6 +32,7 @@
<string name="external_storage_error_msg">Nu exista stocare externă. Asigurați-vă că stocarea externă este conectată pentru ca aplicația să funcționeze corespunzător.</string>
<string name="chapters_label">Capitole</string>
<string name="shownotes_label">Notițe</string>
+ <string name="most_recent_prefix">Cel mai recent episod:\u0020</string>
<string name="episodes_suffix">\u0020episoade</string>
<string name="published_prefix">Publicat:\u0020</string>
<string name="length_prefix">Durată:\u0020</string>
@@ -111,6 +112,8 @@
<!--Queue operations-->
<string name="clear_queue_label">Golește coada</string>
<string name="organize_queue_label">Organizează coada</string>
+ <string name="undo">Refă</string>
+ <string name="removed_from_queue">Element înlăturat</string>
<!--Flattr-->
<string name="flattr_auth_label">Flattr sign-in</string>
<string name="flattr_auth_explanation">Apăsați butonul de mai jos pentru a începe procesul de autentificare. Veți fi îndreptat spre pagina de logare flattr în browser și veți fi rugat să acordați permisiuni AntennaPod sa flattr. După ce veți acorda permisiunile veți fi readuși la acest ecran automat.</string>
@@ -163,6 +166,12 @@
<string name="pref_autodl_wifi_filter_title">Pornește filtru Wi-Fi</string>
<string name="pref_autodl_wifi_filter_sum">Pornește descărcarea automată doar pentru rețele Wi-Fi selectate.</string>
<string name="pref_episode_cache_title">Cache de episoade</string>
+ <string name="pref_theme_title_light">Deschis</string>
+ <string name="pref_theme_title_dark">Întunecat</string>
+ <string name="pref_episode_cache_unlimited">Nelimitat</string>
+ <string name="pref_update_interval_hours_plural">ore</string>
+ <string name="pref_update_interval_hours_singular">oră</string>
+ <string name="pref_update_interval_hours_manual">Manual</string>
<!--Search-->
<string name="search_hint">Caută feeduri sau episoade</string>
<string name="found_in_shownotes_label">Găsit în notițe</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index eac5da4b1..871803e2d 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -9,9 +9,9 @@
<string name="waiting_list_label">В ожидании</string>
<string name="settings_label">Настройки</string>
<string name="add_new_feed_label">Добавить канал</string>
- <string name="downloads_label">Закачки</string>
- <string name="cancel_download_label">Отменить закачку</string>
- <string name="download_log_label">Журнал закачек</string>
+ <string name="downloads_label">Загрузки</string>
+ <string name="cancel_download_label">Отменить загрузку</string>
+ <string name="download_log_label">Журнал загрузок</string>
<string name="playback_history_label">История воспроизведения</string>
<!--Webview actions-->
<string name="open_in_browser_label">Открыть в браузере</string>
@@ -22,16 +22,17 @@
<string name="clear_history_label">Очистить историю</string>
<!--Other-->
<string name="confirm_label">Подтвердить</string>
- <string name="cancel_label">Отменить</string>
+ <string name="cancel_label">Отмена</string>
<string name="author_label">Автор</string>
<string name="language_label">Язык</string>
<string name="cover_label">Обложка</string>
<string name="error_label">Ошибка</string>
<string name="error_msg_prefix">Произошла ошибка:</string>
<string name="refresh_label">Обновить</string>
- <string name="external_storage_error_msg">Внешний носитель данных недоступен. Убедитесь что внешний носитель смонтирован, иначе это приложение не сможет нормально работать.</string>
+ <string name="external_storage_error_msg">Внешний носитель недоступен. Убедитесь что внешний носитель смонтирован, иначе приложение не сможет нормально работать.</string>
<string name="chapters_label">Разделы</string>
<string name="shownotes_label">Описание</string>
+ <string name="most_recent_prefix">Следующий эпизод:\u0020</string>
<string name="episodes_suffix">\u0020 выпуск(ов)</string>
<string name="published_prefix">Опубликовано:\u0020</string>
<string name="length_prefix">Продолжительность:\u0020</string>
@@ -50,25 +51,25 @@
<string name="share_source_label">Поделиться ссылкой на канал</string>
<string name="feed_delete_confirmation_msg">Подтвердите удаление канала и ВСЕХ закачанных выпусков этого канала.</string>
<!--actions on feeditems-->
- <string name="download_label">Закачать</string>
- <string name="play_label">Слушать</string>
+ <string name="download_label">Загрузить</string>
+ <string name="play_label">Воспроизвести</string>
<string name="pause_label">Пауза</string>
- <string name="stream_label">Воспроизвести из сети</string>
+ <string name="stream_label">Потоковое воспроизведение</string>
<string name="remove_label">Удалить</string>
<string name="mark_read_label">Отметить как прочитанное</string>
<string name="mark_unread_label">Отметить как непрочитанное</string>
- <string name="add_to_queue_label">Поставить в очередь</string>
+ <string name="add_to_queue_label">Добавить в очередь</string>
<string name="remove_from_queue_label">Удалить из очереди</string>
<string name="visit_website_label">Посетить сайт</string>
<string name="support_label">Поддержать посредством Flattr</string>
- <string name="enqueue_all_new">Поставить в очередь все</string>
- <string name="download_all">Скачать все</string>
+ <string name="enqueue_all_new">Добавить все в очередь</string>
+ <string name="download_all">Загрузить все</string>
<string name="skip_episode_label">Пропустить выпуск</string>
<!--Download messages and labels-->
- <string name="download_successful">Закачка завершилась успешно</string>
- <string name="download_failed">Закачка </string>
- <string name="download_pending">Закачка в ожидании</string>
- <string name="download_running">Закачка в процессе</string>
+ <string name="download_successful">Загрузка завершена</string>
+ <string name="download_failed">Загрузка прервана</string>
+ <string name="download_pending">Загрузка в ожидании</string>
+ <string name="download_running">Загрузка в процессе</string>
<string name="download_error_device_not_found">Устройство хранения не найдено</string>
<string name="download_error_insufficient_space">Недостаточно свободного места</string>
<string name="download_error_file_error">Ошибка файла</string>
@@ -78,39 +79,41 @@
<string name="download_error_unsupported_type">Неподдерживаемый тип канала</string>
<string name="download_error_connection_error">Ошибка соединения</string>
<string name="download_error_unknown_host">Неизвестный хост</string>
- <string name="cancel_all_downloads_label">Отменить все закачки</string>
- <string name="download_cancelled_msg">Закачка отменена</string>
- <string name="download_report_title">Закачки завершены</string>
+ <string name="cancel_all_downloads_label">Отменить все загрузки</string>
+ <string name="download_cancelled_msg">Загрузка отменена</string>
+ <string name="download_report_title">Загрузки завершены</string>
<string name="download_error_malformed_url">Некорректная ссылка</string>
<string name="download_error_io_error">Ошибка ввода-вывода</string>
<string name="download_error_request_error">Ошибка запроса</string>
- <string name="downloads_left">\u0020закачек осталось</string>
+ <string name="downloads_left">Осталось\u0020загрузок</string>
<string name="download_notification_title">Получение данных подкаста</string>
- <string name="download_report_content">%1$d успешных закачек, %2$d неудачных</string>
+ <string name="download_report_content">%1$d загрузок завершено, %2$d не удалось</string>
<string name="download_log_title_unknown">Неизвестное название</string>
<string name="download_type_feed">Канал</string>
<string name="download_type_media">Медиа файл</string>
<string name="download_type_image">Изображение</string>
- <string name="download_request_error_dialog_message_prefix">Ошибка скачивания файла:\u0020</string>
+ <string name="download_request_error_dialog_message_prefix">Ошибка при загрузки файла:\u0020</string>
<!--Mediaplayer messages-->
<string name="player_error_msg">Ошибка!</string>
<string name="player_stopped_msg">Ничего не воспроизводится</string>
<string name="player_preparing_msg">Подготовка</string>
<string name="player_ready_msg">Готово</string>
- <string name="player_seeking_msg">Поиск</string>
- <string name="playback_error_server_died">Сервер отключился</string>
+ <string name="player_seeking_msg">Перемотка</string>
+ <string name="playback_error_server_died">Сервер отключен</string>
<string name="playback_error_unknown">Неизвестная ошибка</string>
<string name="no_media_playing_label">Ничего не воспроизводится</string>
<string name="position_default_label">00:00:00</string>
<string name="player_buffering_msg">Буферизация</string>
<string name="playbackservice_notification_title">Воспроизведение подкаста</string>
- <string name="playbackservice_notification_content">Для получения дополнительной информации нажмите здесь</string>
+ <string name="playbackservice_notification_content">Нажмите для получения дополнительной информации</string>
<!--Navigation-->
<string name="show_download_log">Показать журнал</string>
<string name="show_player_label">Показать проигрыватель</string>
<!--Queue operations-->
<string name="clear_queue_label">Очистить очередь</string>
<string name="organize_queue_label">Упорядочить очередь</string>
+ <string name="undo">Отмена</string>
+ <string name="removed_from_queue">Удален</string>
<!--Flattr-->
<string name="flattr_auth_label">Авторизоваться в Flattr</string>
<string name="flattr_auth_explanation">Нажмите кнопку чтобы начать процесс авторизации. Вы будете перенаправлены на сайт Flattr где вам нужно будет подключить AntennaPod к вашему аккаунту. После этого вы автоматически будете перенаправлены обратно.</string>
@@ -133,15 +136,15 @@
<string name="other_pref">Прочее</string>
<string name="about_pref">О программе</string>
<string name="queue_label">Очередь</string>
- <string name="pref_pauseOnHeadsetDisconnect_sum">Остановить воспроизведение когда наушники отсоединены</string>
+ <string name="pref_pauseOnHeadsetDisconnect_sum">Остановить воспроизведение, когда наушники отсоединены</string>
<string name="pref_followQueue_sum">После завершения воспроизведения перейти к следующему в очереди</string>
<string name="playback_pref">Воспроизведение</string>
<string name="network_pref">Сеть</string>
<string name="pref_autoUpdateIntervall_title">Интервал обновлений</string>
- <string name="pref_autoUpdateIntervall_sum">Укажите интервал через который каналы обновляются автоматически, или отключите это</string>
- <string name="pref_downloadMediaOnWifiOnly_sum">Закачивать файлы только по WiFi</string>
+ <string name="pref_autoUpdateIntervall_sum">Укажите интервал через который каналы обновляются автоматически, или отключите его</string>
+ <string name="pref_downloadMediaOnWifiOnly_sum">Загружать файлы только по Wi-Fi</string>
<string name="pref_followQueue_title">Непрерывное воспроизведение</string>
- <string name="pref_downloadMediaOnWifiOnly_title">Закачка по WiFi</string>
+ <string name="pref_downloadMediaOnWifiOnly_title">Загрузка по Wi-Fi</string>
<string name="pref_pauseOnHeadsetDisconnect_title">Наушники отсоединены</string>
<string name="pref_mobileUpdate_title">Мобильные обновления</string>
<string name="pref_mobileUpdate_sum">Обновлять каналы через мобильное интернет-подключение</string>
@@ -154,15 +157,21 @@
<string name="pref_revokeAccess_title">Отозвать доступ</string>
<string name="pref_revokeAccess_sum">Отменить доступ этого приложения к вашему аккаунту Flattr.</string>
<string name="pref_display_only_episodes_title">Показывать только выпуски</string>
- <string name="pref_display_only_episodes_sum">Показывать только те элементы списка которые содержат выпуски</string>
+ <string name="pref_display_only_episodes_sum">Показывать только те элементы списка, которые содержат выпуски</string>
<string name="user_interface_label">Интерфейс</string>
<string name="pref_set_theme_title">Выбор темы</string>
- <string name="pref_set_theme_sum">Изменить оформление AntennaPod</string>
+ <string name="pref_set_theme_sum">Изменить тему оформление AntennaPod</string>
<string name="pref_automatic_download_title">Автоматическая загрузка</string>
<string name="pref_automatic_download_sum">Настроить автоматическую загрузку выпусков.</string>
<string name="pref_autodl_wifi_filter_title">Включить фильтр Wi-Fi</string>
- <string name="pref_autodl_wifi_filter_sum">Разрешать автоматическую загрузку тольуо для выбранных Wi-Fi сетей.</string>
+ <string name="pref_autodl_wifi_filter_sum">Разрешать автоматическую загрузку только для выбранных Wi-Fi сетей.</string>
<string name="pref_episode_cache_title">Кэш выпусков</string>
+ <string name="pref_theme_title_light">Светлая</string>
+ <string name="pref_theme_title_dark">Тёмная</string>
+ <string name="pref_episode_cache_unlimited">Неограничен</string>
+ <string name="pref_update_interval_hours_plural">Часы</string>
+ <string name="pref_update_interval_hours_singular">Час</string>
+ <string name="pref_update_interval_hours_manual">Вручную</string>
<!--Search-->
<string name="search_hint">Поиск каналов или выпусков</string>
<string name="found_in_shownotes_label">Найдено в описании выпуска</string>
@@ -174,37 +183,37 @@
<string name="search_label">Поиск</string>
<string name="found_in_title_label">Найдено в заголовке</string>
<!--OPML import and export-->
- <string name="opml_import_txtv_button_lable">Также возможен импорт файла OPML. OPML файлы позволяют переносить ваши подписки с подкастами из одного приложения в другое.</string>
+ <string name="opml_import_txtv_button_lable">Также возможен импорт файла OPML. OPML файлы позволяют переносить ваши подписки из одного приложения в другое:</string>
<string name="opml_import_explanation">Для импорта файла OPML его нужно поместить каталог указанный ниже и нажать кнопку чтобы начать процесс импорта.</string>
<string name="start_import_label">Начать импорт</string>
<string name="opml_import_label">Импорт OPML</string>
<string name="opml_directory_error">ОШИБКА!</string>
<string name="reading_opml_label">Чтение файла OPML</string>
- <string name="opml_reader_error">Ошибка чтения документа OPML</string>
+ <string name="opml_reader_error">Ошибка чтения файла OPML</string>
<string name="opml_import_error_dir_empty">Каталог импорта пуст.</string>
<string name="select_all_label">Отметить все</string>
<string name="deselect_all_label">Снять все отметки</string>
<string name="choose_file_to_import_label">Выбрать файл для импорта</string>
- <string name="opml_export_label">Выгрузка в OPML</string>
- <string name="exporting_label">Выгружается...</string>
- <string name="export_error_label">Ошибка выгрузки</string>
- <string name="opml_export_success_title">Выгружено в OPML успешно</string>
+ <string name="opml_export_label">Экспорт в OPML</string>
+ <string name="exporting_label">Экспортируется...</string>
+ <string name="export_error_label">Ошибка экспорта</string>
+ <string name="opml_export_success_title">Экспорт OPML завершен</string>
<string name="opml_export_success_sum">OPML файл был записан в:\u0020</string>
<!--Sleep timer-->
<string name="set_sleeptimer_label">Установить таймер сна</string>
<string name="disable_sleeptimer_label">Отключить таймер сна</string>
- <string name="enter_time_here_label">Ввести время</string>
+ <string name="enter_time_here_label">Введите время</string>
<string name="sleep_timer_label">Таймер сна</string>
<string name="time_left_label">Времени осталось:\u0020</string>
<string name="time_dialog_invalid_input">Неправильный ввод, время должно быть в виде числа</string>
<!--Miro Guide-->
- <string name="loading_categories_label">Загружаются категории...</string>
- <string name="browse_miroguide_label">Просмотреть каталоги в проекте Miro</string>
- <string name="txtv_browse_miroguide_label">Или просмотреть каталоги в проекте Miro</string>
- <string name="miro_guide_label">Проект Miro</string>
- <string name="miro_search_hint">Поиск в проекте Miro</string>
+ <string name="loading_categories_label">Загрузка категорий...</string>
+ <string name="browse_miroguide_label">Посмотреть в каталоге Miro</string>
+ <string name="txtv_browse_miroguide_label">Или посмотреть в каталоге Miro</string>
+ <string name="miro_guide_label">Miro</string>
+ <string name="miro_search_hint">Поиск в Miro</string>
<string name="popular_label">Популярные</string>
- <string name="best_rating_label">Наивысший рейтинг</string>
+ <string name="best_rating_label">С высоким рейтингом</string>
<string name="add_feed_label">Добавить канал</string>
<string name="miro_feed_added">Канал добавляется</string>
<!--Directory chooser-->
@@ -217,6 +226,6 @@
<string name="create_folder_error_already_exists">Папка уже существует</string>
<string name="create_folder_error">Невозможно создать папку</string>
<string name="folder_not_empty_dialog_title">Папка не пуста</string>
- <string name="folder_not_empty_dialog_msg">Выбранная папка не пуста. Закачки и прочие файлы будут сохранены в эту папку. Продолжить?</string>
+ <string name="folder_not_empty_dialog_msg">Выбранная папка не пуста. Загрузки и прочие файлы будут сохранены в эту папку. Продолжить?</string>
<string name="set_to_default_folder">Выберите папку по умолчанию</string>
</resources>
diff --git a/res/values-uk-rUA/strings.xml b/res/values-uk-rUA/strings.xml
index b2b6e7786..84f51ab7d 100644
--- a/res/values-uk-rUA/strings.xml
+++ b/res/values-uk-rUA/strings.xml
@@ -11,7 +11,7 @@
<string name="add_new_feed_label">Додати канал</string>
<string name="downloads_label">Завантаження</string>
<string name="cancel_download_label">Скасувати завантаження</string>
- <string name="download_log_label">Завантаження</string>
+ <string name="download_log_label">Історія завантажень</string>
<string name="playback_history_label">Що грало</string>
<!--Webview actions-->
<string name="open_in_browser_label">Відкрити в браузері</string>
@@ -32,6 +32,7 @@
<string name="external_storage_error_msg">Немає доступної флешки. Зовнішній носій потрібен для коректної роботи додатку</string>
<string name="chapters_label">Глави</string>
<string name="shownotes_label">Нотатки до епізода</string>
+ <string name="most_recent_prefix">Найновіший епізод:\u0020</string>
<string name="episodes_suffix">\u0020епізодів</string>
<string name="published_prefix">Опубліковано:\u0020</string>
<string name="length_prefix">Довжина:\u0020</string>
@@ -53,11 +54,11 @@
<string name="download_label">Завантажити</string>
<string name="play_label">Грати</string>
<string name="pause_label">Пауза</string>
- <string name="stream_label">Поток</string>
+ <string name="stream_label">Прослухати без завантаження</string>
<string name="remove_label">Видалити</string>
<string name="mark_read_label">Прочитано</string>
<string name="mark_unread_label">Непрочитано</string>
- <string name="add_to_queue_label">Додати канал</string>
+ <string name="add_to_queue_label">Додати до черги</string>
<string name="remove_from_queue_label">Видалити з черги</string>
<string name="visit_website_label">Відкрити сайт</string>
<string name="support_label">Підтримати за допомогою Flattr</string>
@@ -111,14 +112,16 @@
<!--Queue operations-->
<string name="clear_queue_label">Очистити чергу</string>
<string name="organize_queue_label">Впорядкувати чергу</string>
+ <string name="undo">Скасувати</string>
+ <string name="removed_from_queue">Видалено</string>
<!--Flattr-->
- <string name="flattr_auth_label">Увійти дл Flattr</string>
+ <string name="flattr_auth_label">Увійти до Flattr</string>
<string name="flattr_auth_explanation">Нажміть цю кнопку для початку авторізації. Буде відкрито flattr в браузері, буде запит на дозвіл доступу Antennapod до flattr. Після надання доступу ви повернетесь до цього екрану автоматично</string>
<string name="authenticate_label">Ввісти ім\'я та пароль</string>
<string name="return_home_label">Повернення до початку</string>
<string name="flattr_auth_success">Вийшло авторізуватись. Тепер ви можете flattr things за допомогою додатку</string>
- <string name="no_flattr_token_title">Не має flattr token</string>
- <string name="no_flattr_token_msg">Здається ваш обліковий запис flattr не підєднано до AntennaPod. Ви можете або підєднати її або відкривати web сторінку в браузері</string>
+ <string name="no_flattr_token_title">Немає flattr token</string>
+ <string name="no_flattr_token_msg">Здається ваш обліковий запис flattr не під\'єднано до AntennaPod. Ви можете або під\'єднати її або відкривати web сторінку в браузері</string>
<string name="authenticate_now_label">Пароль та логін</string>
<string name="action_forbidden_title">Заборонено</string>
<string name="action_forbidden_msg">AntennaPod не маэ дозвілу це зробити. Можливо відкликаний доступ до AntennaPod. Або ввідіть логін пароль в налаштуваннях або зробить це на сайті</string>
@@ -157,12 +160,18 @@
<string name="pref_display_only_episodes_sum">Відображати тільки канали з наявними епізодами</string>
<string name="user_interface_label">Зовнішній вид</string>
<string name="pref_set_theme_title">Обрати тему</string>
- <string name="pref_set_theme_sum">Змінти появу AntennaPod</string>
+ <string name="pref_set_theme_sum">Змінити появу AntennaPod</string>
<string name="pref_automatic_download_title">Автоматичне завантаження</string>
<string name="pref_automatic_download_sum">Налаштування автоматичного завантаження епізодів</string>
<string name="pref_autodl_wifi_filter_title">Увімкнути фільтр Wi-Fi</string>
- <string name="pref_autodl_wifi_filter_sum">Дозволити автоматичне завантаження тільки для окремих Wi-Fi мережах</string>
+ <string name="pref_autodl_wifi_filter_sum">Дозволити автоматичне завантаження тільки в цих Wi-Fi мережах</string>
<string name="pref_episode_cache_title">Кеш епізодів</string>
+ <string name="pref_theme_title_light">Світла</string>
+ <string name="pref_theme_title_dark">Темна</string>
+ <string name="pref_episode_cache_unlimited">Без обмежень</string>
+ <string name="pref_update_interval_hours_plural">годин</string>
+ <string name="pref_update_interval_hours_singular">година</string>
+ <string name="pref_update_interval_hours_manual">Інструкція</string>
<!--Search-->
<string name="search_hint">Пошук каналів та епізодів</string>
<string name="found_in_shownotes_label">Знайдено у примітках</string>
@@ -174,7 +183,7 @@
<string name="search_label">Пошук</string>
<string name="found_in_title_label">Знайдено у назві</string>
<!--OPML import and export-->
- <string name="opml_import_txtv_button_lable">Ви можете імпортувати OPML файл. Такі файли дозволяют переходити з однієї програми до іншої:</string>
+ <string name="opml_import_txtv_button_lable">Ви можете імпортувати OPML файл. Такі файли дозволяют переходити з однієї програми для подкастів до іншої:</string>
<string name="opml_import_explanation">Для імпорту OPML файлу, скопіюйте його в цю папку та натіснить кнопку внизу для початку імпорту</string>
<string name="start_import_label">Почати імпорт</string>
<string name="opml_import_label">OPML імпорт</string>
@@ -202,7 +211,7 @@
<string name="browse_miroguide_label">Перегляд Miro Guide</string>
<string name="txtv_browse_miroguide_label">Або проглянути Miro Guide</string>
<string name="miro_guide_label">Miro Guide</string>
- <string name="miro_search_hint">Пошук Mirog Guide</string>
+ <string name="miro_search_hint">Пошук в Mirog Guide</string>
<string name="popular_label">Популярні</string>
<string name="best_rating_label">Кращі</string>
<string name="add_feed_label">Додати канал</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 5e2840536..f7bf7d493 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -32,6 +32,7 @@
<string name="external_storage_error_msg">没有可用的外部存储. 请确保安装外部存储器, 这样本应用才可以正常工作.</string>
<string name="chapters_label">章节</string>
<string name="shownotes_label">笔记</string>
+ <string name="most_recent_prefix">最近曲目:\u0020</string>
<string name="episodes_suffix">\u0020 曲</string>
<string name="published_prefix">发表:\u0020</string>
<string name="length_prefix">长度:\u0020</string>
@@ -48,20 +49,20 @@
<string name="remove_feed_label">删除订阅</string>
<string name="share_link_label">分享网站链接</string>
<string name="share_source_label">分享订阅链接</string>
- <string name="feed_delete_confirmation_msg">确认要删除这些订阅吗? 该订阅所有已经下载的插曲将一并删除. </string>
+ <string name="feed_delete_confirmation_msg">确认要删除这些订阅吗? 该订阅所有已经下载的曲目将一并删除. </string>
<!--actions on feeditems-->
<string name="download_label">下载</string>
<string name="play_label">播放</string>
<string name="pause_label">暂停</string>
- <string name="stream_label">流</string>
+ <string name="stream_label">流媒体</string>
<string name="remove_label">删除</string>
<string name="mark_read_label">标记已读</string>
<string name="mark_unread_label">标记未读</string>
- <string name="add_to_queue_label">添加到队列</string>
- <string name="remove_from_queue_label">从队列中删除</string>
+ <string name="add_to_queue_label">添加到播放列表</string>
+ <string name="remove_from_queue_label">从播放列表中删除</string>
<string name="visit_website_label">访问网站</string>
<string name="support_label">Flattr 他</string>
- <string name="enqueue_all_new">全部添加到队列</string>
+ <string name="enqueue_all_new">全部添加到播放列表</string>
<string name="download_all">全部下载</string>
<string name="skip_episode_label">跳过曲目</string>
<!--Download messages and labels-->
@@ -109,8 +110,10 @@
<string name="show_download_log">下载日志</string>
<string name="show_player_label">播放器</string>
<!--Queue operations-->
- <string name="clear_queue_label">清空队列</string>
- <string name="organize_queue_label">组织队列</string>
+ <string name="clear_queue_label">清空播放列表</string>
+ <string name="organize_queue_label">组织播放列表</string>
+ <string name="undo">撤消</string>
+ <string name="removed_from_queue">已删除项</string>
<!--Flattr-->
<string name="flattr_auth_label">Flattr 登录</string>
<string name="flattr_auth_explanation">按下面的按钮开始身份验证过程. 将在浏览器中打开 Flattr 登录界面并要求给予 AntennaPod 访问 Flattr 的权限. 权限许可后, 将自动回到这个界面.</string>
@@ -132,9 +135,9 @@
<!--Preferences-->
<string name="other_pref">其他</string>
<string name="about_pref">关于</string>
- <string name="queue_label">队列</string>
+ <string name="queue_label">清空播放</string>
<string name="pref_pauseOnHeadsetDisconnect_sum">当耳机是断开时暂停播放 </string>
- <string name="pref_followQueue_sum">播放完成跳转到队列下一项</string>
+ <string name="pref_followQueue_sum">播放完成跳转到播放列表下一项</string>
<string name="playback_pref">播放</string>
<string name="network_pref">网络</string>
<string name="pref_autoUpdateIntervall_title">更新周期</string>
@@ -163,8 +166,14 @@
<string name="pref_autodl_wifi_filter_title">打开 Wi-Fi 过滤器</string>
<string name="pref_autodl_wifi_filter_sum">只允许在 Wi-Fi 网络下自动下载</string>
<string name="pref_episode_cache_title">曲目缓存</string>
+ <string name="pref_theme_title_light">浅色</string>
+ <string name="pref_theme_title_dark">暗色</string>
+ <string name="pref_episode_cache_unlimited">无限</string>
+ <string name="pref_update_interval_hours_plural">小时</string>
+ <string name="pref_update_interval_hours_singular">时</string>
+ <string name="pref_update_interval_hours_manual">手动</string>
<!--Search-->
- <string name="search_hint">搜索订阅或者插曲</string>
+ <string name="search_hint">搜索订阅或者曲目</string>
<string name="found_in_shownotes_label">笔记中查找</string>
<string name="found_in_chapters_label">章节中查找</string>
<string name="search_status_searching">搜索中...</string>
@@ -199,11 +208,11 @@
<string name="time_dialog_invalid_input">无效的输入, 时间是一个整数</string>
<!--Miro Guide-->
<string name="loading_categories_label">加载目录中...</string>
- <string name="browse_miroguide_label">浏览 Miro 指南</string>
- <string name="txtv_browse_miroguide_label">或者浏览 Miro 指南</string>
- <string name="miro_guide_label">Miro 指南</string>
- <string name="miro_search_hint">搜索 Miro 指南</string>
- <string name="popular_label">受欢迎的</string>
+ <string name="browse_miroguide_label">浏览 Miro 频道</string>
+ <string name="txtv_browse_miroguide_label">或者浏览 Miro 频道</string>
+ <string name="miro_guide_label">Miro 频道</string>
+ <string name="miro_search_hint">搜索 Miro 频道</string>
+ <string name="popular_label">流行</string>
<string name="best_rating_label">评分最高</string>
<string name="add_feed_label">添加订阅</string>
<string name="miro_feed_added">订阅已被添加</string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b79c561f4..b3f9975cb 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1,5 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
-<resources>
+<resources
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:ignore="MissingTranslation"
+ >
<!-- Activitiy titles -->
<string name="app_name">AntennaPod</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index f32ea3894..d08b8060f 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="Theme.AntennaPod.Light" parent="@style/Theme.Sherlock.Light.ForceOverflow">
- <item name="vpiTabPageIndicatorStyle">@style/AntennaPod.LightTabPageIndicator</item>
+ <style name="Theme.AntennaPod.Light" parent="@style/Theme.AppCompat.Light">
<item name="attr/action_about">@drawable/action_about</item>
<item name="attr/action_search">@drawable/action_search</item>
<item name="attr/action_settings">@drawable/action_settings</item>
@@ -38,21 +37,7 @@
<item name="attr/dragview_float_background">@color/white</item>
</style>
- <style name="AntennaPod.TabPageIndicator" parent="Widget.TabPageIndicator">
- <item name="android:paddingTop">16dp</item>
- <item name="android:paddingLeft">8dp</item>
- <item name="android:paddingRight">8dp</item>
- <item name="android:paddingBottom">16dp</item>
- <item name="android:textSize">12sp</item>
- <item name="android:textStyle">bold</item>
- </style>
-
- <style name="AntennaPod.LightTabPageIndicator" parent="AntennaPod.TabPageIndicator">
- <item name="android:textColor">@color/black</item>
- </style>
-
- <style name="Theme.AntennaPod.Dark" parent="@style/Theme.Sherlock.ForceOverflow">
- <item name="vpiTabPageIndicatorStyle">@style/AntennaPod.DarkTabPageIndicator</item>
+ <style name="Theme.AntennaPod.Dark" parent="@style/Theme.AppCompat">
<item name="attr/action_about">@drawable/action_about_dark</item>
<item name="attr/action_search">@drawable/action_search_dark</item>
<item name="attr/action_settings">@drawable/action_settings_dark</item>
@@ -88,10 +73,6 @@
<item name="attr/dragview_float_background">@color/black</item>
</style>
- <style name="AntennaPod.DarkTabPageIndicator" parent="AntennaPod.TabPageIndicator">
- <item name="android:textColor">#FFFFFF</item>
- </style>
-
<style name="UndoBar">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">48dp</item>
@@ -102,9 +83,7 @@
<item name="android:orientation">horizontal</item>
<item name="android:background">@drawable/undobar</item>
<item name="android:clickable">true</item>
- <item name="android:showDividers">middle</item>
<item name="android:divider">@drawable/undobar_divider</item>
- <item name="android:dividerPadding">10dp</item>
</style>
<style name="UndoBarMessage">
<item name="android:layout_width">0dp</item>
@@ -125,7 +104,6 @@
<item name="android:drawableLeft">@drawable/ic_undobar_undo</item>
<item name="android:drawablePadding">12dp</item>
<item name="android:textAppearance">?android:textAppearanceSmall</item>
- <item name="android:textAllCaps">true</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">#fff</item>
<item name="android:text">@string/undo</item>
diff --git a/res/xml/searchable.xml b/res/xml/searchable.xml
index 522bd9be5..ee73aca8d 100644
--- a/res/xml/searchable.xml
+++ b/res/xml/searchable.xml
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
-<searchable xmlns:android="http://schemas.android.com/apk/res/android" android:hint="@string/search_hint" android:label="@string/app_name" android:icon="@drawable/ic_launcher">
-
-
-</searchable> \ No newline at end of file
+<searchable xmlns:android="http://schemas.android.com/apk/res/android"
+ android:hint="@string/search_hint"
+ android:label="@string/app_name"/> \ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 000000000..6de43d84c
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':submodules:dslv:library'
diff --git a/src/de/danoeh/antennapod/PodcastApp.java b/src/de/danoeh/antennapod/PodcastApp.java
index e9f46256b..2141f71e8 100644
--- a/src/de/danoeh/antennapod/PodcastApp.java
+++ b/src/de/danoeh/antennapod/PodcastApp.java
@@ -5,7 +5,6 @@ import android.content.res.Configuration;
import android.util.Log;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.feed.EventDistributor;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.PlaybackPreferences;
import de.danoeh.antennapod.preferences.UserPreferences;
@@ -32,8 +31,6 @@ public class PodcastApp extends Application {
UserPreferences.createInstance(this);
PlaybackPreferences.createInstance(this);
EventDistributor.getInstance();
- FeedManager manager = FeedManager.getInstance();
- manager.loadDBData(getApplicationContext());
}
@Override
diff --git a/src/de/danoeh/antennapod/activity/AboutActivity.java b/src/de/danoeh/antennapod/activity/AboutActivity.java
index e3265d1eb..27fdbe241 100644
--- a/src/de/danoeh/antennapod/activity/AboutActivity.java
+++ b/src/de/danoeh/antennapod/activity/AboutActivity.java
@@ -1,15 +1,15 @@
package de.danoeh.antennapod.activity;
import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
import android.webkit.WebView;
import android.webkit.WebViewClient;
-import com.actionbarsherlock.app.SherlockActivity;
import de.danoeh.antennapod.R;
/** Displays the 'about' screen */
-public class AboutActivity extends SherlockActivity {
+public class AboutActivity extends ActionBarActivity {
private WebView webview;
diff --git a/src/de/danoeh/antennapod/activity/AddFeedActivity.java b/src/de/danoeh/antennapod/activity/AddFeedActivity.java
index 39434fa87..4085fc8d2 100644
--- a/src/de/danoeh/antennapod/activity/AddFeedActivity.java
+++ b/src/de/danoeh/antennapod/activity/AddFeedActivity.java
@@ -2,6 +2,11 @@ package de.danoeh.antennapod.activity;
import java.util.Date;
+import android.support.v7.app.ActionBarActivity;
+import android.view.Menu;
+import android.view.MenuItem;
+import org.apache.commons.lang3.StringUtils;
+
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
@@ -13,10 +18,6 @@ import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
-import com.actionbarsherlock.app.SherlockActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.feed.Feed;
@@ -29,7 +30,7 @@ import de.danoeh.antennapod.util.StorageUtils;
import de.danoeh.antennapod.util.URLChecker;
/** Activity for adding a Feed */
-public class AddFeedActivity extends SherlockActivity {
+public class AddFeedActivity extends ActionBarActivity {
private static final String TAG = "AddFeedActivity";
private DownloadRequester requester;
@@ -44,6 +45,9 @@ public class AddFeedActivity extends SherlockActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Was started with Intent " + getIntent().getAction()
+ + " and Data " + getIntent().getDataString());
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
@@ -54,6 +58,10 @@ public class AddFeedActivity extends SherlockActivity {
progDialog = new ProgressDialog(this);
etxtFeedurl = (EditText) findViewById(R.id.etxtFeedurl);
+ if (StringUtils.equals(getIntent().getAction(), Intent.ACTION_VIEW)) {
+ etxtFeedurl.setText(getIntent().getDataString());
+ }
+
butBrowseMiroGuide = (Button) findViewById(R.id.butBrowseMiroguide);
butOpmlImport = (Button) findViewById(R.id.butOpmlImport);
butConfirm = (Button) findViewById(R.id.butConfirm);
@@ -101,7 +109,7 @@ public class AddFeedActivity extends SherlockActivity {
if (intent.getAction() != null
&& intent.getAction().equals(Intent.ACTION_SEND)) {
if (AppConfig.DEBUG)
- Log.d(TAG, "Was started with ACTION_SEND intent");
+ Log.d(TAG, "Resuming with ACTION_SEND intent");
String text = intent.getStringExtra(Intent.EXTRA_TEXT);
if (text != null) {
etxtFeedurl.setText(text);
@@ -152,7 +160,7 @@ public class AddFeedActivity extends SherlockActivity {
}
@Override
- public void onConnectionFailure(int reason) {
+ public void onConnectionFailure(DownloadError reason) {
handleDownloadError(reason);
}
});
@@ -168,11 +176,11 @@ public class AddFeedActivity extends SherlockActivity {
progDialog.setMessage(getString(R.string.loading_label));
}
- private void handleDownloadError(int reason) {
+ private void handleDownloadError(DownloadError reason) {
final AlertDialog errorDialog = new AlertDialog.Builder(this).create();
errorDialog.setTitle(R.string.error_label);
errorDialog.setMessage(getString(R.string.error_msg_prefix) + " "
- + DownloadError.getErrorString(this, reason));
+ + reason.getErrorString(this));
errorDialog.setButton(getString(android.R.string.ok),
new DialogInterface.OnClickListener() {
@Override
@@ -191,6 +199,8 @@ public class AddFeedActivity extends SherlockActivity {
return true;
}
+
+
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
diff --git a/src/de/danoeh/antennapod/activity/AudioplayerActivity.java b/src/de/danoeh/antennapod/activity/AudioplayerActivity.java
index f43f29db3..cf82d936d 100644
--- a/src/de/danoeh/antennapod/activity/AudioplayerActivity.java
+++ b/src/de/danoeh/antennapod/activity/AudioplayerActivity.java
@@ -7,9 +7,11 @@ import android.content.res.TypedArray;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
+import android.support.v4.app.ListFragment;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
+import android.view.Window;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageButton;
@@ -17,9 +19,6 @@ import android.widget.ImageView.ScaleType;
import android.widget.ListView;
import android.widget.TextView;
-import com.actionbarsherlock.app.SherlockListFragment;
-import com.actionbarsherlock.view.Window;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.ChapterListAdapter;
@@ -49,7 +48,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
private CoverFragment coverFragment;
private ItemDescriptionFragment descriptionFragment;
- private SherlockListFragment chapterFragment;
+ private ListFragment chapterFragment;
private Fragment currentlyShownFragment;
private int currentlyShownPosition = -1;
@@ -250,8 +249,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
/**
* Changes the currently displayed fragment.
*
- * @param Must
- * be POS_COVER, POS_DESCR, or POS_CHAPTERS
+ * @param pos Must be POS_COVER, POS_DESCR, or POS_CHAPTERS
* */
private void switchToFragment(int pos) {
if (AppConfig.DEBUG)
@@ -282,7 +280,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
break;
case POS_CHAPTERS:
if (chapterFragment == null) {
- chapterFragment = new SherlockListFragment() {
+ chapterFragment = new ListFragment() {
@Override
public void onListItemClick(ListView l, View v,
diff --git a/src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java b/src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java
index 54c4f0589..984491174 100644
--- a/src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java
+++ b/src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java
@@ -12,7 +12,11 @@ import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;
import android.os.FileObserver;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
@@ -24,11 +28,6 @@ import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
-import com.actionbarsherlock.app.SherlockActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuInflater;
-import com.actionbarsherlock.view.MenuItem;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.preferences.UserPreferences;
@@ -37,7 +36,7 @@ import de.danoeh.antennapod.preferences.UserPreferences;
* Let's the user choose a directory on the storage device. The selected folder
* will be sent back to the starting activity as an activity result.
*/
-public class DirectoryChooserActivity extends SherlockActivity {
+public class DirectoryChooserActivity extends ActionBarActivity {
private static final String TAG = "DirectoryChooserActivity";
private static final String CREATE_DIRECTORY_NAME = "AntennaPod";
diff --git a/src/de/danoeh/antennapod/activity/DownloadActivity.java b/src/de/danoeh/antennapod/activity/DownloadActivity.java
index 10ebb1285..40c75d336 100644
--- a/src/de/danoeh/antennapod/activity/DownloadActivity.java
+++ b/src/de/danoeh/antennapod/activity/DownloadActivity.java
@@ -11,21 +11,23 @@ import android.content.res.TypedArray;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.ActionBarActivity;
+import android.support.v7.view.ActionMode;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
-import com.actionbarsherlock.app.SherlockListActivity;
-import com.actionbarsherlock.view.ActionMode;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
+import android.widget.ListView;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.DownloadlistAdapter;
-import de.danoeh.antennapod.asynctask.DownloadStatus;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.service.download.DownloadRequest;
import de.danoeh.antennapod.service.download.DownloadService;
import de.danoeh.antennapod.storage.DownloadRequester;
@@ -33,221 +35,228 @@ import de.danoeh.antennapod.storage.DownloadRequester;
* Shows all running downloads in a list. The list objects are DownloadStatus
* objects created by a DownloadObserver.
*/
-public class DownloadActivity extends SherlockListActivity implements
- ActionMode.Callback {
-
- private static final String TAG = "DownloadActivity";
- private static final int MENU_SHOW_LOG = 0;
- private static final int MENU_CANCEL_ALL_DOWNLOADS = 1;
- private DownloadlistAdapter dla;
- private DownloadRequester requester;
-
- private ActionMode mActionMode;
- private DownloadStatus selectedDownload;
-
- private DownloadService downloadService = null;
- boolean mIsBound;
-
- private AsyncTask<Void, Void, Void> contentRefresher;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- setTheme(UserPreferences.getTheme());
- super.onCreate(savedInstanceState);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Creating Activity");
- requester = DownloadRequester.getInstance();
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- unbindService(mConnection);
- unregisterReceiver(contentChanged);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- registerReceiver(contentChanged, new IntentFilter(
- DownloadService.ACTION_DOWNLOADS_CONTENT_CHANGED));
- bindService(new Intent(this, DownloadService.class), mConnection, 0);
- startContentRefresher();
- if (dla != null) {
- dla.notifyDataSetChanged();
- }
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- if (AppConfig.DEBUG)
- Log.d(TAG, "Stopping Activity");
- stopContentRefresher();
- }
-
- private ServiceConnection mConnection = new ServiceConnection() {
- public void onServiceDisconnected(ComponentName className) {
- downloadService = null;
- mIsBound = false;
- Log.i(TAG, "Closed connection with DownloadService.");
- }
-
- public void onServiceConnected(ComponentName name, IBinder service) {
- downloadService = ((DownloadService.LocalBinder) service)
- .getService();
- mIsBound = true;
- if (AppConfig.DEBUG)
- Log.d(TAG, "Connection to service established");
- dla = new DownloadlistAdapter(DownloadActivity.this, 0,
- downloadService.getDownloads());
- setListAdapter(dla);
- dla.notifyDataSetChanged();
- }
- };
-
- @SuppressLint("NewApi")
- private void startContentRefresher() {
- if (contentRefresher != null) {
- contentRefresher.cancel(true);
- }
- contentRefresher = new AsyncTask<Void, Void, Void>() {
- private final int WAITING_INTERVALL = 1000;
-
- @Override
- protected void onProgressUpdate(Void... values) {
- super.onProgressUpdate(values);
- if (dla != null) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Refreshing content automatically");
- dla.notifyDataSetChanged();
- }
- }
-
- @Override
- protected Void doInBackground(Void... params) {
- while (!isCancelled()) {
- try {
- Thread.sleep(WAITING_INTERVALL);
- publishProgress();
- } catch (InterruptedException e) {
- return null;
- }
- }
- return null;
- }
- };
- if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
- contentRefresher.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- } else {
- contentRefresher.execute();
- }
- }
-
- private void stopContentRefresher() {
- if (contentRefresher != null) {
- contentRefresher.cancel(true);
- }
- }
-
- @Override
- protected void onPostCreate(Bundle savedInstanceState) {
- super.onPostCreate(savedInstanceState);
- getListView().setOnItemLongClickListener(new OnItemLongClickListener() {
-
- @Override
- public boolean onItemLongClick(AdapterView<?> arg0, View view,
- int position, long id) {
- DownloadStatus selection = dla.getItem(position).getStatus();
- if (selection != null && mActionMode != null) {
- mActionMode.finish();
- }
- dla.setSelectedItemIndex(position);
- selectedDownload = selection;
- mActionMode = startActionMode(DownloadActivity.this);
- return true;
- }
-
- });
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- menu.add(Menu.NONE, MENU_SHOW_LOG, Menu.NONE,
- R.string.show_download_log).setShowAsAction(
- MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
- menu.add(Menu.NONE, MENU_CANCEL_ALL_DOWNLOADS, Menu.NONE,
- R.string.cancel_all_downloads_label).setShowAsAction(
- MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- Intent intent = new Intent(this, MainActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
- | Intent.FLAG_ACTIVITY_NEW_TASK);
- startActivity(intent);
- break;
- case MENU_SHOW_LOG:
- startActivity(new Intent(this, DownloadLogActivity.class));
- break;
- case MENU_CANCEL_ALL_DOWNLOADS:
- requester.cancelAllDownloads(this);
- break;
- }
- return true;
- }
-
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- if (!selectedDownload.isDone()) {
- TypedArray drawables = obtainStyledAttributes(new int[] { R.attr.navigation_cancel });
- menu.add(Menu.NONE, R.id.cancel_download_item, Menu.NONE,
- R.string.cancel_download_label).setIcon(
- drawables.getDrawable(0));
- }
- return true;
- }
-
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- return false;
- }
-
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- boolean handled = false;
- switch (item.getItemId()) {
- case R.id.cancel_download_item:
- requester.cancelDownload(this, selectedDownload.getFeedFile());
- handled = true;
- break;
- }
- mActionMode.finish();
- return handled;
- }
-
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- mActionMode = null;
- selectedDownload = null;
- dla.setSelectedItemIndex(DownloadlistAdapter.SELECTION_NONE);
- }
-
- private BroadcastReceiver contentChanged = new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (dla != null) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Refreshing content");
- dla.notifyDataSetChanged();
- }
- }
- };
+public class DownloadActivity extends ActionBarActivity implements
+ ActionMode.Callback {
+
+ private static final String TAG = "DownloadActivity";
+ private static final int MENU_SHOW_LOG = 0;
+ private static final int MENU_CANCEL_ALL_DOWNLOADS = 1;
+ private DownloadlistAdapter dla;
+ private DownloadRequester requester;
+
+ private ActionMode mActionMode;
+ private DownloadRequest selectedDownload;
+
+ private DownloadService downloadService = null;
+ boolean mIsBound;
+
+ private AsyncTask<Void, Void, Void> contentRefresher;
+
+ private ListView listview;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.listview_activity);
+
+ listview = (ListView) findViewById(R.id.listview);
+
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Creating Activity");
+ requester = DownloadRequester.getInstance();
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ unbindService(mConnection);
+ unregisterReceiver(contentChanged);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ registerReceiver(contentChanged, new IntentFilter(
+ DownloadService.ACTION_DOWNLOADS_CONTENT_CHANGED));
+ bindService(new Intent(this, DownloadService.class), mConnection, 0);
+ startContentRefresher();
+ if (dla != null) {
+ dla.notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Stopping Activity");
+ stopContentRefresher();
+ }
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+ public void onServiceDisconnected(ComponentName className) {
+ downloadService = null;
+ mIsBound = false;
+ Log.i(TAG, "Closed connection with DownloadService.");
+ }
+
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ downloadService = ((DownloadService.LocalBinder) service)
+ .getService();
+ mIsBound = true;
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Connection to service established");
+ dla = new DownloadlistAdapter(DownloadActivity.this, 0,
+ downloadService.getDownloads());
+ listview.setAdapter(dla);
+ dla.notifyDataSetChanged();
+ }
+ };
+
+ @SuppressLint("NewApi")
+ private void startContentRefresher() {
+ if (contentRefresher != null) {
+ contentRefresher.cancel(true);
+ }
+ contentRefresher = new AsyncTask<Void, Void, Void>() {
+ private final int WAITING_INTERVALL = 1000;
+
+ @Override
+ protected void onProgressUpdate(Void... values) {
+ super.onProgressUpdate(values);
+ if (dla != null) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Refreshing content automatically");
+ dla.notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ while (!isCancelled()) {
+ try {
+ Thread.sleep(WAITING_INTERVALL);
+ publishProgress();
+ } catch (InterruptedException e) {
+ return null;
+ }
+ }
+ return null;
+ }
+ };
+ if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
+ contentRefresher.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ } else {
+ contentRefresher.execute();
+ }
+ }
+
+ private void stopContentRefresher() {
+ if (contentRefresher != null) {
+ contentRefresher.cancel(true);
+ }
+ }
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ listview.setOnItemLongClickListener(new OnItemLongClickListener() {
+
+ @Override
+ public boolean onItemLongClick(AdapterView<?> arg0, View view,
+ int position, long id) {
+ DownloadRequest selection = dla.getItem(position)
+ .getDownloadRequest();
+ if (selection != null && mActionMode != null) {
+ mActionMode.finish();
+ }
+ dla.setSelectedItemIndex(position);
+ selectedDownload = selection;
+ mActionMode = startSupportActionMode(DownloadActivity.this);
+ return true;
+ }
+
+ });
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, MENU_SHOW_LOG, Menu.NONE,
+ R.string.show_download_log),
+ MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
+ MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, MENU_CANCEL_ALL_DOWNLOADS, Menu.NONE,
+ R.string.cancel_all_downloads_label),
+ MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ Intent intent = new Intent(this, MainActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
+ | Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ break;
+ case MENU_SHOW_LOG:
+ startActivity(new Intent(this, DownloadLogActivity.class));
+ break;
+ case MENU_CANCEL_ALL_DOWNLOADS:
+ requester.cancelAllDownloads(this);
+ break;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ if (selectedDownload != null) {
+ TypedArray drawables = obtainStyledAttributes(new int[]{R.attr.navigation_cancel});
+ menu.add(Menu.NONE, R.id.cancel_download_item, Menu.NONE,
+ R.string.cancel_download_label).setIcon(
+ drawables.getDrawable(0));
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ boolean handled = false;
+ switch (item.getItemId()) {
+ case R.id.cancel_download_item:
+ requester.cancelDownload(this, selectedDownload.getSource());
+ handled = true;
+ break;
+ }
+ mActionMode.finish();
+ return handled;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ mActionMode = null;
+ selectedDownload = null;
+ dla.setSelectedItemIndex(DownloadlistAdapter.SELECTION_NONE);
+ }
+
+ private BroadcastReceiver contentChanged = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (dla != null) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Refreshing content");
+ dla.notifyDataSetChanged();
+ }
+ }
+ };
}
diff --git a/src/de/danoeh/antennapod/activity/DownloadLogActivity.java b/src/de/danoeh/antennapod/activity/DownloadLogActivity.java
index 232a7ba1d..5e48371b8 100644
--- a/src/de/danoeh/antennapod/activity/DownloadLogActivity.java
+++ b/src/de/danoeh/antennapod/activity/DownloadLogActivity.java
@@ -1,35 +1,47 @@
package de.danoeh.antennapod.activity;
+import android.os.AsyncTask;
import android.os.Bundle;
-import com.actionbarsherlock.app.SherlockListActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
+import android.support.v7.app.ActionBarActivity;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.ListView;
+import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.DownloadLogAdapter;
import de.danoeh.antennapod.feed.EventDistributor;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.service.download.DownloadStatus;
+import de.danoeh.antennapod.storage.DBReader;
+
+import java.util.List;
/**
- * Displays completed and failed downloads in a list. The data comes from the
- * FeedManager.
+ * Displays completed and failed downloads in a list.
*/
-public class DownloadLogActivity extends SherlockListActivity {
+public class DownloadLogActivity extends ActionBarActivity {
private static final String TAG = "DownloadLogActivity";
- DownloadLogAdapter dla;
- FeedManager manager;
+ private List<DownloadStatus> downloadLog;
+ private DownloadLogAdapter dla;
+
+ private ListView listview;
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
- manager = FeedManager.getInstance();
+ setContentView(R.layout.listview_activity);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
- dla = new DownloadLogAdapter(this);
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- setListAdapter(dla);
+ listview = (ListView) findViewById(R.id.listview);
+
+ dla = new DownloadLogAdapter(this, itemAccess);
+ listview.setAdapter(dla);
+ loadData();
}
@Override
@@ -62,12 +74,48 @@ public class DownloadLogActivity extends SherlockListActivity {
return true;
}
+ private void loadData() {
+ AsyncTask<Void, Void, List<DownloadStatus>> loadTask = new AsyncTask<Void, Void, List<DownloadStatus>>() {
+ @Override
+ protected List<DownloadStatus> doInBackground(Void... voids) {
+ return DBReader.getDownloadLog(DownloadLogActivity.this);
+ }
+
+ @Override
+ protected void onPostExecute(List<DownloadStatus> downloadStatuses) {
+ super.onPostExecute(downloadStatuses);
+ if (downloadStatuses != null) {
+ downloadLog = downloadStatuses;
+ if (dla != null) {
+ dla.notifyDataSetChanged();
+ }
+ } else {
+ Log.e(TAG, "Could not load download log");
+ }
+ }
+ };
+ loadTask.execute();
+ }
+
+ private DownloadLogAdapter.ItemAccess itemAccess = new DownloadLogAdapter.ItemAccess() {
+
+ @Override
+ public int getCount() {
+ return (downloadLog != null) ? downloadLog.size() : 0;
+ }
+
+ @Override
+ public DownloadStatus getItem(int position) {
+ return (downloadLog != null) ? downloadLog.get(position) : null;
+ }
+ };
+
private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
@Override
public void update(EventDistributor eventDistributor, Integer arg) {
if ((arg & EventDistributor.DOWNLOADLOG_UPDATE) != 0) {
- dla.notifyDataSetChanged();
+ loadData();
}
}
};
diff --git a/src/de/danoeh/antennapod/activity/FeedInfoActivity.java b/src/de/danoeh/antennapod/activity/FeedInfoActivity.java
index c57a5794b..4a8a2f1f8 100644
--- a/src/de/danoeh/antennapod/activity/FeedInfoActivity.java
+++ b/src/de/danoeh/antennapod/activity/FeedInfoActivity.java
@@ -1,28 +1,28 @@
package de.danoeh.antennapod.activity;
+import android.os.AsyncTask;
import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.widget.ImageView;
import android.widget.TextView;
-import com.actionbarsherlock.app.SherlockActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuInflater;
-import com.actionbarsherlock.view.MenuItem;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.feed.Feed;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.util.LangUtils;
import de.danoeh.antennapod.util.menuhandler.FeedMenuHandler;
/** Displays information about a feed. */
-public class FeedInfoActivity extends SherlockActivity {
+public class FeedInfoActivity extends ActionBarActivity {
private static final String TAG = "FeedInfoActivity";
public static final String EXTRA_FEED_ID = "de.danoeh.antennapod.extra.feedId";
@@ -42,47 +42,66 @@ public class FeedInfoActivity extends SherlockActivity {
setContentView(R.layout.feedinfo);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
long feedId = getIntent().getLongExtra(EXTRA_FEED_ID, -1);
- FeedManager manager = FeedManager.getInstance();
- feed = manager.getFeed(feedId);
- if (feed != null) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Language is " + feed.getLanguage());
- if (AppConfig.DEBUG)
- Log.d(TAG, "Author is " + feed.getAuthor());
- imgvCover = (ImageView) findViewById(R.id.imgvCover);
- txtvTitle = (TextView) findViewById(R.id.txtvTitle);
- txtvDescription = (TextView) findViewById(R.id.txtvDescription);
- txtvLanguage = (TextView) findViewById(R.id.txtvLanguage);
- txtvAuthor = (TextView) findViewById(R.id.txtvAuthor);
- imgvCover.post(new Runnable() {
-
- @Override
- public void run() {
- ImageLoader.getInstance().loadThumbnailBitmap(
- feed.getImage(), imgvCover);
- }
- });
+
+ AsyncTask<Long, Void, Feed> loadTask = new AsyncTask<Long, Void, Feed>() {
- txtvTitle.setText(feed.getTitle());
- txtvDescription.setText(feed.getDescription());
- if (feed.getAuthor() != null) {
- txtvAuthor.setText(feed.getAuthor());
+ @Override
+ protected Feed doInBackground(Long... params) {
+ return DBReader.getFeed(FeedInfoActivity.this, params[0]);
}
- if (feed.getLanguage() != null) {
- txtvLanguage.setText(LangUtils.getLanguageString(feed
- .getLanguage()));
- }
- } else {
- Log.e(TAG, "Activity was started with invalid arguments");
- }
+ @Override
+ protected void onPostExecute(Feed result) {
+ super.onPostExecute(result);
+ if (result != null) {
+ feed = result;
+ if (feed != null) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Language is " + feed.getLanguage());
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Author is " + feed.getAuthor());
+ imgvCover = (ImageView) findViewById(R.id.imgvCover);
+ txtvTitle = (TextView) findViewById(R.id.txtvTitle);
+ txtvDescription = (TextView) findViewById(R.id.txtvDescription);
+ txtvLanguage = (TextView) findViewById(R.id.txtvLanguage);
+ txtvAuthor = (TextView) findViewById(R.id.txtvAuthor);
+ imgvCover.post(new Runnable() {
+
+ @Override
+ public void run() {
+ ImageLoader.getInstance().loadThumbnailBitmap(
+ feed.getImage(), imgvCover);
+ }
+ });
+
+ txtvTitle.setText(feed.getTitle());
+ txtvDescription.setText(feed.getDescription());
+ if (feed.getAuthor() != null) {
+ txtvAuthor.setText(feed.getAuthor());
+ }
+ if (feed.getLanguage() != null) {
+ txtvLanguage.setText(LangUtils
+ .getLanguageString(feed.getLanguage()));
+ }
+ supportInvalidateOptionsMenu();
+ }
+ } else {
+ Log.e(TAG, "Activity was started with invalid arguments");
+ }
+ }
+ };
+ loadTask.execute(feedId);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
- MenuInflater inflater = new MenuInflater(this);
- inflater.inflate(R.menu.feedinfo, menu);
- return true;
+ if (feed != null) {
+ MenuInflater inflater = new MenuInflater(this);
+ inflater.inflate(R.menu.feedinfo, menu);
+ return true;
+ } else {
+ return false;
+ }
}
@Override
@@ -105,7 +124,8 @@ public class FeedInfoActivity extends SherlockActivity {
return FeedMenuHandler.onOptionsItemClicked(this, item, feed);
} catch (DownloadRequestException e) {
e.printStackTrace();
- DownloadRequestErrorDialogCreator.newRequestErrorDialog(this, e.getMessage());
+ DownloadRequestErrorDialogCreator.newRequestErrorDialog(this,
+ e.getMessage());
}
return super.onOptionsItemSelected(item);
}
diff --git a/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java b/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java
index fdca48e8a..ee4e39b9d 100644
--- a/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java
+++ b/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java
@@ -1,151 +1,213 @@
package de.danoeh.antennapod.activity;
import android.annotation.SuppressLint;
+import android.app.SearchManager;
+import android.app.SearchableInfo;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.TypedArray;
+import android.media.AudioManager;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.ActionBarActivity;
+import android.support.v7.widget.SearchView;
import android.util.Log;
-import com.actionbarsherlock.app.SherlockFragmentActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuInflater;
-import com.actionbarsherlock.view.MenuItem;
-import com.actionbarsherlock.view.Window;
-
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.Window;
+import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.FeedRemover;
import de.danoeh.antennapod.dialog.ConfirmationDialog;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.feed.Feed;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.fragment.ExternalPlayerFragment;
import de.danoeh.antennapod.fragment.FeedlistFragment;
import de.danoeh.antennapod.fragment.ItemlistFragment;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.util.StorageUtils;
import de.danoeh.antennapod.util.menuhandler.FeedMenuHandler;
-/** Displays a List of FeedItems */
-public class FeedItemlistActivity extends SherlockFragmentActivity {
- private static final String TAG = "FeedItemlistActivity";
-
- private FeedManager manager;
-
- /** The feed which the activity displays */
- private Feed feed;
- private ItemlistFragment filf;
- private ExternalPlayerFragment externalPlayerFragment;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- setTheme(UserPreferences.getTheme());
- super.onCreate(savedInstanceState);
- StorageUtils.checkStorageAvailability(this);
- requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
-
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- setContentView(R.layout.feeditemlist_activity);
-
- manager = FeedManager.getInstance();
- long feedId = getIntent().getLongExtra(
- FeedlistFragment.EXTRA_SELECTED_FEED, -1);
- if (feedId == -1)
- Log.e(TAG, "Received invalid feed selection.");
-
- feed = manager.getFeed(feedId);
- setTitle(feed.getTitle());
-
- FragmentManager fragmentManager = getSupportFragmentManager();
- FragmentTransaction fT = fragmentManager.beginTransaction();
-
- filf = ItemlistFragment.newInstance(feed.getId());
- fT.replace(R.id.feeditemlistFragment, filf);
-
- externalPlayerFragment = new ExternalPlayerFragment();
- fT.replace(R.id.playerFragment, externalPlayerFragment);
- fT.commit();
-
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- StorageUtils.checkStorageAvailability(this);
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- TypedArray drawables = obtainStyledAttributes(new int[] { R.attr.action_search });
- menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
- .setIcon(drawables.getDrawable(0))
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
- return FeedMenuHandler
- .onCreateOptionsMenu(new MenuInflater(this), menu);
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- return FeedMenuHandler.onPrepareOptionsMenu(menu, feed);
- }
-
- @SuppressLint("NewApi")
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- try {
- if (FeedMenuHandler.onOptionsItemClicked(this, item, feed)) {
- filf.getListAdapter().notifyDataSetChanged();
- } else {
- switch (item.getItemId()) {
- case R.id.remove_item:
- final FeedRemover remover = new FeedRemover(
- FeedItemlistActivity.this, feed) {
- @Override
- protected void onPostExecute(Void result) {
- super.onPostExecute(result);
- finish();
- }
- };
- ConfirmationDialog conDialog = new ConfirmationDialog(this,
- R.string.remove_feed_label,
- R.string.feed_delete_confirmation_msg) {
-
- @Override
- public void onConfirmButtonPressed(
- DialogInterface dialog) {
- dialog.dismiss();
- remover.executeAsync();
- }
- };
- conDialog.createNewDialog().show();
- break;
- case R.id.search_item:
- onSearchRequested();
- break;
- case android.R.id.home:
- Intent intent = new Intent(this, MainActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- startActivity(intent);
- break;
- }
- }
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- DownloadRequestErrorDialogCreator.newRequestErrorDialog(this,
- e.getMessage());
- }
- return true;
- }
-
- @Override
- public boolean onSearchRequested() {
- Bundle bundle = new Bundle();
- bundle.putLong(SearchActivity.EXTRA_FEED_ID, feed.getId());
- startSearch(null, false, bundle, false);
- return true;
- }
+/**
+ * Displays a List of FeedItems
+ */
+public class FeedItemlistActivity extends ActionBarActivity {
+ private static final String TAG = "FeedItemlistActivity";
+
+ /**
+ * The feed which the activity displays
+ */
+ private Feed feed;
+ private ItemlistFragment filf;
+ private ExternalPlayerFragment externalPlayerFragment;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+ StorageUtils.checkStorageAvailability(this);
+ requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ setContentView(R.layout.feeditemlist_activity);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ long feedId = getIntent().getLongExtra(
+ FeedlistFragment.EXTRA_SELECTED_FEED, -1);
+ if (feedId == -1) {
+ Log.e(TAG, "Received invalid feed selection.");
+ } else {
+ loadData(feedId);
+ }
+
+ }
+
+ private void loadData(long id) {
+ AsyncTask<Long, Void, Feed> loadTask = new AsyncTask<Long, Void, Feed>() {
+
+ @Override
+ protected Feed doInBackground(Long... longs) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Loading feed data in background");
+ return DBReader.getFeed(FeedItemlistActivity.this, longs[0]);
+ }
+
+ @Override
+ protected void onPostExecute(Feed result) {
+ super.onPostExecute(result);
+ if (result != null) {
+ if (AppConfig.DEBUG) Log.d(TAG, "Finished loading feed data");
+ feed = result;
+ setTitle(feed.getTitle());
+
+ FragmentManager fragmentManager = getSupportFragmentManager();
+ FragmentTransaction fT = fragmentManager.beginTransaction();
+
+ filf = ItemlistFragment.newInstance(feed.getId());
+ fT.replace(R.id.feeditemlistFragment, filf);
+
+ externalPlayerFragment = new ExternalPlayerFragment();
+ fT.replace(R.id.playerFragment, externalPlayerFragment);
+ fT.commit();
+ supportInvalidateOptionsMenu();
+ } else {
+ Log.e(TAG, "Error: Feed was null");
+ }
+ }
+ };
+ loadTask.execute(id);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ StorageUtils.checkStorageAvailability(this);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ if (feed != null) {
+ TypedArray drawables = obtainStyledAttributes(new int[]{R.attr.action_search});
+ MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
+ .setIcon(drawables.getDrawable(0)),
+ MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
+ MenuItemCompat.setActionView(menu.findItem(R.id.search_item), new SearchView(this));
+
+ SearchManager searchManager =
+ (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+ SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.search_item));
+ searchView.setIconifiedByDefault(true);
+
+ searchView.setSearchableInfo(
+ searchManager.getSearchableInfo(getComponentName()));
+ return FeedMenuHandler
+ .onCreateOptionsMenu(new MenuInflater(this), menu);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ return FeedMenuHandler.onPrepareOptionsMenu(menu, feed);
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ try {
+ if (FeedMenuHandler.onOptionsItemClicked(this, item, feed)) {
+ filf.getListAdapter().notifyDataSetChanged();
+ } else {
+ switch (item.getItemId()) {
+ case R.id.remove_item:
+ final FeedRemover remover = new FeedRemover(
+ FeedItemlistActivity.this, feed) {
+ @Override
+ protected void onPostExecute(Void result) {
+ super.onPostExecute(result);
+ finish();
+ }
+ };
+ ConfirmationDialog conDialog = new ConfirmationDialog(this,
+ R.string.remove_feed_label,
+ R.string.feed_delete_confirmation_msg) {
+
+ @Override
+ public void onConfirmButtonPressed(
+ DialogInterface dialog) {
+ dialog.dismiss();
+ remover.executeAsync();
+ }
+ };
+ conDialog.createNewDialog().show();
+ break;
+ case android.R.id.home:
+ Intent intent = new Intent(this, MainActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ break;
+ }
+ }
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ DownloadRequestErrorDialogCreator.newRequestErrorDialog(this,
+ e.getMessage());
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onSearchRequested() {
+ if (feed != null) {
+ Bundle bundle = new Bundle();
+ bundle.putLong(SearchActivity.EXTRA_FEED_ID, feed.getId());
+ startSearch(null, false, bundle, false);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void startActivity(Intent intent) {
+ if (intent.getAction() != null &&
+ intent.getAction().equals(Intent.ACTION_SEARCH)) {
+ addSearchInformation(intent);
+ }
+ super.startActivity(intent);
+ }
+
+ private void addSearchInformation(Intent startIntent) {
+ startIntent.putExtra(SearchActivity.EXTRA_FEED_ID, feed.getId());
+ }
}
diff --git a/src/de/danoeh/antennapod/activity/FlattrAuthActivity.java b/src/de/danoeh/antennapod/activity/FlattrAuthActivity.java
index 75e513816..2ab95e287 100644
--- a/src/de/danoeh/antennapod/activity/FlattrAuthActivity.java
+++ b/src/de/danoeh/antennapod/activity/FlattrAuthActivity.java
@@ -1,6 +1,9 @@
package de.danoeh.antennapod.activity;
+import android.support.v7.app.ActionBarActivity;
+import android.view.Menu;
+import android.view.MenuItem;
import org.shredzone.flattr4j.exception.FlattrException;
import android.content.Intent;
@@ -12,10 +15,6 @@ import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
-import com.actionbarsherlock.app.SherlockActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.preferences.UserPreferences;
@@ -23,7 +22,7 @@ import de.danoeh.antennapod.util.flattr.FlattrUtils;
/** Guides the user through the authentication process */
-public class FlattrAuthActivity extends SherlockActivity {
+public class FlattrAuthActivity extends ActionBarActivity {
private static final String TAG = "FlattrAuthActivity";
private TextView txtvExplanation;
diff --git a/src/de/danoeh/antennapod/activity/ItemviewActivity.java b/src/de/danoeh/antennapod/activity/ItemviewActivity.java
index 5ead667dc..c2833760d 100644
--- a/src/de/danoeh/antennapod/activity/ItemviewActivity.java
+++ b/src/de/danoeh/antennapod/activity/ItemviewActivity.java
@@ -1,55 +1,60 @@
package de.danoeh.antennapod.activity;
-import java.text.DateFormat;
-
+import android.media.AudioManager;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
+import android.support.v7.app.ActionBarActivity;
import android.text.format.DateUtils;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.Window;
import android.widget.TextView;
-import com.actionbarsherlock.app.SherlockFragmentActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuInflater;
-import com.actionbarsherlock.view.MenuItem;
-import com.actionbarsherlock.view.Window;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
-import de.danoeh.antennapod.feed.Feed;
+import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
-import de.danoeh.antennapod.fragment.FeedlistFragment;
import de.danoeh.antennapod.fragment.ItemDescriptionFragment;
import de.danoeh.antennapod.fragment.ItemlistFragment;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DownloadRequestException;
+import de.danoeh.antennapod.util.QueueAccess;
import de.danoeh.antennapod.util.StorageUtils;
import de.danoeh.antennapod.util.menuhandler.FeedItemMenuHandler;
+import java.text.DateFormat;
+
/** Displays a single FeedItem and provides various actions */
-public class ItemviewActivity extends SherlockFragmentActivity {
+public class ItemviewActivity extends ActionBarActivity {
private static final String TAG = "ItemviewActivity";
- private FeedManager manager;
- private FeedItem item;
+ private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED | EventDistributor.DOWNLOAD_QUEUED;
- // Widgets
- private TextView txtvTitle;
- private TextView txtvPublished;
+ private FeedItem item;
@Override
public void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
StorageUtils.checkStorageAvailability(this);
- manager = FeedManager.getInstance();
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
getSupportActionBar().setDisplayShowTitleEnabled(false);
- extractFeeditem();
- populateUI();
+ EventDistributor.getInstance().register(contentUpdate);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ long itemId = getIntent().getLongExtra(
+ ItemlistFragment.EXTRA_SELECTED_FEEDITEM, -1);
+ if (itemId == -1) {
+ Log.e(TAG, "Received invalid selection of either feeditem or feed.");
+ } else {
+ loadData(itemId);
+ }
}
@Override
@@ -62,32 +67,43 @@ public class ItemviewActivity extends SherlockFragmentActivity {
@Override
public void onStop() {
super.onStop();
+ EventDistributor.getInstance().unregister(contentUpdate);
if (AppConfig.DEBUG)
Log.d(TAG, "Stopping Activity");
}
- /** Extracts FeedItem object the activity is supposed to display */
- private void extractFeeditem() {
- long itemId = getIntent().getLongExtra(
- ItemlistFragment.EXTRA_SELECTED_FEEDITEM, -1);
- long feedId = getIntent().getLongExtra(
- FeedlistFragment.EXTRA_SELECTED_FEED, -1);
- if (itemId == -1 || feedId == -1) {
- Log.e(TAG, "Received invalid selection of either feeditem or feed.");
- }
- Feed feed = manager.getFeed(feedId);
- item = manager.getFeedItem(itemId, feed);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Title of item is " + item.getTitle());
- if (AppConfig.DEBUG)
- Log.d(TAG, "Title of feed is " + item.getFeed().getTitle());
- }
+ private void loadData(long itemId) {
+ AsyncTask<Long, Void, FeedItem> loadTask = new AsyncTask<Long, Void, FeedItem>() {
+
+ @Override
+ protected FeedItem doInBackground(Long... longs) {
+ return DBReader.getFeedItem(ItemviewActivity.this, longs[0]);
+ }
+
+ @Override
+ protected void onPostExecute(FeedItem feedItem) {
+ super.onPostExecute(feedItem);
+ if (feedItem != null && feedItem.getFeed() != null) {
+ item = feedItem;
+ populateUI();
+ supportInvalidateOptionsMenu();
+ } else {
+ if (feedItem == null) {
+ Log.e(TAG, "Error: FeedItem was null");
+ } else if (feedItem.getFeed() == null) {
+ Log.e(TAG, "Error: Feed was null");
+ }
+ }
+ }
+ };
+ loadTask.execute(itemId);
+ }
private void populateUI() {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.feeditemview);
- txtvTitle = (TextView) findViewById(R.id.txtvItemname);
- txtvPublished = (TextView) findViewById(R.id.txtvPublished);
+ TextView txtvTitle = (TextView) findViewById(R.id.txtvItemname);
+ TextView txtvPublished = (TextView) findViewById(R.id.txtvPublished);
setTitle(item.getFeed().getTitle());
txtvPublished.setText(DateUtils.formatSameDayTime(item.getPubDate()
@@ -106,9 +122,13 @@ public class ItemviewActivity extends SherlockFragmentActivity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
- MenuInflater inflater = getSupportMenuInflater();
- inflater.inflate(R.menu.feeditem, menu);
- return true;
+ if (item != null) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.feeditem, menu);
+ return true;
+ } else {
+ return false;
+ }
}
@Override
@@ -134,13 +154,28 @@ public class ItemviewActivity extends SherlockFragmentActivity {
@Override
public boolean onPrepareOptionsMenu(final Menu menu) {
return FeedItemMenuHandler.onPrepareMenu(
- new FeedItemMenuHandler.MenuInterface() {
+ new FeedItemMenuHandler.MenuInterface() {
- @Override
- public void setItemVisibility(int id, boolean visible) {
- menu.findItem(id).setVisible(visible);
- }
- }, item, true);
+ @Override
+ public void setItemVisibility(int id, boolean visible) {
+ menu.findItem(id).setVisible(visible);
+ }
+ }, item, true, QueueAccess.NotInQueueAccess());
}
+ private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
+
+ @Override
+ public void update(EventDistributor eventDistributor, Integer arg) {
+ if ((EVENTS & arg) != 0) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Received contentUpdate Intent.");
+ if (item != null) {
+ loadData(item.getId());
+ }
+ }
+ }
+ };
+
+
}
diff --git a/src/de/danoeh/antennapod/activity/MainActivity.java b/src/de/danoeh/antennapod/activity/MainActivity.java
index 410617b23..447a436cf 100644
--- a/src/de/danoeh/antennapod/activity/MainActivity.java
+++ b/src/de/danoeh/antennapod/activity/MainActivity.java
@@ -2,44 +2,47 @@ package de.danoeh.antennapod.activity;
import java.util.ArrayList;
+import android.app.SearchManager;
+import android.app.SearchableInfo;
import android.content.Context;
import android.content.Intent;
+import android.media.AudioManager;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.app.FragmentTransaction;
+import android.support.v4.view.MenuItemCompat;
import android.support.v4.view.ViewPager;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.ActionBarActivity;
+import android.support.v7.widget.SearchView;
import android.util.Log;
-import com.actionbarsherlock.app.ActionBar;
-import com.actionbarsherlock.app.ActionBar.Tab;
-import com.actionbarsherlock.app.SherlockFragmentActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuInflater;
-import com.actionbarsherlock.view.MenuItem;
-import com.actionbarsherlock.view.Window;
-
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.Window;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.feed.EventDistributor;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.fragment.EpisodesFragment;
import de.danoeh.antennapod.fragment.ExternalPlayerFragment;
import de.danoeh.antennapod.fragment.FeedlistFragment;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.service.download.DownloadService;
+import de.danoeh.antennapod.storage.DBReader;
+import de.danoeh.antennapod.storage.DBTasks;
import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.util.StorageUtils;
/** The activity that is shown when the user launches the app. */
-public class MainActivity extends SherlockFragmentActivity {
+public class MainActivity extends ActionBarActivity {
private static final String TAG = "MainActivity";
private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED
| EventDistributor.DOWNLOAD_QUEUED;
- private FeedManager manager;
private ViewPager viewpager;
private TabsAdapter pagerAdapter;
private ExternalPlayerFragment externalPlayerFragment;
@@ -51,20 +54,20 @@ public class MainActivity extends SherlockFragmentActivity {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
StorageUtils.checkStorageAvailability(this);
- manager = FeedManager.getInstance();
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.main);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
- getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+ getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
viewpager = (ViewPager) findViewById(R.id.viewpager);
pagerAdapter = new TabsAdapter(this, viewpager);
viewpager.setAdapter(pagerAdapter);
- Tab feedsTab = getSupportActionBar().newTab();
+ ActionBar.Tab feedsTab = getSupportActionBar().newTab();
feedsTab.setText(R.string.podcasts_label);
- Tab episodesTab = getSupportActionBar().newTab();
+ ActionBar.Tab episodesTab = getSupportActionBar().newTab();
episodesTab.setText(R.string.episodes_label);
pagerAdapter.addTab(feedsTab, FeedlistFragment.class, null);
@@ -80,7 +83,7 @@ public class MainActivity extends SherlockFragmentActivity {
if (!appLaunched && getIntent().getAction() != null
&& getIntent().getAction().equals(Intent.ACTION_MAIN)) {
appLaunched = true;
- if (manager.getUnreadItemsSize(true) > 0) {
+ if (DBReader.getNumberOfUnreadItems(this) > 0) {
// select 'episodes' tab
getSupportActionBar().setSelectedNavigationItem(1);
}
@@ -142,7 +145,7 @@ public class MainActivity extends SherlockFragmentActivity {
startActivity(new Intent(this, AddFeedActivity.class));
return true;
case R.id.all_feed_refresh:
- manager.refreshAllFeeds(this);
+ DBTasks.refreshAllFeeds(this, null);
return true;
case R.id.show_downloads:
startActivity(new Intent(this, DownloadActivity.class));
@@ -153,9 +156,6 @@ public class MainActivity extends SherlockFragmentActivity {
case R.id.show_player:
startActivity(PlaybackService.getPlayerActivityIntent(this));
return true;
- case R.id.search_item:
- onSearchRequested();
- return true;
case R.id.show_playback_history:
startActivity(new Intent(this, PlaybackHistoryActivity.class));
return true;
@@ -173,9 +173,6 @@ public class MainActivity extends SherlockFragmentActivity {
} else {
refreshAll.setVisible(true);
}
-
- boolean hasFeeds = manager.getFeedsSize() > 0;
- menu.findItem(R.id.all_feed_refresh).setVisible(hasFeeds);
return true;
}
@@ -183,7 +180,18 @@ public class MainActivity extends SherlockFragmentActivity {
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = new MenuInflater(this);
inflater.inflate(R.menu.main, menu);
- return true;
+
+ SearchManager searchManager =
+ (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+ SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.search_item));
+ searchView.setIconifiedByDefault(true);
+
+ SearchableInfo info = searchManager.getSearchableInfo(getComponentName());
+ searchView.setSearchableInfo(
+ searchManager.getSearchableInfo(getComponentName()));
+
+
+ return true;
}
public static class TabsAdapter extends FragmentPagerAdapter implements
@@ -248,7 +256,7 @@ public class MainActivity extends SherlockFragmentActivity {
}
@Override
- public void onTabSelected(Tab tab, FragmentTransaction ft) {
+ public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
Object tag = tab.getTag();
for (int i = 0; i < mTabs.size(); i++) {
if (mTabs.get(i) == tag) {
@@ -258,12 +266,12 @@ public class MainActivity extends SherlockFragmentActivity {
}
@Override
- public void onTabUnselected(Tab tab, FragmentTransaction ft) {
+ public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) {
}
@Override
- public void onTabReselected(Tab tab, FragmentTransaction ft) {
+ public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) {
}
}
diff --git a/src/de/danoeh/antennapod/activity/MediaplayerActivity.java b/src/de/danoeh/antennapod/activity/MediaplayerActivity.java
index 9fa9fbf52..af244f2ed 100644
--- a/src/de/danoeh/antennapod/activity/MediaplayerActivity.java
+++ b/src/de/danoeh/antennapod/activity/MediaplayerActivity.java
@@ -4,24 +4,23 @@ import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.PixelFormat;
+import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.widget.ImageButton;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
-import com.actionbarsherlock.app.SherlockFragmentActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuInflater;
-import com.actionbarsherlock.view.MenuItem;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.dialog.TimeDialog;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.util.Converter;
@@ -35,12 +34,10 @@ import de.danoeh.antennapod.util.playback.PlaybackController;
* Provides general features which are both needed for playing audio and video
* files.
*/
-public abstract class MediaplayerActivity extends SherlockFragmentActivity
+public abstract class MediaplayerActivity extends ActionBarActivity
implements OnSeekBarChangeListener {
private static final String TAG = "MediaplayerActivity";
- protected FeedManager manager;
-
protected PlaybackController controller;
protected TextView txtvPosition;
@@ -156,9 +153,9 @@ public abstract class MediaplayerActivity extends SherlockFragmentActivity
if (AppConfig.DEBUG)
Log.d(TAG, "Creating Activity");
StorageUtils.checkStorageAvailability(this);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
- orientation = getResources().getConfiguration().orientation;
- manager = FeedManager.getInstance();
+ orientation = getResources().getConfiguration().orientation;
getWindow().setFormat(PixelFormat.TRANSPARENT);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
diff --git a/src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java b/src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java
index bb50944cc..c039e96f8 100644
--- a/src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java
+++ b/src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java
@@ -5,13 +5,11 @@ import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
-import com.actionbarsherlock.app.SherlockFragmentActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-import com.viewpagerindicator.TabPageIndicator;
-
+import android.view.Menu;
+import android.view.MenuItem;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.fragment.MiroGuideChannellistFragment;
import de.danoeh.antennapod.preferences.UserPreferences;
@@ -21,14 +19,13 @@ import de.danoeh.antennapod.preferences.UserPreferences;
* activity uses MiroGuideChannelListFragments for these lists. If the user
* selects a channel, the MiroGuideChannelViewActivity is started.
*/
-public class MiroGuideCategoryActivity extends SherlockFragmentActivity {
+public class MiroGuideCategoryActivity extends ActionBarActivity {
private static final String TAG = "MiroGuideCategoryActivity";
public static String EXTRA_CATEGORY = "category";
private ViewPager viewpager;
private CategoryPagerAdapter pagerAdapter;
- private TabPageIndicator tabs;
private String category;
@@ -40,14 +37,12 @@ public class MiroGuideCategoryActivity extends SherlockFragmentActivity {
setContentView(R.layout.miroguide_category);
viewpager = (ViewPager) findViewById(R.id.viewpager);
- tabs = (TabPageIndicator) findViewById(R.id.tabs);
category = getIntent().getStringExtra(EXTRA_CATEGORY);
if (category != null) {
getSupportActionBar().setTitle(category);
pagerAdapter = new CategoryPagerAdapter(getSupportFragmentManager());
viewpager.setAdapter(pagerAdapter);
- tabs.setViewPager(viewpager);
} else {
Log.e(TAG, "Activity was started with invalid arguments");
}
diff --git a/src/de/danoeh/antennapod/activity/MiroGuideChannelViewActivity.java b/src/de/danoeh/antennapod/activity/MiroGuideChannelViewActivity.java
index f9fe912cd..d4b4597f2 100644
--- a/src/de/danoeh/antennapod/activity/MiroGuideChannelViewActivity.java
+++ b/src/de/danoeh/antennapod/activity/MiroGuideChannelViewActivity.java
@@ -1,13 +1,18 @@
package de.danoeh.antennapod.activity;
import java.util.Date;
+import java.util.List;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
import android.widget.ProgressBar;
@@ -15,21 +20,16 @@ import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
-import com.actionbarsherlock.app.SherlockActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuInflater;
-import com.actionbarsherlock.view.MenuItem;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.MiroGuideItemlistAdapter;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.feed.Feed;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.miroguide.conn.MiroGuideException;
import de.danoeh.antennapod.miroguide.conn.MiroGuideService;
import de.danoeh.antennapod.miroguide.model.MiroGuideChannel;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.storage.DownloadRequester;
@@ -37,147 +37,164 @@ import de.danoeh.antennapod.storage.DownloadRequester;
* Displays information about one channel and lets the user add this channel to
* his library.
*/
-public class MiroGuideChannelViewActivity extends SherlockActivity {
+public class MiroGuideChannelViewActivity extends ActionBarActivity {
private static final String TAG = "MiroGuideChannelViewActivity";
- public static final String EXTRA_CHANNEL_ID = "id";
- public static final String EXTRA_CHANNEL_URL = "url";
-
- private RelativeLayout layoutContent;
- private ProgressBar progLoading;
- private TextView txtvTitle;
- private TextView txtVDescription;
- private ListView listEntries;
-
- private long channelId;
- private String channelUrl;
- private MiroGuideChannel channel;
-
- @Override
- protected void onPause() {
- super.onPause();
- channelLoader.cancel(true);
- }
-
- @SuppressLint("NewApi")
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- setTheme(UserPreferences.getTheme());
- super.onCreate(savedInstanceState);
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- setContentView(R.layout.miroguide_channelview);
-
- layoutContent = (RelativeLayout) findViewById(R.id.layout_content);
- progLoading = (ProgressBar) findViewById(R.id.progLoading);
- txtvTitle = (TextView) findViewById(R.id.txtvTitle);
- txtVDescription = (TextView) findViewById(R.id.txtvDescription);
- listEntries = (ListView) findViewById(R.id.itemlist);
-
- channelId = getIntent().getLongExtra(EXTRA_CHANNEL_ID, -1);
- channelUrl = getIntent().getStringExtra(EXTRA_CHANNEL_URL);
-
- if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
- channelLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- } else {
- channelLoader.execute();
- }
-
- }
-
- /** Is used to load channel information asynchronously. */
- private AsyncTask<Void, Void, Void> channelLoader = new AsyncTask<Void, Void, Void>() {
- private static final String TAG = "ChannelLoader";
- private Exception exception;
-
- @Override
- protected Void doInBackground(Void... params) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Starting background task");
- MiroGuideService service = new MiroGuideService();
- try {
- channel = service.getChannel(channelId);
- } catch (MiroGuideException e) {
- e.printStackTrace();
- exception = e;
- }
- return null;
- }
-
- @SuppressLint("NewApi")
- @Override
- protected void onPostExecute(Void result) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Loading finished");
- if (exception == null) {
- txtvTitle.setText(channel.getName());
- txtVDescription.setText(channel.getDescription());
-
- MiroGuideItemlistAdapter listAdapter = new MiroGuideItemlistAdapter(
- MiroGuideChannelViewActivity.this, 0,
- channel.getItems());
- listEntries.setAdapter(listAdapter);
- progLoading.setVisibility(View.GONE);
- layoutContent.setVisibility(View.VISIBLE);
- supportInvalidateOptionsMenu();
- } else {
- finish();
- }
- }
-
- };
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- MenuInflater inflater = new MenuInflater(this);
- inflater.inflate(R.menu.channelview, menu);
- return true;
- }
-
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- boolean channelLoaded = channel != null;
- boolean beingDownloaded = channelLoaded
- && DownloadRequester.getInstance().isDownloadingFile(
- channel.getDownloadUrl());
- boolean notAdded = channelLoaded
- && !((FeedManager.getInstance().feedExists(
- channel.getDownloadUrl()) || beingDownloaded));
- menu.findItem(R.id.add_feed).setVisible(notAdded);
- menu.findItem(R.id.visit_website_item).setVisible(
- channelLoaded && channel.getWebsiteUrl() != null);
- return true;
- }
-
- @SuppressLint("NewApi")
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- finish();
- return true;
- case R.id.visit_website_item:
- Uri uri = Uri.parse(channel.getWebsiteUrl());
- startActivity(new Intent(Intent.ACTION_VIEW, uri));
- return true;
- case R.id.add_feed:
- try {
- DownloadRequester.getInstance().downloadFeed(
- this,
- new Feed(channel.getDownloadUrl(), new Date(), channel
- .getName()));
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- DownloadRequestErrorDialogCreator.newRequestErrorDialog(this,
- e.getMessage());
- }
- Toast toast = Toast.makeText(this, R.string.miro_feed_added,
- Toast.LENGTH_LONG);
- toast.show();
- supportInvalidateOptionsMenu();
- return true;
- default:
- return false;
- }
- }
+ public static final String EXTRA_CHANNEL_ID = "id";
+ public static final String EXTRA_CHANNEL_URL = "url";
+
+ private RelativeLayout layoutContent;
+ private ProgressBar progLoading;
+ private TextView txtvTitle;
+ private TextView txtVDescription;
+ private ListView listEntries;
+
+ private long channelId;
+ private String channelUrl;
+ private MiroGuideChannel channel;
+ private volatile List<Feed> feeds;
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ channelLoader.cancel(true);
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ setContentView(R.layout.miroguide_channelview);
+
+ layoutContent = (RelativeLayout) findViewById(R.id.layout_content);
+ progLoading = (ProgressBar) findViewById(R.id.progLoading);
+ txtvTitle = (TextView) findViewById(R.id.txtvTitle);
+ txtVDescription = (TextView) findViewById(R.id.txtvDescription);
+ listEntries = (ListView) findViewById(R.id.itemlist);
+
+ channelId = getIntent().getLongExtra(EXTRA_CHANNEL_ID, -1);
+ channelUrl = getIntent().getStringExtra(EXTRA_CHANNEL_URL);
+
+ if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
+ channelLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ } else {
+ channelLoader.execute();
+ }
+
+ }
+
+ /**
+ * Is used to load channel information asynchronously.
+ */
+ private AsyncTask<Void, Void, Void> channelLoader = new AsyncTask<Void, Void, Void>() {
+ private static final String TAG = "ChannelLoader";
+ private Exception exception;
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Starting background task");
+ feeds = DBReader.getFeedList(MiroGuideChannelViewActivity.this);
+ MiroGuideService service = new MiroGuideService();
+ try {
+ channel = service.getChannel(channelId);
+ } catch (MiroGuideException e) {
+ e.printStackTrace();
+ exception = e;
+ }
+ return null;
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ protected void onPostExecute(Void result) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Loading finished");
+ if (exception == null) {
+ txtvTitle.setText(channel.getName());
+ txtVDescription.setText(channel.getDescription());
+
+ MiroGuideItemlistAdapter listAdapter = new MiroGuideItemlistAdapter(
+ MiroGuideChannelViewActivity.this, 0,
+ channel.getItems());
+ listEntries.setAdapter(listAdapter);
+ progLoading.setVisibility(View.GONE);
+ layoutContent.setVisibility(View.VISIBLE);
+ supportInvalidateOptionsMenu();
+ } else {
+ finish();
+ }
+ }
+
+ };
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = new MenuInflater(this);
+ inflater.inflate(R.menu.channelview, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ boolean channelLoaded = channel != null;
+ boolean beingDownloaded = channelLoaded
+ && DownloadRequester.getInstance().isDownloadingFile(
+ channel.getDownloadUrl());
+ boolean notAdded = channelLoaded
+ && !((feedExists(
+ channel.getDownloadUrl()) || beingDownloaded));
+ menu.findItem(R.id.add_feed).setVisible(notAdded);
+ menu.findItem(R.id.visit_website_item).setVisible(
+ channelLoaded && channel.getWebsiteUrl() != null);
+ return true;
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ case R.id.visit_website_item:
+ Uri uri = Uri.parse(channel.getWebsiteUrl());
+ startActivity(new Intent(Intent.ACTION_VIEW, uri));
+ return true;
+ case R.id.add_feed:
+ try {
+ DownloadRequester.getInstance().downloadFeed(
+ this,
+ new Feed(channel.getDownloadUrl(), new Date(), channel
+ .getName()));
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ DownloadRequestErrorDialogCreator.newRequestErrorDialog(this,
+ e.getMessage());
+ }
+ Toast toast = Toast.makeText(this, R.string.miro_feed_added,
+ Toast.LENGTH_LONG);
+ toast.show();
+ supportInvalidateOptionsMenu();
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private boolean feedExists(String downloadUrl) {
+ if (feeds == null) {
+ return false;
+ }
+
+ for (Feed feed : feeds) {
+ if (feed.getDownload_url().equals(downloadUrl)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/src/de/danoeh/antennapod/activity/MiroGuideMainActivity.java b/src/de/danoeh/antennapod/activity/MiroGuideMainActivity.java
index 8b33ef1da..40306e4da 100644
--- a/src/de/danoeh/antennapod/activity/MiroGuideMainActivity.java
+++ b/src/de/danoeh/antennapod/activity/MiroGuideMainActivity.java
@@ -1,19 +1,24 @@
package de.danoeh.antennapod.activity;
import android.annotation.SuppressLint;
+import android.app.SearchManager;
+import android.app.SearchableInfo;
+import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.ActionBarActivity;
+import android.support.v7.widget.SearchView;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
+import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
-import com.actionbarsherlock.app.SherlockListActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.miroguide.conn.MiroGuideException;
@@ -24,132 +29,140 @@ import de.danoeh.antennapod.preferences.UserPreferences;
* Shows a list of available categories and offers a search button. If the user
* selects a category, the MiroGuideCategoryActivity is started.
*/
-public class MiroGuideMainActivity extends SherlockListActivity {
- private static final String TAG = "MiroGuideMainActivity";
-
- private static String[] categories;
- private ArrayAdapter<String> listAdapter;
-
- private TextView txtvStatus;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- setTheme(UserPreferences.getTheme());
- super.onCreate(savedInstanceState);
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- setContentView(R.layout.miroguide_categorylist);
-
- txtvStatus = (TextView) findViewById(android.R.id.empty);
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- if (categories != null) {
- createAdapter();
- } else {
- loadCategories();
- }
- }
-
- @Override
- protected void onListItemClick(ListView l, View v, int position, long id) {
- super.onListItemClick(l, v, position, id);
- String selection = listAdapter.getItem(position);
- Intent launchIntent = new Intent(this, MiroGuideCategoryActivity.class);
- launchIntent.putExtra(MiroGuideCategoryActivity.EXTRA_CATEGORY,
- selection);
- startActivity(launchIntent);
- }
-
- private void createAdapter() {
- if (categories != null) {
- listAdapter = new ArrayAdapter<String>(this,
- android.R.layout.simple_list_item_1, categories);
- txtvStatus.setText(R.string.no_items_label);
- setListAdapter(listAdapter);
- }
- }
-
- /**
- * Launches an AsyncTask to load the available categories in the background.
- */
- @SuppressLint("NewApi")
- private void loadCategories() {
- AsyncTask<Void, Void, Void> listLoader = new AsyncTask<Void, Void, Void>() {
-
- private String[] c;
- private MiroGuideException exception;
-
- @Override
- protected void onPostExecute(Void result) {
- if (exception == null) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Successfully loaded categories");
- categories = c;
- createAdapter();
- } else {
- Log.e(TAG, "Error happened while trying to load categories");
- txtvStatus.setText(exception.getMessage());
- }
- }
-
- @Override
- protected void onPreExecute() {
- txtvStatus.setText(R.string.loading_categories_label);
- }
-
- @Override
- protected Void doInBackground(Void... params) {
- MiroGuideService service = new MiroGuideService();
- try {
- c = service.getCategories();
- } catch (MiroGuideException e) {
- e.printStackTrace();
- exception = e;
- } finally {
- service.close();
- }
- return null;
- }
-
- };
-
- if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
- listLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- } else {
- listLoader.execute();
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
- .setIcon(
- obtainStyledAttributes(
- new int[] { R.attr.action_search })
- .getDrawable(0))
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- finish();
- return true;
- case R.id.search_item:
- onSearchRequested();
- return true;
- default:
- return false;
- }
- }
-
+public class MiroGuideMainActivity extends ActionBarActivity implements AdapterView.OnItemClickListener {
+ private static final String TAG = "MiroGuideMainActivity";
+
+ private static String[] categories;
+ private ArrayAdapter<String> listAdapter;
+
+ private TextView txtvStatus;
+ private ListView listView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ setContentView(R.layout.miroguide_categorylist);
+
+ txtvStatus = (TextView) findViewById(android.R.id.empty);
+ listView = (ListView) findViewById(android.R.id.list);
+ listView.setOnItemClickListener(this);
+ listView.setEmptyView(txtvStatus);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (categories != null) {
+ createAdapter();
+ } else {
+ loadCategories();
+ }
+ }
+
+ private void createAdapter() {
+ if (categories != null) {
+ listAdapter = new ArrayAdapter<String>(this,
+ android.R.layout.simple_list_item_1, categories);
+ txtvStatus.setText(R.string.no_items_label);
+ listView.setAdapter(listAdapter);
+ }
+ }
+
+ /**
+ * Launches an AsyncTask to load the available categories in the background.
+ */
+ @SuppressLint("NewApi")
+ private void loadCategories() {
+ AsyncTask<Void, Void, Void> listLoader = new AsyncTask<Void, Void, Void>() {
+
+ private String[] c;
+ private MiroGuideException exception;
+
+ @Override
+ protected void onPostExecute(Void result) {
+ if (exception == null) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Successfully loaded categories");
+ categories = c;
+ createAdapter();
+ } else {
+ Log.e(TAG, "Error happened while trying to load categories");
+ txtvStatus.setText(exception.getMessage());
+ }
+ }
+
+ @Override
+ protected void onPreExecute() {
+ txtvStatus.setText(R.string.loading_categories_label);
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ MiroGuideService service = new MiroGuideService();
+ try {
+ c = service.getCategories();
+ } catch (MiroGuideException e) {
+ e.printStackTrace();
+ exception = e;
+ } finally {
+ service.close();
+ }
+ return null;
+ }
+
+ };
+
+ if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
+ listLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ } else {
+ listLoader.execute();
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
+ .setIcon(
+ obtainStyledAttributes(
+ new int[]{R.attr.action_search})
+ .getDrawable(0)),
+ MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ MenuItemCompat.setActionView(menu.findItem(R.id.search_item), new SearchView(this));
+
+ SearchManager searchManager =
+ (SearchManager) getSystemService(Context.SEARCH_SERVICE);
+ SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.search_item));
+ searchView.setIconifiedByDefault(true);
+ searchView.setSearchableInfo(
+ searchManager.getSearchableInfo(getComponentName()));
+
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
+ String selection = listAdapter.getItem(position);
+ Intent launchIntent = new Intent(this, MiroGuideCategoryActivity.class);
+ launchIntent.putExtra(MiroGuideCategoryActivity.EXTRA_CATEGORY,
+ selection);
+ startActivity(launchIntent);
+ }
}
diff --git a/src/de/danoeh/antennapod/activity/MiroGuideSearchActivity.java b/src/de/danoeh/antennapod/activity/MiroGuideSearchActivity.java
index a30777fb1..4ea0b1699 100644
--- a/src/de/danoeh/antennapod/activity/MiroGuideSearchActivity.java
+++ b/src/de/danoeh/antennapod/activity/MiroGuideSearchActivity.java
@@ -4,12 +4,12 @@ import android.app.SearchManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
-import com.actionbarsherlock.app.SherlockFragmentActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-
+import android.view.Menu;
+import android.view.MenuItem;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.fragment.MiroGuideChannellistFragment;
@@ -19,72 +19,72 @@ import de.danoeh.antennapod.preferences.UserPreferences;
* Displays results when a search for miroguide channels has been performed. It
* uses a MiroGuideChannelListFragment to display the results.
*/
-public class MiroGuideSearchActivity extends SherlockFragmentActivity {
- private static final String TAG = "MiroGuideSearchActivity";
+public class MiroGuideSearchActivity extends ActionBarActivity {
+ private static final String TAG = "MiroGuideSearchActivity";
- private MiroGuideChannellistFragment listFragment;
+ private MiroGuideChannellistFragment listFragment;
- @Override
- protected void onCreate(Bundle arg0) {
- setTheme(UserPreferences.getTheme());
- super.onCreate(arg0);
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- setContentView(R.layout.miroguidesearch);
- }
+ @Override
+ protected void onCreate(Bundle arg0) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(arg0);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ setContentView(R.layout.miroguidesearch);
+ }
- @Override
- protected void onResume() {
- super.onResume();
- Intent intent = getIntent();
- if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
- String query = intent.getStringExtra(SearchManager.QUERY);
- getSupportActionBar()
- .setSubtitle(
- getString(R.string.search_term_label) + "\""
- + query + "\"");
- handleSearchRequest(query);
- }
- }
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Intent intent = getIntent();
+ if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
+ String query = intent.getStringExtra(SearchManager.QUERY);
+ getSupportActionBar()
+ .setSubtitle(
+ getString(R.string.search_term_label) + "\""
+ + query + "\"");
+ handleSearchRequest(query);
+ }
+ }
- private void handleSearchRequest(String query) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Performing search");
- FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
- listFragment = MiroGuideChannellistFragment.newInstance("name", query,
- "name");
- ft.replace(R.id.channellistFragment, listFragment);
- ft.commit();
- }
+ private void handleSearchRequest(String query) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Performing search");
+ FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+ listFragment = MiroGuideChannellistFragment.newInstance("name", query,
+ "name");
+ ft.replace(R.id.channellistFragment, listFragment);
+ ft.commit();
+ }
- @Override
- protected void onNewIntent(Intent intent) {
- setIntent(intent);
- }
+ @Override
+ protected void onNewIntent(Intent intent) {
+ setIntent(intent);
+ }
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
- .setIcon(
- obtainStyledAttributes(
- new int[] { R.attr.action_search })
- .getDrawable(0))
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
+ .setIcon(
+ obtainStyledAttributes(
+ new int[]{R.attr.action_search})
+ .getDrawable(0)),
+ MenuItem.SHOW_AS_ACTION_IF_ROOM);
- return true;
- }
+ return true;
+ }
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- finish();
- return true;
- case R.id.search_item:
- onSearchRequested();
- return true;
- default:
- return false;
- }
- }
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ case R.id.search_item:
+ onSearchRequested();
+ return true;
+ default:
+ return false;
+ }
+ }
}
diff --git a/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java b/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
index d6ff537a3..cb1c66cab 100644
--- a/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
+++ b/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
@@ -6,6 +6,7 @@ import java.util.Date;
import javax.xml.parsers.ParserConfigurationException;
+import android.support.v7.app.ActionBarActivity;
import org.xml.sax.SAXException;
import android.app.AlertDialog;
@@ -17,13 +18,12 @@ import android.view.Gravity;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
-import com.actionbarsherlock.app.SherlockFragmentActivity;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.asynctask.DownloadStatus;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.service.download.DownloadRequest;
+import de.danoeh.antennapod.service.download.DownloadStatus;
import de.danoeh.antennapod.service.download.Downloader;
import de.danoeh.antennapod.service.download.DownloaderCallback;
import de.danoeh.antennapod.service.download.HttpDownloader;
@@ -42,10 +42,10 @@ import de.danoeh.antennapod.util.URLChecker;
* If the feed cannot be downloaded or parsed, an error dialog will be displayed
* and the activity will finish as soon as the error dialog is closed.
*/
-public abstract class OnlineFeedViewActivity extends SherlockFragmentActivity {
+public abstract class OnlineFeedViewActivity extends ActionBarActivity {
private static final String TAG = "OnlineFeedViewActivity";
private static final String ARG_FEEDURL = "arg.feedurl";
-
+
public static final int RESULT_ERROR = 2;
private Feed feed;
@@ -70,7 +70,7 @@ public abstract class OnlineFeedViewActivity extends SherlockFragmentActivity {
@Override
protected void onStop() {
super.onStop();
- if (downloader != null && downloader.getStatus().isDone() == false) {
+ if (downloader != null && !downloader.isFinished()) {
downloader.cancel();
}
}
@@ -82,15 +82,14 @@ public abstract class OnlineFeedViewActivity extends SherlockFragmentActivity {
@Override
public void run() {
- DownloadStatus status = downloader.getStatus();
+ DownloadStatus status = downloader.getResult();
if (status != null) {
if (!status.isCancelled()) {
if (status.isSuccessful()) {
parseFeed();
} else {
- String errorMsg = DownloadError.getErrorString(
- OnlineFeedViewActivity.this,
- status.getReason());
+ String errorMsg = status.getReason().getErrorString(
+ OnlineFeedViewActivity.this);
if (errorMsg != null
&& status.getReasonDetailed() != null) {
errorMsg += " ("
@@ -119,10 +118,13 @@ public abstract class OnlineFeedViewActivity extends SherlockFragmentActivity {
FileNameGenerator.generateFileName(feed.getDownload_url()))
.toString();
feed.setFile_url(fileUrl);
- DownloadStatus status = new DownloadStatus(feed, "OnlineFeed");
+ DownloadRequest request = new DownloadRequest(feed.getFile_url(),
+ feed.getDownload_url(), "OnlineFeed", 0, Feed.FEEDFILETYPE_FEED);
+ /* TODO update
HttpDownloader httpDownloader = new HttpDownloader(downloaderCallback,
- status);
+ request);
httpDownloader.start();
+ */
}
/** Displays a progress indicator. */
@@ -187,9 +189,9 @@ public abstract class OnlineFeedViewActivity extends SherlockFragmentActivity {
}
});
} else {
- final String errorMsg = DownloadError.getErrorString(
- OnlineFeedViewActivity.this,
- DownloadError.ERROR_PARSER_EXCEPTION)
+ final String errorMsg =
+ DownloadError.ERROR_PARSER_EXCEPTION.getErrorString(
+ OnlineFeedViewActivity.this)
+ " (" + reasonDetailed + ")";
runOnUiThread(new Runnable() {
@@ -217,13 +219,14 @@ public abstract class OnlineFeedViewActivity extends SherlockFragmentActivity {
} else {
builder.setMessage(R.string.error_msg_prefix);
}
- builder.setNeutralButton(android.R.string.ok, new DialogInterface.OnClickListener() {
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.cancel();
- }
- });
+ builder.setNeutralButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.cancel();
+ }
+ });
builder.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
diff --git a/src/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java b/src/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java
index 9ba355baf..2ec42e9ef 100644
--- a/src/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java
+++ b/src/de/danoeh/antennapod/activity/OpmlFeedChooserActivity.java
@@ -5,17 +5,17 @@ import java.util.List;
import android.content.Intent;
import android.os.Bundle;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.ActionBarActivity;
import android.util.SparseBooleanArray;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
-import com.actionbarsherlock.app.SherlockActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.opml.OpmlElement;
import de.danoeh.antennapod.preferences.UserPreferences;
@@ -24,110 +24,111 @@ import de.danoeh.antennapod.preferences.UserPreferences;
* Displays the feeds that the OPML-Importer has read and lets the user choose
* which feeds he wants to import.
*/
-public class OpmlFeedChooserActivity extends SherlockActivity {
- private static final String TAG = "OpmlFeedChooserActivity";
-
- public static final String EXTRA_SELECTED_ITEMS = "de.danoeh.antennapod.selectedItems";
-
- private Button butConfirm;
- private Button butCancel;
- private ListView feedlist;
- private ArrayAdapter<String> listAdapter;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- setTheme(UserPreferences.getTheme());
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.opml_selection);
- butConfirm = (Button) findViewById(R.id.butConfirm);
- butCancel = (Button) findViewById(R.id.butCancel);
- feedlist = (ListView) findViewById(R.id.feedlist);
-
- feedlist.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
- listAdapter = new ArrayAdapter<String>(this,
- android.R.layout.simple_list_item_multiple_choice,
- getTitleList());
-
- feedlist.setAdapter(listAdapter);
-
- butCancel.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- setResult(RESULT_CANCELED);
- finish();
- }
- });
-
- butConfirm.setOnClickListener(new OnClickListener() {
-
- @Override
- public void onClick(View v) {
- Intent intent = new Intent();
- SparseBooleanArray checked = feedlist.getCheckedItemPositions();
-
- int checkedCount = 0;
- // Get number of checked items
- for (int i = 0; i < checked.size(); i++) {
- if (checked.valueAt(i)) {
- checkedCount++;
- }
- }
- int[] selection = new int[checkedCount];
- for (int i = 0, collected = 0; collected < checkedCount; i++) {
- if (checked.valueAt(i)) {
- selection[collected] = checked.keyAt(i);
- collected++;
- }
- }
- intent.putExtra(EXTRA_SELECTED_ITEMS, selection);
- setResult(RESULT_OK, intent);
- finish();
- }
- });
-
- }
-
- private List<String> getTitleList() {
- List<String> result = new ArrayList<String>();
- if (OpmlImportHolder.getReadElements() != null) {
- for (OpmlElement element : OpmlImportHolder.getReadElements()) {
- result.add(element.getText());
- }
-
- }
- return result;
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- menu.add(Menu.NONE, R.id.select_all_item, Menu.NONE,
- R.string.select_all_label).setShowAsAction(
- MenuItem.SHOW_AS_ACTION_IF_ROOM);
- menu.add(Menu.NONE, R.id.deselect_all_item, Menu.NONE,
- R.string.deselect_all_label).setShowAsAction(
- MenuItem.SHOW_AS_ACTION_IF_ROOM);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.select_all_item:
- selectAllItems(true);
- return true;
- case R.id.deselect_all_item:
- selectAllItems(false);
- return true;
- default:
- return false;
- }
- }
-
- private void selectAllItems(boolean b) {
- for (int i = 0; i < feedlist.getCount(); i++) {
- feedlist.setItemChecked(i, b);
- }
- }
+public class OpmlFeedChooserActivity extends ActionBarActivity {
+ private static final String TAG = "OpmlFeedChooserActivity";
+
+ public static final String EXTRA_SELECTED_ITEMS = "de.danoeh.antennapod.selectedItems";
+
+ private Button butConfirm;
+ private Button butCancel;
+ private ListView feedlist;
+ private ArrayAdapter<String> listAdapter;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.opml_selection);
+ butConfirm = (Button) findViewById(R.id.butConfirm);
+ butCancel = (Button) findViewById(R.id.butCancel);
+ feedlist = (ListView) findViewById(R.id.feedlist);
+
+ feedlist.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+ listAdapter = new ArrayAdapter<String>(this,
+ android.R.layout.simple_list_item_multiple_choice,
+ getTitleList());
+
+ feedlist.setAdapter(listAdapter);
+
+ butCancel.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ });
+
+ butConfirm.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent();
+ SparseBooleanArray checked = feedlist.getCheckedItemPositions();
+
+ int checkedCount = 0;
+ // Get number of checked items
+ for (int i = 0; i < checked.size(); i++) {
+ if (checked.valueAt(i)) {
+ checkedCount++;
+ }
+ }
+ int[] selection = new int[checkedCount];
+ for (int i = 0, collected = 0; collected < checkedCount; i++) {
+ if (checked.valueAt(i)) {
+ selection[collected] = checked.keyAt(i);
+ collected++;
+ }
+ }
+ intent.putExtra(EXTRA_SELECTED_ITEMS, selection);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+ });
+
+ }
+
+ private List<String> getTitleList() {
+ List<String> result = new ArrayList<String>();
+ if (OpmlImportHolder.getReadElements() != null) {
+ for (OpmlElement element : OpmlImportHolder.getReadElements()) {
+ result.add(element.getText());
+ }
+
+ }
+ return result;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.select_all_item, Menu.NONE,
+ R.string.select_all_label),
+ MenuItem.SHOW_AS_ACTION_IF_ROOM);
+
+ MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.deselect_all_item, Menu.NONE,
+ R.string.deselect_all_label),
+ MenuItem.SHOW_AS_ACTION_IF_ROOM);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.select_all_item:
+ selectAllItems(true);
+ return true;
+ case R.id.deselect_all_item:
+ selectAllItems(false);
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private void selectAllItems(boolean b) {
+ for (int i = 0; i < feedlist.getCount(); i++) {
+ feedlist.setItemChecked(i, b);
+ }
+ }
}
diff --git a/src/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java b/src/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java
index f887fdd94..905183aa2 100644
--- a/src/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java
+++ b/src/de/danoeh/antennapod/activity/OpmlImportBaseActivity.java
@@ -4,10 +4,9 @@ import java.io.Reader;
import java.util.ArrayList;
import android.content.Intent;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
-import com.actionbarsherlock.app.SherlockActivity;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.asynctask.OpmlFeedQueuer;
import de.danoeh.antennapod.asynctask.OpmlImportWorker;
@@ -16,7 +15,7 @@ import de.danoeh.antennapod.opml.OpmlElement;
/**
* Base activity for Opml Import - e.g. with code what to do afterwards
* */
-public class OpmlImportBaseActivity extends SherlockActivity {
+public class OpmlImportBaseActivity extends ActionBarActivity {
private static final String TAG = "OpmlImportBaseActivity";
private OpmlImportWorker importWorker;
diff --git a/src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java b/src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java
index b38e0c443..259689abf 100644
--- a/src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java
+++ b/src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java
@@ -9,15 +9,14 @@ import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.preferences.UserPreferences;
diff --git a/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java b/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java
index 7269f7549..e376b08b8 100644
--- a/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java
+++ b/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java
@@ -1,10 +1,13 @@
package de.danoeh.antennapod.activity;
import android.content.Context;
-import android.content.res.TypedArray;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.support.v7.app.ActionBarActivity;
+import android.view.*;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -12,28 +15,32 @@ import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
-import com.actionbarsherlock.app.SherlockListActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
import com.mobeta.android.dslv.DragSortListView;
-
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.storage.DBReader;
+import de.danoeh.antennapod.storage.DBTasks;
+import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.util.UndoBarController;
-public class OrganizeQueueActivity extends SherlockListActivity implements
+import java.util.List;
+
+public class OrganizeQueueActivity extends ActionBarActivity implements
UndoBarController.UndoListener {
private static final String TAG = "OrganizeQueueActivity";
private static final int MENU_ID_ACCEPT = 2;
+ private List<FeedItem> queue;
+
private OrganizeAdapter adapter;
private UndoBarController undoBarController;
+ private DragSortListView listView;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
@@ -41,17 +48,42 @@ public class OrganizeQueueActivity extends SherlockListActivity implements
setContentView(R.layout.organize_queue);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- DragSortListView listView = (DragSortListView) getListView();
+ listView = (DragSortListView) findViewById(android.R.id.list);
listView.setDropListener(dropListener);
listView.setRemoveListener(removeListener);
+ listView.setEmptyView(findViewById(android.R.id.empty));
- adapter = new OrganizeAdapter(this);
- setListAdapter(adapter);
-
+ loadData();
undoBarController = new UndoBarController(findViewById(R.id.undobar),
this);
}
+ private void loadData() {
+ AsyncTask<Void, Void, List<FeedItem>> loadTask = new AsyncTask<Void, Void, List<FeedItem>>() {
+
+ @Override
+ protected List<FeedItem> doInBackground(Void... voids) {
+ return DBReader.getQueue(OrganizeQueueActivity.this);
+ }
+
+ @Override
+ protected void onPostExecute(List<FeedItem> feedItems) {
+ super.onPostExecute(feedItems);
+ if (feedItems != null) {
+ queue = feedItems;
+ if (adapter == null) {
+ adapter = new OrganizeAdapter(OrganizeQueueActivity.this);
+ listView.setAdapter(adapter);
+ }
+ adapter.notifyDataSetChanged();
+ } else {
+ Log.e(TAG, "Queue was null");
+ }
+ }
+ };
+ loadTask.execute();
+ }
+
@Override
protected void onPause() {
super.onPause();
@@ -61,8 +93,7 @@ public class OrganizeQueueActivity extends SherlockListActivity implements
@Override
protected void onStop() {
super.onStop();
- FeedManager.getInstance().autodownloadUndownloadedItems(
- getApplicationContext());
+ DBTasks.autodownloadUndownloadedItems(getApplicationContext());
}
@Override
@@ -76,9 +107,7 @@ public class OrganizeQueueActivity extends SherlockListActivity implements
@Override
public void update(EventDistributor eventDistributor, Integer arg) {
if (((EventDistributor.QUEUE_UPDATE | EventDistributor.FEED_LIST_UPDATE) & arg) != 0) {
- if (adapter != null) {
- adapter.notifyDataSetChanged();
- }
+ loadData();
}
}
};
@@ -87,9 +116,10 @@ public class OrganizeQueueActivity extends SherlockListActivity implements
@Override
public void drop(int from, int to) {
- FeedManager manager = FeedManager.getInstance();
- manager.moveQueueItem(OrganizeQueueActivity.this, from, to, false);
- adapter.notifyDataSetChanged();
+ final FeedItem item = queue.remove(from);
+ queue.add(to, item);
+ adapter.notifyDataSetChanged();
+ DBWriter.moveQueueItem(OrganizeQueueActivity.this, from, to, true);
}
};
@@ -97,9 +127,8 @@ public class OrganizeQueueActivity extends SherlockListActivity implements
@Override
public void remove(int which) {
- FeedManager manager = FeedManager.getInstance();
- FeedItem item = (FeedItem) getListAdapter().getItem(which);
- manager.removeQueueItem(OrganizeQueueActivity.this, item, false);
+ FeedItem item = (FeedItem) listView.getAdapter().getItem(which);
+ DBWriter.removeQueueItem(OrganizeQueueActivity.this, item.getId(), true);
undoBarController.showUndoBar(false,
getString(R.string.removed_from_queue), new UndoToken(item,
which));
@@ -127,22 +156,20 @@ public class OrganizeQueueActivity extends SherlockListActivity implements
public void onUndo(Parcelable token) {
// Perform the undo
UndoToken undoToken = (UndoToken) token;
- FeedItem feedItem = undoToken.getFeedItem();
- int position = undoToken.getPosition();
-
- FeedManager manager = FeedManager.getInstance();
- manager.addQueueItemAt(OrganizeQueueActivity.this, feedItem, position,
- false);
+ if (token != null) {
+ long itemId = undoToken.getFeedItemId();
+ int position = undoToken.getPosition();
+ DBWriter.addQueueItemAt(OrganizeQueueActivity.this, itemId, position, false);
+ }
}
private static class OrganizeAdapter extends BaseAdapter {
- private Context context;
- private FeedManager manager = FeedManager.getInstance();
+ private OrganizeQueueActivity organizeQueueActivity;
- public OrganizeAdapter(Context context) {
+ public OrganizeAdapter(OrganizeQueueActivity organizeQueueActivity) {
super();
- this.context = context;
+ this.organizeQueueActivity = organizeQueueActivity;
}
@Override
@@ -152,7 +179,7 @@ public class OrganizeQueueActivity extends SherlockListActivity implements
if (convertView == null) {
holder = new Holder();
- LayoutInflater inflater = (LayoutInflater) context
+ LayoutInflater inflater = (LayoutInflater) organizeQueueActivity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(
R.layout.organize_queue_listitem, null);
@@ -189,13 +216,20 @@ public class OrganizeQueueActivity extends SherlockListActivity implements
@Override
public int getCount() {
- int queueSize = manager.getQueueSize(true);
- return queueSize;
+ if (organizeQueueActivity.queue != null) {
+ return organizeQueueActivity.queue.size();
+ } else {
+ return 0;
+ }
}
@Override
public FeedItem getItem(int position) {
- return manager.getQueueItemAtIndex(position, true);
+ if (organizeQueueActivity.queue != null) {
+ return organizeQueueActivity.queue.get(position);
+ } else {
+ return null;
+ }
}
@Override
@@ -211,7 +245,6 @@ public class OrganizeQueueActivity extends SherlockListActivity implements
private int position;
public UndoToken(FeedItem item, int position) {
- FeedManager manager = FeedManager.getInstance();
this.itemId = item.getId();
this.feedId = item.getFeed().getId();
this.position = position;
@@ -243,9 +276,8 @@ public class OrganizeQueueActivity extends SherlockListActivity implements
out.writeInt(position);
}
- public FeedItem getFeedItem() {
- FeedManager manager = FeedManager.getInstance();
- return manager.getFeedItem(itemId, feedId);
+ public long getFeedItemId() {
+ return itemId;
}
public int getPosition() {
diff --git a/src/de/danoeh/antennapod/activity/PlaybackHistoryActivity.java b/src/de/danoeh/antennapod/activity/PlaybackHistoryActivity.java
index 1a5a2cac9..f329581e5 100644
--- a/src/de/danoeh/antennapod/activity/PlaybackHistoryActivity.java
+++ b/src/de/danoeh/antennapod/activity/PlaybackHistoryActivity.java
@@ -3,25 +3,25 @@ package de.danoeh.antennapod.activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
-import com.actionbarsherlock.app.SherlockFragmentActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-
+import android.view.Menu;
+import android.view.MenuItem;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.fragment.PlaybackHistoryFragment;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.storage.DBWriter;
-public class PlaybackHistoryActivity extends SherlockFragmentActivity {
+public class PlaybackHistoryActivity extends ActionBarActivity {
private static final String TAG = "PlaybackHistoryActivity";
@Override
public boolean onCreateOptionsMenu(Menu menu) {
- menu.add(Menu.NONE, R.id.clear_history_item, Menu.NONE,
- R.string.clear_history_label).setShowAsAction(
+ MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.clear_history_item, Menu.NONE,
+ R.string.clear_history_label),
MenuItem.SHOW_AS_ACTION_IF_ROOM);
return true;
}
@@ -36,7 +36,7 @@ public class PlaybackHistoryActivity extends SherlockFragmentActivity {
startActivity(intent);
return true;
case R.id.clear_history_item:
- FeedManager.getInstance().clearPlaybackHistory(this);
+ DBWriter.clearPlaybackHistory(this);
return true;
}
return super.onOptionsItemSelected(item);
diff --git a/src/de/danoeh/antennapod/activity/PreferenceActivity.java b/src/de/danoeh/antennapod/activity/PreferenceActivity.java
index 9fcf57ac2..880724c28 100644
--- a/src/de/danoeh/antennapod/activity/PreferenceActivity.java
+++ b/src/de/danoeh/antennapod/activity/PreferenceActivity.java
@@ -19,369 +19,358 @@ import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceScreen;
import android.util.Log;
-import com.actionbarsherlock.app.SherlockPreferenceActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-
+import android.view.Menu;
+import android.view.MenuItem;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.asynctask.OpmlExportWorker;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.util.flattr.FlattrUtils;
-/** The main preference activity */
-public class PreferenceActivity extends SherlockPreferenceActivity {
- private static final String TAG = "PreferenceActivity";
+/**
+ * The main preference activity
+ */
+public class PreferenceActivity extends android.preference.PreferenceActivity {
+ private static final String TAG = "PreferenceActivity";
+
+ private static final String PREF_FLATTR_THIS_APP = "prefFlattrThisApp";
+ private static final String PREF_FLATTR_AUTH = "pref_flattr_authenticate";
+ private static final String PREF_FLATTR_REVOKE = "prefRevokeAccess";
+ private static final String PREF_OPML_EXPORT = "prefOpmlExport";
+ private static final String PREF_ABOUT = "prefAbout";
+ private static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir";
+ private static final String AUTO_DL_PREF_SCREEN = "prefAutoDownloadSettings";
- private static final String PREF_FLATTR_THIS_APP = "prefFlattrThisApp";
- private static final String PREF_FLATTR_AUTH = "pref_flattr_authenticate";
- private static final String PREF_FLATTR_REVOKE = "prefRevokeAccess";
- private static final String PREF_OPML_EXPORT = "prefOpmlExport";
- private static final String PREF_ABOUT = "prefAbout";
- private static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir";
- private static final String AUTO_DL_PREF_SCREEN = "prefAutoDownloadSettings";
+ private CheckBoxPreference[] selectedNetworks;
- private CheckBoxPreference[] selectedNetworks;
+ @SuppressWarnings("deprecation")
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
- @SuppressWarnings("deprecation")
- @Override
- public void onCreate(Bundle savedInstanceState) {
- setTheme(UserPreferences.getTheme());
- super.onCreate(savedInstanceState);
+ if (android.os.Build.VERSION.SDK_INT >= 11) {
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+ }
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
addPreferencesFromResource(R.xml.preferences);
findPreference(PREF_FLATTR_THIS_APP).setOnPreferenceClickListener(
new OnPreferenceClickListener() {
- @Override
- public boolean onPreferenceClick(Preference preference) {
- new FlattrClickWorker(PreferenceActivity.this,
- FlattrUtils.APP_URL).executeAsync();
-
- return true;
- }
- });
-
- findPreference(PREF_FLATTR_REVOKE).setOnPreferenceClickListener(
- new OnPreferenceClickListener() {
-
- @Override
- public boolean onPreferenceClick(Preference preference) {
- FlattrUtils.revokeAccessToken(PreferenceActivity.this);
- checkItemVisibility();
- return true;
- }
-
- });
-
- findPreference(PREF_ABOUT).setOnPreferenceClickListener(
- new OnPreferenceClickListener() {
-
- @Override
- public boolean onPreferenceClick(Preference preference) {
- PreferenceActivity.this.startActivity(new Intent(
- PreferenceActivity.this, AboutActivity.class));
- return true;
- }
-
- });
-
- findPreference(PREF_OPML_EXPORT).setOnPreferenceClickListener(
- new OnPreferenceClickListener() {
-
- @Override
- public boolean onPreferenceClick(Preference preference) {
- if (FeedManager.getInstance().getFeedsSize() > 0) {
- new OpmlExportWorker(PreferenceActivity.this)
- .executeAsync();
- }
- return true;
- }
- });
-
- findPreference(PREF_CHOOSE_DATA_DIR).setOnPreferenceClickListener(
- new OnPreferenceClickListener() {
-
- @Override
- public boolean onPreferenceClick(Preference preference) {
- startActivityForResult(
- new Intent(PreferenceActivity.this,
- DirectoryChooserActivity.class),
- DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED);
- return true;
- }
- });
- findPreference(UserPreferences.PREF_THEME)
- .setOnPreferenceChangeListener(
- new OnPreferenceChangeListener() {
-
- @Override
- public boolean onPreferenceChange(
- Preference preference, Object newValue) {
- Intent i = getIntent();
- i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK
- | Intent.FLAG_ACTIVITY_NEW_TASK);
- finish();
- startActivity(i);
- return true;
- }
- });
- findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER)
- .setOnPreferenceChangeListener(
- new OnPreferenceChangeListener() {
-
- @Override
- public boolean onPreferenceChange(
- Preference preference, Object newValue) {
- if (newValue instanceof Boolean) {
- setSelectedNetworksEnabled((Boolean) newValue);
- return true;
- } else {
- return false;
- }
- }
- });
- findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE)
- .setOnPreferenceChangeListener(
- new OnPreferenceChangeListener() {
-
- @Override
- public boolean onPreferenceChange(
- Preference preference, Object newValue) {
- if (newValue instanceof String) {
- setEpisodeCacheSizeText(Integer
- .valueOf((String) newValue));
- }
- return true;
- }
- });
- findPreference(UserPreferences.PREF_ENABLE_AUTODL)
- .setOnPreferenceClickListener(new OnPreferenceClickListener() {
-
- @Override
- public boolean onPreferenceClick(Preference preference) {
- checkItemVisibility();
- return true;
- }
- });
- buildUpdateIntervalPreference();
- buildAutodownloadSelectedNetworsPreference();
- setSelectedNetworksEnabled(UserPreferences
- .isEnableAutodownloadWifiFilter());
-
- }
-
- private void buildUpdateIntervalPreference() {
- ListPreference pref = (ListPreference) findPreference(UserPreferences.PREF_UPDATE_INTERVAL);
- String[] values = getResources().getStringArray(
- R.array.update_intervall_values);
- String[] entries = new String[values.length];
- for (int x = 0; x < values.length; x++) {
- Integer v = Integer.parseInt(values[x]);
- switch (v) {
- case 0:
- entries[x] = getString(R.string.pref_update_interval_hours_manual);
- break;
- case 1:
- entries[x] = v
- + " "
- + getString(R.string.pref_update_interval_hours_singular);
- break;
- default:
- entries[x] = v + " "
- + getString(R.string.pref_update_interval_hours_plural);
- break;
-
- }
- }
- pref.setEntries(entries);
-
- }
-
- private void setSelectedNetworksEnabled(boolean b) {
- if (selectedNetworks != null) {
- for (Preference p : selectedNetworks) {
- p.setEnabled(b);
- }
- }
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- checkItemVisibility();
- setEpisodeCacheSizeText(UserPreferences.getEpisodeCacheSize());
- setDataFolderText();
- }
-
- @SuppressWarnings("deprecation")
- private void checkItemVisibility() {
-
- boolean hasFlattrToken = FlattrUtils.hasToken();
-
- findPreference(PREF_FLATTR_AUTH).setEnabled(!hasFlattrToken);
- findPreference(PREF_FLATTR_REVOKE).setEnabled(hasFlattrToken);
-
- findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER)
- .setEnabled(UserPreferences.isEnableAutodownload());
- setSelectedNetworksEnabled(UserPreferences.isEnableAutodownload()
- && UserPreferences.isEnableAutodownloadWifiFilter());
-
- }
-
- private void setEpisodeCacheSizeText(int cacheSize) {
- String s;
- if (cacheSize == getResources().getInteger(
- R.integer.episode_cache_size_unlimited)) {
- s = getString(R.string.pref_episode_cache_unlimited);
- } else {
- s = Integer.toString(cacheSize)
- + getString(R.string.episodes_suffix);
- }
- findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE).setSummary(s);
- }
-
- private void setDataFolderText() {
- File f = UserPreferences.getDataFolder(this, null);
- if (f != null) {
- findPreference(PREF_CHOOSE_DATA_DIR)
- .setSummary(f.getAbsolutePath());
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- Intent intent = new Intent(this, MainActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- startActivity(intent);
- break;
- default:
- return false;
- }
- return true;
- }
-
- @Override
- protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
- theme.applyStyle(UserPreferences.getTheme(), true);
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- if (resultCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) {
- String dir = data
- .getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Setting data folder");
- UserPreferences.setDataFolder(dir);
- }
- }
-
- private void buildAutodownloadSelectedNetworsPreference() {
- if (selectedNetworks != null) {
- clearAutodownloadSelectedNetworsPreference();
- }
- // get configured networks
- WifiManager wifiservice = (WifiManager) getSystemService(Context.WIFI_SERVICE);
- List<WifiConfiguration> networks = wifiservice.getConfiguredNetworks();
-
- if (networks != null) {
- selectedNetworks = new CheckBoxPreference[networks.size()];
- List<String> prefValues = Arrays.asList(UserPreferences
- .getAutodownloadSelectedNetworks());
- PreferenceScreen prefScreen = (PreferenceScreen) findPreference(AUTO_DL_PREF_SCREEN);
- OnPreferenceClickListener clickListener = new OnPreferenceClickListener() {
-
- @Override
- public boolean onPreferenceClick(Preference preference) {
- if (preference instanceof CheckBoxPreference) {
- String key = preference.getKey();
- ArrayList<String> prefValuesList = new ArrayList<String>(
- Arrays.asList(UserPreferences
- .getAutodownloadSelectedNetworks()));
- boolean newValue = ((CheckBoxPreference) preference)
- .isChecked();
- if (AppConfig.DEBUG)
- Log.d(TAG, "Selected network " + key
- + ". New state: " + newValue);
-
- int index = prefValuesList.indexOf(key);
- if (index >= 0 && newValue == false) {
- // remove network
- prefValuesList.remove(index);
- } else if (index < 0 && newValue == true) {
- prefValuesList.add(key);
- }
-
- UserPreferences.setAutodownloadSelectedNetworks(
- PreferenceActivity.this, prefValuesList
- .toArray(new String[prefValuesList
- .size()]));
- return true;
- } else {
- return false;
- }
- }
- };
- // create preference for each known network. attach listener and set
- // value
- for (int i = 0; i < networks.size(); i++) {
- WifiConfiguration config = networks.get(i);
-
- CheckBoxPreference pref = new CheckBoxPreference(this);
- String key = Integer.toString(config.networkId);
- pref.setTitle(config.SSID);
- pref.setKey(key);
- pref.setOnPreferenceClickListener(clickListener);
- pref.setPersistent(false);
- pref.setChecked(prefValues.contains(key));
- selectedNetworks[i] = pref;
- prefScreen.addPreference(pref);
- }
- } else {
- Log.e(TAG, "Couldn't get list of configure Wi-Fi networks");
- }
- }
-
- private void clearAutodownloadSelectedNetworsPreference() {
- if (selectedNetworks != null) {
- PreferenceScreen prefScreen = (PreferenceScreen) findPreference(AUTO_DL_PREF_SCREEN);
-
- for (int i = 0; i < selectedNetworks.length; i++) {
- if (selectedNetworks[i] != null) {
- prefScreen.removePreference(selectedNetworks[i]);
- }
- }
- }
- }
-
- @SuppressWarnings("deprecation")
- @Override
- public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
- Preference preference) {
- super.onPreferenceTreeClick(preferenceScreen, preference);
- if (preference != null)
- if (preference instanceof PreferenceScreen)
- if (((PreferenceScreen) preference).getDialog() != null)
- ((PreferenceScreen) preference)
- .getDialog()
- .getWindow()
- .getDecorView()
- .setBackgroundDrawable(
- this.getWindow().getDecorView()
- .getBackground().getConstantState()
- .newDrawable());
- return false;
- }
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ new FlattrClickWorker(PreferenceActivity.this,
+ FlattrUtils.APP_URL).executeAsync();
+
+ return true;
+ }
+ });
+
+ findPreference(PREF_FLATTR_REVOKE).setOnPreferenceClickListener(
+ new OnPreferenceClickListener() {
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ FlattrUtils.revokeAccessToken(PreferenceActivity.this);
+ checkItemVisibility();
+ return true;
+ }
+
+ });
+
+ findPreference(PREF_ABOUT).setOnPreferenceClickListener(
+ new OnPreferenceClickListener() {
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ PreferenceActivity.this.startActivity(new Intent(
+ PreferenceActivity.this, AboutActivity.class));
+ return true;
+ }
+
+ });
+
+ findPreference(PREF_OPML_EXPORT).setOnPreferenceClickListener(
+ new OnPreferenceClickListener() {
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ new OpmlExportWorker(PreferenceActivity.this)
+ .executeAsync();
+
+ return true;
+ }
+ });
+
+ findPreference(PREF_CHOOSE_DATA_DIR).setOnPreferenceClickListener(
+ new OnPreferenceClickListener() {
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ startActivityForResult(
+ new Intent(PreferenceActivity.this,
+ DirectoryChooserActivity.class),
+ DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED);
+ return true;
+ }
+ });
+ findPreference(UserPreferences.PREF_THEME)
+ .setOnPreferenceChangeListener(
+ new OnPreferenceChangeListener() {
+
+ @Override
+ public boolean onPreferenceChange(
+ Preference preference, Object newValue) {
+ Intent i = getIntent();
+ i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK
+ | Intent.FLAG_ACTIVITY_NEW_TASK);
+ finish();
+ startActivity(i);
+ return true;
+ }
+ });
+ findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER)
+ .setOnPreferenceChangeListener(
+ new OnPreferenceChangeListener() {
+
+ @Override
+ public boolean onPreferenceChange(
+ Preference preference, Object newValue) {
+ if (newValue instanceof Boolean) {
+ setSelectedNetworksEnabled((Boolean) newValue);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ });
+ findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE)
+ .setOnPreferenceChangeListener(
+ new OnPreferenceChangeListener() {
+
+
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object o) {
+ checkItemVisibility();
+ return true;
+ }
+ });
+ buildUpdateIntervalPreference();
+ buildAutodownloadSelectedNetworsPreference();
+ setSelectedNetworksEnabled(UserPreferences
+ .isEnableAutodownloadWifiFilter());
+
+ }
+
+ private void buildUpdateIntervalPreference() {
+ ListPreference pref = (ListPreference) findPreference(UserPreferences.PREF_UPDATE_INTERVAL);
+ String[] values = getResources().getStringArray(
+ R.array.update_intervall_values);
+ String[] entries = new String[values.length];
+ for (int x = 0; x < values.length; x++) {
+ Integer v = Integer.parseInt(values[x]);
+ switch (v) {
+ case 0:
+ entries[x] = getString(R.string.pref_update_interval_hours_manual);
+ break;
+ case 1:
+ entries[x] = v
+ + " "
+ + getString(R.string.pref_update_interval_hours_singular);
+ break;
+ default:
+ entries[x] = v + " "
+ + getString(R.string.pref_update_interval_hours_plural);
+ break;
+
+ }
+ }
+ pref.setEntries(entries);
+
+ }
+
+ private void setSelectedNetworksEnabled(boolean b) {
+ if (selectedNetworks != null) {
+ for (Preference p : selectedNetworks) {
+ p.setEnabled(b);
+ }
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ checkItemVisibility();
+ setEpisodeCacheSizeText(UserPreferences.getEpisodeCacheSize());
+ setDataFolderText();
+ }
+
+ @SuppressWarnings("deprecation")
+ private void checkItemVisibility() {
+
+ boolean hasFlattrToken = FlattrUtils.hasToken();
+
+ findPreference(PREF_FLATTR_AUTH).setEnabled(!hasFlattrToken);
+ findPreference(PREF_FLATTR_REVOKE).setEnabled(hasFlattrToken);
+
+ findPreference(UserPreferences.PREF_ENABLE_AUTODL_WIFI_FILTER)
+ .setEnabled(UserPreferences.isEnableAutodownload());
+ setSelectedNetworksEnabled(UserPreferences.isEnableAutodownload()
+ && UserPreferences.isEnableAutodownloadWifiFilter());
+
+ }
+
+ private void setEpisodeCacheSizeText(int cacheSize) {
+ String s;
+ if (cacheSize == getResources().getInteger(
+ R.integer.episode_cache_size_unlimited)) {
+ s = getString(R.string.pref_episode_cache_unlimited);
+ } else {
+ s = Integer.toString(cacheSize)
+ + getString(R.string.episodes_suffix);
+ }
+ findPreference(UserPreferences.PREF_EPISODE_CACHE_SIZE).setSummary(s);
+ }
+
+ private void setDataFolderText() {
+ File f = UserPreferences.getDataFolder(this, null);
+ if (f != null) {
+ findPreference(PREF_CHOOSE_DATA_DIR)
+ .setSummary(f.getAbsolutePath());
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ Intent intent = new Intent(this, MainActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ break;
+ default:
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
+ theme.applyStyle(UserPreferences.getTheme(), true);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (resultCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) {
+ String dir = data
+ .getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Setting data folder");
+ UserPreferences.setDataFolder(dir);
+ }
+ }
+
+ private void buildAutodownloadSelectedNetworsPreference() {
+ if (selectedNetworks != null) {
+ clearAutodownloadSelectedNetworsPreference();
+ }
+ // get configured networks
+ WifiManager wifiservice = (WifiManager) getSystemService(Context.WIFI_SERVICE);
+ List<WifiConfiguration> networks = wifiservice.getConfiguredNetworks();
+
+ if (networks != null) {
+ selectedNetworks = new CheckBoxPreference[networks.size()];
+ List<String> prefValues = Arrays.asList(UserPreferences
+ .getAutodownloadSelectedNetworks());
+ PreferenceScreen prefScreen = (PreferenceScreen) findPreference(AUTO_DL_PREF_SCREEN);
+ OnPreferenceClickListener clickListener = new OnPreferenceClickListener() {
+
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ if (preference instanceof CheckBoxPreference) {
+ String key = preference.getKey();
+ ArrayList<String> prefValuesList = new ArrayList<String>(
+ Arrays.asList(UserPreferences
+ .getAutodownloadSelectedNetworks()));
+ boolean newValue = ((CheckBoxPreference) preference)
+ .isChecked();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Selected network " + key
+ + ". New state: " + newValue);
+
+ int index = prefValuesList.indexOf(key);
+ if (index >= 0 && newValue == false) {
+ // remove network
+ prefValuesList.remove(index);
+ } else if (index < 0 && newValue == true) {
+ prefValuesList.add(key);
+ }
+
+ UserPreferences.setAutodownloadSelectedNetworks(
+ PreferenceActivity.this, prefValuesList
+ .toArray(new String[prefValuesList
+ .size()]));
+ return true;
+ } else {
+ return false;
+ }
+ }
+ };
+ // create preference for each known network. attach listener and set
+ // value
+ for (int i = 0; i < networks.size(); i++) {
+ WifiConfiguration config = networks.get(i);
+
+ CheckBoxPreference pref = new CheckBoxPreference(this);
+ String key = Integer.toString(config.networkId);
+ pref.setTitle(config.SSID);
+ pref.setKey(key);
+ pref.setOnPreferenceClickListener(clickListener);
+ pref.setPersistent(false);
+ pref.setChecked(prefValues.contains(key));
+ selectedNetworks[i] = pref;
+ prefScreen.addPreference(pref);
+ }
+ } else {
+ Log.e(TAG, "Couldn't get list of configure Wi-Fi networks");
+ }
+ }
+
+ private void clearAutodownloadSelectedNetworsPreference() {
+ if (selectedNetworks != null) {
+ PreferenceScreen prefScreen = (PreferenceScreen) findPreference(AUTO_DL_PREF_SCREEN);
+
+ for (int i = 0; i < selectedNetworks.length; i++) {
+ if (selectedNetworks[i] != null) {
+ prefScreen.removePreference(selectedNetworks[i]);
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
+ Preference preference) {
+ super.onPreferenceTreeClick(preferenceScreen, preference);
+ if (preference != null)
+ if (preference instanceof PreferenceScreen)
+ if (((PreferenceScreen) preference).getDialog() != null)
+ ((PreferenceScreen) preference)
+ .getDialog()
+ .getWindow()
+ .getDecorView()
+ .setBackgroundDrawable(
+ this.getWindow().getDecorView()
+ .getBackground().getConstantState()
+ .newDrawable());
+ return false;
+ }
}
diff --git a/src/de/danoeh/antennapod/activity/SearchActivity.java b/src/de/danoeh/antennapod/activity/SearchActivity.java
index 152710112..257ae86ae 100644
--- a/src/de/danoeh/antennapod/activity/SearchActivity.java
+++ b/src/de/danoeh/antennapod/activity/SearchActivity.java
@@ -1,185 +1,195 @@
package de.danoeh.antennapod.activity;
import java.util.ArrayList;
+import java.util.List;
import android.annotation.SuppressLint;
import android.app.SearchManager;
import android.content.Intent;
import android.os.Bundle;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
+import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
-import com.actionbarsherlock.app.SherlockListActivity;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.SearchlistAdapter;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
-import de.danoeh.antennapod.feed.FeedSearcher;
+import de.danoeh.antennapod.storage.FeedSearcher;
import de.danoeh.antennapod.feed.SearchResult;
import de.danoeh.antennapod.fragment.FeedlistFragment;
import de.danoeh.antennapod.fragment.ItemlistFragment;
import de.danoeh.antennapod.preferences.UserPreferences;
-/** Displays the results when the user searches for FeedItems or Feeds. */
-public class SearchActivity extends SherlockListActivity {
- private static final String TAG = "SearchActivity";
-
- public static final String EXTRA_FEED_ID = "de.danoeh.antennapod.searchactivity.extra.feedId";
-
- private SearchlistAdapter searchAdapter;
- private ArrayList<SearchResult> content;
-
- /** Feed that is being searched or null if the search is global. */
- private Feed selectedFeed;
-
- private TextView txtvStatus;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- setTheme(UserPreferences.getTheme());
- super.onCreate(savedInstanceState);
-
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- setContentView(R.layout.searchlist);
- txtvStatus = (TextView) findViewById(android.R.id.empty);
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- setIntent(intent);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- Intent intent = getIntent();
- if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
- Bundle extra = intent.getBundleExtra(SearchManager.APP_DATA);
- if (extra != null) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Found bundle extra");
- long feedId = extra.getLong(EXTRA_FEED_ID);
- selectedFeed = FeedManager.getInstance().getFeed(feedId);
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "Starting search");
- String query = intent.getStringExtra(SearchManager.QUERY);
- getSupportActionBar()
- .setSubtitle(
- getString(R.string.search_term_label) + "\""
- + query + "\"");
- handleSearchRequest(query);
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
- .setIcon(
- obtainStyledAttributes(
- new int[] { R.attr.action_search })
- .getDrawable(0))
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- Intent intent = new Intent(this, MainActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- startActivity(intent);
- return true;
- case R.id.search_item:
- onSearchRequested();
- return true;
- default:
- return false;
- }
- }
-
- @Override
- public boolean onSearchRequested() {
- Bundle extra = null;
- if (selectedFeed != null) {
- extra = new Bundle();
- extra.putLong(EXTRA_FEED_ID, selectedFeed.getId());
- }
- startSearch(null, false, extra, false);
- return true;
- }
-
- @Override
- protected void onListItemClick(ListView l, View v, int position, long id) {
- super.onListItemClick(l, v, position, id);
- SearchResult selection = searchAdapter.getItem(position);
- if (selection.getComponent().getClass() == Feed.class) {
- Feed feed = (Feed) selection.getComponent();
- Intent launchIntent = new Intent(this, FeedItemlistActivity.class);
- launchIntent.putExtra(FeedlistFragment.EXTRA_SELECTED_FEED,
- feed.getId());
- startActivity(launchIntent);
-
- } else if (selection.getComponent().getClass() == FeedItem.class) {
- FeedItem item = (FeedItem) selection.getComponent();
- Intent launchIntent = new Intent(this, ItemviewActivity.class);
- launchIntent.putExtra(FeedlistFragment.EXTRA_SELECTED_FEED, item
- .getFeed().getId());
- launchIntent.putExtra(ItemlistFragment.EXTRA_SELECTED_FEEDITEM,
- item.getId());
- startActivity(launchIntent);
- }
- }
-
- @SuppressLint({ "NewApi", "NewApi" })
- private void handleSearchRequest(final String query) {
- if (searchAdapter != null) {
- searchAdapter.clear();
- searchAdapter.notifyDataSetChanged();
- }
- txtvStatus.setText(R.string.search_status_searching);
-
- Thread thread = new Thread() {
-
- @Override
- public void run() {
- Log.d(TAG, "Starting background work");
- final ArrayList<SearchResult> result = FeedSearcher
- .performSearch(SearchActivity.this, query, selectedFeed);
- if (SearchActivity.this != null) {
- SearchActivity.this.runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Background work finished");
- if (AppConfig.DEBUG)
- Log.d(TAG, "Found " + result.size()
- + " results");
- content = result;
-
- searchAdapter = new SearchlistAdapter(
- SearchActivity.this, 0, content);
- getListView().setAdapter(searchAdapter);
- searchAdapter.notifyDataSetChanged();
- if (content.isEmpty()) {
- txtvStatus
- .setText(R.string.search_status_no_results);
- }
- }
- });
- }
- }
- };
- thread.start();
-
- }
+/**
+ * Displays the results when the user searches for FeedItems or Feeds.
+ */
+public class SearchActivity extends ActionBarActivity implements AdapterView.OnItemClickListener {
+ private static final String TAG = "SearchActivity";
+
+ public static final String EXTRA_FEED_ID = "de.danoeh.antennapod.searchactivity.extra.feedId";
+
+ private SearchlistAdapter searchAdapter;
+
+ /**
+ * ID of the feed that is being searched or null if the search is global.
+ */
+ private long feedID;
+
+ private ListView listView;
+ private TextView txtvStatus;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setTheme(UserPreferences.getTheme());
+ super.onCreate(savedInstanceState);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ setContentView(R.layout.searchlist);
+ listView = (ListView) findViewById(android.R.id.list);
+ txtvStatus = (TextView) findViewById(android.R.id.empty);
+
+ listView.setOnItemClickListener(this);
+ searchAdapter = new SearchlistAdapter(this, 0, new ArrayList<SearchResult>());
+ listView.setAdapter(searchAdapter);
+ listView.setEmptyView(txtvStatus);
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ setIntent(intent);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Intent intent = getIntent();
+ if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
+ if (intent.hasExtra(SearchActivity.EXTRA_FEED_ID)) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Found bundle extra");
+ feedID = intent.getLongExtra(SearchActivity.EXTRA_FEED_ID, 0);
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Starting search");
+ String query = intent.getStringExtra(SearchManager.QUERY);
+ getSupportActionBar()
+ .setSubtitle(
+ getString(R.string.search_term_label) + "\""
+ + query + "\"");
+ handleSearchRequest(query);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
+ .setIcon(
+ obtainStyledAttributes(
+ new int[]{R.attr.action_search})
+ .getDrawable(0)),
+ (MenuItem.SHOW_AS_ACTION_IF_ROOM));
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ Intent intent = new Intent(this, MainActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ startActivity(intent);
+ return true;
+ case R.id.search_item:
+ onSearchRequested();
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ public boolean onSearchRequested() {
+ Bundle extra = null;
+ if (feedID != 0) {
+ extra = new Bundle();
+ extra.putLong(EXTRA_FEED_ID, feedID);
+ }
+ startSearch(null, false, extra, false);
+ return true;
+ }
+
+ @SuppressLint({"NewApi", "NewApi"})
+ private void handleSearchRequest(final String query) {
+ if (searchAdapter != null) {
+ searchAdapter.clear();
+ searchAdapter.notifyDataSetChanged();
+ }
+ txtvStatus.setText(R.string.search_status_searching);
+
+ Thread thread = new Thread() {
+
+ @Override
+ public void run() {
+ Log.d(TAG, "Starting background work");
+ final List<SearchResult> result = FeedSearcher
+ .performSearch(SearchActivity.this, query, feedID);
+ if (SearchActivity.this != null) {
+ SearchActivity.this.runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Background work finished");
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Found " + result.size()
+ + " results");
+
+ searchAdapter.clear();
+ searchAdapter.addAll(result);
+ searchAdapter.notifyDataSetChanged();
+ txtvStatus
+ .setText(R.string.search_status_no_results);
+ if (!searchAdapter.isEmpty()) {
+ txtvStatus.setVisibility(View.GONE);
+ } else {
+ txtvStatus.setVisibility(View.VISIBLE);
+ }
+ }
+ });
+ }
+ }
+ };
+ thread.start();
+
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
+ SearchResult selection = searchAdapter.getItem(position);
+ if (selection.getComponent().getClass() == Feed.class) {
+ Feed feed = (Feed) selection.getComponent();
+ Intent launchIntent = new Intent(this, FeedItemlistActivity.class);
+ launchIntent.putExtra(FeedlistFragment.EXTRA_SELECTED_FEED,
+ feed.getId());
+ startActivity(launchIntent);
+
+ } else if (selection.getComponent().getClass() == FeedItem.class) {
+ FeedItem item = (FeedItem) selection.getComponent();
+ Intent launchIntent = new Intent(this, ItemviewActivity.class);
+ launchIntent.putExtra(FeedlistFragment.EXTRA_SELECTED_FEED, item
+ .getFeed().getId());
+ launchIntent.putExtra(ItemlistFragment.EXTRA_SELECTED_FEEDITEM,
+ item.getId());
+ startActivity(launchIntent);
+ }
+ }
}
diff --git a/src/de/danoeh/antennapod/activity/StorageErrorActivity.java b/src/de/danoeh/antennapod/activity/StorageErrorActivity.java
index 4d9184dcf..33277ebc9 100644
--- a/src/de/danoeh/antennapod/activity/StorageErrorActivity.java
+++ b/src/de/danoeh/antennapod/activity/StorageErrorActivity.java
@@ -5,17 +5,16 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
-import com.actionbarsherlock.app.SherlockActivity;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.util.StorageUtils;
/** Is show if there is now external storage available. */
-public class StorageErrorActivity extends SherlockActivity {
+public class StorageErrorActivity extends ActionBarActivity {
private static final String TAG = "StorageErrorActivity";
@Override
diff --git a/src/de/danoeh/antennapod/activity/VideoplayerActivity.java b/src/de/danoeh/antennapod/activity/VideoplayerActivity.java
index b3567e417..01841f099 100644
--- a/src/de/danoeh/antennapod/activity/VideoplayerActivity.java
+++ b/src/de/danoeh/antennapod/activity/VideoplayerActivity.java
@@ -5,18 +5,13 @@ import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
-import android.view.MotionEvent;
-import android.view.SurfaceHolder;
-import android.view.View;
-import android.view.WindowManager;
+import android.view.*;
import android.view.animation.AnimationUtils;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.SeekBar;
import android.widget.VideoView;
-import com.actionbarsherlock.view.Window;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.feed.MediaType;
diff --git a/src/de/danoeh/antennapod/adapter/DownloadLogAdapter.java b/src/de/danoeh/antennapod/adapter/DownloadLogAdapter.java
index f97210cf3..c067ac5d2 100644
--- a/src/de/danoeh/antennapod/adapter/DownloadLogAdapter.java
+++ b/src/de/danoeh/antennapod/adapter/DownloadLogAdapter.java
@@ -8,21 +8,22 @@ import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.asynctask.DownloadStatus;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedImage;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.feed.FeedMedia;
+import de.danoeh.antennapod.service.download.DownloadStatus;
import de.danoeh.antennapod.util.DownloadError;
/** Displays a list of DownloadStatus entries. */
public class DownloadLogAdapter extends BaseAdapter {
private Context context;
- private FeedManager manager = FeedManager.getInstance();
- public DownloadLogAdapter(Context context) {
+ private ItemAccess itemAccess;
+
+ public DownloadLogAdapter(Context context, ItemAccess itemAccess) {
super();
+ this.itemAccess = itemAccess;
this.context = context;
}
@@ -70,8 +71,7 @@ public class DownloadLogAdapter extends BaseAdapter {
holder.successful.setTextColor(convertView.getResources().getColor(
R.color.download_failed_red));
holder.successful.setText(R.string.download_failed);
- String reasonText = DownloadError.getErrorString(context,
- status.getReason());
+ String reasonText = status.getReason().getErrorString(context);
if (status.getReasonDetailed() != null) {
reasonText += ": " + status.getReasonDetailed();
}
@@ -92,12 +92,12 @@ public class DownloadLogAdapter extends BaseAdapter {
@Override
public int getCount() {
- return manager.getDownloadLogSize();
+ return itemAccess.getCount();
}
@Override
public DownloadStatus getItem(int position) {
- return manager.getDownloadStatusFromLogAtIndex(position);
+ return itemAccess.getItem(position);
}
@Override
@@ -105,4 +105,9 @@ public class DownloadLogAdapter extends BaseAdapter {
return position;
}
+ public static interface ItemAccess {
+ public int getCount();
+ public DownloadStatus getItem(int position);
+ }
+
}
diff --git a/src/de/danoeh/antennapod/adapter/DownloadlistAdapter.java b/src/de/danoeh/antennapod/adapter/DownloadlistAdapter.java
index 685906d6f..75e837969 100644
--- a/src/de/danoeh/antennapod/adapter/DownloadlistAdapter.java
+++ b/src/de/danoeh/antennapod/adapter/DownloadlistAdapter.java
@@ -10,11 +10,12 @@ import android.widget.ArrayAdapter;
import android.widget.ProgressBar;
import android.widget.TextView;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.asynctask.DownloadStatus;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedFile;
import de.danoeh.antennapod.feed.FeedImage;
import de.danoeh.antennapod.feed.FeedMedia;
+import de.danoeh.antennapod.service.download.DownloadRequest;
+import de.danoeh.antennapod.service.download.DownloadStatus;
import de.danoeh.antennapod.service.download.Downloader;
import de.danoeh.antennapod.util.Converter;
import de.danoeh.antennapod.util.ThemeUtils;
@@ -33,8 +34,7 @@ public class DownloadlistAdapter extends ArrayAdapter<Downloader> {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Holder holder;
- DownloadStatus status = getItem(position).getStatus();
- FeedFile feedFile = status.getFeedFile();
+ DownloadRequest request = getItem(position).getDownloadRequest();
// Inflate layout
if (convertView == null) {
holder = new Holder();
@@ -62,31 +62,16 @@ public class DownloadlistAdapter extends ArrayAdapter<Downloader> {
} else {
convertView.setBackgroundResource(0);
}
-
- String titleText = null;
- if (feedFile.getClass() == FeedMedia.class) {
- titleText = ((FeedMedia) feedFile).getItem().getTitle();
- } else if (feedFile.getClass() == Feed.class) {
- titleText = ((Feed) feedFile).getTitle();
- } else if (feedFile.getClass() == FeedImage.class) {
- FeedImage image = (FeedImage) feedFile;
- if (image.getFeed() != null) {
- titleText = convertView.getResources().getString(
- R.string.image_of_prefix)
- + image.getFeed().getTitle();
- } else {
- titleText = ((FeedImage) feedFile).getTitle();
- }
- }
- holder.title.setText(titleText);
- if (status.getStatusMsg() != 0) {
- holder.message.setText(status.getStatusMsg());
+
+ holder.title.setText(request.getTitle());
+ if (request.getStatusMsg() != 0) {
+ holder.message.setText(request.getStatusMsg());
}
- String strDownloaded = Converter.byteToString(status.getSoFar());
- if (status.getSize() != DownloadStatus.SIZE_UNKNOWN) {
- strDownloaded += " / " + Converter.byteToString(status.getSize());
- holder.percent.setText(status.getProgressPercent() + "%");
- holder.progbar.setProgress(status.getProgressPercent());
+ String strDownloaded = Converter.byteToString(request.getSoFar());
+ if (request.getSize() != DownloadStatus.SIZE_UNKNOWN) {
+ strDownloaded += " / " + Converter.byteToString(request.getSize());
+ holder.percent.setText(request.getProgressPercent() + "%");
+ holder.progbar.setProgress(request.getProgressPercent());
holder.percent.setVisibility(View.VISIBLE);
} else {
holder.progbar.setProgress(0);
diff --git a/src/de/danoeh/antennapod/adapter/ExternalEpisodesListAdapter.java b/src/de/danoeh/antennapod/adapter/ExternalEpisodesListAdapter.java
index 916e13469..b3156f765 100644
--- a/src/de/danoeh/antennapod/adapter/ExternalEpisodesListAdapter.java
+++ b/src/de/danoeh/antennapod/adapter/ExternalEpisodesListAdapter.java
@@ -14,7 +14,6 @@ import android.widget.TextView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.util.Converter;
@@ -30,17 +29,18 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter {
public static final int GROUP_POS_UNREAD = 1;
private Context context;
- private FeedManager manager = FeedManager.getInstance();
+ private ItemAccess itemAccess;
private ActionButtonCallback feedItemActionCallback;
private OnGroupActionClicked groupActionCallback;
public ExternalEpisodesListAdapter(Context context,
ActionButtonCallback callback,
- OnGroupActionClicked groupActionCallback) {
+ OnGroupActionClicked groupActionCallback,
+ ItemAccess itemAccess) {
super();
this.context = context;
-
+ this.itemAccess = itemAccess;
this.feedItemActionCallback = callback;
this.groupActionCallback = groupActionCallback;
}
@@ -53,10 +53,10 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter {
@Override
public FeedItem getChild(int groupPosition, int childPosition) {
if (groupPosition == GROUP_POS_QUEUE) {
- return manager.getQueueItemAtIndex(childPosition, true);
+ return itemAccess.getQueueItemAt(childPosition);
} else if (groupPosition == GROUP_POS_UNREAD) {
- return manager.getUnreadItemAtIndex(childPosition, true);
- }
+ return itemAccess.getUnreadItemAt(childPosition);
+ }
return null;
}
@@ -200,9 +200,9 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter {
@Override
public int getChildrenCount(int groupPosition) {
if (groupPosition == GROUP_POS_QUEUE) {
- return manager.getQueueSize(true);
+ return itemAccess.getQueueSize();
} else if (groupPosition == GROUP_POS_UNREAD) {
- return manager.getUnreadItemsSize(true);
+ return itemAccess.getUnreadItemsSize();
}
return 0;
}
@@ -210,7 +210,7 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter {
@Override
public int getGroupCount() {
// Hide 'unread items' group if empty
- if (manager.getUnreadItemsSize(true) > 0) {
+ if (itemAccess.getUnreadItemsSize() > 0) {
return 2;
} else {
return 1;
@@ -264,8 +264,8 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter {
@Override
public boolean isEmpty() {
- return manager.getUnreadItemsSize(true) == 0
- && manager.getQueueSize(true) == 0;
+ return itemAccess.getUnreadItemsSize() == 0
+ && itemAccess.getQueueSize() == 0;
}
@Override
@@ -287,4 +287,11 @@ public class ExternalEpisodesListAdapter extends BaseExpandableListAdapter {
public void onClick(long groupId);
}
+ public static interface ItemAccess {
+ public int getQueueSize();
+ public int getUnreadItemsSize();
+ public FeedItem getQueueItemAt(int position);
+ public FeedItem getUnreadItemAt(int position);
+ }
+
}
diff --git a/src/de/danoeh/antennapod/adapter/FeedlistAdapter.java b/src/de/danoeh/antennapod/adapter/FeedlistAdapter.java
index 03b46cce5..89427a47e 100644
--- a/src/de/danoeh/antennapod/adapter/FeedlistAdapter.java
+++ b/src/de/danoeh/antennapod/adapter/FeedlistAdapter.java
@@ -11,23 +11,31 @@ import android.widget.TextView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.feed.Feed;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.storage.DownloadRequester;
+import de.danoeh.antennapod.storage.FeedItemStatistics;
import de.danoeh.antennapod.util.ThemeUtils;
public class FeedlistAdapter extends BaseAdapter {
private static final String TAG = "FeedlistAdapter";
private Context context;
- private FeedManager manager = FeedManager.getInstance();
+ protected ItemAccess itemAccess;
private int selectedItemIndex;
private ImageLoader imageLoader;
public static final int SELECTION_NONE = -1;
- public FeedlistAdapter(Context context) {
+ public FeedlistAdapter(Context context, ItemAccess itemAccess) {
super();
+ if (context == null) {
+ throw new IllegalArgumentException("context must not be null");
+ }
+ if (itemAccess == null) {
+ throw new IllegalArgumentException("itemAccess must not be null");
+ }
+
this.context = context;
+ this.itemAccess = itemAccess;
selectedItemIndex = SELECTION_NONE;
imageLoader = ImageLoader.getInstance();
}
@@ -36,6 +44,7 @@ public class FeedlistAdapter extends BaseAdapter {
public View getView(int position, View convertView, ViewGroup parent) {
final Holder holder;
final Feed feed = getItem(position);
+ final FeedItemStatistics feedItemStatistics = itemAccess.getFeedItemStatistics(position);
// Inflate Layout
if (convertView == null) {
@@ -75,42 +84,40 @@ public class FeedlistAdapter extends BaseAdapter {
}
holder.title.setText(feed.getTitle());
- int numOfItems = feed.getNumOfItems(true);
- if (DownloadRequester.getInstance().isDownloadingFile(feed)) {
- holder.lastUpdate.setText(R.string.refreshing_label);
- } else {
- if (numOfItems > 0) {
- holder.lastUpdate.setText(convertView.getResources().getString(
- R.string.most_recent_prefix)
- + DateUtils.getRelativeTimeSpanString(
- feed.getItemAtIndex(true, 0).getPubDate().getTime(),
- System.currentTimeMillis(), 0, 0));
- } else {
- holder.lastUpdate.setText("");
- }
- }
- holder.numberOfEpisodes.setText(numOfItems
- + convertView.getResources()
- .getString(R.string.episodes_suffix));
-
- int newItems = feed.getNumOfNewItems();
- int inProgressItems = feed.getNumOfStartedItems();
-
- if (newItems > 0) {
- holder.newEpisodes.setText(Integer.toString(newItems));
- holder.newEpisodesLabel.setVisibility(View.VISIBLE);
- } else {
- holder.newEpisodesLabel.setVisibility(View.INVISIBLE);
- }
-
- if (inProgressItems > 0) {
- holder.inProgressEpisodes
- .setText(Integer.toString(inProgressItems));
- holder.inProgressEpisodesLabel.setVisibility(View.VISIBLE);
- } else {
- holder.inProgressEpisodesLabel.setVisibility(View.INVISIBLE);
- }
+ if (feedItemStatistics != null) {
+ if (DownloadRequester.getInstance().isDownloadingFile(feed)) {
+ holder.lastUpdate.setText(R.string.refreshing_label);
+ } else {
+ if (feedItemStatistics.getNumberOfItems() > 0) {
+ holder.lastUpdate.setText(convertView.getResources().getString(
+ R.string.most_recent_prefix)
+ + DateUtils.getRelativeTimeSpanString(
+ feedItemStatistics.getLastUpdate().getTime(),
+ System.currentTimeMillis(), 0, 0));
+ } else {
+ holder.lastUpdate.setText("");
+ }
+ }
+ holder.numberOfEpisodes.setText(feedItemStatistics.getNumberOfItems()
+ + convertView.getResources()
+ .getString(R.string.episodes_suffix));
+
+ if (feedItemStatistics.getNumberOfNewItems() > 0) {
+ holder.newEpisodes.setText(Integer.toString(feedItemStatistics.getNumberOfNewItems()));
+ holder.newEpisodesLabel.setVisibility(View.VISIBLE);
+ } else {
+ holder.newEpisodesLabel.setVisibility(View.INVISIBLE);
+ }
+
+ if (feedItemStatistics.getNumberOfInProgressItems() > 0) {
+ holder.inProgressEpisodes
+ .setText(Integer.toString(feedItemStatistics.getNumberOfInProgressItems()));
+ holder.inProgressEpisodesLabel.setVisibility(View.VISIBLE);
+ } else {
+ holder.inProgressEpisodesLabel.setVisibility(View.INVISIBLE);
+ }
+ }
final String imageUrl = (feed.getImage() != null) ? feed.getImage()
.getFile_url() : null;
holder.image.setTag(imageUrl);
@@ -145,12 +152,12 @@ public class FeedlistAdapter extends BaseAdapter {
@Override
public int getCount() {
- return manager.getFeedsSize();
+ return itemAccess.getCount();
}
@Override
public Feed getItem(int position) {
- return manager.getFeedAtIndex(position);
+ return itemAccess.getItem(position);
}
@Override
@@ -158,4 +165,11 @@ public class FeedlistAdapter extends BaseAdapter {
return position;
}
+ public interface ItemAccess {
+ int getCount();
+
+ Feed getItem(int position);
+
+ FeedItemStatistics getFeedItemStatistics(int position);
+ }
}
diff --git a/src/de/danoeh/antennapod/adapter/InternalFeedItemlistAdapter.java b/src/de/danoeh/antennapod/adapter/InternalFeedItemlistAdapter.java
index e5c12f018..b8bec44c8 100644
--- a/src/de/danoeh/antennapod/adapter/InternalFeedItemlistAdapter.java
+++ b/src/de/danoeh/antennapod/adapter/InternalFeedItemlistAdapter.java
@@ -14,13 +14,14 @@ import android.widget.ProgressBar;
import android.widget.TextView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.feed.MediaType;
import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.util.Converter;
import de.danoeh.antennapod.util.ThemeUtils;
+import java.util.Iterator;
+
/** List adapter for items of feeds that the user has already subscribed to. */
public class InternalFeedItemlistAdapter extends DefaultFeedItemlistAdapter {
@@ -31,7 +32,7 @@ public class InternalFeedItemlistAdapter extends DefaultFeedItemlistAdapter {
public static final int SELECTION_NONE = -1;
public InternalFeedItemlistAdapter(Context context,
- DefaultFeedItemlistAdapter.ItemAccess itemAccess,
+ ItemAccess itemAccess,
ActionButtonCallback callback, boolean showFeedtitle) {
super(context, itemAccess);
this.callback = callback;
@@ -155,7 +156,7 @@ public class InternalFeedItemlistAdapter extends DefaultFeedItemlistAdapter {
}
holder.lenSize.setVisibility(View.VISIBLE);
- if (FeedManager.getInstance().isInQueue(item)) {
+ if (((ItemAccess) itemAccess).isInQueue(item)) {
holder.inPlaylist.setVisibility(View.VISIBLE);
} else {
holder.inPlaylist.setVisibility(View.GONE);
@@ -224,4 +225,8 @@ public class InternalFeedItemlistAdapter extends DefaultFeedItemlistAdapter {
notifyDataSetChanged();
}
+ public static interface ItemAccess extends DefaultFeedItemlistAdapter.ItemAccess {
+ public boolean isInQueue(FeedItem item);
+ }
+
}
diff --git a/src/de/danoeh/antennapod/asynctask/DownloadStatus.java b/src/de/danoeh/antennapod/asynctask/DownloadStatus.java
deleted file mode 100644
index e9225d33b..000000000
--- a/src/de/danoeh/antennapod/asynctask/DownloadStatus.java
+++ /dev/null
@@ -1,198 +0,0 @@
-package de.danoeh.antennapod.asynctask;
-
-import java.util.Date;
-
-import de.danoeh.antennapod.feed.FeedFile;
-
-/** Contains status attributes for one download */
-public class DownloadStatus {
- /**
- * Downloaders should use this constant for the size attribute if necessary
- * so that the listadapters etc. can react properly.
- */
- public static final int SIZE_UNKNOWN = -1;
-
- public Date getCompletionDate() {
- return completionDate;
- }
-
- // ----------------------------------- ATTRIBUTES STORED IN DB
- /** Unique id for storing the object in database. */
- protected long id;
- /**
- * A human-readable string which is shown to the user so that he can
- * identify the download. Should be the title of the item/feed/media or the
- * URL if the download has no other title.
- */
- protected String title;
- protected int reason;
- /**
- * A message which can be presented to the user to give more information.
- * Should be null if Download was successful.
- */
- protected String reasonDetailed;
- protected boolean successful;
- protected Date completionDate;
- protected FeedFile feedfile;
- /**
- * Is used to determine the type of the feedfile even if the feedfile does
- * not exist anymore. The value should be FEEDFILETYPE_FEED,
- * FEEDFILETYPE_FEEDIMAGE or FEEDFILETYPE_FEEDMEDIA
- */
- protected int feedfileType;
-
- // ------------------------------------ NOT STORED IN DB
- protected int progressPercent;
- protected long soFar;
- protected long size;
- protected int statusMsg;
- protected boolean done;
- protected boolean cancelled;
-
- public DownloadStatus(FeedFile feedfile, String title) {
- this.feedfile = feedfile;
- if (feedfile != null) {
- feedfileType = feedfile.getTypeAsInt();
- }
- this.title = title;
- }
-
- /** Constructor for restoring Download status entries from DB. */
- public DownloadStatus(long id, String title, FeedFile feedfile,
- int feedfileType, boolean successful, int reason,
- Date completionDate, String reasonDetailed) {
- progressPercent = 100;
- soFar = 0;
- size = 0;
-
- this.id = id;
- this.title = title;
- this.done = true;
- this.feedfile = feedfile;
- this.reason = reason;
- this.successful = successful;
- this.completionDate = completionDate;
- this.reasonDetailed = reasonDetailed;
- this.feedfileType = feedfileType;
- }
-
- /** Constructor for creating new completed downloads. */
- public DownloadStatus(FeedFile feedfile, String title, int reason,
- boolean successful, String reasonDetailed) {
- this(0, title, feedfile, feedfile.getTypeAsInt(), successful, reason,
- new Date(), reasonDetailed);
- }
-
- @Override
- public String toString() {
- return "DownloadStatus [id=" + id + ", title=" + title + ", reason="
- + reason + ", reasonDetailed=" + reasonDetailed
- + ", successful=" + successful + ", completionDate="
- + completionDate + ", feedfile=" + feedfile + ", feedfileType="
- + feedfileType + ", progressPercent=" + progressPercent
- + ", soFar=" + soFar + ", size=" + size + ", statusMsg="
- + statusMsg + ", done=" + done + ", cancelled=" + cancelled
- + "]";
- }
-
- public FeedFile getFeedFile() {
- return feedfile;
- }
-
- public int getProgressPercent() {
- return progressPercent;
- }
-
- public long getSoFar() {
- return soFar;
- }
-
- public long getSize() {
- return size;
- }
-
- public int getStatusMsg() {
- return statusMsg;
- }
-
- public int getReason() {
- return reason;
- }
-
- public boolean isSuccessful() {
- return successful;
- }
-
- public long getId() {
- return id;
- }
-
- public void setId(long id) {
- this.id = id;
- }
-
- public boolean isDone() {
- return done;
- }
-
- public void setProgressPercent(int progressPercent) {
- this.progressPercent = progressPercent;
- }
-
- public void setSoFar(long soFar) {
- this.soFar = soFar;
- }
-
- public void setSize(long size) {
- this.size = size;
- }
-
- public void setStatusMsg(int statusMsg) {
- this.statusMsg = statusMsg;
- }
-
- public void setReason(int reason) {
- this.reason = reason;
- }
-
- public void setSuccessful(boolean successful) {
- this.successful = successful;
- }
-
- public void setDone(boolean done) {
- this.done = done;
- }
-
- public void setCompletionDate(Date completionDate) {
- this.completionDate = completionDate;
- }
-
- public String getReasonDetailed() {
- return reasonDetailed;
- }
-
- public void setReasonDetailed(String reasonDetailed) {
- this.reasonDetailed = reasonDetailed;
- }
-
- public String getTitle() {
- return title;
- }
-
- public void setTitle(String title) {
- this.title = title;
- }
-
- public int getFeedfileType() {
- return feedfileType;
- }
-
- public boolean isCancelled() {
- return cancelled;
- }
-
- public void setCancelled(boolean cancelled) {
- this.cancelled = cancelled;
- }
-
-} \ No newline at end of file
diff --git a/src/de/danoeh/antennapod/asynctask/FeedRemover.java b/src/de/danoeh/antennapod/asynctask/FeedRemover.java
index 829a14602..244312a6e 100644
--- a/src/de/danoeh/antennapod/asynctask/FeedRemover.java
+++ b/src/de/danoeh/antennapod/asynctask/FeedRemover.java
@@ -7,7 +7,9 @@ import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.os.AsyncTask;
import de.danoeh.antennapod.feed.Feed;
-import de.danoeh.antennapod.feed.FeedManager;
+import de.danoeh.antennapod.storage.DBWriter;
+
+import java.util.concurrent.ExecutionException;
/** Removes a feed in the background. */
public class FeedRemover extends AsyncTask<Void, Void, Void> {
@@ -23,9 +25,14 @@ public class FeedRemover extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
- FeedManager manager = FeedManager.getInstance();
- manager.deleteFeed(context, feed);
- return null;
+ try {
+ DBWriter.deleteFeed(context, feed.getId()).get();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ }
+ return null;
}
@Override
diff --git a/src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java b/src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java
index 978f53ac6..e14e22917 100644
--- a/src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java
+++ b/src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java
@@ -14,9 +14,9 @@ import android.os.AsyncTask;
import android.util.Log;
import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.opml.OpmlWriter;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.storage.DBReader;
/** Writes an OPML file into the export directory in the background. */
public class OpmlExportWorker extends AsyncTask<Void, Void, Void> {
@@ -51,8 +51,7 @@ public class OpmlExportWorker extends AsyncTask<Void, Void, Void> {
}
try {
FileWriter writer = new FileWriter(output);
- opmlWriter.writeDocument(Arrays.asList(FeedManager.getInstance().getFeedsArray()),
- writer);
+ opmlWriter.writeDocument(DBReader.getFeedList(context), writer);
writer.close();
} catch (IOException e) {
e.printStackTrace();
diff --git a/src/de/danoeh/antennapod/feed/EventDistributor.java b/src/de/danoeh/antennapod/feed/EventDistributor.java
index 1fc7e2c35..c538808e2 100644
--- a/src/de/danoeh/antennapod/feed/EventDistributor.java
+++ b/src/de/danoeh/antennapod/feed/EventDistributor.java
@@ -92,7 +92,7 @@ public class EventDistributor extends Observable {
super.addObserver(observer);
if (!(observer instanceof EventListener)) {
throw new IllegalArgumentException(
- "Observer must be instance of FeedManager.EventListener");
+ "Observer must be instance of EventListener");
}
}
diff --git a/src/de/danoeh/antennapod/feed/Feed.java b/src/de/danoeh/antennapod/feed/Feed.java
index 6220bde00..34505dda9 100644
--- a/src/de/danoeh/antennapod/feed/Feed.java
+++ b/src/de/danoeh/antennapod/feed/Feed.java
@@ -10,339 +10,356 @@ import de.danoeh.antennapod.util.EpisodeFilter;
/**
* Data Object for a whole feed
- *
+ *
* @author daniel
- *
*/
public class Feed extends FeedFile {
- public static final int FEEDFILETYPE_FEED = 0;
- public static final String TYPE_RSS2 = "rss";
- public static final String TYPE_RSS091 = "rss";
- public static final String TYPE_ATOM1 = "atom";
-
- private String title;
- /** Contains 'id'-element in Atom feed. */
- private String feedIdentifier;
- /** Link to the website. */
- private String link;
- private String description;
- private String language;
- /** Name of the author */
- private String author;
- private FeedImage image;
- private List<FeedItem> items;
- /** Date of last refresh. */
- private Date lastUpdate;
- private String paymentLink;
- /** Feed type, for example RSS 2 or Atom */
- private String type;
-
- public Feed(Date lastUpdate) {
- super();
- items = Collections.synchronizedList(new ArrayList<FeedItem>());
- this.lastUpdate = lastUpdate;
- }
-
- /**
- * This constructor is used for requesting a feed download. It should NOT be
- * used if the title of the feed is already known.
- * */
- public Feed(String url, Date lastUpdate) {
- this(lastUpdate);
- this.download_url = url;
- }
-
- /**
- * This constructor is used for requesting a feed download. It should be
- * used if the title of the feed is already known.
- * */
- public Feed(String url, Date lastUpdate, String title) {
- this(url, lastUpdate);
- this.title = title;
- }
-
- /**
- * Returns the number of FeedItems where 'read' is false. If the 'display
- * only episodes' - preference is set to true, this method will only count
- * items with episodes.
- * */
- public int getNumOfNewItems() {
- int count = 0;
- for (FeedItem item : items) {
- if (item.getState() == FeedItem.State.NEW) {
- if (!UserPreferences.isDisplayOnlyEpisodes()
- || item.getMedia() != null) {
- count++;
- }
- }
- }
- return count;
- }
-
- /**
- * Returns the number of FeedItems where the media started to play but
- * wasn't finished yet.
- * */
- public int getNumOfStartedItems() {
- int count = 0;
-
- for (FeedItem item : items) {
- FeedItem.State state = item.getState();
- if (state == FeedItem.State.IN_PROGRESS
- || state == FeedItem.State.PLAYING) {
- count++;
- }
- }
- return count;
- }
-
- /**
- * Returns true if at least one item in the itemlist is unread.
- *
- * @param enableEpisodeFilter
- * true if this method should only count items with episodes if
- * the 'display only episodes' - preference is set to true by the
- * user.
- */
- public boolean hasNewItems(boolean enableEpisodeFilter) {
- for (FeedItem item : items) {
- if (item.getState() == FeedItem.State.NEW) {
- if (!(enableEpisodeFilter && UserPreferences
- .isDisplayOnlyEpisodes()) || item.getMedia() != null) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Returns the number of FeedItems.
- *
- * @param enableEpisodeFilter
- * true if this method should only count items with episodes if
- * the 'display only episodes' - preference is set to true by the
- * user.
- * */
- public int getNumOfItems(boolean enableEpisodeFilter) {
- if (enableEpisodeFilter && UserPreferences.isDisplayOnlyEpisodes()) {
- return EpisodeFilter.countItemsWithEpisodes(items);
- } else {
- return items.size();
- }
- }
-
- /**
- * Returns the item at the specified index.
- *
- * @param enableEpisodeFilter
- * true if this method should ignore items without episdodes if
- * the episodes filter has been enabled by the user.
- */
- public FeedItem getItemAtIndex(boolean enableEpisodeFilter, int position) {
- if (enableEpisodeFilter && UserPreferences.isDisplayOnlyEpisodes()) {
- return EpisodeFilter.accessEpisodeByIndex(items, position);
- } else {
- return items.get(position);
- }
- }
-
- /**
- * Returns the value that uniquely identifies this Feed. If the
- * feedIdentifier attribute is not null, it will be returned. Else it will
- * try to return the title. If the title is not given, it will use the link
- * of the feed.
- * */
- public String getIdentifyingValue() {
- if (feedIdentifier != null && !feedIdentifier.isEmpty()) {
- return feedIdentifier;
- } else if (title != null && !title.isEmpty()) {
- return title;
- } else {
- return link;
- }
- }
-
- @Override
- public String getHumanReadableIdentifier() {
- if (title != null) {
- return title;
- } else {
- return download_url;
- }
- }
-
- /** Calls cacheDescriptions on all items. */
- protected void cacheDescriptionsOfItems() {
- if (items != null) {
- for (FeedItem item : items) {
- item.cacheDescriptions();
- }
- }
- }
-
- public void updateFromOther(Feed other) {
- super.updateFromOther(other);
- if (other.title != null) {
- title = other.title;
- }
- if (other.feedIdentifier != null) {
- feedIdentifier = other.feedIdentifier;
- }
- if (other.link != null) {
- link = other.link;
- }
- if (other.description != null) {
- description = other.description;
- }
- if (other.language != null) {
- language = other.language;
- }
- if (other.author != null) {
- author = other.author;
- }
- if (other.paymentLink != null) {
- paymentLink = other.paymentLink;
- }
- }
-
- public boolean compareWithOther(Feed other) {
- if (super.compareWithOther(other)) {
- return true;
- }
- if (!title.equals(other.title)) {
- return true;
- }
- if (other.feedIdentifier != null) {
- if (feedIdentifier == null
- || !feedIdentifier.equals(other.feedIdentifier)) {
- return true;
- }
- }
- if (other.link != null) {
- if (link == null || !link.equals(other.link)) {
- return true;
- }
- }
- if (other.description != null) {
- if (description == null || !description.equals(other.description)) {
- return true;
- }
- }
- if (other.language != null) {
- if (language == null || !language.equals(other.language)) {
- return true;
- }
- }
- if (other.author != null) {
- if (author == null || !author.equals(other.author)) {
- return true;
- }
- }
- if (other.paymentLink != null) {
- if (paymentLink == null || !paymentLink.equals(other.paymentLink)) {
- return true;
- }
- }
- return false;
- }
-
- @Override
- public int getTypeAsInt() {
- return FEEDFILETYPE_FEED;
- }
-
- public String getTitle() {
- return title;
- }
-
- public void setTitle(String title) {
- this.title = title;
- }
-
- public String getLink() {
- return link;
- }
-
- public void setLink(String link) {
- this.link = link;
- }
-
- public String getDescription() {
- return description;
- }
-
- public void setDescription(String description) {
- this.description = description;
- }
-
- public FeedImage getImage() {
- return image;
- }
-
- public void setImage(FeedImage image) {
- this.image = image;
- }
-
- List<FeedItem> getItems() {
- return items;
- }
-
- public void setItems(ArrayList<FeedItem> items) {
- this.items = Collections.synchronizedList(items);
- }
-
- /** Returns an array that contains all the feeditems of this feed. */
- public FeedItem[] getItemsArray() {
- return items.toArray(new FeedItem[items.size()]);
- }
-
- public Date getLastUpdate() {
- return lastUpdate;
- }
-
- public void setLastUpdate(Date lastUpdate) {
- this.lastUpdate = lastUpdate;
- }
-
- public String getFeedIdentifier() {
- return feedIdentifier;
- }
-
- public void setFeedIdentifier(String feedIdentifier) {
- this.feedIdentifier = feedIdentifier;
- }
-
- public String getPaymentLink() {
- return paymentLink;
- }
-
- public void setPaymentLink(String paymentLink) {
- this.paymentLink = paymentLink;
- }
-
- public String getLanguage() {
- return language;
- }
-
- public void setLanguage(String language) {
- this.language = language;
- }
-
- public String getAuthor() {
- return author;
- }
-
- public void setAuthor(String author) {
- this.author = author;
- }
-
- public String getType() {
- return type;
- }
-
- public void setType(String type) {
- this.type = type;
- }
+ public static final int FEEDFILETYPE_FEED = 0;
+ public static final String TYPE_RSS2 = "rss";
+ public static final String TYPE_RSS091 = "rss";
+ public static final String TYPE_ATOM1 = "atom";
+
+ private String title;
+ /**
+ * Contains 'id'-element in Atom feed.
+ */
+ private String feedIdentifier;
+ /**
+ * Link to the website.
+ */
+ private String link;
+ private String description;
+ private String language;
+ /**
+ * Name of the author
+ */
+ private String author;
+ private FeedImage image;
+ private List<FeedItem> items;
+ /**
+ * Date of last refresh.
+ */
+ private Date lastUpdate;
+ private String paymentLink;
+ /**
+ * Feed type, for example RSS 2 or Atom
+ */
+ private String type;
+
+ /**
+ * This constructor is used for restoring a feed from the database.
+ */
+ public Feed(long id, Date lastUpdate, String title, String link, String description, String paymentLink,
+ String author, String language, String type, String feedIdentifier, FeedImage image, String fileUrl,
+ String downloadUrl, boolean downloaded) {
+ super(fileUrl, downloadUrl, downloaded);
+ this.id = id;
+ this.title = title;
+ this.lastUpdate = lastUpdate;
+ this.link = link;
+ this.description = description;
+ this.paymentLink = paymentLink;
+ this.author = author;
+ this.language = language;
+ this.type = type;
+ this.feedIdentifier = feedIdentifier;
+ this.image = image;
+
+ items = new ArrayList<FeedItem>();
+ }
+
+ /**
+ * This constructor can be used when parsing feed data. Only the 'lastUpdate' and 'items' field are initialized.
+ */
+ public Feed() {
+ super();
+ items = new ArrayList<FeedItem>();
+ lastUpdate = new Date();
+ }
+
+ /**
+ * This constructor is used for requesting a feed download (it must not be used for anything else!). It should NOT be
+ * used if the title of the feed is already known.
+ */
+ public Feed(String url, Date lastUpdate) {
+ super(null, url, false);
+ this.lastUpdate = lastUpdate;
+ }
+
+ /**
+ * This constructor is used for requesting a feed download (it must not be used for anything else!). It should be
+ * used if the title of the feed is already known.
+ */
+ public Feed(String url, Date lastUpdate, String title) {
+ this(url, lastUpdate);
+ this.title = title;
+ }
+
+ /**
+ * Returns the number of FeedItems where 'read' is false. If the 'display
+ * only episodes' - preference is set to true, this method will only count
+ * items with episodes.
+ */
+ public int getNumOfNewItems() {
+ int count = 0;
+ for (FeedItem item : items) {
+ if (item.getState() == FeedItem.State.NEW) {
+ if (!UserPreferences.isDisplayOnlyEpisodes()
+ || item.getMedia() != null) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Returns the number of FeedItems where the media started to play but
+ * wasn't finished yet.
+ */
+ public int getNumOfStartedItems() {
+ int count = 0;
+
+ for (FeedItem item : items) {
+ FeedItem.State state = item.getState();
+ if (state == FeedItem.State.IN_PROGRESS
+ || state == FeedItem.State.PLAYING) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Returns true if at least one item in the itemlist is unread.
+ *
+ * @param enableEpisodeFilter true if this method should only count items with episodes if
+ * the 'display only episodes' - preference is set to true by the
+ * user.
+ */
+ public boolean hasNewItems(boolean enableEpisodeFilter) {
+ for (FeedItem item : items) {
+ if (item.getState() == FeedItem.State.NEW) {
+ if (!(enableEpisodeFilter && UserPreferences
+ .isDisplayOnlyEpisodes()) || item.getMedia() != null) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the number of FeedItems.
+ *
+ * @param enableEpisodeFilter true if this method should only count items with episodes if
+ * the 'display only episodes' - preference is set to true by the
+ * user.
+ */
+ public int getNumOfItems(boolean enableEpisodeFilter) {
+ if (enableEpisodeFilter && UserPreferences.isDisplayOnlyEpisodes()) {
+ return EpisodeFilter.countItemsWithEpisodes(items);
+ } else {
+ return items.size();
+ }
+ }
+
+ /**
+ * Returns the item at the specified index.
+ *
+ * @param enableEpisodeFilter true if this method should ignore items without episdodes if
+ * the episodes filter has been enabled by the user.
+ */
+ public FeedItem getItemAtIndex(boolean enableEpisodeFilter, int position) {
+ if (enableEpisodeFilter && UserPreferences.isDisplayOnlyEpisodes()) {
+ return EpisodeFilter.accessEpisodeByIndex(items, position);
+ } else {
+ return items.get(position);
+ }
+ }
+
+ /**
+ * Returns the value that uniquely identifies this Feed. If the
+ * feedIdentifier attribute is not null, it will be returned. Else it will
+ * try to return the title. If the title is not given, it will use the link
+ * of the feed.
+ */
+ public String getIdentifyingValue() {
+ if (feedIdentifier != null && !feedIdentifier.isEmpty()) {
+ return feedIdentifier;
+ } else if (title != null && !title.isEmpty()) {
+ return title;
+ } else {
+ return link;
+ }
+ }
+
+ @Override
+ public String getHumanReadableIdentifier() {
+ if (title != null) {
+ return title;
+ } else {
+ return download_url;
+ }
+ }
+
+ public void updateFromOther(Feed other) {
+ super.updateFromOther(other);
+ if (other.title != null) {
+ title = other.title;
+ }
+ if (other.feedIdentifier != null) {
+ feedIdentifier = other.feedIdentifier;
+ }
+ if (other.link != null) {
+ link = other.link;
+ }
+ if (other.description != null) {
+ description = other.description;
+ }
+ if (other.language != null) {
+ language = other.language;
+ }
+ if (other.author != null) {
+ author = other.author;
+ }
+ if (other.paymentLink != null) {
+ paymentLink = other.paymentLink;
+ }
+ }
+
+ public boolean compareWithOther(Feed other) {
+ if (super.compareWithOther(other)) {
+ return true;
+ }
+ if (!title.equals(other.title)) {
+ return true;
+ }
+ if (other.feedIdentifier != null) {
+ if (feedIdentifier == null
+ || !feedIdentifier.equals(other.feedIdentifier)) {
+ return true;
+ }
+ }
+ if (other.link != null) {
+ if (link == null || !link.equals(other.link)) {
+ return true;
+ }
+ }
+ if (other.description != null) {
+ if (description == null || !description.equals(other.description)) {
+ return true;
+ }
+ }
+ if (other.language != null) {
+ if (language == null || !language.equals(other.language)) {
+ return true;
+ }
+ }
+ if (other.author != null) {
+ if (author == null || !author.equals(other.author)) {
+ return true;
+ }
+ }
+ if (other.paymentLink != null) {
+ if (paymentLink == null || !paymentLink.equals(other.paymentLink)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int getTypeAsInt() {
+ return FEEDFILETYPE_FEED;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getLink() {
+ return link;
+ }
+
+ public void setLink(String link) {
+ this.link = link;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public FeedImage getImage() {
+ return image;
+ }
+
+ public void setImage(FeedImage image) {
+ this.image = image;
+ }
+
+ public List<FeedItem> getItems() {
+ return items;
+ }
+
+ public void setItems(List<FeedItem> list) {
+ this.items = list;
+ }
+
+ public Date getLastUpdate() {
+ return lastUpdate;
+ }
+
+ public void setLastUpdate(Date lastUpdate) {
+ this.lastUpdate = lastUpdate;
+ }
+
+ public String getFeedIdentifier() {
+ return feedIdentifier;
+ }
+
+ public void setFeedIdentifier(String feedIdentifier) {
+ this.feedIdentifier = feedIdentifier;
+ }
+
+ public String getPaymentLink() {
+ return paymentLink;
+ }
+
+ public void setPaymentLink(String paymentLink) {
+ this.paymentLink = paymentLink;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+
+ public void setLanguage(String language) {
+ this.language = language;
+ }
+
+ public String getAuthor() {
+ return author;
+ }
+
+ public void setAuthor(String author) {
+ this.author = author;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
}
diff --git a/src/de/danoeh/antennapod/feed/FeedImage.java b/src/de/danoeh/antennapod/feed/FeedImage.java
index 09595f5eb..3cc99d1c2 100644
--- a/src/de/danoeh/antennapod/feed/FeedImage.java
+++ b/src/de/danoeh/antennapod/feed/FeedImage.java
@@ -9,7 +9,7 @@ import org.apache.commons.io.IOUtils;
import de.danoeh.antennapod.asynctask.ImageLoader;
-;
+
public class FeedImage extends FeedFile implements
ImageLoader.ImageWorkerTaskResource {
diff --git a/src/de/danoeh/antennapod/feed/FeedItem.java b/src/de/danoeh/antennapod/feed/FeedItem.java
index 0df384b60..54682397e 100644
--- a/src/de/danoeh/antennapod/feed/FeedItem.java
+++ b/src/de/danoeh/antennapod/feed/FeedItem.java
@@ -4,279 +4,276 @@ import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.util.Date;
import java.util.List;
+import java.util.concurrent.Callable;
+import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.asynctask.ImageLoader;
+import de.danoeh.antennapod.storage.DBReader;
+import de.danoeh.antennapod.util.ShownotesProvider;
/**
* Data Object for a XML message
- *
+ *
* @author daniel
- *
*/
public class FeedItem extends FeedComponent implements
- ImageLoader.ImageWorkerTaskResource {
-
- /** The id/guid that can be found in the rss/atom feed. Might not be set. */
- private String itemIdentifier;
- private String title;
- /**
- * The description of a feeditem. This field should only be set by the
- * parser.
- */
- private String description;
- /**
- * The content of the content-encoded tag of a feeditem. This field should
- * only be set by the parser.
- */
- private String contentEncoded;
-
- private SoftReference<String> cachedDescription;
- private SoftReference<String> cachedContentEncoded;
-
- private String link;
- private Date pubDate;
- private FeedMedia media;
- private Feed feed;
- private boolean read;
- private String paymentLink;
- private List<Chapter> chapters;
-
- public FeedItem() {
- this.read = true;
- }
-
- public void updateFromOther(FeedItem other) {
- super.updateFromOther(other);
- if (other.title != null) {
- title = other.title;
- }
- if (other.getDescription() != null) {
- description = other.getDescription();
- }
- if (other.getContentEncoded() != null) {
- contentEncoded = other.contentEncoded;
- }
- if (other.link != null) {
- link = other.link;
- }
- if (other.pubDate != null && other.pubDate != pubDate) {
- pubDate = other.pubDate;
- }
- if (other.media != null) {
- if (media == null) {
- media = other.media;
- } else if (media.compareWithOther(other)) {
- media.updateFromOther(other);
- }
- }
- if (other.paymentLink != null) {
- paymentLink = other.paymentLink;
- }
- if (other.chapters != null) {
- if (chapters == null) {
- chapters = other.chapters;
- }
- }
- }
-
- /**
- * Moves the 'description' and 'contentEncoded' field of feeditem to their
- * SoftReference fields.
- */
- protected void cacheDescriptions() {
- if (description != null) {
- cachedDescription = new SoftReference<String>(description);
- }
- if (contentEncoded != null) {
- cachedContentEncoded = new SoftReference<String>(contentEncoded);
- }
- description = null;
- contentEncoded = null;
- }
-
- /**
- * Returns the value that uniquely identifies this FeedItem. If the
- * itemIdentifier attribute is not null, it will be returned. Else it will
- * try to return the title. If the title is not given, it will use the link
- * of the entry.
- * */
- public String getIdentifyingValue() {
- if (itemIdentifier != null) {
- return itemIdentifier;
- } else if (title != null) {
- return title;
- } else {
- return link;
- }
- }
-
- public String getTitle() {
- return title;
- }
-
- public void setTitle(String title) {
- this.title = title;
- }
-
- public String getDescription() {
- if (description == null && cachedDescription != null) {
- return cachedDescription.get();
- }
- return description;
- }
-
- public void setDescription(String description) {
- this.description = description;
- }
-
- public String getLink() {
- return link;
- }
-
- public void setLink(String link) {
- this.link = link;
- }
-
- public Date getPubDate() {
- return pubDate;
- }
-
- public void setPubDate(Date pubDate) {
- this.pubDate = pubDate;
- }
-
- public FeedMedia getMedia() {
- return media;
- }
-
- public void setMedia(FeedMedia media) {
- this.media = media;
- }
-
- public Feed getFeed() {
- return feed;
- }
-
- public void setFeed(Feed feed) {
- this.feed = feed;
- }
-
- public boolean isRead() {
- return read || isInProgress();
- }
-
- public void setRead(boolean read) {
- this.read = read;
- }
-
- private boolean isInProgress() {
- return (media != null && media.isInProgress());
- }
-
- public String getContentEncoded() {
- if (contentEncoded == null && cachedContentEncoded != null) {
- return cachedContentEncoded.get();
-
- }
- return contentEncoded;
- }
-
- public void setContentEncoded(String contentEncoded) {
- this.contentEncoded = contentEncoded;
- }
-
- public String getPaymentLink() {
- return paymentLink;
- }
-
- public void setPaymentLink(String paymentLink) {
- this.paymentLink = paymentLink;
- }
-
- public List<Chapter> getChapters() {
- return chapters;
- }
-
- public void setChapters(List<Chapter> chapters) {
- this.chapters = chapters;
- }
-
- public String getItemIdentifier() {
- return itemIdentifier;
- }
-
- public void setItemIdentifier(String itemIdentifier) {
- this.itemIdentifier = itemIdentifier;
- }
-
- public boolean hasMedia() {
- return media != null;
- }
-
- private boolean isPlaying() {
- if (media != null) {
- return media.isPlaying();
- }
- return false;
- }
-
- public void setCachedDescription(String d) {
- cachedDescription = new SoftReference<String>(d);
- }
-
- public void setCachedContentEncoded(String c) {
- cachedContentEncoded = new SoftReference<String>(c);
- }
-
- public enum State {
- NEW, IN_PROGRESS, READ, PLAYING
- }
-
- public State getState() {
- if (hasMedia()) {
- if (isPlaying()) {
- return State.PLAYING;
- }
- if (isInProgress()) {
- return State.IN_PROGRESS;
- }
- }
- return (isRead() ? State.READ : State.NEW);
- }
-
- @Override
- public InputStream openImageInputStream() {
- InputStream out = null;
- if (hasMedia()) {
- out = media.openImageInputStream();
- }
- if (out == null && feed.getImage() != null) {
- out = feed.getImage().openImageInputStream();
- }
- return out;
- }
-
- @Override
- public InputStream reopenImageInputStream(InputStream input) {
- InputStream out = null;
- if (hasMedia()) {
- out = media.reopenImageInputStream(input);
- }
- if (out == null && feed.getImage() != null) {
- out = feed.getImage().reopenImageInputStream(input);
- }
- return out;
- }
-
- @Override
- public String getImageLoaderCacheKey() {
- String out = null;
- if (hasMedia()) {
- out = media.getImageLoaderCacheKey();
- }
- if (out == null && feed.getImage() != null) {
- out = feed.getImage().getImageLoaderCacheKey();
- }
- return out;
- }
+ ImageLoader.ImageWorkerTaskResource, ShownotesProvider {
+
+ /**
+ * The id/guid that can be found in the rss/atom feed. Might not be set.
+ */
+ private String itemIdentifier;
+ private String title;
+ /**
+ * The description of a feeditem.
+ */
+ private String description;
+ /**
+ * The content of the content-encoded tag of a feeditem.
+ */
+ private String contentEncoded;
+
+ private String link;
+ private Date pubDate;
+ private FeedMedia media;
+
+ private Feed feed;
+ private long feedId;
+
+ private boolean read;
+ private String paymentLink;
+ private List<Chapter> chapters;
+
+ public FeedItem() {
+ this.read = true;
+ }
+
+ public void updateFromOther(FeedItem other) {
+ super.updateFromOther(other);
+ if (other.title != null) {
+ title = other.title;
+ }
+ if (other.getDescription() != null) {
+ description = other.getDescription();
+ }
+ if (other.getContentEncoded() != null) {
+ contentEncoded = other.contentEncoded;
+ }
+ if (other.link != null) {
+ link = other.link;
+ }
+ if (other.pubDate != null && other.pubDate != pubDate) {
+ pubDate = other.pubDate;
+ }
+ if (other.media != null) {
+ if (media == null) {
+ media = other.media;
+ } else if (media.compareWithOther(other)) {
+ media.updateFromOther(other);
+ }
+ }
+ if (other.paymentLink != null) {
+ paymentLink = other.paymentLink;
+ }
+ if (other.chapters != null) {
+ if (chapters == null) {
+ chapters = other.chapters;
+ }
+ }
+ }
+
+ /**
+ * Returns the value that uniquely identifies this FeedItem. If the
+ * itemIdentifier attribute is not null, it will be returned. Else it will
+ * try to return the title. If the title is not given, it will use the link
+ * of the entry.
+ */
+ public String getIdentifyingValue() {
+ if (itemIdentifier != null) {
+ return itemIdentifier;
+ } else if (title != null) {
+ return title;
+ } else {
+ return link;
+ }
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getLink() {
+ return link;
+ }
+
+ public void setLink(String link) {
+ this.link = link;
+ }
+
+ public Date getPubDate() {
+ return pubDate;
+ }
+
+ public void setPubDate(Date pubDate) {
+ this.pubDate = pubDate;
+ }
+
+ public FeedMedia getMedia() {
+ return media;
+ }
+
+ public void setMedia(FeedMedia media) {
+ this.media = media;
+ }
+
+ public Feed getFeed() {
+ return feed;
+ }
+
+ public void setFeed(Feed feed) {
+ this.feed = feed;
+ }
+
+ public boolean isRead() {
+ return read || isInProgress();
+ }
+
+ public void setRead(boolean read) {
+ this.read = read;
+ }
+
+ private boolean isInProgress() {
+ return (media != null && media.isInProgress());
+ }
+
+ public String getContentEncoded() {
+ return contentEncoded;
+ }
+
+ public void setContentEncoded(String contentEncoded) {
+ this.contentEncoded = contentEncoded;
+ }
+
+ public String getPaymentLink() {
+ return paymentLink;
+ }
+
+ public void setPaymentLink(String paymentLink) {
+ this.paymentLink = paymentLink;
+ }
+
+ public List<Chapter> getChapters() {
+ return chapters;
+ }
+
+ public void setChapters(List<Chapter> chapters) {
+ this.chapters = chapters;
+ }
+
+ public String getItemIdentifier() {
+ return itemIdentifier;
+ }
+
+ public void setItemIdentifier(String itemIdentifier) {
+ this.itemIdentifier = itemIdentifier;
+ }
+
+ public boolean hasMedia() {
+ return media != null;
+ }
+
+ private boolean isPlaying() {
+ if (media != null) {
+ return media.isPlaying();
+ }
+ return false;
+ }
+
+ @Override
+ public Callable<String> loadShownotes() {
+ return new Callable<String>() {
+ @Override
+ public String call() throws Exception {
+
+ if (contentEncoded == null || description == null) {
+ DBReader.loadExtraInformationOfFeedItem(PodcastApp.getInstance(), FeedItem.this);
+
+ }
+ return (contentEncoded != null) ? contentEncoded : description;
+ }
+ };
+ }
+
+ public enum State {
+ NEW, IN_PROGRESS, READ, PLAYING
+ }
+
+ public State getState() {
+ if (hasMedia()) {
+ if (isPlaying()) {
+ return State.PLAYING;
+ }
+ if (isInProgress()) {
+ return State.IN_PROGRESS;
+ }
+ }
+ return (isRead() ? State.READ : State.NEW);
+ }
+
+ @Override
+ public InputStream openImageInputStream() {
+ InputStream out = null;
+ if (hasMedia()) {
+ out = media.openImageInputStream();
+ }
+ if (out == null && feed.getImage() != null) {
+ out = feed.getImage().openImageInputStream();
+ }
+ return out;
+ }
+
+ @Override
+ public InputStream reopenImageInputStream(InputStream input) {
+ InputStream out = null;
+ if (hasMedia()) {
+ out = media.reopenImageInputStream(input);
+ }
+ if (out == null && feed.getImage() != null) {
+ out = feed.getImage().reopenImageInputStream(input);
+ }
+ return out;
+ }
+
+ @Override
+ public String getImageLoaderCacheKey() {
+ String out = null;
+ if (hasMedia()) {
+ out = media.getImageLoaderCacheKey();
+ }
+ if (out == null && feed.getImage() != null) {
+ out = feed.getImage().getImageLoaderCacheKey();
+ }
+ return out;
+ }
+
+ public long getFeedId() {
+ return feedId;
+ }
+
+ public void setFeedId(long feedId) {
+ this.feedId = feedId;
+ }
+
}
diff --git a/src/de/danoeh/antennapod/feed/FeedManager.java b/src/de/danoeh/antennapod/feed/FeedManager.java
deleted file mode 100644
index a1a8c6c32..000000000
--- a/src/de/danoeh/antennapod/feed/FeedManager.java
+++ /dev/null
@@ -1,2014 +0,0 @@
-package de.danoeh.antennapod.feed;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Date;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.TreeSet;
-import java.util.Comparator;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ThreadFactory;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.preference.PreferenceManager;
-import android.util.Log;
-import de.danoeh.antennapod.AppConfig;
-import de.danoeh.antennapod.asynctask.DownloadStatus;
-import de.danoeh.antennapod.preferences.PlaybackPreferences;
-import de.danoeh.antennapod.preferences.UserPreferences;
-import de.danoeh.antennapod.service.PlaybackService;
-import de.danoeh.antennapod.storage.DownloadRequestException;
-import de.danoeh.antennapod.storage.DownloadRequester;
-import de.danoeh.antennapod.storage.PodDBAdapter;
-import de.danoeh.antennapod.util.DownloadError;
-import de.danoeh.antennapod.util.EpisodeFilter;
-import de.danoeh.antennapod.util.FeedtitleComparator;
-import de.danoeh.antennapod.util.NetworkUtils;
-import de.danoeh.antennapod.util.comparator.DownloadStatusComparator;
-import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator;
-import de.danoeh.antennapod.util.comparator.PlaybackCompletionDateComparator;
-import de.danoeh.antennapod.util.exception.MediaFileNotFoundException;
-
-/**
- * Singleton class that - provides access to all Feeds and FeedItems and to
- * several lists of FeedItems. - provides methods for modifying the
- * application's data - takes care of updating the information stored in the
- * database when something is modified
- *
- * An instance of this class can be retrieved via getInstance().
- * */
-public class FeedManager {
- private static final String TAG = "FeedManager";
-
- /** Number of completed Download status entries to store. */
- private static final int DOWNLOAD_LOG_SIZE = 50;
-
- private static FeedManager singleton;
-
- private List<Feed> feeds;
-
- /** Contains all items where 'read' is false */
- private List<FeedItem> unreadItems;
-
- /** Contains completed Download status entries */
- private List<DownloadStatus> downloadLog;
-
- /** Contains the queue of items to be played. */
- private List<FeedItem> queue;
-
- /** Contains the last played items */
- private List<FeedItem> playbackHistory;
-
- /** Maximum number of items in the playback history. */
- private static final int PLAYBACK_HISTORY_SIZE = 15;
-
- private DownloadRequester requester = DownloadRequester.getInstance();
- private EventDistributor eventDist = EventDistributor.getInstance();
-
- /**
- * Should be used to change the content of the arrays from another thread to
- * ensure that arrays are only modified on the main thread.
- */
- private Handler contentChanger;
-
- /** Ensures that there are no parallel db operations. */
- private Executor dbExec;
-
- /** Prevents user from starting several feed updates at the same time. */
- private static boolean isStartingFeedRefresh = false;
-
- private FeedManager() {
- feeds = Collections.synchronizedList(new ArrayList<Feed>());
- unreadItems = Collections.synchronizedList(new ArrayList<FeedItem>());
- downloadLog = new ArrayList<DownloadStatus>();
- queue = Collections.synchronizedList(new ArrayList<FeedItem>());
- playbackHistory = Collections
- .synchronizedList(new ArrayList<FeedItem>());
- contentChanger = new Handler();
- dbExec = Executors.newSingleThreadExecutor(new ThreadFactory() {
-
- @Override
- public Thread newThread(Runnable r) {
- Thread t = new Thread(r);
- t.setPriority(Thread.MIN_PRIORITY);
- return t;
- }
- });
- }
-
- /** Creates a new instance of this class if necessary and returns it. */
- public static FeedManager getInstance() {
- if (singleton == null) {
- singleton = new FeedManager();
- }
- return singleton;
- }
-
- /**
- * Play FeedMedia and start the playback service + launch Mediaplayer
- * Activity. The FeedItem will be added at the top of the queue if it isn't
- * in there yet.
- *
- * @param context
- * for starting the playbackservice
- * @param media
- * that shall be played
- * @param showPlayer
- * if Mediaplayer activity shall be started
- * @param startWhenPrepared
- * if Mediaplayer shall be started after it has been prepared
- * @param shouldStream
- * if Mediaplayer should stream the file
- */
- public void playMedia(Context context, FeedMedia media, boolean showPlayer,
- boolean startWhenPrepared, boolean shouldStream) {
- try {
- if (!shouldStream) {
- if (media.fileExists() == false) {
- throw new MediaFileNotFoundException(
- "No episode was found at " + media.getFile_url(),
- media);
- }
- }
- // Start playback Service
- Intent launchIntent = new Intent(context, PlaybackService.class);
- launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
- launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED,
- startWhenPrepared);
- launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM,
- shouldStream);
- launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY,
- true);
- context.startService(launchIntent);
- if (showPlayer) {
- // Launch Mediaplayer
- context.startActivity(PlaybackService.getPlayerActivityIntent(
- context, media));
- }
- if (!queue.contains(media.getItem())) {
- addQueueItemAt(context, media.getItem(), 0, false);
- }
- } catch (MediaFileNotFoundException e) {
- e.printStackTrace();
- if (media.isPlaying()) {
- context.sendBroadcast(new Intent(
- PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
- }
- notifyMissingFeedMediaFile(context, media);
- }
- }
-
- /** Remove media item that has been downloaded. */
- public boolean deleteFeedMedia(Context context, FeedMedia media) {
- boolean result = false;
- if (media.isDownloaded()) {
- File mediaFile = new File(media.file_url);
- if (mediaFile.exists()) {
- result = mediaFile.delete();
- }
- media.setDownloaded(false);
- media.setFile_url(null);
- setFeedMedia(context, media);
-
- SharedPreferences prefs = PreferenceManager
- .getDefaultSharedPreferences(context);
- if (PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA) {
- if (media.getId() == PlaybackPreferences
- .getCurrentlyPlayingFeedMediaId()) {
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(
- PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM,
- true);
- editor.commit();
- }
- if (PlaybackPreferences.getCurrentlyPlayingFeedMediaId() == media
- .getId()) {
- context.sendBroadcast(new Intent(
- PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
- }
- }
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "Deleting File. Result: " + result);
- return result;
- }
-
- /** Remove a feed with all its items and media files and its image. */
- public void deleteFeed(final Context context, final Feed feed) {
- SharedPreferences prefs = PreferenceManager
- .getDefaultSharedPreferences(context.getApplicationContext());
- if (PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA
- && PlaybackPreferences.getLastPlayedFeedId() == feed.getId()) {
- context.sendBroadcast(new Intent(
- PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
- SharedPreferences.Editor editor = prefs.edit();
- editor.putLong(PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID,
- -1);
- editor.commit();
- }
-
- contentChanger.post(new Runnable() {
-
- @Override
- public void run() {
- feeds.remove(feed);
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- DownloadRequester requester = DownloadRequester
- .getInstance();
- adapter.open();
- // delete image file
- if (feed.getImage() != null) {
- if (feed.getImage().isDownloaded()
- && feed.getImage().getFile_url() != null) {
- File imageFile = new File(feed.getImage()
- .getFile_url());
- imageFile.delete();
- } else if (requester.isDownloadingFile(feed
- .getImage())) {
- requester.cancelDownload(context,
- feed.getImage());
- }
- }
- // delete stored media files and mark them as read
- for (FeedItem item : feed.getItems()) {
- if (item.getState() == FeedItem.State.NEW) {
- unreadItems.remove(item);
- }
- if (queue.contains(item)) {
- removeQueueItem(item, adapter);
- }
- removeItemFromPlaybackHistory(context, item);
- if (item.getMedia() != null
- && item.getMedia().isDownloaded()) {
- File mediaFile = new File(item.getMedia()
- .getFile_url());
- mediaFile.delete();
- } else if (item.getMedia() != null
- && requester.isDownloadingFile(item
- .getMedia())) {
- requester.cancelDownload(context,
- item.getMedia());
- }
- }
-
- adapter.removeFeed(feed);
- adapter.close();
- eventDist.sendFeedUpdateBroadcast();
- }
-
- });
- }
- });
-
- }
-
- /**
- * Makes sure that playback history is sorted and is not larger than
- * PLAYBACK_HISTORY_SIZE.
- *
- * @return an array of all feeditems that were remove from the playback
- * history or null if no items were removed.
- */
- private FeedItem[] cleanupPlaybackHistory() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Cleaning up playback history.");
-
- Collections.sort(playbackHistory,
- new PlaybackCompletionDateComparator());
- final int initialSize = playbackHistory.size();
- if (initialSize > PLAYBACK_HISTORY_SIZE) {
- FeedItem[] removed = new FeedItem[initialSize
- - PLAYBACK_HISTORY_SIZE];
-
- for (int i = 0; i < removed.length; i++) {
- removed[i] = playbackHistory.remove(playbackHistory.size() - 1);
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "Removed " + removed.length
- + " items from playback history.");
- return removed;
- }
- return null;
- }
-
- /**
- * Executes cleanupPlaybackHistory and deletes the playbackCompletionDate of
- * all item that were removed from the history.
- */
- private void cleanupPlaybackHistoryWithDBCleanup(final Context context) {
- final FeedItem[] removedItems = cleanupPlaybackHistory();
- if (removedItems != null) {
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- for (FeedItem item : removedItems) {
- if (item.getMedia() != null) {
- item.getMedia().setPlaybackCompletionDate(null);
- adapter.setMedia(item.getMedia());
- }
- }
- adapter.close();
- }
- });
- }
- }
-
- /** Removes all items from the playback history. */
- public void clearPlaybackHistory(final Context context) {
- if (!playbackHistory.isEmpty()) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Clearing playback history.");
- final FeedItem[] items = playbackHistory
- .toArray(new FeedItem[playbackHistory.size()]);
- playbackHistory.clear();
- eventDist.sendPlaybackHistoryUpdateBroadcast();
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- for (FeedItem item : items) {
- if (item.getMedia() != null
- && item.getMedia().getPlaybackCompletionDate() != null) {
- item.getMedia().setPlaybackCompletionDate(null);
- adapter.setMedia(item.getMedia());
- }
- }
- adapter.close();
- }
- });
- }
- }
-
- /** Adds a FeedItem to the playback history. */
- public void addItemToPlaybackHistory(Context context, FeedItem item) {
- if (item.getMedia() != null
- && item.getMedia().getPlaybackCompletionDate() != null) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Adding new item to playback history");
- if (!playbackHistory.contains(item)) {
- playbackHistory.add(item);
- }
- cleanupPlaybackHistoryWithDBCleanup(context);
- eventDist.sendPlaybackHistoryUpdateBroadcast();
- }
- }
-
- private void removeItemFromPlaybackHistory(Context context, FeedItem item) {
- playbackHistory.remove(item);
- eventDist.sendPlaybackHistoryUpdateBroadcast();
- }
-
- /**
- * Sets the 'read'-attribute of a FeedItem. Should be used by all Classes
- * instead of the setters of FeedItem.
- */
- public void markItemRead(final Context context, final FeedItem item,
- final boolean read, boolean resetMediaPosition) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Setting item with title " + item.getTitle()
- + " as read/unread");
-
- item.setRead(read);
- if (item.hasMedia() && resetMediaPosition) {
- item.getMedia().setPosition(0);
- }
- setFeedItem(context, item);
- if (item.hasMedia() && resetMediaPosition)
- setFeedMedia(context, item.getMedia());
-
- contentChanger.post(new Runnable() {
-
- @Override
- public void run() {
- if (read == true) {
- unreadItems.remove(item);
- } else {
- unreadItems.add(item);
- Collections.sort(unreadItems,
- new FeedItemPubdateComparator());
- }
- eventDist.sendUnreadItemsUpdateBroadcast();
- }
- });
-
- }
-
- /**
- * Sets the 'read' attribute of all FeedItems of a specific feed to true
- */
- public void markFeedRead(Context context, Feed feed) {
- for (FeedItem item : feed.getItems()) {
- if (unreadItems.contains(item)) {
- markItemRead(context, item, true, false);
- }
- }
- }
-
- /** Marks all items in the unread items list as read */
- public void markAllItemsRead(final Context context) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "marking all items as read");
- for (FeedItem item : unreadItems) {
- item.setRead(true);
- }
- final ArrayList<FeedItem> unreadItemsCopy = new ArrayList<FeedItem>(
- unreadItems);
- unreadItems.clear();
- eventDist.sendUnreadItemsUpdateBroadcast();
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- for (FeedItem item : unreadItemsCopy) {
- setFeedItem(item, adapter);
- if (item.hasMedia())
- setFeedMedia(context, item.getMedia());
- }
- adapter.close();
- }
- });
-
- }
-
- /** Updates all feeds in the feed list. */
- public void refreshAllFeeds(final Context context) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Refreshing all feeds.");
- refreshFeeds(context, feeds);
- }
-
- /** Updates all feeds in the feed list. */
- public void refreshExpiredFeeds(final Context context) {
- long millis = UserPreferences.getUpdateInterval();
-
- if (AppConfig.DEBUG)
- Log.d(TAG, "Refreshing expired feeds, " + millis + " ms");
-
- if (millis > 0) {
- List<Feed> feedList = new ArrayList<Feed>();
- long now = Calendar.getInstance().getTime().getTime();
-
- // Allow a 10 minute window
- millis -= 10 * 60 * 1000;
- for (Feed feed : feeds) {
- Date date = feed.getLastUpdate();
- if (date != null) {
- if (date.getTime() + millis <= now) {
- if (AppConfig.DEBUG) {
- Log.d(TAG, "Adding expired feed " + feed.getTitle());
- }
- feedList.add(feed);
- } else {
- if (AppConfig.DEBUG) {
- Log.d(TAG, "Skipping feed " + feed.getTitle());
- }
- }
- }
- }
- if (feedList.size() > 0) {
- refreshFeeds(context, feedList);
- }
- }
- }
-
- @SuppressLint("NewApi")
- private void refreshFeeds(final Context context, final List<Feed> feedList) {
- if (!isStartingFeedRefresh) {
- isStartingFeedRefresh = true;
- AsyncTask<Void, Void, Void> updateWorker = new AsyncTask<Void, Void, Void>() {
-
- @Override
- protected void onPostExecute(Void result) {
- if (AppConfig.DEBUG)
- Log.d(TAG,
- "All feeds have been sent to the downloadmanager");
- isStartingFeedRefresh = false;
- }
-
- @Override
- protected Void doInBackground(Void... params) {
- for (Feed feed : feedList) {
- try {
- refreshFeed(context, feed);
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- addDownloadStatus(
- context,
- new DownloadStatus(feed, feed
- .getHumanReadableIdentifier(),
- DownloadError.ERROR_REQUEST_ERROR,
- false, e.getMessage()));
- }
- }
- return null;
- }
-
- };
- if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
- updateWorker.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- } else {
- updateWorker.execute();
- }
- }
-
- }
-
- /**
- * Notifies the feed manager that the an image file is invalid. It will try
- * to redownload it
- */
- public void notifyInvalidImageFile(Context context, FeedImage image) {
- Log.i(TAG,
- "The feedmanager was notified about an invalid image download. It will now try to redownload the image file");
- try {
- requester.downloadImage(context, image);
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- Log.w(TAG, "Failed to download invalid feed image");
- }
- }
-
- /**
- * Notifies the feed manager that a downloaded episode doesn't exist
- * anymore. It will update the values of the FeedMedia object accordingly.
- */
- public void notifyMissingFeedMediaFile(Context context, FeedMedia media) {
- Log.i(TAG,
- "The feedmanager was notified about a missing episode. It will update its database now.");
- media.setDownloaded(false);
- media.setFile_url(null);
- setFeedMedia(context, media);
- eventDist.sendFeedUpdateBroadcast();
- }
-
- /** Updates a specific feed. */
- public void refreshFeed(Context context, Feed feed)
- throws DownloadRequestException {
- requester.downloadFeed(context, new Feed(feed.getDownload_url(),
- new Date(), feed.getTitle()));
- }
-
- /** Adds a download status object to the download log. */
- public void addDownloadStatus(final Context context,
- final DownloadStatus status) {
- contentChanger.post(new Runnable() {
-
- @Override
- public void run() {
- downloadLog.add(status);
- Collections.sort(downloadLog, new DownloadStatusComparator());
- final DownloadStatus removedStatus;
- if (downloadLog.size() > DOWNLOAD_LOG_SIZE) {
- removedStatus = downloadLog.remove(downloadLog.size() - 1);
- } else {
- removedStatus = null;
- }
- eventDist.sendDownloadLogUpdateBroadcast();
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- if (removedStatus != null) {
- adapter.removeDownloadStatus(removedStatus);
- }
- adapter.setDownloadStatus(status);
- adapter.close();
- }
- });
- }
- });
-
- }
-
- /** Downloads all items in the queue that have not been downloaded yet. */
- public void downloadAllItemsInQueue(final Context context) {
- if (!queue.isEmpty()) {
- try {
- downloadFeedItem(context,
- queue.toArray(new FeedItem[queue.size()]));
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- }
- }
- }
-
- public void downloadFeedItem(final Context context, FeedItem... items)
- throws DownloadRequestException {
- downloadFeedItem(true, context, items);
- }
-
- /** Downloads FeedItems if they have not been downloaded yet. */
- private void downloadFeedItem(boolean performAutoCleanup,
- final Context context, final FeedItem... items)
- throws DownloadRequestException {
- if (performAutoCleanup) {
- new Thread() {
-
- @Override
- public void run() {
- performAutoCleanup(context,
- getPerformAutoCleanupArgs(items.length));
- }
-
- }.start();
- }
- for (FeedItem item : items) {
- if (item.getMedia() != null
- && !requester.isDownloadingFile(item.getMedia())
- && !item.getMedia().isDownloaded()) {
- if (items.length > 1) {
- try {
- requester.downloadMedia(context, item.getMedia());
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- addDownloadStatus(context,
- new DownloadStatus(item.getMedia(), item
- .getMedia()
- .getHumanReadableIdentifier(),
- DownloadError.ERROR_REQUEST_ERROR,
- false, e.getMessage()));
- }
- } else {
- requester.downloadMedia(context, item.getMedia());
- }
- }
- }
- }
-
- /**
- * This method will try to download undownloaded items in the queue or the
- * unread items list. If not enough space is available, an episode cleanup
- * will be performed first.
- *
- * This method will not try to download the currently playing item.
- */
- public void autodownloadUndownloadedItems(Context context) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Performing auto-dl of undownloaded episodes");
- if (NetworkUtils.autodownloadNetworkAvailable(context)
- && UserPreferences.isEnableAutodownload()) {
- int undownloadedEpisodes = getNumberOfUndownloadedEpisodes();
- int downloadedEpisodes = getNumberOfDownloadedEpisodes();
- int deletedEpisodes = performAutoCleanup(context,
- getPerformAutoCleanupArgs(undownloadedEpisodes));
- int episodeSpaceLeft = undownloadedEpisodes;
- boolean cacheIsUnlimited = UserPreferences.getEpisodeCacheSize() == UserPreferences
- .getEpisodeCacheSizeUnlimited();
-
- if (!cacheIsUnlimited
- && UserPreferences.getEpisodeCacheSize() < downloadedEpisodes
- + undownloadedEpisodes) {
- episodeSpaceLeft = UserPreferences.getEpisodeCacheSize()
- - (downloadedEpisodes - deletedEpisodes);
- }
-
- List<FeedItem> itemsToDownload = new ArrayList<FeedItem>();
- if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) {
- for (int i = 0; i < queue.size(); i++) { // ignore playing item
- FeedItem item = queue.get(i);
- if (item.hasMedia() && !item.getMedia().isDownloaded()
- && !item.getMedia().isPlaying()) {
- itemsToDownload.add(item);
- episodeSpaceLeft--;
- undownloadedEpisodes--;
- if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) {
- break;
- }
- }
- }
- }
- if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) {
- for (FeedItem item : unreadItems) {
- if (item.hasMedia() && !item.getMedia().isDownloaded()) {
- itemsToDownload.add(item);
- episodeSpaceLeft--;
- undownloadedEpisodes--;
- if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) {
- break;
- }
- }
- }
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "Enqueueing " + itemsToDownload.size()
- + " items for download");
-
- try {
- downloadFeedItem(false, context,
- itemsToDownload.toArray(new FeedItem[itemsToDownload
- .size()]));
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- }
-
- }
- }
-
- /**
- * This method will determine the number of episodes that have to be deleted
- * depending on a given number of episodes.
- *
- * @return The argument that has to be passed to performAutoCleanup() so
- * that the number of episodes fits into the episode cache.
- * */
- private int getPerformAutoCleanupArgs(final int episodeNumber) {
- if (episodeNumber >= 0
- && UserPreferences.getEpisodeCacheSize() != UserPreferences
- .getEpisodeCacheSizeUnlimited()) {
- int downloadedEpisodes = getNumberOfDownloadedEpisodes();
- if (downloadedEpisodes + episodeNumber >= UserPreferences
- .getEpisodeCacheSize()) {
-
- return downloadedEpisodes + episodeNumber
- - UserPreferences.getEpisodeCacheSize();
- }
- }
- return 0;
- }
-
- /**
- * Performs an auto-cleanup so that the number of downloaded episodes is
- * below or equal to the episode cache size. The method will be executed in
- * the caller's thread.
- */
- public void performAutoCleanup(Context context) {
- performAutoCleanup(context, getPerformAutoCleanupArgs(0));
- }
-
- /**
- * This method will try to delete a given number of episodes. An episode
- * will only be deleted if it is not in the queue.
- *
- * @return The number of episodes that were actually deleted
- * */
- private int performAutoCleanup(Context context, final int episodeNumber) {
- List<FeedItem> candidates = new ArrayList<FeedItem>();
- List<FeedItem> delete;
- for (Feed feed : feeds) {
- for (FeedItem item : feed.getItems()) {
- if (item.hasMedia() && item.getMedia().isDownloaded()
- && !isInQueue(item) && item.isRead()) {
- candidates.add(item);
- }
- }
- }
-
- Collections.sort(candidates, new Comparator<FeedItem>() {
- @Override
- public int compare(FeedItem lhs, FeedItem rhs) {
- Date l = lhs.getMedia().getPlaybackCompletionDate();
- Date r = rhs.getMedia().getPlaybackCompletionDate();
-
- if (l == null) {
- l = new Date(0);
- }
- if (r == null) {
- r = new Date(0);
- }
- return l.compareTo(r);
- }
- });
-
- if (candidates.size() > episodeNumber) {
- delete = candidates.subList(0, episodeNumber);
- } else {
- delete = candidates;
- }
-
- for (FeedItem item : delete) {
- deleteFeedMedia(context, item.getMedia());
- }
-
- int counter = delete.size();
-
- if (AppConfig.DEBUG)
- Log.d(TAG, String.format(
- "Auto-delete deleted %d episodes (%d requested)", counter,
- episodeNumber));
-
- return counter;
- }
-
- /**
- * Counts items in the queue and the unread items list which haven't been
- * downloaded yet.
- *
- * This method will not count the playing item
- */
- private int getNumberOfUndownloadedEpisodes() {
- int counter = 0;
- for (FeedItem item : queue) {
- if (item.hasMedia() && !item.getMedia().isDownloaded()
- && !item.getMedia().isPlaying()) {
- counter++;
- }
- }
- for (FeedItem item : unreadItems) {
- if (item.hasMedia() && !item.getMedia().isDownloaded()) {
- counter++;
- }
- }
- return counter;
-
- }
-
- /** Counts all downloaded items. */
- private int getNumberOfDownloadedEpisodes() {
- int counter = 0;
- for (Feed feed : feeds) {
- for (FeedItem item : feed.getItems()) {
- if (item.hasMedia() && item.getMedia().isDownloaded()) {
- counter++;
- }
- }
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "Number of downloaded episodes: " + counter);
- return counter;
- }
-
- /**
- * Enqueues all items that are currently in the unreadItems list and marks
- * them as 'read'.
- */
- public void enqueueAllNewItems(final Context context) {
- if (!unreadItems.isEmpty()) {
- addQueueItem(context,
- unreadItems.toArray(new FeedItem[unreadItems.size()]));
- markAllItemsRead(context);
- }
- }
-
- /**
- * Adds a feeditem to the queue at the specified index if it is not in the
- * queue yet. The item is marked as 'read'.
- */
- public void addQueueItemAt(final Context context, final FeedItem item,
- final int index, final boolean performAutoDownload) {
- contentChanger.post(new Runnable() {
-
- @Override
- public void run() {
- if (!queue.contains(item)) {
- queue.add(index, item);
- if (!item.isRead()) {
- markItemRead(context, item, true, false);
- }
- }
- eventDist.sendQueueUpdateBroadcast();
-
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setQueue(queue);
- adapter.close();
- }
- });
- if (performAutoDownload) {
- new Thread() {
- @Override
- public void run() {
- autodownloadUndownloadedItems(context);
- }
- }.start();
- }
- }
- });
-
- }
-
- /**
- * Adds FeedItems to the queue if they are not in the queue yet. The items
- * are marked as 'read'.
- */
- public void addQueueItem(final Context context, final FeedItem... items) {
- if (items.length > 0) {
- contentChanger.post(new Runnable() {
-
- @Override
- public void run() {
- for (FeedItem item : items) {
- if (!queue.contains(item)) {
- queue.add(item);
- if (!item.isRead()) {
- markItemRead(context, item, true, false);
- }
- }
- }
- eventDist.sendQueueUpdateBroadcast();
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setQueue(queue);
- adapter.close();
- }
- });
- new Thread() {
- @Override
- public void run() {
- autodownloadUndownloadedItems(context);
- }
- }.start();
- }
- });
- }
-
- }
-
- /**
- * Return the item that comes after this item in the queue or null if this
- * item is not in the queue or if this item has no successor.
- */
- public FeedItem getQueueSuccessorOfItem(FeedItem item) {
- if (isInQueue(item)) {
- int itemIndex = queue.indexOf(item);
- if (itemIndex != -1 && itemIndex < (queue.size() - 1)) {
- return queue.get(itemIndex + 1);
- }
- }
- return null;
- }
-
- /** Removes all items in queue */
- public void clearQueue(final Context context) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Clearing queue");
- Iterator<FeedItem> iter = queue.iterator();
- while (iter.hasNext()) {
- FeedItem item = iter.next();
- if (item.getState() != FeedItem.State.PLAYING) {
- iter.remove();
- } else {
- if (AppConfig.DEBUG)
- Log.d(TAG,
- "FeedItem is playing and is therefore not removed from the queue");
- }
- }
- eventDist.sendQueueUpdateBroadcast();
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setQueue(queue);
- adapter.close();
- }
- });
-
- }
-
- /** Removes a FeedItem from the queue. Uses external PodDBAdapter. */
- private void removeQueueItem(FeedItem item, PodDBAdapter adapter) {
- boolean removed = queue.remove(item);
- if (removed) {
- adapter.setQueue(queue);
- }
- }
-
- /** Removes a FeedItem from the queue. */
- public void removeQueueItem(final Context context, FeedItem item,
- final boolean performAutoDownload) {
- boolean removed = queue.remove(item);
- if (removed) {
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setQueue(queue);
- adapter.close();
- }
- });
-
- }
- if (performAutoDownload) {
- new Thread() {
- @Override
- public void run() {
- autodownloadUndownloadedItems(context);
- }
- }.start();
- }
- eventDist.sendQueueUpdateBroadcast();
- }
-
- /**
- * Moves the queue item at the specified index to another position. If the
- * indices are out of range, no operation will be performed.
- *
- * @param from
- * index of the item that is going to be moved
- * @param to
- * destination index of item
- * @param broadcastUpdate
- * true if the method should send a queue update broadcast after
- * the operation has been performed. This should be set to false
- * if the order of the queue is changed through drag & drop
- * reordering to avoid visual glitches.
- */
- public void moveQueueItem(final Context context, int from, int to,
- boolean broadcastUpdate) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Moving queue item from index " + from + " to index "
- + to);
- if (from >= 0 && from < queue.size() && to >= 0 && to < queue.size()) {
- FeedItem item = queue.remove(from);
- queue.add(to, item);
- dbExec.execute(new Runnable() {
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setQueue(queue);
- adapter.close();
- }
- });
- if (broadcastUpdate) {
- eventDist.sendQueueUpdateBroadcast();
- }
- }
- }
-
- /** Returns true if the specified item is in the queue. */
- public boolean isInQueue(FeedItem item) {
- return queue.contains(item);
- }
-
- /**
- * Returns the FeedItem at the beginning of the queue or null if the queue
- * is empty.
- */
- public FeedItem getFirstQueueItem() {
- if (queue.isEmpty()) {
- return null;
- } else {
- return queue.get(0);
- }
- }
-
- private void addNewFeed(final Context context, final Feed feed) {
- contentChanger.post(new Runnable() {
-
- @Override
- public void run() {
- feeds.add(feed);
- Collections.sort(feeds, new FeedtitleComparator());
- eventDist.sendFeedUpdateBroadcast();
- }
- });
- setCompleteFeed(context, feed);
- }
-
- /**
- * Updates an existing feed or adds it as a new one if it doesn't exist.
- *
- * @return The saved Feed with a database ID
- */
- public Feed updateFeed(final Context context, final Feed newFeed) {
- // Look up feed in the feedslist
- final Feed savedFeed = searchFeedByIdentifyingValue(newFeed
- .getIdentifyingValue());
- if (savedFeed == null) {
- if (AppConfig.DEBUG)
- Log.d(TAG,
- "Found no existing Feed with title "
- + newFeed.getTitle() + ". Adding as new one.");
- // Add a new Feed
- addNewFeed(context, newFeed);
- return newFeed;
- } else {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Feed with title " + newFeed.getTitle()
- + " already exists. Syncing new with existing one.");
- if (savedFeed.compareWithOther(newFeed)) {
- if (AppConfig.DEBUG)
- Log.d(TAG,
- "Feed has updated attribute values. Updating old feed's attributes");
- savedFeed.updateFromOther(newFeed);
- }
- // Look for new or updated Items
- for (int idx = 0; idx < newFeed.getItems().size(); idx++) {
- final FeedItem item = newFeed.getItems().get(idx);
- FeedItem oldItem = searchFeedItemByIdentifyingValue(savedFeed,
- item.getIdentifyingValue());
- if (oldItem == null) {
- // item is new
- final int i = idx;
- item.setFeed(savedFeed);
- contentChanger.post(new Runnable() {
- @Override
- public void run() {
- savedFeed.getItems().add(i, item);
-
- }
- });
- markItemRead(context, item, false, false);
- } else {
- oldItem.updateFromOther(item);
- }
- }
- // update attributes
- savedFeed.setLastUpdate(newFeed.getLastUpdate());
- savedFeed.setType(newFeed.getType());
- setCompleteFeed(context, savedFeed);
- new Thread() {
- @Override
- public void run() {
- autodownloadUndownloadedItems(context);
- }
- }.start();
- return savedFeed;
- }
-
- }
-
- /** Get a Feed by its identifying value. */
- private Feed searchFeedByIdentifyingValue(String identifier) {
- for (Feed feed : feeds) {
- if (feed.getIdentifyingValue().equals(identifier)) {
- return feed;
- }
- }
- return null;
- }
-
- /**
- * Returns true if a feed with the given download link is already in the
- * feedlist.
- */
- public boolean feedExists(String downloadUrl) {
- for (Feed feed : feeds) {
- if (feed.getDownload_url().equals(downloadUrl)) {
- return true;
- }
- }
- return false;
- }
-
- /** Get a FeedItem by its identifying value. */
- private FeedItem searchFeedItemByIdentifyingValue(Feed feed,
- String identifier) {
- for (FeedItem item : feed.getItems()) {
- if (item.getIdentifyingValue().equals(identifier)) {
- return item;
- }
- }
- return null;
- }
-
- /** Updates Information of an existing Feed. Uses external adapter. */
- private void setFeed(Feed feed, PodDBAdapter adapter) {
- if (adapter != null) {
- adapter.setFeed(feed);
- feed.cacheDescriptionsOfItems();
- } else {
- Log.w(TAG, "Adapter in setFeed was null");
- }
- }
-
- /** Updates Information of an existing Feeditem. Uses external adapter. */
- private void setFeedItem(FeedItem item, PodDBAdapter adapter) {
- if (adapter != null) {
- adapter.setSingleFeedItem(item);
- } else {
- Log.w(TAG, "Adapter in setFeedItem was null");
- }
- }
-
- /** Updates Information of an existing Feedimage. Uses external adapter. */
- private void setFeedImage(FeedImage image, PodDBAdapter adapter) {
- if (adapter != null) {
- adapter.setImage(image);
- } else {
- Log.w(TAG, "Adapter in setFeedImage was null");
- }
- }
-
- /**
- * Updates Information of an existing Feedmedia object. Uses external
- * adapter.
- */
- private void setFeedImage(FeedMedia media, PodDBAdapter adapter) {
- if (adapter != null) {
- adapter.setMedia(media);
- } else {
- Log.w(TAG, "Adapter in setFeedMedia was null");
- }
- }
-
- /**
- * Updates Information of an existing Feed. Creates and opens its own
- * adapter.
- */
- public void setFeed(final Context context, final Feed feed) {
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setFeed(feed);
- feed.cacheDescriptionsOfItems();
- adapter.close();
- }
- });
-
- }
-
- /**
- * Updates Information of an existing Feed and its FeedItems. Creates and
- * opens its own adapter.
- */
- public void setCompleteFeed(final Context context, final Feed feed) {
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setCompleteFeed(feed);
- feed.cacheDescriptionsOfItems();
- adapter.close();
- }
- });
-
- }
-
- /**
- * Updates information of an existing FeedItem. Creates and opens its own
- * adapter.
- */
- public void setFeedItem(final Context context, final FeedItem item) {
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setSingleFeedItem(item);
- adapter.close();
- }
- });
-
- }
-
- /**
- * Updates information of an existing FeedImage. Creates and opens its own
- * adapter.
- */
- public void setFeedImage(final Context context, final FeedImage image) {
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setImage(image);
- adapter.close();
- }
- });
-
- }
-
- /**
- * Updates information of an existing FeedMedia object. Creates and opens
- * its own adapter.
- */
- public void setFeedMedia(final Context context, final FeedMedia media) {
- dbExec.execute(new Runnable() {
-
- @Override
- public void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- adapter.setMedia(media);
- adapter.close();
- }
- });
-
- }
-
- /** Get a Feed by its id */
- public Feed getFeed(long id) {
- for (Feed f : feeds) {
- if (f.id == id) {
- return f;
- }
- }
- Log.e(TAG, "Couldn't find Feed with id " + id);
- return null;
- }
-
- /** Get a Feed Image by its id */
- public FeedImage getFeedImage(long id) {
- for (Feed f : feeds) {
- FeedImage image = f.getImage();
- if (image != null && image.getId() == id) {
- return image;
- }
- }
- return null;
- }
-
- /** Get a Feed Item by its id and its feed */
- public FeedItem getFeedItem(long id, Feed feed) {
- if (feed != null) {
- for (FeedItem item : feed.getItems()) {
- if (item.getId() == id) {
- return item;
- }
- }
- }
- Log.e(TAG, "Couldn't find FeedItem with id " + id);
- return null;
- }
-
- /** Get a FeedItem by its id and the id of its feed. */
- public FeedItem getFeedItem(long itemId, long feedId) {
- Feed feed = getFeed(feedId);
- if (feed != null && feed.getItems() != null) {
- for (FeedItem item : feed.getItems()) {
- if (item.getId() == itemId) {
- return item;
- }
- }
- }
- return null;
- }
-
- /** Get a FeedMedia object by the id of the Media object and the feed object */
- public FeedMedia getFeedMedia(long id, Feed feed) {
- if (feed != null) {
- for (FeedItem item : feed.getItems()) {
- if (item.getMedia() != null && item.getMedia().getId() == id) {
- return item.getMedia();
- }
- }
- }
- Log.e(TAG, "Couldn't find FeedMedia with id " + id);
- if (feed == null)
- Log.e(TAG, "Feed was null");
- return null;
- }
-
- /** Get a FeedMedia object by the id of the Media object. */
- public FeedMedia getFeedMedia(long id) {
- for (Feed feed : feeds) {
- for (FeedItem item : feed.getItems()) {
- if (item.getMedia() != null && item.getMedia().getId() == id) {
- return item.getMedia();
- }
- }
- }
- Log.w(TAG, "Couldn't find FeedMedia with id " + id);
- return null;
- }
-
- /** Get a download status object from the download log by its FeedFile. */
- public DownloadStatus getDownloadStatus(FeedFile feedFile) {
- for (DownloadStatus status : downloadLog) {
- if (status.getFeedFile() == feedFile) {
- return status;
- }
- }
- return null;
- }
-
- /** Reads the database */
- public void loadDBData(Context context) {
- feeds.clear();
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- extractFeedlistFromCursor(context, adapter);
- extractDownloadLogFromCursor(context, adapter);
- extractQueueFromCursor(context, adapter);
- adapter.close();
- Collections.sort(feeds, new FeedtitleComparator());
- Collections.sort(unreadItems, new FeedItemPubdateComparator());
- cleanupPlaybackHistory();
- }
-
- private void extractFeedlistFromCursor(Context context, PodDBAdapter adapter) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Extracting Feedlist");
- Cursor feedlistCursor = adapter.getAllFeedsCursor();
- if (feedlistCursor.moveToFirst()) {
- do {
- Date lastUpdate = new Date(
- feedlistCursor
- .getLong(PodDBAdapter.KEY_LAST_UPDATE_INDEX));
- Feed feed = new Feed(lastUpdate);
-
- feed.id = feedlistCursor.getLong(PodDBAdapter.KEY_ID_INDEX);
- feed.setTitle(feedlistCursor
- .getString(PodDBAdapter.KEY_TITLE_INDEX));
- feed.setLink(feedlistCursor
- .getString(PodDBAdapter.KEY_LINK_INDEX));
- feed.setDescription(feedlistCursor
- .getString(PodDBAdapter.KEY_DESCRIPTION_INDEX));
- feed.setPaymentLink(feedlistCursor
- .getString(PodDBAdapter.KEY_PAYMENT_LINK_INDEX));
- feed.setAuthor(feedlistCursor
- .getString(PodDBAdapter.KEY_AUTHOR_INDEX));
- feed.setLanguage(feedlistCursor
- .getString(PodDBAdapter.KEY_LANGUAGE_INDEX));
- feed.setType(feedlistCursor
- .getString(PodDBAdapter.KEY_TYPE_INDEX));
- feed.setFeedIdentifier(feedlistCursor
- .getString(PodDBAdapter.KEY_FEED_IDENTIFIER_INDEX));
- long imageIndex = feedlistCursor
- .getLong(PodDBAdapter.KEY_IMAGE_INDEX);
- if (imageIndex != 0) {
- feed.setImage(adapter.getFeedImage(imageIndex));
- feed.getImage().setFeed(feed);
- }
- feed.file_url = feedlistCursor
- .getString(PodDBAdapter.KEY_FILE_URL_INDEX);
- feed.download_url = feedlistCursor
- .getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX);
- feed.setDownloaded(feedlistCursor
- .getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0);
- // Get FeedItem-Object
- Cursor itemlistCursor = adapter.getAllItemsOfFeedCursor(feed);
- feed.setItems(extractFeedItemsFromCursor(context, feed,
- itemlistCursor, adapter));
- itemlistCursor.close();
-
- feeds.add(feed);
- } while (feedlistCursor.moveToNext());
- }
- feedlistCursor.close();
-
- }
-
- private ArrayList<FeedItem> extractFeedItemsFromCursor(Context context,
- Feed feed, Cursor itemlistCursor, PodDBAdapter adapter) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Extracting Feeditems of feed " + feed.getTitle());
- ArrayList<FeedItem> items = new ArrayList<FeedItem>();
- ArrayList<String> mediaIds = new ArrayList<String>();
-
- if (itemlistCursor.moveToFirst()) {
- do {
- FeedItem item = new FeedItem();
-
- item.id = itemlistCursor.getLong(PodDBAdapter.IDX_FI_SMALL_ID);
- item.setFeed(feed);
- item.setTitle(itemlistCursor
- .getString(PodDBAdapter.IDX_FI_SMALL_TITLE));
- item.setLink(itemlistCursor
- .getString(PodDBAdapter.IDX_FI_SMALL_LINK));
- item.setPubDate(new Date(itemlistCursor
- .getLong(PodDBAdapter.IDX_FI_SMALL_PUBDATE)));
- item.setPaymentLink(itemlistCursor
- .getString(PodDBAdapter.IDX_FI_SMALL_PAYMENT_LINK));
- long mediaId = itemlistCursor
- .getLong(PodDBAdapter.IDX_FI_SMALL_MEDIA);
- if (mediaId != 0) {
- mediaIds.add(String.valueOf(mediaId));
- item.setMedia(new FeedMedia(mediaId, item));
- }
- item.setRead((itemlistCursor
- .getInt(PodDBAdapter.IDX_FI_SMALL_READ) > 0) ? true
- : false);
- item.setItemIdentifier(itemlistCursor
- .getString(PodDBAdapter.IDX_FI_SMALL_ITEM_IDENTIFIER));
- if (item.getState() == FeedItem.State.NEW) {
- unreadItems.add(item);
- }
-
- // extract chapters
- boolean hasSimpleChapters = itemlistCursor
- .getInt(PodDBAdapter.IDX_FI_SMALL_HAS_CHAPTERS) > 0;
- if (hasSimpleChapters) {
- Cursor chapterCursor = adapter
- .getSimpleChaptersOfFeedItemCursor(item);
- if (chapterCursor.moveToFirst()) {
- item.setChapters(new ArrayList<Chapter>());
- do {
- int chapterType = chapterCursor
- .getInt(PodDBAdapter.KEY_CHAPTER_TYPE_INDEX);
- Chapter chapter = null;
- long start = chapterCursor
- .getLong(PodDBAdapter.KEY_CHAPTER_START_INDEX);
- String title = chapterCursor
- .getString(PodDBAdapter.KEY_TITLE_INDEX);
- String link = chapterCursor
- .getString(PodDBAdapter.KEY_CHAPTER_LINK_INDEX);
-
- switch (chapterType) {
- case SimpleChapter.CHAPTERTYPE_SIMPLECHAPTER:
- chapter = new SimpleChapter(start, title, item,
- link);
- break;
- case ID3Chapter.CHAPTERTYPE_ID3CHAPTER:
- chapter = new ID3Chapter(start, title, item,
- link);
- break;
- case VorbisCommentChapter.CHAPTERTYPE_VORBISCOMMENT_CHAPTER:
- chapter = new VorbisCommentChapter(start,
- title, item, link);
- break;
- }
- chapter.setId(chapterCursor
- .getLong(PodDBAdapter.KEY_ID_INDEX));
- item.getChapters().add(chapter);
- } while (chapterCursor.moveToNext());
- }
- chapterCursor.close();
- }
- items.add(item);
- } while (itemlistCursor.moveToNext());
- }
- extractMediafromFeedItemlist(adapter, items, mediaIds);
- Collections.sort(items, new FeedItemPubdateComparator());
- return items;
- }
-
- private void extractMediafromFeedItemlist(PodDBAdapter adapter,
- ArrayList<FeedItem> items, ArrayList<String> mediaIds) {
- ArrayList<FeedItem> itemsCopy = new ArrayList<FeedItem>(items);
- Cursor cursor = adapter.getFeedMediaCursor(mediaIds
- .toArray(new String[mediaIds.size()]));
- if (cursor.moveToFirst()) {
- do {
- long mediaId = cursor.getLong(PodDBAdapter.KEY_ID_INDEX);
- // find matching feed item
- FeedItem item = getMatchingItemForMedia(mediaId, itemsCopy);
- itemsCopy.remove(item);
- if (item != null) {
- Date playbackCompletionDate = null;
- long playbackCompletionTime = cursor
- .getLong(PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE_INDEX);
- if (playbackCompletionTime > 0) {
- playbackCompletionDate = new Date(
- playbackCompletionTime);
- }
-
- item.setMedia(new FeedMedia(
- mediaId,
- item,
- cursor.getInt(PodDBAdapter.KEY_DURATION_INDEX),
- cursor.getInt(PodDBAdapter.KEY_POSITION_INDEX),
- cursor.getLong(PodDBAdapter.KEY_SIZE_INDEX),
- cursor.getString(PodDBAdapter.KEY_MIME_TYPE_INDEX),
- cursor.getString(PodDBAdapter.KEY_FILE_URL_INDEX),
- cursor.getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX),
- cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0,
- playbackCompletionDate));
- if (playbackCompletionDate != null) {
- playbackHistory.add(item);
- }
-
- }
- } while (cursor.moveToNext());
- cursor.close();
- }
- }
-
- private FeedItem getMatchingItemForMedia(long mediaId,
- ArrayList<FeedItem> items) {
- for (FeedItem item : items) {
- if (item.getMedia() != null && item.getMedia().getId() == mediaId) {
- return item;
- }
- }
- return null;
- }
-
- private void extractDownloadLogFromCursor(Context context,
- PodDBAdapter adapter) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Extracting DownloadLog");
- Cursor logCursor = adapter.getDownloadLogCursor();
- if (logCursor.moveToFirst()) {
- do {
- long id = logCursor.getLong(PodDBAdapter.KEY_ID_INDEX);
- FeedFile feedfile = null;
-
- long feedfileId = logCursor
- .getLong(PodDBAdapter.KEY_FEEDFILE_INDEX);
- int feedfileType = logCursor
- .getInt(PodDBAdapter.KEY_FEEDFILETYPE_INDEX);
- if (feedfileId != 0) {
- switch (feedfileType) {
- case Feed.FEEDFILETYPE_FEED:
- feedfile = getFeed(feedfileId);
- break;
- case FeedImage.FEEDFILETYPE_FEEDIMAGE:
- feedfile = getFeedImage(feedfileId);
- break;
- case FeedMedia.FEEDFILETYPE_FEEDMEDIA:
- feedfile = getFeedMedia(feedfileId);
- }
- }
- boolean successful = logCursor
- .getInt(PodDBAdapter.KEY_SUCCESSFUL_INDEX) > 0;
- int reason = logCursor.getInt(PodDBAdapter.KEY_REASON_INDEX);
- String reasonDetailed = logCursor
- .getString(PodDBAdapter.KEY_REASON_DETAILED_INDEX);
- String title = logCursor
- .getString(PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE_INDEX);
- Date completionDate = new Date(
- logCursor
- .getLong(PodDBAdapter.KEY_COMPLETION_DATE_INDEX));
- downloadLog.add(new DownloadStatus(id, title, feedfile,
- feedfileType, successful, reason, completionDate,
- reasonDetailed));
-
- } while (logCursor.moveToNext());
- }
- logCursor.close();
- Collections.sort(downloadLog, new DownloadStatusComparator());
- }
-
- private void extractQueueFromCursor(Context context, PodDBAdapter adapter) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Extracting Queue");
- Cursor cursor = adapter.getQueueCursor();
-
- // Sort cursor results by ID with TreeMap
- TreeMap<Integer, FeedItem> map = new TreeMap<Integer, FeedItem>();
-
- if (cursor.moveToFirst()) {
- do {
- int index = cursor.getInt(PodDBAdapter.KEY_ID_INDEX);
- Feed feed = getFeed(cursor
- .getLong(PodDBAdapter.KEY_QUEUE_FEED_INDEX));
- if (feed != null) {
- FeedItem item = getFeedItem(
- cursor.getLong(PodDBAdapter.KEY_FEEDITEM_INDEX),
- feed);
- if (item != null) {
- map.put(index, item);
- }
- }
- } while (cursor.moveToNext());
- }
- cursor.close();
-
- for (Map.Entry<Integer, FeedItem> entry : map.entrySet()) {
- FeedItem item = entry.getValue();
- queue.add(item);
- }
- }
-
- /**
- * Loads description and contentEncoded values from the database and caches
- * it in the feeditem. The task callback will contain a String-array with
- * the description at index 0 and the value of contentEncoded at index 1.
- */
- public void loadExtraInformationOfItem(final Context context,
- final FeedItem item, FeedManager.TaskCallback<String[]> callback) {
- if (AppConfig.DEBUG) {
- Log.d(TAG,
- "Loading extra information of item with id " + item.getId());
- if (item.getTitle() != null) {
- Log.d(TAG, "Title: " + item.getTitle());
- }
- }
- dbExec.execute(new FeedManager.Task<String[]>(new Handler(), callback) {
-
- @Override
- public void execute() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- Cursor extraCursor = adapter.getExtraInformationOfItem(item);
- if (extraCursor.moveToFirst()) {
- String description = extraCursor
- .getString(PodDBAdapter.IDX_FI_EXTRA_DESCRIPTION);
- String contentEncoded = extraCursor
- .getString(PodDBAdapter.IDX_FI_EXTRA_CONTENT_ENCODED);
- item.setCachedDescription(description);
- item.setCachedContentEncoded(contentEncoded);
- setResult(new String[] { description, contentEncoded });
- }
- adapter.close();
- }
- });
- }
-
- /**
- * Searches the descriptions of FeedItems of a specific feed for a given
- * string.
- *
- * @param feed
- * The feed whose items should be searched.
- * @param query
- * The search string
- * @param callback
- * A callback which will be used to return the search result
- * */
- public void searchFeedItemDescription(final Context context,
- final Feed feed, final String query,
- FeedManager.QueryTaskCallback callback) {
- dbExec.execute(new FeedManager.QueryTask(context, new Handler(),
- callback) {
-
- @Override
- public void execute(PodDBAdapter adapter) {
- Cursor searchResult = adapter.searchItemDescriptions(feed,
- query);
- setResult(searchResult);
- }
- });
- }
-
- /**
- * Searches the 'contentEncoded' field of FeedItems of a specific feed for a
- * given string.
- *
- * @param feed
- * The feed whose items should be searched.
- * @param query
- * The search string
- * @param callback
- * A callback which will be used to return the search result
- * */
- public void searchFeedItemContentEncoded(final Context context,
- final Feed feed, final String query,
- FeedManager.QueryTaskCallback callback) {
- dbExec.execute(new FeedManager.QueryTask(context, new Handler(),
- callback) {
-
- @Override
- public void execute(PodDBAdapter adapter) {
- Cursor searchResult = adapter.searchItemContentEncoded(feed,
- query);
- setResult(searchResult);
- }
- });
- }
-
- /** Returns the number of feeds that are currently in the feeds list. */
- public int getFeedsSize() {
- return feeds.size();
- }
-
- /** Returns the feed at the specified index of the feeds list. */
- public Feed getFeedAtIndex(int index) {
- return feeds.get(index);
- }
-
- /** Returns an array that contains all feeds of the feed manager. */
- public Feed[] getFeedsArray() {
- return feeds.toArray(new Feed[feeds.size()]);
- }
-
- List<Feed> getFeeds() {
- return feeds;
- }
-
- /**
- * Returns the number of items that are currently in the queue.
- *
- * @param enableEpisodeFilter
- * true if items without episodes should be ignored by this
- * method if the episode filter was enabled by the user.
- * */
- public int getQueueSize(boolean enableEpisodeFilter) {
- if (UserPreferences.isDisplayOnlyEpisodes() && enableEpisodeFilter) {
- return EpisodeFilter.countItemsWithEpisodes(queue);
- } else {
- return queue.size();
- }
- }
-
- /**
- * Returns the FeedItem at the specified index of the queue.
- *
- * @param enableEpisodeFilter
- * true if items without episodes should be ignored by this
- * method if the episode filter was enabled by the user.
- *
- * @throws IndexOutOfBoundsException
- * if index is out of range
- * */
- public FeedItem getQueueItemAtIndex(int index, boolean enableEpisodeFilter) {
- if (UserPreferences.isDisplayOnlyEpisodes() && enableEpisodeFilter) {
- return EpisodeFilter.accessEpisodeByIndex(queue, index);
- } else {
- return queue.get(index);
- }
- }
-
- /**
- * Returns the index of the episode that is currently being played in the
- * queue or -1 if the queue is empty or no episode in the queue is being
- * played.
- * */
- public int getQueuePlayingEpisodeIndex() {
- FeedManager manager = FeedManager.getInstance();
- int queueSize = manager.getQueueSize(true);
- if (queueSize == 0) {
- return -1;
- } else {
- for (int x = 0; x < queueSize; x++) {
- FeedItem item = getQueueItemAtIndex(x, true);
- if (item.getState() == FeedItem.State.PLAYING) {
- return x;
- }
- }
- return -1;
- }
- }
-
- /**
- * Returns the number of unread items.
- *
- * @param enableEpisodeFilter
- * true if items without episodes should be ignored by this
- * method if the episode filter was enabled by the user.
- * */
- public int getUnreadItemsSize(boolean enableEpisodeFilter) {
- if (UserPreferences.isDisplayOnlyEpisodes() && enableEpisodeFilter) {
- return EpisodeFilter.countItemsWithEpisodes(unreadItems);
- } else {
- return unreadItems.size();
- }
- }
-
- /**
- * Returns the FeedItem at the specified index of the unread items list.
- *
- * @param enableEpisodeFilter
- * true if items without episodes should be ignored by this
- * method if the episode filter was enabled by the user.
- *
- * @throws IndexOutOfBoundsException
- * if index is out of range
- * */
- public FeedItem getUnreadItemAtIndex(int index, boolean enableEpisodeFilter) {
- if (UserPreferences.isDisplayOnlyEpisodes() && enableEpisodeFilter) {
- return EpisodeFilter.accessEpisodeByIndex(unreadItems, index);
- } else {
- return unreadItems.get(index);
- }
- }
-
- /**
- * Returns the number of items in the playback history.
- * */
- public int getPlaybackHistorySize() {
- return playbackHistory.size();
- }
-
- /**
- * Returns the FeedItem at the specified index of the playback history.
- *
- * @throws IndexOutOfBoundsException
- * if index is out of range
- * */
- public FeedItem getPlaybackHistoryItemIndex(int index) {
- return playbackHistory.get(index);
- }
-
- /** Returns the number of items in the download log */
- public int getDownloadLogSize() {
- return downloadLog.size();
- }
-
- /** Returns the download status at the specified index of the download log. */
- public DownloadStatus getDownloadStatusFromLogAtIndex(int index) {
- return downloadLog.get(index);
- }
-
- /** Is called by a FeedManagerTask after completion. */
- public interface TaskCallback<V> {
- void onCompletion(V result);
- }
-
- /** Is called by a FeedManager.QueryTask after completion. */
- public interface QueryTaskCallback {
- void handleResult(Cursor result);
-
- void onCompletion();
- }
-
- /** A runnable that can post a callback to a handler after completion. */
- abstract class Task<V> implements Runnable {
- private Handler handler;
- private TaskCallback<V> callback;
- private V result;
-
- /**
- * Standard contructor. No callbacks are going to be posted to a
- * handler.
- */
- public Task() {
- super();
- }
-
- /**
- * The Task will post a Runnable to 'handler' that will execute the
- * 'callback' after completion.
- */
- public Task(Handler handler, TaskCallback<V> callback) {
- super();
- this.handler = handler;
- this.callback = callback;
- }
-
- @Override
- public final void run() {
- execute();
- if (handler != null && callback != null) {
- handler.post(new Runnable() {
- @Override
- public void run() {
- callback.onCompletion(result);
- }
- });
- }
- }
-
- /** This method will be executed in the same thread as the run() method. */
- public abstract void execute();
-
- public void setResult(V result) {
- this.result = result;
- }
- }
-
- /**
- * A runnable which should be used for database queries. The onCompletion
- * method is executed on the database executor to handle Cursors correctly.
- * This class automatically creates a PodDBAdapter object and closes it when
- * it is no longer in use.
- */
- abstract class QueryTask implements Runnable {
- private QueryTaskCallback callback;
- private Cursor result;
- private Context context;
- private Handler handler;
-
- public QueryTask(Context context, Handler handler,
- QueryTaskCallback callback) {
- this.callback = callback;
- this.context = context;
- this.handler = handler;
- }
-
- @Override
- public final void run() {
- PodDBAdapter adapter = new PodDBAdapter(context);
- adapter.open();
- execute(adapter);
- callback.handleResult(result);
- if (result != null && !result.isClosed()) {
- result.close();
- }
- adapter.close();
- if (handler != null && callback != null) {
- handler.post(new Runnable() {
-
- @Override
- public void run() {
- callback.onCompletion();
- }
-
- });
- }
- }
-
- public abstract void execute(PodDBAdapter adapter);
-
- protected void setResult(Cursor c) {
- result = c;
- }
- }
-
-}
diff --git a/src/de/danoeh/antennapod/feed/FeedMedia.java b/src/de/danoeh/antennapod/feed/FeedMedia.java
index 1368cf854..f140a37e6 100644
--- a/src/de/danoeh/antennapod/feed/FeedMedia.java
+++ b/src/de/danoeh/antennapod/feed/FeedMedia.java
@@ -4,6 +4,7 @@ import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
+import java.util.concurrent.Callable;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
@@ -11,358 +12,383 @@ import android.os.Parcel;
import android.os.Parcelable;
import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.preferences.PlaybackPreferences;
+import de.danoeh.antennapod.storage.DBReader;
+import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.util.ChapterUtils;
import de.danoeh.antennapod.util.playback.Playable;
public class FeedMedia extends FeedFile implements Playable {
- public static final int FEEDFILETYPE_FEEDMEDIA = 2;
- public static final int PLAYABLE_TYPE_FEEDMEDIA = 1;
-
- public static final String PREF_MEDIA_ID = "FeedMedia.PrefMediaId";
- public static final String PREF_FEED_ID = "FeedMedia.PrefFeedId";
-
- private int duration;
- private int position; // Current position in file
- private long size; // File size in Byte
- private String mime_type;
- private FeedItem item;
- private Date playbackCompletionDate;
-
- public FeedMedia(FeedItem i, String download_url, long size,
- String mime_type) {
- super(null, download_url, false);
- this.item = i;
- this.size = size;
- this.mime_type = mime_type;
- }
-
- public FeedMedia(long id, FeedItem item, int duration, int position,
- long size, String mime_type, String file_url, String download_url,
- boolean downloaded, Date playbackCompletionDate) {
- super(file_url, download_url, downloaded);
- this.id = id;
- this.item = item;
- this.duration = duration;
- this.position = position;
- this.size = size;
- this.mime_type = mime_type;
- this.playbackCompletionDate = playbackCompletionDate;
- }
-
- public FeedMedia(long id, FeedItem item) {
- super();
- this.id = id;
- this.item = item;
- }
-
- @Override
- public String getHumanReadableIdentifier() {
- if (item != null && item.getTitle() != null) {
- return item.getTitle();
- } else {
- return download_url;
- }
- }
-
- /** Uses mimetype to determine the type of media. */
- public MediaType getMediaType() {
- if (mime_type == null || mime_type.isEmpty()) {
- return MediaType.UNKNOWN;
- } else {
- if (mime_type.startsWith("audio")) {
- return MediaType.AUDIO;
- } else if (mime_type.startsWith("video")) {
- return MediaType.VIDEO;
- } else if (mime_type.equals("application/ogg")) {
- return MediaType.AUDIO;
- }
- }
- return MediaType.UNKNOWN;
- }
-
- public void updateFromOther(FeedMedia other) {
- super.updateFromOther(other);
- if (other.size > 0) {
- size = other.size;
- }
- if (other.mime_type != null) {
- mime_type = other.mime_type;
- }
- }
-
- public boolean compareWithOther(FeedMedia other) {
- if (super.compareWithOther(other)) {
- return true;
- }
- if (other.mime_type != null) {
- if (mime_type == null || !mime_type.equals(other.mime_type)) {
- return true;
- }
- }
- if (other.size > 0 && other.size != size) {
- return true;
- }
- return false;
- }
-
- /**
- * Reads playback preferences to determine whether this FeedMedia object is
- * currently being played.
- */
- public boolean isPlaying() {
- return PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA
- && PlaybackPreferences.getCurrentlyPlayingFeedMediaId() == id;
- }
-
- @Override
- public int getTypeAsInt() {
- return FEEDFILETYPE_FEEDMEDIA;
- }
-
- public int getDuration() {
- return duration;
- }
-
- public void setDuration(int duration) {
- this.duration = duration;
- }
-
- public int getPosition() {
- return position;
- }
-
- public void setPosition(int position) {
- this.position = position;
- }
-
- public long getSize() {
- return size;
- }
-
- public void setSize(long size) {
- this.size = size;
- }
-
- public String getMime_type() {
- return mime_type;
- }
-
- public void setMime_type(String mime_type) {
- this.mime_type = mime_type;
- }
-
- public FeedItem getItem() {
- return item;
- }
-
- public void setItem(FeedItem item) {
- this.item = item;
- }
-
- public Date getPlaybackCompletionDate() {
- return playbackCompletionDate;
- }
-
- public void setPlaybackCompletionDate(Date playbackCompletionDate) {
- this.playbackCompletionDate = playbackCompletionDate;
- }
-
- public boolean isInProgress() {
- return (this.position > 0);
- }
-
- public FeedImage getImage() {
- if (item != null && item.getFeed() != null) {
- return item.getFeed().getImage();
- }
- return null;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeLong(item.getFeed().getId());
- dest.writeLong(item.getId());
- }
-
- @Override
- public void writeToPreferences(Editor prefEditor) {
- prefEditor.putLong(PREF_FEED_ID, item.getFeed().getId());
- prefEditor.putLong(PREF_MEDIA_ID, id);
- }
-
- @Override
- public void loadMetadata() throws PlayableException {
- }
-
- @Override
- public void loadChapterMarks() {
- if (getChapters() == null && !localFileAvailable()) {
- ChapterUtils.loadChaptersFromStreamUrl(this);
- if (getChapters() != null) {
- FeedManager.getInstance().setFeedItem(PodcastApp.getInstance(),
- item);
- }
- }
-
- }
-
- @Override
- public String getEpisodeTitle() {
- if (getItem().getTitle() != null) {
- return getItem().getTitle();
- } else {
- return getItem().getIdentifyingValue();
- }
- }
-
- @Override
- public List<Chapter> getChapters() {
- return getItem().getChapters();
- }
-
- @Override
- public String getWebsiteLink() {
- return getItem().getLink();
- }
-
- @Override
- public String getFeedTitle() {
- return getItem().getFeed().getTitle();
- }
-
- @Override
- public Object getIdentifier() {
- return id;
- }
-
- @Override
- public String getLocalMediaUrl() {
- return file_url;
- }
-
- @Override
- public String getStreamUrl() {
- return download_url;
- }
-
- @Override
- public boolean localFileAvailable() {
- return isDownloaded() && file_url != null;
- }
-
- @Override
- public boolean streamAvailable() {
- return download_url != null;
- }
-
- @Override
- public void saveCurrentPosition(SharedPreferences pref, int newPosition) {
- position = newPosition;
- FeedManager.getInstance().setFeedMedia(PodcastApp.getInstance(), this);
- }
-
- @Override
- public void onPlaybackStart() {
- }
-
- @Override
- public void onPlaybackCompleted() {
-
- }
-
- @Override
- public int getPlayableType() {
- return PLAYABLE_TYPE_FEEDMEDIA;
- }
-
- @Override
- public void setChapters(List<Chapter> chapters) {
- getItem().setChapters(chapters);
- }
-
- @Override
- public String getPaymentLink() {
- return getItem().getPaymentLink();
- }
-
- @Override
- public void loadShownotes(final ShownoteLoaderCallback callback) {
- String contentEncoded = item.getContentEncoded();
- if (item.getDescription() == null || contentEncoded == null) {
- FeedManager.getInstance().loadExtraInformationOfItem(
- PodcastApp.getInstance(), item,
- new FeedManager.TaskCallback<String[]>() {
- @Override
- public void onCompletion(String[] result) {
- if (result[1] != null) {
- callback.onShownotesLoaded(result[1]);
- } else {
- callback.onShownotesLoaded(result[0]);
-
- }
-
- }
- });
- } else {
- callback.onShownotesLoaded(contentEncoded);
- }
- }
-
- public static final Parcelable.Creator<FeedMedia> CREATOR = new Parcelable.Creator<FeedMedia>() {
- public FeedMedia createFromParcel(Parcel in) {
- long feedId = in.readLong();
- long itemId = in.readLong();
- FeedItem item = FeedManager.getInstance().getFeedItem(itemId,
- feedId);
- if (item != null) {
- return item.getMedia();
- } else {
- return null;
- }
- }
-
- public FeedMedia[] newArray(int size) {
- return new FeedMedia[size];
- }
- };
-
- @Override
- public InputStream openImageInputStream() {
- InputStream out = new Playable.DefaultPlayableImageLoader(this)
- .openImageInputStream();
- if (out == null) {
- if (item.getFeed().getImage() != null) {
- return item.getFeed().getImage().openImageInputStream();
- }
- }
- return out;
- }
-
- @Override
- public String getImageLoaderCacheKey() {
- String out = new Playable.DefaultPlayableImageLoader(this)
- .getImageLoaderCacheKey();
- if (out == null) {
- if (item.getFeed().getImage() != null) {
- return item.getFeed().getImage().getImageLoaderCacheKey();
- }
- }
- return out;
- }
-
- @Override
- public InputStream reopenImageInputStream(InputStream input) {
- if (input instanceof FileInputStream) {
- return item.getFeed().getImage().reopenImageInputStream(input);
- } else {
- return new Playable.DefaultPlayableImageLoader(this)
- .reopenImageInputStream(input);
- }
- }
+ public static final int FEEDFILETYPE_FEEDMEDIA = 2;
+ public static final int PLAYABLE_TYPE_FEEDMEDIA = 1;
+
+ public static final String PREF_MEDIA_ID = "FeedMedia.PrefMediaId";
+ public static final String PREF_FEED_ID = "FeedMedia.PrefFeedId";
+
+ private int duration;
+ private int position; // Current position in file
+ private long size; // File size in Byte
+ private String mime_type;
+ private volatile FeedItem item;
+ private Date playbackCompletionDate;
+
+ /* Used for loading item when restoring from parcel. */
+ private long itemID;
+
+ public FeedMedia(FeedItem i, String download_url, long size,
+ String mime_type) {
+ super(null, download_url, false);
+ this.item = i;
+ this.size = size;
+ this.mime_type = mime_type;
+ }
+
+ public FeedMedia(long id, FeedItem item, int duration, int position,
+ long size, String mime_type, String file_url, String download_url,
+ boolean downloaded, Date playbackCompletionDate) {
+ super(file_url, download_url, downloaded);
+ this.id = id;
+ this.item = item;
+ this.duration = duration;
+ this.position = position;
+ this.size = size;
+ this.mime_type = mime_type;
+ this.playbackCompletionDate = playbackCompletionDate;
+ }
+
+ public FeedMedia(long id, FeedItem item) {
+ super();
+ this.id = id;
+ this.item = item;
+ }
+
+ @Override
+ public String getHumanReadableIdentifier() {
+ if (item != null && item.getTitle() != null) {
+ return item.getTitle();
+ } else {
+ return download_url;
+ }
+ }
+
+ /**
+ * Uses mimetype to determine the type of media.
+ */
+ public MediaType getMediaType() {
+ if (mime_type == null || mime_type.isEmpty()) {
+ return MediaType.UNKNOWN;
+ } else {
+ if (mime_type.startsWith("audio")) {
+ return MediaType.AUDIO;
+ } else if (mime_type.startsWith("video")) {
+ return MediaType.VIDEO;
+ } else if (mime_type.equals("application/ogg")) {
+ return MediaType.AUDIO;
+ }
+ }
+ return MediaType.UNKNOWN;
+ }
+
+ public void updateFromOther(FeedMedia other) {
+ super.updateFromOther(other);
+ if (other.size > 0) {
+ size = other.size;
+ }
+ if (other.mime_type != null) {
+ mime_type = other.mime_type;
+ }
+ }
+
+ public boolean compareWithOther(FeedMedia other) {
+ if (super.compareWithOther(other)) {
+ return true;
+ }
+ if (other.mime_type != null) {
+ if (mime_type == null || !mime_type.equals(other.mime_type)) {
+ return true;
+ }
+ }
+ if (other.size > 0 && other.size != size) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Reads playback preferences to determine whether this FeedMedia object is
+ * currently being played.
+ */
+ public boolean isPlaying() {
+ return PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA
+ && PlaybackPreferences.getCurrentlyPlayingFeedMediaId() == id;
+ }
+
+ @Override
+ public int getTypeAsInt() {
+ return FEEDFILETYPE_FEEDMEDIA;
+ }
+
+ public int getDuration() {
+ return duration;
+ }
+
+ public void setDuration(int duration) {
+ this.duration = duration;
+ }
+
+ public int getPosition() {
+ return position;
+ }
+
+ public void setPosition(int position) {
+ this.position = position;
+ }
+
+ public long getSize() {
+ return size;
+ }
+
+ public void setSize(long size) {
+ this.size = size;
+ }
+
+ public String getMime_type() {
+ return mime_type;
+ }
+
+ public void setMime_type(String mime_type) {
+ this.mime_type = mime_type;
+ }
+
+ public FeedItem getItem() {
+ return item;
+ }
+
+ public void setItem(FeedItem item) {
+ this.item = item;
+ }
+
+ public Date getPlaybackCompletionDate() {
+ return playbackCompletionDate;
+ }
+
+ public void setPlaybackCompletionDate(Date playbackCompletionDate) {
+ this.playbackCompletionDate = playbackCompletionDate;
+ }
+
+ public boolean isInProgress() {
+ return (this.position > 0);
+ }
+
+ public FeedImage getImage() {
+ if (item != null && item.getFeed() != null) {
+ return item.getFeed().getImage();
+ }
+ return null;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(id);
+ dest.writeLong(item.getId());
+
+ dest.writeInt(duration);
+ dest.writeInt(position);
+ dest.writeLong(size);
+ dest.writeString(mime_type);
+ dest.writeString(file_url);
+ dest.writeString(download_url);
+ dest.writeByte((byte) ((downloaded) ? 1 : 0));
+ dest.writeLong((playbackCompletionDate != null) ? playbackCompletionDate.getTime() : 0);
+ }
+
+ @Override
+ public void writeToPreferences(Editor prefEditor) {
+ prefEditor.putLong(PREF_FEED_ID, item.getFeed().getId());
+ prefEditor.putLong(PREF_MEDIA_ID, id);
+ }
+
+ @Override
+ public void loadMetadata() throws PlayableException {
+ if (item == null && itemID != 0) {
+ item = DBReader.getFeedItem(PodcastApp.getInstance(), itemID);
+ }
+ }
+
+ @Override
+ public void loadChapterMarks() {
+ if (getChapters() == null && !localFileAvailable()) {
+ ChapterUtils.loadChaptersFromStreamUrl(this);
+ if (getChapters() != null && item != null) {
+ DBWriter.setFeedItem(PodcastApp.getInstance(),
+ item);
+ }
+ }
+
+ }
+
+ @Override
+ public String getEpisodeTitle() {
+ if (item == null) {
+ return null;
+ }
+ if (getItem().getTitle() != null) {
+ return getItem().getTitle();
+ } else {
+ return getItem().getIdentifyingValue();
+ }
+ }
+
+ @Override
+ public List<Chapter> getChapters() {
+ if (item == null) {
+ return null;
+ }
+ return getItem().getChapters();
+ }
+
+ @Override
+ public String getWebsiteLink() {
+ if (item == null) {
+ return null;
+ }
+ return getItem().getLink();
+ }
+
+ @Override
+ public String getFeedTitle() {
+ if (item == null) {
+ return null;
+ }
+ return getItem().getFeed().getTitle();
+ }
+
+ @Override
+ public Object getIdentifier() {
+ return id;
+ }
+
+ @Override
+ public String getLocalMediaUrl() {
+ return file_url;
+ }
+
+ @Override
+ public String getStreamUrl() {
+ return download_url;
+ }
+
+ @Override
+ public String getPaymentLink() {
+ if (item == null) {
+ return null;
+ }
+ return getItem().getPaymentLink();
+ }
+
+ @Override
+ public boolean localFileAvailable() {
+ return isDownloaded() && file_url != null;
+ }
+
+ @Override
+ public boolean streamAvailable() {
+ return download_url != null;
+ }
+
+ @Override
+ public void saveCurrentPosition(SharedPreferences pref, int newPosition) {
+ position = newPosition;
+ DBWriter.setFeedMediaPlaybackInformation(PodcastApp.getInstance(), this);
+ }
+
+ @Override
+ public void onPlaybackStart() {
+ }
+
+ @Override
+ public void onPlaybackCompleted() {
+
+ }
+
+ @Override
+ public int getPlayableType() {
+ return PLAYABLE_TYPE_FEEDMEDIA;
+ }
+
+ @Override
+ public void setChapters(List<Chapter> chapters) {
+ getItem().setChapters(chapters);
+ }
+
+ @Override
+ public Callable<String> loadShownotes() {
+ return new Callable<String>() {
+ @Override
+ public String call() throws Exception {
+ if (item == null) {
+ item = DBReader.getFeedItem(PodcastApp.getInstance(), itemID);
+ }
+ if (item.getContentEncoded() == null || item.getDescription() == null) {
+ DBReader.loadExtraInformationOfFeedItem(PodcastApp.getInstance(), item);
+
+ }
+ return (item.getContentEncoded() != null) ? item.getContentEncoded() : item.getDescription();
+ }
+ };
+ }
+
+ public static final Parcelable.Creator<FeedMedia> CREATOR = new Parcelable.Creator<FeedMedia>() {
+ public FeedMedia createFromParcel(Parcel in) {
+ final long id = in.readLong();
+ final long itemID = in.readLong();
+ FeedMedia result = new FeedMedia(id, null, in.readInt(), in.readInt(), in.readLong(), in.readString(), in.readString(),
+ in.readString(), in.readByte() != 0, new Date(in.readLong()));
+ result.itemID = itemID;
+ return result;
+ }
+
+ public FeedMedia[] newArray(int size) {
+ return new FeedMedia[size];
+ }
+ };
+
+ @Override
+ public InputStream openImageInputStream() {
+ InputStream out = new Playable.DefaultPlayableImageLoader(this)
+ .openImageInputStream();
+ if (out == null) {
+ if (item.getFeed().getImage() != null) {
+ return item.getFeed().getImage().openImageInputStream();
+ }
+ }
+ return out;
+ }
+
+ @Override
+ public String getImageLoaderCacheKey() {
+ String out = new Playable.DefaultPlayableImageLoader(this)
+ .getImageLoaderCacheKey();
+ if (out == null) {
+ if (item.getFeed().getImage() != null) {
+ return item.getFeed().getImage().getImageLoaderCacheKey();
+ }
+ }
+ return out;
+ }
+
+ @Override
+ public InputStream reopenImageInputStream(InputStream input) {
+ if (input instanceof FileInputStream) {
+ return item.getFeed().getImage().reopenImageInputStream(input);
+ } else {
+ return new Playable.DefaultPlayableImageLoader(this)
+ .reopenImageInputStream(input);
+ }
+ }
}
diff --git a/src/de/danoeh/antennapod/feed/FeedSearcher.java b/src/de/danoeh/antennapod/feed/FeedSearcher.java
deleted file mode 100644
index ab7c174bc..000000000
--- a/src/de/danoeh/antennapod/feed/FeedSearcher.java
+++ /dev/null
@@ -1,253 +0,0 @@
-package de.danoeh.antennapod.feed;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.os.Looper;
-import android.util.Log;
-import de.danoeh.antennapod.AppConfig;
-import de.danoeh.antennapod.PodcastApp;
-import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.storage.PodDBAdapter;
-import de.danoeh.antennapod.util.comparator.SearchResultValueComparator;
-
-/** Performs search on Feeds and FeedItems */
-public class FeedSearcher {
- private static final String TAG = "FeedSearcher";
-
- // Search result values
- private static final int VALUE_FEED_TITLE = 3;
- private static final int VALUE_ITEM_TITLE = 2;
- private static final int VALUE_ITEM_CHAPTER = 1;
- private static final int VALUE_ITEM_DESCRIPTION = 0;
- private static final int VALUE_WORD_MATCH = 4;
-
- /** Performs a search in all feeds or one specific feed. */
- public static ArrayList<SearchResult> performSearch(final Context context,
- final String query, final Feed selectedFeed) {
- final String lcQuery = query.toLowerCase();
- final ArrayList<SearchResult> result = new ArrayList<SearchResult>();
- if (selectedFeed == null) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Performing global search");
- if (AppConfig.DEBUG)
- Log.d(TAG, "Searching Feed titles");
- searchFeedtitles(lcQuery, result);
- } else if (AppConfig.DEBUG) {
- Log.d(TAG, "Performing search on specific feed");
- }
-
- if (AppConfig.DEBUG)
- Log.d(TAG, "Searching Feeditem titles");
- searchFeedItemTitles(lcQuery, result, selectedFeed);
-
- if (AppConfig.DEBUG)
- Log.d(TAG, "Searching item-chaptertitles");
- searchFeedItemChapters(lcQuery, result, selectedFeed);
-
- final FeedManager manager = FeedManager.getInstance();
- Looper.prepare();
- manager.searchFeedItemDescription(context, selectedFeed, lcQuery,
- new FeedManager.QueryTaskCallback() {
-
- @Override
- public void handleResult(Cursor cResult) {
- searchFeedItemContentEncodedCursor(lcQuery, result,
- selectedFeed, cResult);
-
- }
-
- @Override
- public void onCompletion() {
- manager.searchFeedItemContentEncoded(context,
- selectedFeed, lcQuery,
- new FeedManager.QueryTaskCallback() {
-
- @Override
- public void handleResult(Cursor cResult) {
- searchFeedItemDescriptionCursor(
- lcQuery, result, selectedFeed,
- cResult);
- }
-
- @Override
- public void onCompletion() {
- Looper.myLooper().quit();
- }
- });
- }
- });
-
- Looper.loop();
- if (AppConfig.DEBUG)
- Log.d(TAG, "Sorting results");
- Collections.sort(result, new SearchResultValueComparator());
-
- return result;
- }
-
- private static void searchFeedtitles(String query,
- ArrayList<SearchResult> destination) {
- FeedManager manager = FeedManager.getInstance();
- for (Feed feed : manager.getFeeds()) {
- SearchResult result = createSearchResult(feed, query, feed
- .getTitle().toLowerCase(), VALUE_FEED_TITLE);
- if (result != null) {
- destination.add(result);
- }
- }
- }
-
- private static void searchFeedItemTitles(String query,
- ArrayList<SearchResult> destination, Feed selectedFeed) {
- FeedManager manager = FeedManager.getInstance();
- if (selectedFeed == null) {
- for (Feed feed : manager.getFeeds()) {
- searchFeedItemTitlesSingleFeed(query, destination, feed);
- }
- } else {
- searchFeedItemTitlesSingleFeed(query, destination, selectedFeed);
- }
- }
-
- private static void searchFeedItemTitlesSingleFeed(String query,
- ArrayList<SearchResult> destination, Feed feed) {
- for (FeedItem item : feed.getItems()) {
- SearchResult result = createSearchResult(item, query, item
- .getTitle().toLowerCase(), VALUE_ITEM_TITLE);
- if (result != null) {
- result.setSubtitle(PodcastApp.getInstance().getString(
- R.string.found_in_title_label));
- destination.add(result);
- }
-
- }
- }
-
- private static void searchFeedItemChapters(String query,
- ArrayList<SearchResult> destination, Feed selectedFeed) {
- FeedManager manager = FeedManager.getInstance();
- if (selectedFeed == null) {
- for (Feed feed : manager.getFeeds()) {
- searchFeedItemChaptersSingleFeed(query, destination, feed);
- }
- } else {
- searchFeedItemChaptersSingleFeed(query, destination, selectedFeed);
- }
- }
-
- private static void searchFeedItemChaptersSingleFeed(String query,
- ArrayList<SearchResult> destination, Feed feed) {
- for (FeedItem item : feed.getItems()) {
- if (item.getChapters() != null) {
- for (Chapter sc : item.getChapters()) {
- SearchResult result = createSearchResult(item, query, sc
- .getTitle().toLowerCase(), VALUE_ITEM_CHAPTER);
- if (result != null) {
- result.setSubtitle(PodcastApp.getInstance().getString(
- R.string.found_in_chapters_label));
- destination.add(result);
- }
- }
- }
- }
- }
-
- private static void searchFeedItemDescriptionCursor(String query,
- ArrayList<SearchResult> destination, Feed feed, Cursor cursor) {
- FeedManager manager = FeedManager.getInstance();
- if (cursor.moveToFirst()) {
- do {
- final long itemId = cursor
- .getLong(PodDBAdapter.IDX_FI_EXTRA_ID);
- String content = cursor
- .getString(PodDBAdapter.IDX_FI_EXTRA_DESCRIPTION);
- if (content != null) {
- content = content.toLowerCase();
- final long feedId = cursor
- .getLong(PodDBAdapter.IDX_FI_EXTRA_FEED);
- FeedItem item = null;
- if (feed == null) {
- item = manager.getFeedItem(itemId, feedId);
- } else {
- item = manager.getFeedItem(itemId, feed);
- }
- if (item != null) {
- SearchResult searchResult = createSearchResult(item,
- query, content, VALUE_ITEM_DESCRIPTION);
- if (searchResult != null) {
- searchResult.setSubtitle(PodcastApp.getInstance()
- .getString(
- R.string.found_in_shownotes_label));
- destination.add(searchResult);
-
- }
- }
- }
-
- } while (cursor.moveToNext());
- }
- }
-
- private static void searchFeedItemContentEncodedCursor(String query,
- ArrayList<SearchResult> destination, Feed feed, Cursor cursor) {
- FeedManager manager = FeedManager.getInstance();
- if (cursor.moveToFirst()) {
- do {
- final long itemId = cursor
- .getLong(PodDBAdapter.IDX_FI_EXTRA_ID);
- String content = cursor
- .getString(PodDBAdapter.IDX_FI_EXTRA_CONTENT_ENCODED);
- if (content != null) {
- content = content.toLowerCase();
-
- final long feedId = cursor
- .getLong(PodDBAdapter.IDX_FI_EXTRA_FEED);
- FeedItem item = null;
- if (feed == null) {
- item = manager.getFeedItem(itemId, feedId);
- } else {
- item = manager.getFeedItem(itemId, feed);
- }
- if (item != null) {
- SearchResult searchResult = createSearchResult(item,
- query, content, VALUE_ITEM_DESCRIPTION);
- if (searchResult != null) {
- searchResult.setSubtitle(PodcastApp.getInstance()
- .getString(
- R.string.found_in_shownotes_label));
- destination.add(searchResult);
- }
- }
- }
- } while (cursor.moveToNext());
- }
- }
-
- private static SearchResult createSearchResult(FeedComponent component,
- String query, String text, int baseValue) {
- int bonus = 0;
- boolean found = false;
- // try word search
- Pattern word = Pattern.compile("\b" + query + "\b");
- Matcher matcher = word.matcher(text);
- found = matcher.find();
- if (found) {
- bonus = VALUE_WORD_MATCH;
- } else {
- // search for other occurence
- found = text.contains(query);
- }
-
- if (found) {
- return new SearchResult(component, baseValue + bonus);
- } else {
- return null;
- }
- }
-
-}
diff --git a/src/de/danoeh/antennapod/feed/SearchResult.java b/src/de/danoeh/antennapod/feed/SearchResult.java
index b4016f2e8..1cba389ec 100644
--- a/src/de/danoeh/antennapod/feed/SearchResult.java
+++ b/src/de/danoeh/antennapod/feed/SearchResult.java
@@ -7,10 +7,11 @@ public class SearchResult {
/** Higher value means more importance */
private int value;
- public SearchResult(FeedComponent component, int value) {
+ public SearchResult(FeedComponent component, int value, String subtitle) {
super();
this.component = component;
this.value = value;
+ this.subtitle = subtitle;
}
public FeedComponent getComponent() {
diff --git a/src/de/danoeh/antennapod/fragment/CoverFragment.java b/src/de/danoeh/antennapod/fragment/CoverFragment.java
index 6be76f515..791315719 100644
--- a/src/de/danoeh/antennapod/fragment/CoverFragment.java
+++ b/src/de/danoeh/antennapod/fragment/CoverFragment.java
@@ -1,14 +1,13 @@
package de.danoeh.antennapod.fragment;
import android.os.Bundle;
+import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
-import com.actionbarsherlock.app.SherlockFragment;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.AudioplayerActivity.AudioplayerContentFragment;
@@ -16,7 +15,7 @@ import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.util.playback.Playable;
/** Displays the cover and the title of a FeedItem. */
-public class CoverFragment extends SherlockFragment implements
+public class CoverFragment extends Fragment implements
AudioplayerContentFragment {
private static final String TAG = "CoverFragment";
private static final String ARG_PLAYABLE = "arg.playable";
diff --git a/src/de/danoeh/antennapod/fragment/EpisodesFragment.java b/src/de/danoeh/antennapod/fragment/EpisodesFragment.java
index 4039235e0..a99056c9a 100644
--- a/src/de/danoeh/antennapod/fragment/EpisodesFragment.java
+++ b/src/de/danoeh/antennapod/fragment/EpisodesFragment.java
@@ -1,20 +1,16 @@
package de.danoeh.antennapod.fragment;
+import android.content.Context;
import android.content.Intent;
+import android.os.AsyncTask;
import android.os.Bundle;
+import android.support.v4.app.Fragment;
import android.util.Log;
-import android.view.ContextMenu;
+import android.view.*;
import android.view.ContextMenu.ContextMenuInfo;
-import android.view.LayoutInflater;
-import android.view.MenuInflater;
-import android.view.View;
-import android.view.ViewGroup;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnChildClickListener;
-import com.actionbarsherlock.app.SherlockFragment;
-import com.actionbarsherlock.view.Menu;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.ItemviewActivity;
@@ -24,11 +20,16 @@ import de.danoeh.antennapod.adapter.ExternalEpisodesListAdapter;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
+import de.danoeh.antennapod.storage.DBReader;
+import de.danoeh.antennapod.storage.DBTasks;
+import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.storage.DownloadRequestException;
+import de.danoeh.antennapod.util.QueueAccess;
import de.danoeh.antennapod.util.menuhandler.FeedItemMenuHandler;
-public class EpisodesFragment extends SherlockFragment {
+import java.util.List;
+
+public class EpisodesFragment extends Fragment {
private static final String TAG = "EpisodesFragment";
private static final int EVENTS = EventDistributor.QUEUE_UPDATE
@@ -40,6 +41,9 @@ public class EpisodesFragment extends SherlockFragment {
private ExpandableListView listView;
private ExternalEpisodesListAdapter adapter;
+ private List<FeedItem> queue;
+ private List<FeedItem> unreadItems;
+
protected FeedItem selectedItem = null;
protected long selectedGroupId = -1;
protected boolean contextMenuClosed = true;
@@ -92,7 +96,7 @@ public class EpisodesFragment extends SherlockFragment {
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
adapter = new ExternalEpisodesListAdapter(getActivity(),
- adapterCallback, groupActionCallback);
+ adapterCallback, groupActionCallback, itemAccess);
listView.setAdapter(adapter);
listView.expandGroup(ExternalEpisodesListAdapter.GROUP_POS_QUEUE);
listView.expandGroup(ExternalEpisodesListAdapter.GROUP_POS_UNREAD);
@@ -117,9 +121,73 @@ public class EpisodesFragment extends SherlockFragment {
return true;
}
});
+ loadData();
registerForContextMenu(listView);
+
}
+ ExternalEpisodesListAdapter.ItemAccess itemAccess = new ExternalEpisodesListAdapter.ItemAccess() {
+
+ @Override
+ public int getQueueSize() {
+ return (queue != null) ? queue.size() : 0;
+ }
+
+ @Override
+ public int getUnreadItemsSize() {
+ return (unreadItems != null) ? unreadItems.size() : 0;
+ }
+
+ @Override
+ public FeedItem getQueueItemAt(int position) {
+ return (queue != null) ? queue.get(position) : null;
+ }
+
+ @Override
+ public FeedItem getUnreadItemAt(int position) {
+ return (unreadItems != null) ? unreadItems.get(position) : null;
+ }
+ };
+
+ private void loadData() {
+ AsyncTask<Void, Void, Void> loadTask = new AsyncTask<Void, Void, Void>() {
+ private volatile List<FeedItem> queueRef;
+ private volatile List<FeedItem> unreadItemsRef;
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ if (AppConfig.DEBUG) Log.d(TAG, "Starting to load list data");
+ Context context = EpisodesFragment.this.getActivity();
+ if (context != null) {
+ queueRef = DBReader.getQueue(context);
+ unreadItemsRef = DBReader.getUnreadItemsList(context);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ super.onPostExecute(aVoid);
+ if (queueRef != null && unreadItemsRef != null) {
+ if (AppConfig.DEBUG) Log.d(TAG, "Done loading list data");
+ queue = queueRef;
+ unreadItems = unreadItemsRef;
+ if (adapter != null) {
+ adapter.notifyDataSetChanged();
+ }
+ } else {
+ if (queueRef == null) {
+ Log.e(TAG, "Could not load queue");
+ }
+ if (unreadItemsRef == null) {
+ Log.e(TAG, "Could not load unread items");
+ }
+ }
+ }
+ };
+ loadTask.execute();
+ }
+
private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
@Override
@@ -127,7 +195,7 @@ public class EpisodesFragment extends SherlockFragment {
if ((EVENTS & arg) != 0) {
if (AppConfig.DEBUG)
Log.d(TAG, "Received contentUpdate Intent.");
- adapter.notifyDataSetChanged();
+ loadData();
}
}
};
@@ -146,13 +214,13 @@ public class EpisodesFragment extends SherlockFragment {
menu.setHeaderTitle(selectedItem.getTitle());
FeedItemMenuHandler.onPrepareMenu(
- new FeedItemMenuHandler.MenuInterface() {
+ new FeedItemMenuHandler.MenuInterface() {
- @Override
- public void setItemVisibility(int id, boolean visible) {
- menu.findItem(id).setVisible(visible);
- }
- }, selectedItem, false);
+ @Override
+ public void setItemVisibility(int id, boolean visible) {
+ menu.findItem(id).setVisible(visible);
+ }
+ }, selectedItem, false, QueueAccess.ItemListAccess(queue));
} else if (selectedGroupId == ExternalEpisodesListAdapter.GROUP_POS_QUEUE) {
menu.add(Menu.NONE, R.id.organize_queue_item, Menu.NONE,
@@ -172,11 +240,10 @@ public class EpisodesFragment extends SherlockFragment {
@Override
public boolean onContextItemSelected(android.view.MenuItem item) {
boolean handled = false;
- FeedManager manager = FeedManager.getInstance();
if (selectedItem != null) {
try {
handled = FeedItemMenuHandler.onMenuItemClicked(
- getSherlockActivity(), item.getItemId(), selectedItem);
+ getActivity(), item.getItemId(), selectedItem);
} catch (DownloadRequestException e) {
e.printStackTrace();
DownloadRequestErrorDialogCreator.newRequestErrorDialog(
@@ -191,10 +258,10 @@ public class EpisodesFragment extends SherlockFragment {
OrganizeQueueActivity.class));
break;
case R.id.clear_queue_item:
- manager.clearQueue(getActivity());
+ DBWriter.clearQueue(getActivity());
break;
case R.id.download_all_item:
- manager.downloadAllItemsInQueue(getActivity());
+ DBTasks.downloadAllItemsInQueue(getActivity());
break;
default:
handled = false;
@@ -203,10 +270,10 @@ public class EpisodesFragment extends SherlockFragment {
handled = true;
switch (item.getItemId()) {
case R.id.mark_all_read_item:
- manager.markAllItemsRead(getActivity());
+ DBWriter.markAllItemsRead(getActivity());
break;
case R.id.enqueue_all_item:
- manager.enqueueAllNewItems(getActivity());
+ DBTasks.enqueueAllNewItems(getActivity());
break;
default:
handled = false;
diff --git a/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java b/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java
index e46017328..933263d7d 100644
--- a/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java
+++ b/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java
@@ -1,6 +1,7 @@
package de.danoeh.antennapod.fragment;
import android.os.Bundle;
+import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -10,8 +11,6 @@ import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
-import com.actionbarsherlock.app.SherlockFragment;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.ImageLoader;
@@ -24,7 +23,7 @@ import de.danoeh.antennapod.util.playback.PlaybackController;
* Fragment which is supposed to be displayed outside of the MediaplayerActivity
* if the PlaybackService is running
*/
-public class ExternalPlayerFragment extends SherlockFragment {
+public class ExternalPlayerFragment extends Fragment {
private static final String TAG = "ExternalPlayerFragment";
private ViewGroup fragmentLayout;
diff --git a/src/de/danoeh/antennapod/fragment/FeedlistFragment.java b/src/de/danoeh/antennapod/fragment/FeedlistFragment.java
index c3034c2af..0e06e546e 100644
--- a/src/de/danoeh/antennapod/fragment/FeedlistFragment.java
+++ b/src/de/danoeh/antennapod/fragment/FeedlistFragment.java
@@ -1,23 +1,19 @@
package de.danoeh.antennapod.fragment;
+import java.util.List;
+
import android.annotation.SuppressLint;
-import android.app.Activity;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.os.AsyncTask;
import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v7.app.ActionBarActivity;
+import android.support.v7.view.ActionMode;
import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.GridView;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import com.actionbarsherlock.app.SherlockFragment;
-import com.actionbarsherlock.view.ActionMode;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuItem;
+import android.view.*;
+import android.widget.*;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
@@ -28,201 +24,270 @@ import de.danoeh.antennapod.dialog.ConfirmationDialog;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.Feed;
-import de.danoeh.antennapod.feed.FeedManager;
+import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DownloadRequestException;
+import de.danoeh.antennapod.storage.FeedItemStatistics;
import de.danoeh.antennapod.util.menuhandler.FeedMenuHandler;
-public class FeedlistFragment extends SherlockFragment implements
- ActionMode.Callback, AdapterView.OnItemClickListener,
- AdapterView.OnItemLongClickListener {
- private static final String TAG = "FeedlistFragment";
-
- private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED
- | EventDistributor.DOWNLOAD_QUEUED
- | EventDistributor.FEED_LIST_UPDATE
- | EventDistributor.UNREAD_ITEMS_UPDATE;
-
- public static final String EXTRA_SELECTED_FEED = "extra.de.danoeh.antennapod.activity.selected_feed";
-
- private FeedManager manager;
- private FeedlistAdapter fla;
-
- private Feed selectedFeed;
- private ActionMode mActionMode;
-
- private GridView gridView;
- private ListView listView;
- private TextView txtvEmpty;
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Creating");
- manager = FeedManager.getInstance();
- fla = new FeedlistAdapter(getActivity());
-
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View result = inflater.inflate(R.layout.feedlist, container, false);
- listView = (ListView) result.findViewById(android.R.id.list);
- gridView = (GridView) result.findViewById(R.id.grid);
- txtvEmpty = (TextView) result.findViewById(android.R.id.empty);
-
- return result;
-
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- if (listView != null) {
- listView.setOnItemClickListener(this);
- listView.setOnItemLongClickListener(this);
- listView.setAdapter(fla);
- listView.setEmptyView(txtvEmpty);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Using ListView");
- } else {
- gridView.setOnItemClickListener(this);
- gridView.setOnItemLongClickListener(this);
- gridView.setAdapter(fla);
- gridView.setEmptyView(txtvEmpty);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Using GridView");
- }
- }
-
- @Override
- public void onResume() {
- super.onResume();
- if (AppConfig.DEBUG)
- Log.d(TAG, "Resuming");
- EventDistributor.getInstance().register(contentUpdate);
- fla.notifyDataSetChanged();
- }
-
- @Override
- public void onPause() {
- super.onPause();
- EventDistributor.getInstance().unregister(contentUpdate);
- if (mActionMode != null) {
- mActionMode.finish();
- }
- }
-
- private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
-
- @Override
- public void update(EventDistributor eventDistributor, Integer arg) {
- if ((EVENTS & arg) != 0) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Received contentUpdate Intent.");
- fla.notifyDataSetChanged();
- }
- }
- };
-
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- FeedMenuHandler.onCreateOptionsMenu(mode.getMenuInflater(), menu);
- mode.setTitle(selectedFeed.getTitle());
- return true;
- }
-
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- return FeedMenuHandler.onPrepareOptionsMenu(menu, selectedFeed);
- }
-
- @SuppressLint("NewApi")
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- try {
- if (FeedMenuHandler.onOptionsItemClicked(getSherlockActivity(),
- item, selectedFeed)) {
- fla.notifyDataSetChanged();
- } else {
- switch (item.getItemId()) {
- case R.id.remove_item:
- final FeedRemover remover = new FeedRemover(
- getSherlockActivity(), selectedFeed) {
- @Override
- protected void onPostExecute(Void result) {
- super.onPostExecute(result);
- fla.notifyDataSetChanged();
- }
- };
- ConfirmationDialog conDialog = new ConfirmationDialog(
- getActivity(), R.string.remove_feed_label,
- R.string.feed_delete_confirmation_msg) {
-
- @Override
- public void onConfirmButtonPressed(
- DialogInterface dialog) {
- dialog.dismiss();
- remover.executeAsync();
- }
- };
- conDialog.createNewDialog().show();
- break;
- }
- }
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- DownloadRequestErrorDialogCreator.newRequestErrorDialog(
- getActivity(), e.getMessage());
- }
- mode.finish();
- return true;
- }
-
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- mActionMode = null;
- selectedFeed = null;
- fla.setSelectedItemIndex(FeedlistAdapter.SELECTION_NONE);
- }
-
- @Override
- public void onItemClick(AdapterView<?> arg0, View arg1, int position,
- long id) {
- Feed selection = fla.getItem(position);
- Intent showFeed = new Intent(getActivity(), FeedItemlistActivity.class);
- showFeed.putExtra(EXTRA_SELECTED_FEED, selection.getId());
-
- getActivity().startActivity(showFeed);
- }
-
- @Override
- public boolean onItemLongClick(AdapterView<?> parent, View view,
- int position, long id) {
- Feed selection = fla.getItem(position);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Selected Feed with title " + selection.getTitle());
- if (selection != null) {
- if (mActionMode != null) {
- mActionMode.finish();
- }
- fla.setSelectedItemIndex(position);
- selectedFeed = selection;
- mActionMode = getSherlockActivity().startActionMode(
- FeedlistFragment.this);
-
- }
- return true;
- }
+public class FeedlistFragment extends Fragment implements
+ ActionMode.Callback, AdapterView.OnItemClickListener,
+ AdapterView.OnItemLongClickListener {
+ private static final String TAG = "FeedlistFragment";
+
+ private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED
+ | EventDistributor.DOWNLOAD_QUEUED
+ | EventDistributor.FEED_LIST_UPDATE
+ | EventDistributor.UNREAD_ITEMS_UPDATE;
+
+ public static final String EXTRA_SELECTED_FEED = "extra.de.danoeh.antennapod.activity.selected_feed";
+
+ private FeedlistAdapter fla;
+ private List<Feed> feeds;
+ private List<FeedItemStatistics> feedItemStatistics;
+
+ private Feed selectedFeed;
+ private ActionMode mActionMode;
+
+ private GridView gridView;
+ private ListView listView;
+ private TextView emptyView;
+
+ private FeedlistAdapter.ItemAccess itemAccess = new FeedlistAdapter.ItemAccess() {
+
+ @Override
+ public Feed getItem(int position) {
+ if (feeds != null) {
+ return feeds.get(position);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public FeedItemStatistics getFeedItemStatistics(int position) {
+ if (feedItemStatistics != null && position < feedItemStatistics.size()) {
+ return feedItemStatistics.get(position);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public int getCount() {
+ if (feeds != null) {
+ return feeds.size();
+ } else {
+ return 0;
+ }
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Creating");
+ fla = new FeedlistAdapter(getActivity(), itemAccess);
+ loadFeeds();
+ }
+
+ private void loadFeeds() {
+ AsyncTask<Void, Void, List[]> loadTask = new AsyncTask<Void, Void, List[]>() {
+ @Override
+ protected List[] doInBackground(Void... params) {
+ Context context = getActivity();
+ if (context != null) {
+ return new List[]{DBReader.getFeedList(context),
+ DBReader.getFeedStatisticsList(context)};
+ } else {
+ return null;
+ }
+ }
+
+
+ @Override
+ protected void onPostExecute(List[] result) {
+ super.onPostExecute(result);
+ if (result != null) {
+ feeds = result[0];
+ feedItemStatistics = result[1];
+ setEmptyViewIfListIsEmpty();
+ if (fla != null) {
+ fla.notifyDataSetChanged();
+ }
+ } else {
+ Log.e(TAG, "Failed to load feeds");
+ }
+ }
+ };
+ loadTask.execute();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View result = inflater.inflate(R.layout.feedlist, container, false);
+ listView = (ListView) result.findViewById(android.R.id.list);
+ gridView = (GridView) result.findViewById(R.id.grid);
+ emptyView = (TextView) result.findViewById(android.R.id.empty);
+
+ return result;
+
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ if (listView != null) {
+ listView.setOnItemClickListener(this);
+ listView.setOnItemLongClickListener(this);
+ listView.setAdapter(fla);
+ listView.setEmptyView(emptyView);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Using ListView");
+ } else {
+ gridView.setOnItemClickListener(this);
+ gridView.setOnItemLongClickListener(this);
+ gridView.setAdapter(fla);
+ gridView.setEmptyView(emptyView);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Using GridView");
+ }
+ setEmptyViewIfListIsEmpty();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Resuming");
+ EventDistributor.getInstance().register(contentUpdate);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ EventDistributor.getInstance().unregister(contentUpdate);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ if (mActionMode != null) {
+ mActionMode.finish();
+ }
+ }
+
+ private EventDistributor.EventListener contentUpdate = new EventDistributor.EventListener() {
+
+ @Override
+ public void update(EventDistributor eventDistributor, Integer arg) {
+ if ((EVENTS & arg) != 0) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Received contentUpdate Intent.");
+ loadFeeds();
+ }
+ }
+ };
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ FeedMenuHandler.onCreateOptionsMenu(mode.getMenuInflater(), menu);
+ mode.setTitle(selectedFeed.getTitle());
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return FeedMenuHandler.onPrepareOptionsMenu(menu, selectedFeed);
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ try {
+ if (FeedMenuHandler.onOptionsItemClicked(getActivity(),
+ item, selectedFeed)) {
+ loadFeeds();
+ } else {
+ switch (item.getItemId()) {
+ case R.id.remove_item:
+ final FeedRemover remover = new FeedRemover(
+ getActivity(), selectedFeed) {
+ @Override
+ protected void onPostExecute(Void result) {
+ super.onPostExecute(result);
+ loadFeeds();
+ }
+ };
+ ConfirmationDialog conDialog = new ConfirmationDialog(
+ getActivity(), R.string.remove_feed_label,
+ R.string.feed_delete_confirmation_msg) {
+
+ @Override
+ public void onConfirmButtonPressed(
+ DialogInterface dialog) {
+ dialog.dismiss();
+ remover.executeAsync();
+ }
+ };
+ conDialog.createNewDialog().show();
+ break;
+ }
+ }
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ DownloadRequestErrorDialogCreator.newRequestErrorDialog(
+ getActivity(), e.getMessage());
+ }
+ mode.finish();
+ return true;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ mActionMode = null;
+ selectedFeed = null;
+ fla.setSelectedItemIndex(FeedlistAdapter.SELECTION_NONE);
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> arg0, View arg1, int position,
+ long id) {
+ Feed selection = fla.getItem(position);
+ Intent showFeed = new Intent(getActivity(), FeedItemlistActivity.class);
+ showFeed.putExtra(EXTRA_SELECTED_FEED, selection.getId());
+
+ getActivity().startActivity(showFeed);
+ }
+
+ @Override
+ public boolean onItemLongClick(AdapterView<?> parent, View view,
+ int position, long id) {
+ Feed selection = fla.getItem(position);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Selected Feed with title " + selection.getTitle());
+ if (selection != null) {
+ if (mActionMode != null) {
+ mActionMode.finish();
+ }
+ fla.setSelectedItemIndex(position);
+ selectedFeed = selection;
+ mActionMode = ((ActionBarActivity) getActivity()).startSupportActionMode(FeedlistFragment.this);
+
+ }
+ return true;
+ }
+
+ private AbsListView getMainView() {
+ return (listView != null) ? listView : gridView;
+ }
+
+ private void setEmptyViewIfListIsEmpty() {
+ if (getMainView() != null && emptyView != null && feeds != null) {
+ if (feeds.isEmpty()) {
+ emptyView.setText(R.string.no_feeds_label);
+ }
+ }
+ }
}
diff --git a/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java b/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java
index 10f43718f..9183180c1 100644
--- a/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java
+++ b/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java
@@ -1,5 +1,10 @@
package de.danoeh.antennapod.fragment;
+import android.support.v4.app.Fragment;
+import android.support.v7.app.ActionBarActivity;
+import de.danoeh.antennapod.feed.FeedItem;
+import de.danoeh.antennapod.storage.DBReader;
+import de.danoeh.antennapod.util.ShownotesProvider;
import org.apache.commons.lang3.StringEscapeUtils;
import android.annotation.SuppressLint;
@@ -27,442 +32,424 @@ import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;
-import com.actionbarsherlock.app.SherlockFragment;
-
import de.danoeh.antennapod.AppConfig;
-import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.util.ShareUtils;
import de.danoeh.antennapod.util.playback.Playable;
+import java.util.concurrent.Callable;
+
/** Displays the description of a Playable object in a Webview. */
-public class ItemDescriptionFragment extends SherlockFragment {
-
- private static final String TAG = "ItemDescriptionFragment";
-
- private static final String PREF = "ItemDescriptionFragmentPrefs";
- private static final String PREF_SCROLL_Y = "prefScrollY";
- private static final String PREF_PLAYABLE_ID = "prefPlayableId";
-
- private static final String ARG_PLAYABLE = "arg.playable";
-
- private static final String ARG_FEED_ID = "arg.feedId";
- private static final String ARG_FEED_ITEM_ID = "arg.feeditemId";
- private static final String ARG_SAVE_STATE = "arg.saveState";
-
- private WebView webvDescription;
- private Playable media;
-
- private FeedItem item;
-
- private AsyncTask<Void, Void, Void> webViewLoader;
-
- private String shownotes;
-
- /** URL that was selected via long-press. */
- private String selectedURL;
-
- /**
- * True if Fragment should save its state (e.g. scrolling position) in a
- * shared preference.
- */
- private boolean saveState;
-
- public static ItemDescriptionFragment newInstance(Playable media,
- boolean saveState) {
- ItemDescriptionFragment f = new ItemDescriptionFragment();
- Bundle args = new Bundle();
- args.putParcelable(ARG_PLAYABLE, media);
- args.putBoolean(ARG_SAVE_STATE, saveState);
- f.setArguments(args);
- return f;
- }
-
- public static ItemDescriptionFragment newInstance(FeedItem item,
- boolean saveState) {
- ItemDescriptionFragment f = new ItemDescriptionFragment();
- Bundle args = new Bundle();
- args.putLong(ARG_FEED_ID, item.getFeed().getId());
- args.putLong(ARG_FEED_ITEM_ID, item.getId());
- args.putBoolean(ARG_SAVE_STATE, saveState);
- f.setArguments(args);
- return f;
- }
-
- @SuppressLint("NewApi")
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Creating view");
- webvDescription = new WebView(getActivity());
- if (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) {
- if (Build.VERSION.SDK_INT >= 11
- && Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
- webvDescription.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
- }
- webvDescription.setBackgroundColor(getResources().getColor(
- R.color.black));
- }
- webvDescription.getSettings().setUseWideViewPort(false);
- webvDescription.getSettings().setLayoutAlgorithm(
- LayoutAlgorithm.NARROW_COLUMNS);
- webvDescription.getSettings().setLoadWithOverviewMode(true);
- webvDescription.setOnLongClickListener(webViewLongClickListener);
- webvDescription.setWebViewClient(new WebViewClient() {
-
- @Override
- public boolean shouldOverrideUrlLoading(WebView view, String url) {
- Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
- startActivity(intent);
- return true;
- }
-
- @Override
- public void onPageFinished(WebView view, String url) {
- super.onPageFinished(view, url);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Page finished");
- // Restoring the scroll position might not always work
- view.postDelayed(new Runnable() {
-
- @Override
- public void run() {
- restoreFromPreference();
- }
-
- }, 50);
- }
-
- });
- registerForContextMenu(webvDescription);
- return webvDescription;
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Fragment attached");
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
- if (AppConfig.DEBUG)
- Log.d(TAG, "Fragment detached");
- if (webViewLoader != null) {
- webViewLoader.cancel(true);
- }
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (AppConfig.DEBUG)
- Log.d(TAG, "Fragment destroyed");
- if (webViewLoader != null) {
- webViewLoader.cancel(true);
- }
- }
-
- @SuppressLint("NewApi")
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Creating fragment");
- Bundle args = getArguments();
- saveState = args.getBoolean(ARG_SAVE_STATE, false);
- if (args.containsKey(ARG_PLAYABLE)) {
- media = args.getParcelable(ARG_PLAYABLE);
- } else if (args.containsKey(ARG_FEED_ID)
- && args.containsKey(ARG_FEED_ITEM_ID)) {
- long feedId = args.getLong(ARG_FEED_ID);
- long itemId = args.getLong(ARG_FEED_ITEM_ID);
- FeedItem f = FeedManager.getInstance().getFeedItem(itemId, feedId);
- if (f != null) {
- item = f;
- }
- }
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- if (media != null) {
- media.loadShownotes(new Playable.ShownoteLoaderCallback() {
-
- @Override
- public void onShownotesLoaded(String shownotes) {
- ItemDescriptionFragment.this.shownotes = shownotes;
- if (ItemDescriptionFragment.this.shownotes != null) {
- startLoader();
- }
- }
- });
- } else if (item != null) {
- if (item.getDescription() == null
- || item.getContentEncoded() == null) {
- FeedManager.getInstance().loadExtraInformationOfItem(
- PodcastApp.getInstance(), item,
- new FeedManager.TaskCallback<String[]>() {
- @Override
- public void onCompletion(String[] result) {
- if (result[1] != null) {
- shownotes = result[1];
- } else {
- shownotes = result[0];
- }
- if (shownotes != null) {
- startLoader();
- }
-
- }
- });
- } else {
- shownotes = item.getContentEncoded();
- startLoader();
- }
- } else {
- Log.e(TAG, "Error in onViewCreated: Item and media were null");
- }
- }
-
- @Override
- public void onResume() {
- super.onResume();
- }
-
- @SuppressLint("NewApi")
- private void startLoader() {
- webViewLoader = createLoader();
- if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
- webViewLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- } else {
- webViewLoader.execute();
- }
- }
-
- /**
- * Return the CSS style of the Webview.
- *
- * @param textColor
- * the default color to use for the text in the webview. This
- * value is inserted directly into the CSS String.
- * */
- private String applyWebviewStyle(String textColor, String data) {
- final String WEBVIEW_STYLE = "<html><head><style type=\"text/css\"> * { color: %s; font-family: Helvetica; line-height: 1.5em; font-size: 11pt; } a { font-style: normal; text-decoration: none; font-weight: normal; color: #00A8DF; } img { display: block; margin: 10 auto; max-width: %s; height: auto; } body { margin: %dpx %dpx %dpx %dpx; }</style></head><body>%s</body></html>";
- final int pageMargin = (int) TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP, 8, getResources()
- .getDisplayMetrics());
- return String.format(WEBVIEW_STYLE, textColor, "100%", pageMargin,
- pageMargin, pageMargin, pageMargin, data);
- }
-
- private View.OnLongClickListener webViewLongClickListener = new View.OnLongClickListener() {
-
- @Override
- public boolean onLongClick(View v) {
- WebView.HitTestResult r = webvDescription.getHitTestResult();
- if (r != null
- && r.getType() == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
- if (AppConfig.DEBUG)
- Log.d(TAG,
- "Link of webview was long-pressed. Extra: "
- + r.getExtra());
- selectedURL = r.getExtra();
- webvDescription.showContextMenu();
- return true;
- }
- selectedURL = null;
- return false;
- }
- };
-
- @SuppressWarnings("deprecation")
- @SuppressLint("NewApi")
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- boolean handled = selectedURL != null;
- if (selectedURL != null) {
- switch (item.getItemId()) {
- case R.id.open_in_browser_item:
- Uri uri = Uri.parse(selectedURL);
- getActivity()
- .startActivity(new Intent(Intent.ACTION_VIEW, uri));
- break;
- case R.id.share_url_item:
- ShareUtils.shareLink(getActivity(), selectedURL);
- break;
- case R.id.copy_url_item:
- if (android.os.Build.VERSION.SDK_INT >= 11) {
- ClipData clipData = ClipData.newPlainText(selectedURL,
- selectedURL);
- android.content.ClipboardManager cm = (android.content.ClipboardManager) getActivity()
- .getSystemService(Context.CLIPBOARD_SERVICE);
- cm.setPrimaryClip(clipData);
- } else {
- android.text.ClipboardManager cm = (android.text.ClipboardManager) getActivity()
- .getSystemService(Context.CLIPBOARD_SERVICE);
- cm.setText(selectedURL);
- }
- Toast t = Toast.makeText(getActivity(),
- R.string.copied_url_msg, Toast.LENGTH_SHORT);
- t.show();
- break;
- default:
- handled = false;
- break;
-
- }
- selectedURL = null;
- }
- return handled;
-
- }
-
- @Override
- public void onCreateContextMenu(ContextMenu menu, View v,
- ContextMenuInfo menuInfo) {
- if (selectedURL != null) {
- super.onCreateContextMenu(menu, v, menuInfo);
- menu.add(Menu.NONE, R.id.open_in_browser_item, Menu.NONE,
- R.string.open_in_browser_label);
- menu.add(Menu.NONE, R.id.copy_url_item, Menu.NONE,
- R.string.copy_url_label);
- menu.add(Menu.NONE, R.id.share_url_item, Menu.NONE,
- R.string.share_url_label);
- menu.setHeaderTitle(selectedURL);
- }
- }
-
- private AsyncTask<Void, Void, Void> createLoader() {
- return new AsyncTask<Void, Void, Void>() {
- @Override
- protected void onCancelled() {
- super.onCancelled();
- if (getSherlockActivity() != null) {
- getSherlockActivity()
- .setSupportProgressBarIndeterminateVisibility(false);
- }
- webViewLoader = null;
- }
-
- String data;
-
- @Override
- protected void onPostExecute(Void result) {
- super.onPostExecute(result);
- // /webvDescription.loadData(url, "text/html", "utf-8");
- webvDescription.loadDataWithBaseURL(null, data, "text/html",
- "utf-8", "about:blank");
- if (getSherlockActivity() != null) {
- getSherlockActivity()
- .setSupportProgressBarIndeterminateVisibility(false);
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "Webview loaded");
- webViewLoader = null;
- }
-
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- if (getSherlockActivity() != null) {
- getSherlockActivity()
- .setSupportProgressBarIndeterminateVisibility(true);
- }
- }
-
- @Override
- protected Void doInBackground(Void... params) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Loading Webview");
- data = "";
- data = StringEscapeUtils.unescapeHtml4(shownotes);
- Activity activity = getActivity();
- if (activity != null) {
- TypedArray res = getActivity()
- .getTheme()
- .obtainStyledAttributes(
- new int[] { android.R.attr.textColorPrimary });
- int colorResource = res.getColor(0, 0);
- String colorString = String.format("#%06X",
- 0xFFFFFF & colorResource);
- Log.i(TAG, "text color: " + colorString);
- res.recycle();
- data = applyWebviewStyle(colorString, data);
- } else {
- cancel(true);
- }
- return null;
- }
-
- };
- }
-
- @Override
- public void onPause() {
- super.onPause();
- savePreference();
- }
-
- private void savePreference() {
- if (saveState) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Saving preferences");
- SharedPreferences prefs = getActivity().getSharedPreferences(PREF,
- Activity.MODE_PRIVATE);
- SharedPreferences.Editor editor = prefs.edit();
- if (media != null && webvDescription != null) {
- if (AppConfig.DEBUG)
- Log.d(TAG,
- "Saving scroll position: "
- + webvDescription.getScrollY());
- editor.putInt(PREF_SCROLL_Y, webvDescription.getScrollY());
- editor.putString(PREF_PLAYABLE_ID, media.getIdentifier()
- .toString());
- } else {
- if (AppConfig.DEBUG)
- Log.d(TAG,
- "savePreferences was called while media or webview was null");
- editor.putInt(PREF_SCROLL_Y, -1);
- editor.putString(PREF_PLAYABLE_ID, "");
- }
- editor.commit();
- }
- }
-
- private boolean restoreFromPreference() {
- if (saveState) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Restoring from preferences");
- Activity activity = getActivity();
- if (activity != null) {
- SharedPreferences prefs = activity.getSharedPreferences(
- PREF, Activity.MODE_PRIVATE);
- String id = prefs.getString(PREF_PLAYABLE_ID, "");
- int scrollY = prefs.getInt(PREF_SCROLL_Y, -1);
- if (scrollY != -1 && media != null
- && id.equals(media.getIdentifier().toString())
- && webvDescription != null) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Restored scroll Position: " + scrollY);
- webvDescription.scrollTo(webvDescription.getScrollX(),
- scrollY);
- return true;
- }
- }
- }
- return false;
- }
+public class ItemDescriptionFragment extends Fragment {
+
+ private static final String TAG = "ItemDescriptionFragment";
+
+ private static final String PREF = "ItemDescriptionFragmentPrefs";
+ private static final String PREF_SCROLL_Y = "prefScrollY";
+ private static final String PREF_PLAYABLE_ID = "prefPlayableId";
+
+ private static final String ARG_PLAYABLE = "arg.playable";
+ private static final String ARG_FEEDITEM_ID = "arg.feeditem";
+
+ private static final String ARG_SAVE_STATE = "arg.saveState";
+
+ private WebView webvDescription;
+
+ private ShownotesProvider shownotesProvider;
+ private Playable media;
+
+
+ private AsyncTask<Void, Void, Void> webViewLoader;
+
+ /**
+ * URL that was selected via long-press.
+ */
+ private String selectedURL;
+
+ /**
+ * True if Fragment should save its state (e.g. scrolling position) in a
+ * shared preference.
+ */
+ private boolean saveState;
+
+ public static ItemDescriptionFragment newInstance(Playable media,
+ boolean saveState) {
+ ItemDescriptionFragment f = new ItemDescriptionFragment();
+ Bundle args = new Bundle();
+ args.putParcelable(ARG_PLAYABLE, media);
+ args.putBoolean(ARG_SAVE_STATE, saveState);
+ f.setArguments(args);
+ return f;
+ }
+
+ public static ItemDescriptionFragment newInstance(FeedItem item, boolean saveState) {
+ ItemDescriptionFragment f = new ItemDescriptionFragment();
+ Bundle args = new Bundle();
+ args.putLong(ARG_FEEDITEM_ID, item.getId());
+ args.putBoolean(ARG_SAVE_STATE, saveState);
+ f.setArguments(args);
+ return f;
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Creating view");
+ webvDescription = new WebView(getActivity());
+ if (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) {
+ if (Build.VERSION.SDK_INT >= 11
+ && Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
+ webvDescription.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ }
+ webvDescription.setBackgroundColor(getResources().getColor(
+ R.color.black));
+ }
+ webvDescription.getSettings().setUseWideViewPort(false);
+ webvDescription.getSettings().setLayoutAlgorithm(
+ LayoutAlgorithm.NARROW_COLUMNS);
+ webvDescription.getSettings().setLoadWithOverviewMode(true);
+ webvDescription.setOnLongClickListener(webViewLongClickListener);
+ webvDescription.setWebViewClient(new WebViewClient() {
+
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+ startActivity(intent);
+ return true;
+ }
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ super.onPageFinished(view, url);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Page finished");
+ // Restoring the scroll position might not always work
+ view.postDelayed(new Runnable() {
+
+ @Override
+ public void run() {
+ restoreFromPreference();
+ }
+
+ }, 50);
+ }
+
+ });
+ registerForContextMenu(webvDescription);
+ return webvDescription;
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ }
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Fragment attached");
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Fragment detached");
+ if (webViewLoader != null) {
+ webViewLoader.cancel(true);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Fragment destroyed");
+ if (webViewLoader != null) {
+ webViewLoader.cancel(true);
+ }
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Creating fragment");
+ Bundle args = getArguments();
+ saveState = args.getBoolean(ARG_SAVE_STATE, false);
+
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ Bundle args = getArguments();
+ if (args.containsKey(ARG_PLAYABLE)) {
+ media = args.getParcelable(ARG_PLAYABLE);
+ shownotesProvider = media;
+ startLoader();
+ } else if (args.containsKey(ARG_FEEDITEM_ID)) {
+ AsyncTask<Void, Void, FeedItem> itemLoadTask = new AsyncTask<Void, Void, FeedItem>() {
+
+ @Override
+ protected FeedItem doInBackground(Void... voids) {
+ return DBReader.getFeedItem(getActivity(), getArguments().getLong(ARG_FEEDITEM_ID));
+ }
+
+ @Override
+ protected void onPostExecute(FeedItem feedItem) {
+ super.onPostExecute(feedItem);
+ shownotesProvider = feedItem;
+ startLoader();
+ }
+ };
+ if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
+ itemLoadTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ } else {
+ itemLoadTask.execute();
+ }
+ }
+
+
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ }
+
+ @SuppressLint("NewApi")
+ private void startLoader() {
+ webViewLoader = createLoader();
+ if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
+ webViewLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ } else {
+ webViewLoader.execute();
+ }
+ }
+
+ /**
+ * Return the CSS style of the Webview.
+ *
+ * @param textColor the default color to use for the text in the webview. This
+ * value is inserted directly into the CSS String.
+ */
+ private String applyWebviewStyle(String textColor, String data) {
+ final String WEBVIEW_STYLE = "<html><head><style type=\"text/css\"> * { color: %s; font-family: Helvetica; line-height: 1.5em; font-size: 11pt; } a { font-style: normal; text-decoration: none; font-weight: normal; color: #00A8DF; } img { display: block; margin: 10 auto; max-width: %s; height: auto; } body { margin: %dpx %dpx %dpx %dpx; }</style></head><body>%s</body></html>";
+ final int pageMargin = (int) TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP, 8, getResources()
+ .getDisplayMetrics());
+ return String.format(WEBVIEW_STYLE, textColor, "100%", pageMargin,
+ pageMargin, pageMargin, pageMargin, data);
+ }
+
+ private View.OnLongClickListener webViewLongClickListener = new View.OnLongClickListener() {
+
+ @Override
+ public boolean onLongClick(View v) {
+ WebView.HitTestResult r = webvDescription.getHitTestResult();
+ if (r != null
+ && r.getType() == WebView.HitTestResult.SRC_ANCHOR_TYPE) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG,
+ "Link of webview was long-pressed. Extra: "
+ + r.getExtra());
+ selectedURL = r.getExtra();
+ webvDescription.showContextMenu();
+ return true;
+ }
+ selectedURL = null;
+ return false;
+ }
+ };
+
+ @SuppressWarnings("deprecation")
+ @SuppressLint("NewApi")
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ boolean handled = selectedURL != null;
+ if (selectedURL != null) {
+ switch (item.getItemId()) {
+ case R.id.open_in_browser_item:
+ Uri uri = Uri.parse(selectedURL);
+ getActivity()
+ .startActivity(new Intent(Intent.ACTION_VIEW, uri));
+ break;
+ case R.id.share_url_item:
+ ShareUtils.shareLink(getActivity(), selectedURL);
+ break;
+ case R.id.copy_url_item:
+ if (android.os.Build.VERSION.SDK_INT >= 11) {
+ ClipData clipData = ClipData.newPlainText(selectedURL,
+ selectedURL);
+ android.content.ClipboardManager cm = (android.content.ClipboardManager) getActivity()
+ .getSystemService(Context.CLIPBOARD_SERVICE);
+ cm.setPrimaryClip(clipData);
+ } else {
+ android.text.ClipboardManager cm = (android.text.ClipboardManager) getActivity()
+ .getSystemService(Context.CLIPBOARD_SERVICE);
+ cm.setText(selectedURL);
+ }
+ Toast t = Toast.makeText(getActivity(),
+ R.string.copied_url_msg, Toast.LENGTH_SHORT);
+ t.show();
+ break;
+ default:
+ handled = false;
+ break;
+
+ }
+ selectedURL = null;
+ }
+ return handled;
+
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v,
+ ContextMenuInfo menuInfo) {
+ if (selectedURL != null) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+ menu.add(Menu.NONE, R.id.open_in_browser_item, Menu.NONE,
+ R.string.open_in_browser_label);
+ menu.add(Menu.NONE, R.id.copy_url_item, Menu.NONE,
+ R.string.copy_url_label);
+ menu.add(Menu.NONE, R.id.share_url_item, Menu.NONE,
+ R.string.share_url_label);
+ menu.setHeaderTitle(selectedURL);
+ }
+ }
+
+ private AsyncTask<Void, Void, Void> createLoader() {
+ return new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected void onCancelled() {
+ super.onCancelled();
+ if (getActivity() != null) {
+ ((ActionBarActivity) getActivity())
+ .setSupportProgressBarIndeterminateVisibility(false);
+ }
+ webViewLoader = null;
+ }
+
+ String data;
+
+ @Override
+ protected void onPostExecute(Void result) {
+ super.onPostExecute(result);
+ // /webvDescription.loadData(url, "text/html", "utf-8");
+ webvDescription.loadDataWithBaseURL(null, data, "text/html",
+ "utf-8", "about:blank");
+ if (getActivity() != null) {
+ ((ActionBarActivity) getActivity())
+ .setSupportProgressBarIndeterminateVisibility(false);
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Webview loaded");
+ webViewLoader = null;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ if (getActivity() != null) {
+ ((ActionBarActivity) getActivity())
+ .setSupportProgressBarIndeterminateVisibility(false);
+ }
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Loading Webview");
+ try {
+ Callable<String> shownotesLoadTask = shownotesProvider.loadShownotes();
+ final String shownotes = shownotesLoadTask.call();
+
+ data = "";
+ data = StringEscapeUtils.unescapeHtml4(shownotes);
+ Activity activity = getActivity();
+ if (activity != null) {
+ TypedArray res = getActivity()
+ .getTheme()
+ .obtainStyledAttributes(
+ new int[]{android.R.attr.textColorPrimary});
+ int colorResource = res.getColor(0, 0);
+ String colorString = String.format("#%06X",
+ 0xFFFFFF & colorResource);
+ Log.i(TAG, "text color: " + colorString);
+ res.recycle();
+ data = applyWebviewStyle(colorString, data);
+ } else {
+ cancel(true);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ };
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ savePreference();
+ }
+
+ private void savePreference() {
+ if (saveState) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Saving preferences");
+ SharedPreferences prefs = getActivity().getSharedPreferences(PREF,
+ Activity.MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ if (media != null && webvDescription != null) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG,
+ "Saving scroll position: "
+ + webvDescription.getScrollY());
+ editor.putInt(PREF_SCROLL_Y, webvDescription.getScrollY());
+ editor.putString(PREF_PLAYABLE_ID, media.getIdentifier()
+ .toString());
+ } else {
+ if (AppConfig.DEBUG)
+ Log.d(TAG,
+ "savePreferences was called while media or webview was null");
+ editor.putInt(PREF_SCROLL_Y, -1);
+ editor.putString(PREF_PLAYABLE_ID, "");
+ }
+ editor.commit();
+ }
+ }
+
+ private boolean restoreFromPreference() {
+ if (saveState) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Restoring from preferences");
+ Activity activity = getActivity();
+ if (activity != null) {
+ SharedPreferences prefs = activity.getSharedPreferences(
+ PREF, Activity.MODE_PRIVATE);
+ String id = prefs.getString(PREF_PLAYABLE_ID, "");
+ int scrollY = prefs.getInt(PREF_SCROLL_Y, -1);
+ if (scrollY != -1 && media != null
+ && id.equals(media.getIdentifier().toString())
+ && webvDescription != null) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Restored scroll Position: " + scrollY);
+ webvDescription.scrollTo(webvDescription.getScrollX(),
+ scrollY);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
}
diff --git a/src/de/danoeh/antennapod/fragment/ItemlistFragment.java b/src/de/danoeh/antennapod/fragment/ItemlistFragment.java
index 6265694f6..40637544d 100644
--- a/src/de/danoeh/antennapod/fragment/ItemlistFragment.java
+++ b/src/de/danoeh/antennapod/fragment/ItemlistFragment.java
@@ -1,8 +1,12 @@
package de.danoeh.antennapod.fragment;
import android.annotation.SuppressLint;
+import android.content.Context;
import android.content.Intent;
+import android.os.AsyncTask;
import android.os.Bundle;
+import android.support.v4.app.ListFragment;
+import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
@@ -12,27 +16,30 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
-import com.actionbarsherlock.app.SherlockListFragment;
+import android.widget.TextView;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.ItemviewActivity;
import de.danoeh.antennapod.adapter.ActionButtonCallback;
-import de.danoeh.antennapod.adapter.DefaultFeedItemlistAdapter;
import de.danoeh.antennapod.adapter.InternalFeedItemlistAdapter;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.service.download.DownloadService;
+import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.storage.DownloadRequester;
+import de.danoeh.antennapod.util.QueueAccess;
import de.danoeh.antennapod.util.menuhandler.FeedItemMenuHandler;
+import java.util.Iterator;
+import java.util.List;
+
/** Displays a list of FeedItems. */
@SuppressLint("ValidFragment")
-public class ItemlistFragment extends SherlockListFragment {
+public class ItemlistFragment extends ListFragment {
private static final String TAG = "ItemlistFragment";
private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED
@@ -43,12 +50,10 @@ public class ItemlistFragment extends SherlockListFragment {
public static final String EXTRA_SELECTED_FEEDITEM = "extra.de.danoeh.antennapod.activity.selected_feeditem";
public static final String ARGUMENT_FEED_ID = "argument.de.danoeh.antennapod.feed_id";
protected InternalFeedItemlistAdapter fila;
- protected FeedManager manager = FeedManager.getInstance();
protected DownloadRequester requester = DownloadRequester.getInstance();
- private DefaultFeedItemlistAdapter.ItemAccess itemAccess;
-
private Feed feed;
+ protected List<Long> queue;
protected FeedItem selectedItem = null;
protected boolean contextMenuClosed = true;
@@ -56,10 +61,8 @@ public class ItemlistFragment extends SherlockListFragment {
/** Argument for FeeditemlistAdapter */
protected boolean showFeedtitle;
- public ItemlistFragment(DefaultFeedItemlistAdapter.ItemAccess itemAccess,
- boolean showFeedtitle) {
+ public ItemlistFragment(boolean showFeedtitle) {
super();
- this.itemAccess = itemAccess;
this.showFeedtitle = showFeedtitle;
}
@@ -83,6 +86,30 @@ public class ItemlistFragment extends SherlockListFragment {
return i;
}
+ private InternalFeedItemlistAdapter.ItemAccess itemAccessRef;
+ protected InternalFeedItemlistAdapter.ItemAccess itemAccess() {
+ if (itemAccessRef == null) {
+ itemAccessRef = new InternalFeedItemlistAdapter.ItemAccess() {
+
+ @Override
+ public FeedItem getItem(int position) {
+ return (feed != null) ? feed.getItemAtIndex(true, position) : null;
+ }
+
+ @Override
+ public int getCount() {
+ return (feed != null) ? feed.getNumOfItems(true) : 0;
+ }
+
+ @Override
+ public boolean isInQueue(FeedItem item) {
+ return (queue != null) && queue.contains(item.getId());
+ }
+ };
+ }
+ return itemAccessRef;
+ }
+
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@@ -92,27 +119,71 @@ public class ItemlistFragment extends SherlockListFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- if (itemAccess == null) {
- long feedId = getArguments().getLong(ARGUMENT_FEED_ID);
- final Feed feed = FeedManager.getInstance().getFeed(feedId);
- this.feed = feed;
- itemAccess = new DefaultFeedItemlistAdapter.ItemAccess() {
-
- @Override
- public FeedItem getItem(int position) {
- return feed.getItemAtIndex(true, position);
- }
-
- @Override
- public int getCount() {
- return feed.getNumOfItems(true);
- }
- };
- }
+ loadData();
}
+ protected void loadData() {
+ final long feedId;
+ if (feed == null) {
+ feedId = getArguments().getLong(ARGUMENT_FEED_ID);
+ } else {
+ feedId = feed.getId();
+ }
+ AsyncTask<Long, Void, Feed> loadTask = new AsyncTask<Long, Void, Feed>(){
+ private volatile List<Long> queueRef;
+
+ @Override
+ protected Feed doInBackground(Long... longs) {
+ Context context = ItemlistFragment.this.getActivity();
+ if (context != null) {
+ Feed result = DBReader.getFeed(context, longs[0]);
+ if (result != null) {
+ result.setItems(DBReader.getFeedItemList(context, result));
+ queueRef = DBReader.getQueueIDList(context);
+ return result;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Feed result) {
+ super.onPostExecute(result);
+ if (result != null && result.getItems() != null) {
+ feed = result;
+ if (queueRef != null) {
+ queue = queueRef;
+ } else {
+ Log.e(TAG, "Could not load queue");
+ }
+ if (result.getItems().isEmpty()) {
+ }
+ setEmptyViewIfListIsEmpty();
+ if (fila != null) {
+ fila.notifyDataSetChanged();
+ }
+ } else {
+ if (result == null) {
+ Log.e(TAG, "Could not load feed with id " + feedId);
+ } else if (result.getItems() == null) {
+ Log.e(TAG, "Could not load feed items");
+ }
+ }
+ }
+ };
+ loadTask.execute(feedId);
+ }
+
+ private void setEmptyViewIfListIsEmpty() {
+ if (getListView() != null && feed != null && feed.getItems() != null) {
+ if (feed.getItems().isEmpty()) {
+ ((TextView) getActivity().findViewById(android.R.id.empty)).setText(R.string.no_items_label);
+ }
+ }
+ }
+
protected InternalFeedItemlistAdapter createListAdapter() {
- return new InternalFeedItemlistAdapter(getActivity(), itemAccess,
+ return new InternalFeedItemlistAdapter(getActivity(), itemAccess(),
adapterCallback, showFeedtitle);
}
@@ -162,7 +233,9 @@ public class ItemlistFragment extends SherlockListFragment {
if ((EventDistributor.DOWNLOAD_QUEUED & arg) != 0) {
updateProgressBarVisibility();
} else {
- fila.notifyDataSetChanged();
+ if (feed != null) {
+ loadData();
+ }
updateProgressBarVisibility();
}
}
@@ -173,13 +246,13 @@ public class ItemlistFragment extends SherlockListFragment {
if (feed != null) {
if (DownloadService.isRunning
&& DownloadRequester.getInstance().isDownloadingFile(feed)) {
- getSherlockActivity()
+ ((ActionBarActivity) getActivity())
.setSupportProgressBarIndeterminateVisibility(true);
} else {
- getSherlockActivity()
+ ((ActionBarActivity) getActivity())
.setSupportProgressBarIndeterminateVisibility(false);
}
- getSherlockActivity().supportInvalidateOptionsMenu();
+ getActivity().supportInvalidateOptionsMenu();
}
}
@@ -218,13 +291,13 @@ public class ItemlistFragment extends SherlockListFragment {
menu.setHeaderTitle(selectedItem.getTitle());
FeedItemMenuHandler.onPrepareMenu(
- new FeedItemMenuHandler.MenuInterface() {
+ new FeedItemMenuHandler.MenuInterface() {
- @Override
- public void setItemVisibility(int id, boolean visible) {
- menu.findItem(id).setVisible(visible);
- }
- }, selectedItem, false);
+ @Override
+ public void setItemVisibility(int id, boolean visible) {
+ menu.findItem(id).setVisible(visible);
+ }
+ }, selectedItem, false, QueueAccess.IDListAccess(queue));
}
}
@@ -237,7 +310,7 @@ public class ItemlistFragment extends SherlockListFragment {
try {
handled = FeedItemMenuHandler.onMenuItemClicked(
- getSherlockActivity(), item.getItemId(), selectedItem);
+ getActivity(), item.getItemId(), selectedItem);
} catch (DownloadRequestException e) {
e.printStackTrace();
DownloadRequestErrorDialogCreator.newRequestErrorDialog(
diff --git a/src/de/danoeh/antennapod/fragment/MiroGuideChannellistFragment.java b/src/de/danoeh/antennapod/fragment/MiroGuideChannellistFragment.java
index c378c0acd..c6901ad17 100644
--- a/src/de/danoeh/antennapod/fragment/MiroGuideChannellistFragment.java
+++ b/src/de/danoeh/antennapod/fragment/MiroGuideChannellistFragment.java
@@ -10,6 +10,7 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
+import android.support.v4.app.ListFragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -17,8 +18,6 @@ import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ListView;
-import com.actionbarsherlock.app.SherlockListFragment;
-
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MiroGuideChannelViewActivity;
@@ -33,7 +32,7 @@ import de.danoeh.antennapod.miroguide.model.MiroGuideChannel;
* entries will be loaded until all entries have been loaded or the maximum
* number of channels has been reached.
* */
-public class MiroGuideChannellistFragment extends SherlockListFragment {
+public class MiroGuideChannellistFragment extends ListFragment {
private static final String TAG = "MiroGuideChannellistFragment";
private static final String ARG_FILTER = "filter";
diff --git a/src/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java b/src/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java
index b471d5303..d20cb63c4 100644
--- a/src/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java
+++ b/src/de/danoeh/antennapod/fragment/PlaybackHistoryFragment.java
@@ -1,33 +1,53 @@
package de.danoeh.antennapod.fragment;
+import android.content.Context;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
-import de.danoeh.antennapod.adapter.DefaultFeedItemlistAdapter;
+import de.danoeh.antennapod.adapter.InternalFeedItemlistAdapter;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
+import de.danoeh.antennapod.storage.DBReader;
+
+import java.util.Iterator;
+import java.util.List;
public class PlaybackHistoryFragment extends ItemlistFragment {
private static final String TAG = "PlaybackHistoryFragment";
+ private List<FeedItem> playbackHistory;
+
public PlaybackHistoryFragment() {
- super(new DefaultFeedItemlistAdapter.ItemAccess() {
+ super(true);
+ }
- @Override
- public FeedItem getItem(int position) {
- return FeedManager.getInstance().getPlaybackHistoryItemIndex(
- position);
- }
+ InternalFeedItemlistAdapter.ItemAccess itemAccessRef;
+ @Override
+ protected InternalFeedItemlistAdapter.ItemAccess itemAccess() {
+ if (itemAccessRef == null) {
+ itemAccessRef = new InternalFeedItemlistAdapter.ItemAccess() {
- @Override
- public int getCount() {
- return FeedManager.getInstance().getPlaybackHistorySize();
- }
- }, true);
- }
+ @Override
+ public FeedItem getItem(int position) {
+ return (playbackHistory != null) ? playbackHistory.get(position) : null;
+ }
- @Override
+ @Override
+ public int getCount() {
+ return (playbackHistory != null) ? playbackHistory.size() : 0;
+ }
+
+ @Override
+ public boolean isInQueue(FeedItem item) {
+ return (queue != null) ? queue.contains(item.getId()) : false;
+ }
+ };
+ }
+ return itemAccessRef;
+ }
+
+ @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EventDistributor.getInstance().register(historyUpdate);
@@ -46,10 +66,48 @@ public class PlaybackHistoryFragment extends ItemlistFragment {
if ((EventDistributor.PLAYBACK_HISTORY_UPDATE & arg) != 0) {
if (AppConfig.DEBUG)
Log.d(TAG, "Received content update");
- fila.notifyDataSetChanged();
+ loadData();
}
}
};
+ @Override
+ protected void loadData() {
+ AsyncTask<Void, Void, Void> loadTask = new AsyncTask<Void, Void, Void>() {
+ private volatile List<FeedItem> phRef;
+ private volatile List<Long> queueRef;
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ Context context = PlaybackHistoryFragment.this.getActivity();
+ if (context != null) {
+ queueRef = DBReader.getQueueIDList(context);
+ phRef = DBReader.getPlaybackHistory(context);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ super.onPostExecute(aVoid);
+ if (queueRef != null && phRef != null) {
+ queue = queueRef;
+ playbackHistory = phRef;
+ Log.i(TAG, "Number of items in playback history: " + playbackHistory.size());
+ if (fila != null) {
+ fila.notifyDataSetChanged();
+ }
+ } else {
+ if (queueRef == null) {
+ Log.e(TAG, "Could not load queue");
+ }
+ if (phRef == null) {
+ Log.e(TAG, "Could not load playback history");
+ }
+ }
+ }
+ };
+ loadTask.execute();
+ }
}
diff --git a/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java b/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java
index 4637c7725..99bef4bd8 100644
--- a/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java
+++ b/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java
@@ -20,7 +20,7 @@ import android.net.Uri;
public class MiroGuideConnector {
private HttpClient httpClient;
- private static final String HOST_URL = "https://www.miroguide.com/api/";
+ private static final String HOST_URL = "http://www.miroguide.com/api/";
private static final String PATH_GET_CHANNELS = "get_channels";
private static final String PATH_LIST_CATEGORIES = "list_categories";
private static final String PATH_GET_CHANNEL = "get_channel";
diff --git a/src/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java b/src/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java
index b58527130..aebe5a681 100644
--- a/src/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java
+++ b/src/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java
@@ -7,7 +7,7 @@ import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
-import de.danoeh.antennapod.feed.FeedManager;
+import de.danoeh.antennapod.storage.DBTasks;
import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.util.NetworkUtils;
@@ -27,7 +27,7 @@ public class ConnectivityActionReceiver extends BroadcastReceiver {
new Thread() {
@Override
public void run() {
- FeedManager.getInstance()
+ DBTasks
.autodownloadUndownloadedItems(context);
}
}.start();
diff --git a/src/de/danoeh/antennapod/receiver/FeedUpdateReceiver.java b/src/de/danoeh/antennapod/receiver/FeedUpdateReceiver.java
index 821ade4b0..fdbaa97f0 100644
--- a/src/de/danoeh/antennapod/receiver/FeedUpdateReceiver.java
+++ b/src/de/danoeh/antennapod/receiver/FeedUpdateReceiver.java
@@ -7,8 +7,8 @@ import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.storage.DBTasks;
/** Refreshes all feeds when it receives an intent */
public class FeedUpdateReceiver extends BroadcastReceiver {
@@ -22,7 +22,7 @@ public class FeedUpdateReceiver extends BroadcastReceiver {
Log.d(TAG, "Received intent");
boolean mobileUpdate = UserPreferences.isAllowMobileUpdate();
if (mobileUpdate || connectedToWifi(context)) {
- FeedManager.getInstance().refreshExpiredFeeds(context);
+ DBTasks.refreshExpiredFeeds(context);
} else {
if (AppConfig.DEBUG)
Log.d(TAG,
diff --git a/src/de/danoeh/antennapod/service/PlaybackService.java b/src/de/danoeh/antennapod/service/PlaybackService.java
index 6c7619476..ec405d440 100644
--- a/src/de/danoeh/antennapod/service/PlaybackService.java
+++ b/src/de/danoeh/antennapod/service/PlaybackService.java
@@ -2,13 +2,8 @@ package de.danoeh.antennapod.service;
import java.io.IOException;
import java.util.Date;
-import java.util.concurrent.Future;
-import java.util.concurrent.RejectedExecutionHandler;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
+import java.util.List;
+import java.util.concurrent.*;
import android.annotation.SuppressLint;
import android.app.Notification;
@@ -40,17 +35,16 @@ import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.AudioplayerActivity;
import de.danoeh.antennapod.activity.VideoplayerActivity;
-import de.danoeh.antennapod.feed.Chapter;
-import de.danoeh.antennapod.feed.FeedComponent;
-import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
-import de.danoeh.antennapod.feed.FeedMedia;
-import de.danoeh.antennapod.feed.MediaType;
+import de.danoeh.antennapod.feed.*;
import de.danoeh.antennapod.preferences.PlaybackPreferences;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.receiver.MediaButtonReceiver;
import de.danoeh.antennapod.receiver.PlayerWidget;
+import de.danoeh.antennapod.storage.DBReader;
+import de.danoeh.antennapod.storage.DBTasks;
+import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.util.BitmapDecoder;
+import de.danoeh.antennapod.util.QueueAccess;
import de.danoeh.antennapod.util.DuckType;
import de.danoeh.antennapod.util.flattr.FlattrUtils;
import de.danoeh.antennapod.util.playback.AudioPlayer;
@@ -58,213 +52,245 @@ import de.danoeh.antennapod.util.playback.IPlayer;
import de.danoeh.antennapod.util.playback.Playable;
import de.danoeh.antennapod.util.playback.Playable.PlayableException;
import de.danoeh.antennapod.util.playback.VideoPlayer;
+import de.danoeh.antennapod.util.playback.PlaybackController;
-/** Controls the MediaPlayer that plays a FeedMedia-file */
+/**
+ * Controls the MediaPlayer that plays a FeedMedia-file
+ */
public class PlaybackService extends Service {
- /** Logging tag */
- private static final String TAG = "PlaybackService";
-
- /** Parcelable of type Playable. */
- public static final String EXTRA_PLAYABLE = "PlaybackService.PlayableExtra";
- /** True if media should be streamed. */
- public static final String EXTRA_SHOULD_STREAM = "extra.de.danoeh.antennapod.service.shouldStream";
- /**
- * True if playback should be started immediately after media has been
- * prepared.
- */
- public static final String EXTRA_START_WHEN_PREPARED = "extra.de.danoeh.antennapod.service.startWhenPrepared";
-
- public static final String EXTRA_PREPARE_IMMEDIATELY = "extra.de.danoeh.antennapod.service.prepareImmediately";
-
- public static final String ACTION_PLAYER_STATUS_CHANGED = "action.de.danoeh.antennapod.service.playerStatusChanged";
- private static final String AVRCP_ACTION_PLAYER_STATUS_CHANGED = "com.android.music.playstatechanged";
-
- public static final String ACTION_PLAYER_NOTIFICATION = "action.de.danoeh.antennapod.service.playerNotification";
- public static final String EXTRA_NOTIFICATION_CODE = "extra.de.danoeh.antennapod.service.notificationCode";
- public static final String EXTRA_NOTIFICATION_TYPE = "extra.de.danoeh.antennapod.service.notificationType";
-
- /**
- * If the PlaybackService receives this action, it will stop playback and
- * try to shutdown.
- */
- public static final String ACTION_SHUTDOWN_PLAYBACK_SERVICE = "action.de.danoeh.antennapod.service.actionShutdownPlaybackService";
-
- /**
- * If the PlaybackService receives this action, it will end playback of the
- * current episode and load the next episode if there is one available.
- * */
- public static final String ACTION_SKIP_CURRENT_EPISODE = "action.de.danoeh.antennapod.service.skipCurrentEpisode";
-
- /** Used in NOTIFICATION_TYPE_RELOAD. */
- public static final int EXTRA_CODE_AUDIO = 1;
- public static final int EXTRA_CODE_VIDEO = 2;
-
- public static final int NOTIFICATION_TYPE_ERROR = 0;
- public static final int NOTIFICATION_TYPE_INFO = 1;
- public static final int NOTIFICATION_TYPE_BUFFER_UPDATE = 2;
- public static final int NOTIFICATION_TYPE_RELOAD = 3;
- /** The state of the sleeptimer changed. */
- public static final int NOTIFICATION_TYPE_SLEEPTIMER_UPDATE = 4;
- public static final int NOTIFICATION_TYPE_BUFFER_START = 5;
- public static final int NOTIFICATION_TYPE_BUFFER_END = 6;
- /** No more episodes are going to be played. */
- public static final int NOTIFICATION_TYPE_PLAYBACK_END = 7;
- public static final int NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE = 8;
-
- /**
- * Returned by getPositionSafe() or getDurationSafe() if the playbackService
- * is in an invalid state.
- */
- public static final int INVALID_TIME = -1;
-
- /** Is true if service is running. */
- public static boolean isRunning = false;
-
- private static final int NOTIFICATION_ID = 1;
-
- private AudioManager audioManager;
- private ComponentName mediaButtonReceiver;
+ /**
+ * Logging tag
+ */
+ private static final String TAG = "PlaybackService";
+
+ /**
+ * Parcelable of type Playable.
+ */
+ public static final String EXTRA_PLAYABLE = "PlaybackService.PlayableExtra";
+ /**
+ * True if media should be streamed.
+ */
+ public static final String EXTRA_SHOULD_STREAM = "extra.de.danoeh.antennapod.service.shouldStream";
+ /**
+ * True if playback should be started immediately after media has been
+ * prepared.
+ */
+ public static final String EXTRA_START_WHEN_PREPARED = "extra.de.danoeh.antennapod.service.startWhenPrepared";
+
+ public static final String EXTRA_PREPARE_IMMEDIATELY = "extra.de.danoeh.antennapod.service.prepareImmediately";
+
+ public static final String ACTION_PLAYER_STATUS_CHANGED = "action.de.danoeh.antennapod.service.playerStatusChanged";
+ private static final String AVRCP_ACTION_PLAYER_STATUS_CHANGED = "com.android.music.playstatechanged";
+
+ public static final String ACTION_PLAYER_NOTIFICATION = "action.de.danoeh.antennapod.service.playerNotification";
+ public static final String EXTRA_NOTIFICATION_CODE = "extra.de.danoeh.antennapod.service.notificationCode";
+ public static final String EXTRA_NOTIFICATION_TYPE = "extra.de.danoeh.antennapod.service.notificationType";
+
+ /**
+ * If the PlaybackService receives this action, it will stop playback and
+ * try to shutdown.
+ */
+ public static final String ACTION_SHUTDOWN_PLAYBACK_SERVICE = "action.de.danoeh.antennapod.service.actionShutdownPlaybackService";
+
+ /**
+ * If the PlaybackService receives this action, it will end playback of the
+ * current episode and load the next episode if there is one available.
+ */
+ public static final String ACTION_SKIP_CURRENT_EPISODE = "action.de.danoeh.antennapod.service.skipCurrentEpisode";
+
+ /**
+ * Used in NOTIFICATION_TYPE_RELOAD.
+ */
+ public static final int EXTRA_CODE_AUDIO = 1;
+ public static final int EXTRA_CODE_VIDEO = 2;
+
+ public static final int NOTIFICATION_TYPE_ERROR = 0;
+ public static final int NOTIFICATION_TYPE_INFO = 1;
+ public static final int NOTIFICATION_TYPE_BUFFER_UPDATE = 2;
+
+ /**
+ * Receivers of this intent should update their information about the curently playing media
+ */
+ public static final int NOTIFICATION_TYPE_RELOAD = 3;
+ /**
+ * The state of the sleeptimer changed.
+ */
+ public static final int NOTIFICATION_TYPE_SLEEPTIMER_UPDATE = 4;
+ public static final int NOTIFICATION_TYPE_BUFFER_START = 5;
+ public static final int NOTIFICATION_TYPE_BUFFER_END = 6;
+ /**
+ * No more episodes are going to be played.
+ */
+ public static final int NOTIFICATION_TYPE_PLAYBACK_END = 7;
+
+ /**
+ * Playback speed has changed
+ * */
+ public static final int NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE = 8;
+
+ /**
+ * Returned by getPositionSafe() or getDurationSafe() if the playbackService
+ * is in an invalid state.
+ */
+ public static final int INVALID_TIME = -1;
+
+ /**
+ * Is true if service is running.
+ */
+ public static boolean isRunning = false;
+
+ private static final int NOTIFICATION_ID = 1;
private IPlayer player;
private RemoteControlClient remoteControlClient;
-
- private Playable media;
-
- /** True if media should be streamed (Extracted from Intent Extra) . */
- private boolean shouldStream;
-
- /** True if service should prepare playback after it has been initialized */
- private boolean prepareImmediately;
- private boolean startWhenPrepared;
- private FeedManager manager;
- private PlayerStatus status;
-
- private PositionSaver positionSaver;
- private ScheduledFuture positionSaverFuture;
-
- private WidgetUpdateWorker widgetUpdater;
- private ScheduledFuture widgetUpdaterFuture;
-
- private SleepTimer sleepTimer;
- private Future sleepTimerFuture;
-
- private static final int SCHED_EX_POOL_SIZE = 3;
- private ScheduledThreadPoolExecutor schedExecutor;
-
- private volatile PlayerStatus statusBeforeSeek;
-
- private static boolean playingVideo;
-
- /** True if mediaplayer was paused because it lost audio focus temporarily */
- private boolean pausedBecauseOfTransientAudiofocusLoss;
-
- private Thread chapterLoader;
-
- private final IBinder mBinder = new LocalBinder();
-
- public class LocalBinder extends Binder {
- public PlaybackService getService() {
- return PlaybackService.this;
- }
- }
-
- @Override
- public boolean onUnbind(Intent intent) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Received onUnbind event");
- return super.onUnbind(intent);
- }
-
- /**
- * Returns an intent which starts an audio- or videoplayer, depending on the
- * type of media that is being played. If the playbackservice is not
- * running, the type of the last played media will be looked up.
- * */
- public static Intent getPlayerActivityIntent(Context context) {
- if (isRunning) {
- if (playingVideo) {
- return new Intent(context, VideoplayerActivity.class);
- } else {
- return new Intent(context, AudioplayerActivity.class);
- }
- } else {
- if (PlaybackPreferences.getCurrentEpisodeIsVideo()) {
- return new Intent(context, VideoplayerActivity.class);
- } else {
- return new Intent(context, AudioplayerActivity.class);
- }
- }
- }
-
- /**
- * Same as getPlayerActivityIntent(context), but here the type of activity
- * depends on the FeedMedia that is provided as an argument.
- */
- public static Intent getPlayerActivityIntent(Context context, Playable media) {
- MediaType mt = media.getMediaType();
- if (mt == MediaType.VIDEO) {
- return new Intent(context, VideoplayerActivity.class);
- } else {
- return new Intent(context, AudioplayerActivity.class);
- }
- }
-
- @SuppressLint("NewApi")
- @Override
- public void onCreate() {
- super.onCreate();
- if (AppConfig.DEBUG)
- Log.d(TAG, "Service created.");
- isRunning = true;
- pausedBecauseOfTransientAudiofocusLoss = false;
- status = PlayerStatus.STOPPED;
- audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
- manager = FeedManager.getInstance();
- schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE,
- new ThreadFactory() {
-
- @Override
- public Thread newThread(Runnable r) {
- Thread t = new Thread(r);
- t.setPriority(Thread.MIN_PRIORITY);
- return t;
- }
- }, new RejectedExecutionHandler() {
-
- @Override
- public void rejectedExecution(Runnable r,
- ThreadPoolExecutor executor) {
- Log.w(TAG, "SchedEx rejected submission of new task");
- }
- });
-
- mediaButtonReceiver = new ComponentName(getPackageName(),
- MediaButtonReceiver.class.getName());
- audioManager.registerMediaButtonEventReceiver(mediaButtonReceiver);
- if (android.os.Build.VERSION.SDK_INT >= 14) {
- audioManager
- .registerRemoteControlClient(setupRemoteControlClient());
- }
- registerReceiver(headsetDisconnected, new IntentFilter(
- Intent.ACTION_HEADSET_PLUG));
- registerReceiver(shutdownReceiver, new IntentFilter(
- ACTION_SHUTDOWN_PLAYBACK_SERVICE));
- registerReceiver(audioBecomingNoisy, new IntentFilter(
- AudioManager.ACTION_AUDIO_BECOMING_NOISY));
- registerReceiver(skipCurrentEpisodeReceiver, new IntentFilter(
- ACTION_SKIP_CURRENT_EPISODE));
-
- }
-
- private IPlayer createMediaPlayer() {
- IPlayer player;
- if (media == null || media.getMediaType() == MediaType.VIDEO) {
- player = new VideoPlayer();
- } else {
- player = new AudioPlayer(this);
- }
- return createMediaPlayer(player);
- }
+ private AudioManager audioManager;
+ private ComponentName mediaButtonReceiver;
+
+ private Playable media;
+
+ /**
+ * True if media should be streamed (Extracted from Intent Extra) .
+ */
+ private boolean shouldStream;
+
+ private boolean startWhenPrepared;
+ private PlayerStatus status;
+
+ private PositionSaver positionSaver;
+ private ScheduledFuture positionSaverFuture;
+
+ private WidgetUpdateWorker widgetUpdater;
+ private ScheduledFuture widgetUpdaterFuture;
+
+ private SleepTimer sleepTimer;
+ private Future sleepTimerFuture;
+
+ private static final int SCHED_EX_POOL_SIZE = 3;
+ private ScheduledThreadPoolExecutor schedExecutor;
+ private ExecutorService dbLoaderExecutor;
+
+ private volatile PlayerStatus statusBeforeSeek;
+
+ private static boolean playingVideo;
+
+ /**
+ * True if mediaplayer was paused because it lost audio focus temporarily
+ */
+ private boolean pausedBecauseOfTransientAudiofocusLoss;
+
+ private Thread chapterLoader;
+
+ private final IBinder mBinder = new LocalBinder();
+
+ private volatile List<FeedItem> queue;
+
+ public class LocalBinder extends Binder {
+ public PlaybackService getService() {
+ return PlaybackService.this;
+ }
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Received onUnbind event");
+ return super.onUnbind(intent);
+ }
+
+ /**
+ * Returns an intent which starts an audio- or videoplayer, depending on the
+ * type of media that is being played. If the playbackservice is not
+ * running, the type of the last played media will be looked up.
+ */
+ public static Intent getPlayerActivityIntent(Context context) {
+ if (isRunning) {
+ if (playingVideo) {
+ return new Intent(context, VideoplayerActivity.class);
+ } else {
+ return new Intent(context, AudioplayerActivity.class);
+ }
+ } else {
+ if (PlaybackPreferences.getCurrentEpisodeIsVideo()) {
+ return new Intent(context, VideoplayerActivity.class);
+ } else {
+ return new Intent(context, AudioplayerActivity.class);
+ }
+ }
+ }
+
+ /**
+ * Same as getPlayerActivityIntent(context), but here the type of activity
+ * depends on the FeedMedia that is provided as an argument.
+ */
+ public static Intent getPlayerActivityIntent(Context context, Playable media) {
+ MediaType mt = media.getMediaType();
+ if (mt == MediaType.VIDEO) {
+ return new Intent(context, VideoplayerActivity.class);
+ } else {
+ return new Intent(context, AudioplayerActivity.class);
+ }
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Service created.");
+ isRunning = true;
+ pausedBecauseOfTransientAudiofocusLoss = false;
+ status = PlayerStatus.STOPPED;
+ audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE,
+ new ThreadFactory() {
+
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r);
+ t.setPriority(Thread.MIN_PRIORITY);
+ return t;
+ }
+ }, new RejectedExecutionHandler() {
+
+ @Override
+ public void rejectedExecution(Runnable r,
+ ThreadPoolExecutor executor) {
+ Log.w(TAG, "SchedEx rejected submission of new task");
+ }
+ }
+ );
+ dbLoaderExecutor = Executors.newSingleThreadExecutor();
+ player = createMediaPlayer();
+
+
+ mediaButtonReceiver = new ComponentName(getPackageName(),
+ MediaButtonReceiver.class.getName());
+ audioManager.registerMediaButtonEventReceiver(mediaButtonReceiver);
+ if (android.os.Build.VERSION.SDK_INT >= 14) {
+ audioManager
+ .registerRemoteControlClient(setupRemoteControlClient());
+ }
+ registerReceiver(headsetDisconnected, new IntentFilter(
+ Intent.ACTION_HEADSET_PLUG));
+ registerReceiver(shutdownReceiver, new IntentFilter(
+ ACTION_SHUTDOWN_PLAYBACK_SERVICE));
+ registerReceiver(audioBecomingNoisy, new IntentFilter(
+ AudioManager.ACTION_AUDIO_BECOMING_NOISY));
+ registerReceiver(skipCurrentEpisodeReceiver, new IntentFilter(
+ ACTION_SKIP_CURRENT_EPISODE));
+ EventDistributor.getInstance().register(eventDistributorListener);
+ loadQueue();
+ }
+
+ private IPlayer createMediaPlayer() {
+ IPlayer player;
+ if (media == null || media.getMediaType() == MediaType.VIDEO) {
+ player = new VideoPlayer();
+ } else {
+ player = new AudioPlayer(this);
+ }
+ return createMediaPlayer(player);
+ }
private IPlayer createMediaPlayer(IPlayer mp) {
if (mp != null && media != null) {
@@ -293,137 +319,152 @@ public class PlaybackService extends Service {
return mp;
}
- @SuppressLint("NewApi")
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (AppConfig.DEBUG)
- Log.d(TAG, "Service is about to be destroyed");
- isRunning = false;
- if (chapterLoader != null) {
- chapterLoader.interrupt();
- }
- disableSleepTimer();
- unregisterReceiver(headsetDisconnected);
- unregisterReceiver(shutdownReceiver);
- unregisterReceiver(audioBecomingNoisy);
- unregisterReceiver(skipCurrentEpisodeReceiver);
- if (android.os.Build.VERSION.SDK_INT >= 14) {
- audioManager.unregisterRemoteControlClient(remoteControlClient);
- }
- audioManager.unregisterMediaButtonEventReceiver(mediaButtonReceiver);
- audioManager.abandonAudioFocus(audioFocusChangeListener);
- player.release();
- stopWidgetUpdater();
- updateWidget();
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Received onBind event");
- return mBinder;
- }
-
- private final OnAudioFocusChangeListener audioFocusChangeListener = new OnAudioFocusChangeListener() {
-
- @Override
- public void onAudioFocusChange(int focusChange) {
- switch (focusChange) {
- case AudioManager.AUDIOFOCUS_LOSS:
- if (AppConfig.DEBUG)
- Log.d(TAG, "Lost audio focus");
- pause(true, false);
- stopSelf();
- break;
- case AudioManager.AUDIOFOCUS_GAIN:
- if (AppConfig.DEBUG)
- Log.d(TAG, "Gained audio focus");
- if (pausedBecauseOfTransientAudiofocusLoss) {
- audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
- AudioManager.ADJUST_RAISE, 0);
- play();
- }
- break;
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
- if (status == PlayerStatus.PLAYING) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Lost audio focus temporarily. Ducking...");
- audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
- AudioManager.ADJUST_LOWER, 0);
- pausedBecauseOfTransientAudiofocusLoss = true;
- }
- break;
- case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
- if (status == PlayerStatus.PLAYING) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Lost audio focus temporarily. Pausing...");
- pause(false, false);
- pausedBecauseOfTransientAudiofocusLoss = true;
- }
- }
- }
- };
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- super.onStartCommand(intent, flags, startId);
-
- if (AppConfig.DEBUG)
- Log.d(TAG, "OnStartCommand called");
- int keycode = intent.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1);
- if (keycode != -1) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Received media button event");
- handleKeycode(keycode);
- } else {
-
- Playable playable = intent.getParcelableExtra(EXTRA_PLAYABLE);
- boolean playbackType = intent.getBooleanExtra(EXTRA_SHOULD_STREAM,
- true);
- if (playable == null) {
- Log.e(TAG, "Playable extra wasn't sent to the service");
- if (media == null) {
- stopSelf();
- }
- // Intent values appear to be valid
- // check if already playing and playbackType is the same
- } else if (media == null
- || !playable.getIdentifier().equals(media.getIdentifier())
- || playbackType != shouldStream) {
- pause(true, false);
- sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0);
- if (media == null
- || !playable.getIdentifier().equals(
- media.getIdentifier())) {
- media = playable;
- }
-
- if (media != null) {
- shouldStream = playbackType;
- startWhenPrepared = intent.getBooleanExtra(
- EXTRA_START_WHEN_PREPARED, false);
- prepareImmediately = intent.getBooleanExtra(
- EXTRA_PREPARE_IMMEDIATELY, false);
- initMediaplayer();
-
- } else {
- Log.e(TAG, "Media is null");
- stopSelf();
- }
-
- } else if (media != null) {
- if (status == PlayerStatus.PAUSED) {
- play();
- }
-
- } else {
- Log.w(TAG, "Something went wrong. Shutting down...");
- stopSelf();
- }
- }
- return Service.START_NOT_STICKY;
- }
+ @SuppressLint("NewApi")
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Service is about to be destroyed");
+ isRunning = false;
+ if (chapterLoader != null) {
+ chapterLoader.interrupt();
+ }
+ disableSleepTimer();
+ unregisterReceiver(headsetDisconnected);
+ unregisterReceiver(shutdownReceiver);
+ unregisterReceiver(audioBecomingNoisy);
+ unregisterReceiver(skipCurrentEpisodeReceiver);
+ EventDistributor.getInstance().unregister(eventDistributorListener);
+ if (android.os.Build.VERSION.SDK_INT >= 14) {
+ audioManager.unregisterRemoteControlClient(remoteControlClient);
+ }
+ audioManager.unregisterMediaButtonEventReceiver(mediaButtonReceiver);
+ audioManager.abandonAudioFocus(audioFocusChangeListener);
+ player.release();
+ stopWidgetUpdater();
+ updateWidget();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Received onBind event");
+ return mBinder;
+ }
+
+ private final EventDistributor.EventListener eventDistributorListener = new EventDistributor.EventListener() {
+ @Override
+ public void update(EventDistributor eventDistributor, Integer arg) {
+ if ((EventDistributor.QUEUE_UPDATE & arg) != 0) {
+ loadQueue();
+ }
+ }
+ };
+
+ private final OnAudioFocusChangeListener audioFocusChangeListener = new OnAudioFocusChangeListener() {
+
+ @Override
+ public void onAudioFocusChange(int focusChange) {
+ switch (focusChange) {
+ case AudioManager.AUDIOFOCUS_LOSS:
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Lost audio focus");
+ pause(true, false);
+ stopSelf();
+ break;
+ case AudioManager.AUDIOFOCUS_GAIN:
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Gained audio focus");
+ if (pausedBecauseOfTransientAudiofocusLoss) {
+ audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
+ AudioManager.ADJUST_RAISE, 0);
+ play();
+ }
+ break;
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+ if (status == PlayerStatus.PLAYING) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Lost audio focus temporarily. Ducking...");
+ audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
+ AudioManager.ADJUST_LOWER, 0);
+ pausedBecauseOfTransientAudiofocusLoss = true;
+ }
+ break;
+ case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
+ if (status == PlayerStatus.PLAYING) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Lost audio focus temporarily. Pausing...");
+ pause(false, false);
+ pausedBecauseOfTransientAudiofocusLoss = true;
+ }
+ }
+ }
+ };
+
+ /**
+ * 1. Check type of intent
+ * 1.1 Keycode -> handle keycode -> done
+ * 1.2 Playable -> Step 2
+ * 2. Handle playable
+ * 2.1 Check current status
+ * 2.1.1 Not playing -> play new playable
+ * 2.1.2 Playing, new playable is the same -> play if playback is currently paused
+ * 2.1.3 Playing, new playable different -> Stop playback of old media
+ *
+ * @param intent
+ * @param flags
+ * @param startId
+ * @return
+ */
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ super.onStartCommand(intent, flags, startId);
+
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "OnStartCommand called");
+ final int keycode = intent.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1);
+ final Playable playable = intent.getParcelableExtra(EXTRA_PLAYABLE);
+ if (keycode == -1 && playable == null) {
+ Log.e(TAG, "PlaybackService was started with no arguments");
+ stopSelf();
+ }
+
+ if (keycode != -1) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Received media button event");
+ handleKeycode(keycode);
+ } else {
+ boolean playbackType = intent.getBooleanExtra(EXTRA_SHOULD_STREAM,
+ true);
+ if (media == null) {
+ media = playable;
+ shouldStream = playbackType;
+ startWhenPrepared = intent.getBooleanExtra(
+ EXTRA_START_WHEN_PREPARED, false);
+ initMediaplayer(intent.getBooleanExtra(EXTRA_PREPARE_IMMEDIATELY, false));
+ sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0);
+ }
+ if (media != null) {
+ if (!playable.getIdentifier().equals(media.getIdentifier())) {
+ // different media or different playback type
+ pause(true, false);
+ player.reset();
+ media = playable;
+ shouldStream = playbackType;
+ startWhenPrepared = intent.getBooleanExtra(EXTRA_START_WHEN_PREPARED, false);
+ initMediaplayer(intent.getBooleanExtra(EXTRA_PREPARE_IMMEDIATELY, false));
+ sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0);
+ } else {
+ // same media and same playback type
+ if (status == PlayerStatus.PAUSED) {
+ play();
+ }
+ }
+ }
+ }
+
+ return Service.START_NOT_STICKY;
+ }
/** Handles media button events */
private void handleKeycode(int keycode) {
@@ -456,169 +497,180 @@ public class PlaybackService extends Service {
pause(true, true);
}
break;
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
+ seekDelta(PlaybackController.DEFAULT_SEEK_DELTA);
+ break;
}
- }
-
- /**
- * Called by a mediaplayer Activity as soon as it has prepared its
- * mediaplayer.
- */
- public void setVideoSurface(SurfaceHolder sh) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Setting display");
- player.setDisplay(null);
- player.setDisplay(sh);
- if (status == PlayerStatus.STOPPED
- || status == PlayerStatus.AWAITING_VIDEO_SURFACE) {
- try {
- InitTask initTask = new InitTask() {
-
- @Override
- protected void onPostExecute(Playable result) {
- if (status == PlayerStatus.INITIALIZING) {
- if (result != null) {
- try {
- if (shouldStream) {
- player.setDataSource(media
- .getStreamUrl());
- setStatus(PlayerStatus.PREPARING);
- player.prepareAsync();
- } else {
- player.setDataSource(media
- .getLocalMediaUrl());
- setStatus(PlayerStatus.PREPARING);
- player.prepareAsync();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- } else {
- setStatus(PlayerStatus.ERROR);
- sendBroadcast(new Intent(
- ACTION_SHUTDOWN_PLAYBACK_SERVICE));
- }
- }
- }
-
- @Override
- protected void onPreExecute() {
- setStatus(PlayerStatus.INITIALIZING);
- }
-
- };
- initTask.executeAsync(media);
- } catch (IllegalArgumentException e) {
- e.printStackTrace();
- } catch (SecurityException e) {
- e.printStackTrace();
- } catch (IllegalStateException e) {
- e.printStackTrace();
- }
- }
-
- }
-
- /** Called when the surface holder of the mediaplayer has to be changed. */
- private void resetVideoSurface() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Resetting video surface");
- cancelPositionSaver();
- player.setDisplay(null);
- player.reset();
- player.release();
- status = PlayerStatus.STOPPED;
- if (media != null) {
- initMediaplayer();
- }
- }
+ case KeyEvent.KEYCODE_MEDIA_REWIND: {
+ seekDelta(-PlaybackController.DEFAULT_SEEK_DELTA);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Called by a mediaplayer Activity as soon as it has prepared its
+ * mediaplayer.
+ */
+ public void setVideoSurface(SurfaceHolder sh) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Setting display");
+ player.setDisplay(null);
+ player.setDisplay(sh);
+ if (status == PlayerStatus.STOPPED
+ || status == PlayerStatus.AWAITING_VIDEO_SURFACE) {
+ try {
+ InitTask initTask = new InitTask() {
+
+ @Override
+ protected void onPostExecute(Playable result) {
+ if (status == PlayerStatus.INITIALIZING) {
+ if (result != null) {
+ try {
+ if (shouldStream) {
+ player.setDataSource(media
+ .getStreamUrl());
+ setStatus(PlayerStatus.PREPARING);
+ player.prepareAsync();
+ } else {
+ player.setDataSource(media
+ .getLocalMediaUrl());
+ setStatus(PlayerStatus.PREPARING);
+ player.prepareAsync();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ } else {
+ setStatus(PlayerStatus.ERROR);
+ sendBroadcast(new Intent(
+ ACTION_SHUTDOWN_PLAYBACK_SERVICE));
+ }
+ }
+ }
+
+ @Override
+ protected void onPreExecute() {
+ setStatus(PlayerStatus.INITIALIZING);
+ }
+
+ };
+ initTask.executeAsync(media);
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ } catch (SecurityException e) {
+ e.printStackTrace();
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
+ }
+ }
+
+ }
+
+ /**
+ * Called when the surface holder of the mediaplayer has to be changed.
+ */
+ private void resetVideoSurface() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Resetting video surface");
+ cancelPositionSaver();
+ player.setDisplay(null);
+ player.reset();
+ player.release();
+ player = createMediaPlayer();
+ status = PlayerStatus.STOPPED;
+ }
public void notifyVideoSurfaceAbandoned() {
resetVideoSurface();
- }
-
- /** Called after service has extracted the media it is supposed to play. */
- private void initMediaplayer() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Setting up media player");
- try {
- if (player != null) {
- player.release();
- }
- player = createMediaPlayer();
- MediaType mediaType = media.getMediaType();
- if (mediaType == MediaType.AUDIO) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Mime type is audio");
-
- InitTask initTask = new InitTask() {
-
- @Override
- protected void onPostExecute(Playable result) {
- // check if state of service has changed. If it has
- // changed, assume that loaded metadata is not needed
- // anymore.
- if (status == PlayerStatus.INITIALIZING) {
- if (result != null) {
- playingVideo = false;
- try {
- if (shouldStream) {
- player.setDataSource(media
- .getStreamUrl());
- } else if (media.localFileAvailable()) {
- player.setDataSource(media
- .getLocalMediaUrl());
- }
-
- if (prepareImmediately) {
- setStatus(PlayerStatus.PREPARING);
- player.prepareAsync();
- } else {
- setStatus(PlayerStatus.INITIALIZED);
- }
- } catch (IOException e) {
- e.printStackTrace();
- media = null;
- setStatus(PlayerStatus.ERROR);
- sendBroadcast(new Intent(
- ACTION_SHUTDOWN_PLAYBACK_SERVICE));
- }
- } else {
- Log.e(TAG, "InitTask could not load metadata");
- media = null;
- setStatus(PlayerStatus.ERROR);
- sendBroadcast(new Intent(
- ACTION_SHUTDOWN_PLAYBACK_SERVICE));
- }
- } else {
- if (AppConfig.DEBUG)
- Log.d(TAG,
- "Status of player has changed during initialization. Stopping init process.");
- }
- }
-
- @Override
- protected void onPreExecute() {
- setStatus(PlayerStatus.INITIALIZING);
- }
-
- };
- initTask.executeAsync(media);
- } else if (mediaType == MediaType.VIDEO) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Mime type is video");
- playingVideo = true;
- setStatus(PlayerStatus.AWAITING_VIDEO_SURFACE);
- player.setScreenOnWhilePlaying(true);
- }
-
- } catch (IllegalArgumentException e) {
- e.printStackTrace();
- } catch (SecurityException e) {
- e.printStackTrace();
- } catch (IllegalStateException e) {
- e.printStackTrace();
- }
- }
+ if (media != null) {
+ initMediaplayer(true);
+ }
+ }
+
+ /**
+ * Called after service has extracted the media it is supposed to play.
+ *
+ * @param prepareImmediately True if service should prepare playback after it has been initialized
+ */
+ private void initMediaplayer(final boolean prepareImmediately) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Setting up media player");
+ try {
+ MediaType mediaType = media.getMediaType();
+ if (mediaType == MediaType.AUDIO) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Mime type is audio");
+
+ InitTask initTask = new InitTask() {
+
+ @Override
+ protected void onPostExecute(Playable result) {
+ // check if state of service has changed. If it has
+ // changed, assume that loaded metadata is not needed
+ // anymore.
+ if (status == PlayerStatus.INITIALIZING) {
+ if (result != null) {
+ playingVideo = false;
+ try {
+ if (shouldStream) {
+ player.setDataSource(media
+ .getStreamUrl());
+ } else if (media.localFileAvailable()) {
+ player.setDataSource(media
+ .getLocalMediaUrl());
+ }
+
+ if (prepareImmediately) {
+ setStatus(PlayerStatus.PREPARING);
+ player.prepareAsync();
+ } else {
+ setStatus(PlayerStatus.INITIALIZED);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ media = null;
+ setStatus(PlayerStatus.ERROR);
+ sendBroadcast(new Intent(
+ ACTION_SHUTDOWN_PLAYBACK_SERVICE));
+ }
+ } else {
+ Log.e(TAG, "InitTask could not load metadata");
+ media = null;
+ setStatus(PlayerStatus.ERROR);
+ sendBroadcast(new Intent(
+ ACTION_SHUTDOWN_PLAYBACK_SERVICE));
+ }
+ } else {
+ if (AppConfig.DEBUG)
+ Log.d(TAG,
+ "Status of player has changed during initialization. Stopping init process.");
+ }
+ }
+
+ @Override
+ protected void onPreExecute() {
+ setStatus(PlayerStatus.INITIALIZING);
+ }
+
+ };
+ initTask.executeAsync(media);
+ } else if (mediaType == MediaType.VIDEO) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Mime type is video");
+ playingVideo = true;
+ setStatus(PlayerStatus.AWAITING_VIDEO_SURFACE);
+ player.setScreenOnWhilePlaying(true);
+ }
+
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ } catch (SecurityException e) {
+ e.printStackTrace();
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
+ }
+ }
private void setupPositionSaver() {
if (positionSaverFuture == null
@@ -640,244 +692,246 @@ public class PlaybackService extends Service {
}
}
- private final com.aocate.media.MediaPlayer.OnPreparedListener audioPreparedListener = new com.aocate.media.MediaPlayer.OnPreparedListener() {
- @Override
- public void onPrepared(com.aocate.media.MediaPlayer mp) {
- genericOnPrepared(mp);
- }
- };
-
- private final android.media.MediaPlayer.OnPreparedListener videoPreparedListener = new android.media.MediaPlayer.OnPreparedListener() {
- @Override
- public void onPrepared(android.media.MediaPlayer mp) {
- genericOnPrepared(mp);
- }
- };
-
- private final void genericOnPrepared(Object inObj) {
- IPlayer mp = DuckType.coerce(inObj).to(IPlayer.class);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Resource prepared");
- mp.seekTo(media.getPosition());
- if (media.getDuration() == 0) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Setting duration of media");
- media.setDuration(mp.getDuration());
- }
- setStatus(PlayerStatus.PREPARED);
- if (chapterLoader != null) {
- chapterLoader.interrupt();
- }
- chapterLoader = new Thread() {
- @Override
- public void run() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Chapter loader started");
- if (media != null && media.getChapters() == null) {
- media.loadChapterMarks();
- if (!isInterrupted() && media.getChapters() != null) {
- sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0);
- }
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "Chapter loader stopped");
- }
- };
- chapterLoader.start();
-
- if (startWhenPrepared) {
- play();
- }
- }
-
- private final com.aocate.media.MediaPlayer.OnSeekCompleteListener audioSeekCompleteListener = new com.aocate.media.MediaPlayer.OnSeekCompleteListener() {
- @Override
- public void onSeekComplete(com.aocate.media.MediaPlayer mp) {
- genericSeekCompleteListener();
- }
- };
-
- private final android.media.MediaPlayer.OnSeekCompleteListener videoSeekCompleteListener = new android.media.MediaPlayer.OnSeekCompleteListener() {
- @Override
- public void onSeekComplete(android.media.MediaPlayer mp) {
- genericSeekCompleteListener();
- }
- };
-
- private final void genericSeekCompleteListener() {
- if (status == PlayerStatus.SEEKING) {
- setStatus(statusBeforeSeek);
- }
- }
-
- private final com.aocate.media.MediaPlayer.OnInfoListener audioInfoListener = new com.aocate.media.MediaPlayer.OnInfoListener() {
- @Override
- public boolean onInfo(com.aocate.media.MediaPlayer mp, int what,
- int extra) {
- return genericInfoListener(what);
- }
- };
-
- private final android.media.MediaPlayer.OnInfoListener videoInfoListener = new android.media.MediaPlayer.OnInfoListener() {
- @Override
- public boolean onInfo(android.media.MediaPlayer mp, int what, int extra) {
- return genericInfoListener(what);
- }
- };
-
- private boolean genericInfoListener(int what) {
- switch (what) {
- case MediaPlayer.MEDIA_INFO_BUFFERING_START:
- sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_START, 0);
- return true;
- case MediaPlayer.MEDIA_INFO_BUFFERING_END:
- sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_END, 0);
- return true;
- default:
- return false;
- }
- }
-
- private final com.aocate.media.MediaPlayer.OnErrorListener audioErrorListener = new com.aocate.media.MediaPlayer.OnErrorListener() {
- @Override
- public boolean onError(com.aocate.media.MediaPlayer mp, int what,
- int extra) {
- return genericOnError(mp, what, extra);
- }
- };
-
- private final android.media.MediaPlayer.OnErrorListener videoErrorListener = new android.media.MediaPlayer.OnErrorListener() {
- @Override
- public boolean onError(android.media.MediaPlayer mp, int what, int extra) {
- return genericOnError(mp, what, extra);
- }
- };
-
- private boolean genericOnError(Object inObj, int what, int extra) {
- final String TAG = "PlaybackService.onErrorListener";
- Log.w(TAG, "An error has occured: " + what + " " + extra);
- IPlayer mp = DuckType.coerce(inObj).to(IPlayer.class);
- if (mp.isPlaying()) {
- pause(true, true);
- }
- sendNotificationBroadcast(NOTIFICATION_TYPE_ERROR, what);
- setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING);
- stopSelf();
- return true;
- }
-
- private final com.aocate.media.MediaPlayer.OnCompletionListener audioCompletionListener = new com.aocate.media.MediaPlayer.OnCompletionListener() {
- @Override
- public void onCompletion(com.aocate.media.MediaPlayer mp) {
- genericOnCompletion();
- }
- };
-
- private final android.media.MediaPlayer.OnCompletionListener videoCompletionListener = new android.media.MediaPlayer.OnCompletionListener() {
- @Override
- public void onCompletion(android.media.MediaPlayer mp) {
- genericOnCompletion();
- }
- };
-
- private void genericOnCompletion() {
- endPlayback(true);
- }
-
- private final com.aocate.media.MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener = new com.aocate.media.MediaPlayer.OnBufferingUpdateListener() {
- @Override
- public void onBufferingUpdate(com.aocate.media.MediaPlayer mp,
- int percent) {
- genericOnBufferingUpdate(percent);
- }
- };
-
- private final android.media.MediaPlayer.OnBufferingUpdateListener videoBufferingUpdateListener = new android.media.MediaPlayer.OnBufferingUpdateListener() {
- @Override
- public void onBufferingUpdate(android.media.MediaPlayer mp, int percent) {
- genericOnBufferingUpdate(percent);
- }
- };
-
- private void genericOnBufferingUpdate(int percent) {
- sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent);
- }
-
- private void endPlayback(boolean playNextEpisode) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Playback ended");
- audioManager.abandonAudioFocus(audioFocusChangeListener);
-
- // Save state
- cancelPositionSaver();
-
- boolean isInQueue = false;
- FeedItem nextItem = null;
-
- if (media instanceof FeedMedia) {
- FeedItem item = ((FeedMedia) media).getItem();
- ((FeedMedia) media).setPlaybackCompletionDate(new Date());
- manager.markItemRead(PlaybackService.this, item, true, true);
- nextItem = manager.getQueueSuccessorOfItem(item);
- isInQueue = media instanceof FeedMedia
- && manager.isInQueue(((FeedMedia) media).getItem());
- if (isInQueue) {
- manager.removeQueueItem(PlaybackService.this, item, true);
- }
- manager.addItemToPlaybackHistory(PlaybackService.this, item);
- manager.setFeedMedia(PlaybackService.this, (FeedMedia) media);
- long autoDeleteMediaId = ((FeedComponent) media).getId();
- if (shouldStream) {
- autoDeleteMediaId = -1;
- }
- }
-
- // Load next episode if previous episode was in the queue and if there
- // is an episode in the queue left.
- // Start playback immediately if continuous playback is enabled
- boolean loadNextItem = isInQueue && nextItem != null;
- playNextEpisode = playNextEpisode && loadNextItem
- && UserPreferences.isFollowQueue();
- if (loadNextItem) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Loading next item in queue");
- media = nextItem.getMedia();
- }
-
- if (playNextEpisode) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Playback of next episode will start immediately.");
- prepareImmediately = startWhenPrepared = true;
- } else {
- if (AppConfig.DEBUG)
- Log.d(TAG, "No more episodes available to play");
- media = null;
- prepareImmediately = startWhenPrepared = false;
- stopForeground(true);
- stopWidgetUpdater();
- }
-
- int notificationCode = 0;
- if (media != null) {
- shouldStream = !media.localFileAvailable();
- if (media.getMediaType() == MediaType.AUDIO) {
- notificationCode = EXTRA_CODE_AUDIO;
- playingVideo = false;
- } else if (media.getMediaType() == MediaType.VIDEO) {
- notificationCode = EXTRA_CODE_VIDEO;
- }
- }
- writePlaybackPreferences();
- if (media != null) {
- resetVideoSurface();
- refreshRemoteControlClientState();
- sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD,
- notificationCode);
- } else {
- sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0);
- stopSelf();
- }
- }
+ private final com.aocate.media.MediaPlayer.OnPreparedListener audioPreparedListener = new com.aocate.media.MediaPlayer.OnPreparedListener() {
+ @Override
+ public void onPrepared(com.aocate.media.MediaPlayer mp) {
+ genericOnPrepared(mp);
+ }
+ };
+
+ private final android.media.MediaPlayer.OnPreparedListener videoPreparedListener = new android.media.MediaPlayer.OnPreparedListener() {
+ @Override
+ public void onPrepared(android.media.MediaPlayer mp) {
+ genericOnPrepared(mp);
+ }
+ };
+
+ private final void genericOnPrepared(Object inObj) {
+ IPlayer mp = DuckType.coerce(inObj).to(IPlayer.class);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Resource prepared");
+ mp.seekTo(media.getPosition());
+ if (media.getDuration() == 0) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Setting duration of media");
+ media.setDuration(mp.getDuration());
+ }
+ setStatus(PlayerStatus.PREPARED);
+ if (chapterLoader != null) {
+ chapterLoader.interrupt();
+ }
+ chapterLoader = new Thread() {
+ @Override
+ public void run() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Chapter loader started");
+ if (media != null && media.getChapters() == null) {
+ media.loadChapterMarks();
+ if (!isInterrupted() && media.getChapters() != null) {
+ sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD,
+ 0);
+ }
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Chapter loader stopped");
+ }
+ };
+ chapterLoader.start();
+
+ if (startWhenPrepared) {
+ play();
+ }
+ }
+
+ private final com.aocate.media.MediaPlayer.OnSeekCompleteListener audioSeekCompleteListener = new com.aocate.media.MediaPlayer.OnSeekCompleteListener() {
+ @Override
+ public void onSeekComplete(com.aocate.media.MediaPlayer mp) {
+ genericSeekCompleteListener();
+ }
+ };
+
+ private final android.media.MediaPlayer.OnSeekCompleteListener videoSeekCompleteListener = new android.media.MediaPlayer.OnSeekCompleteListener() {
+ @Override
+ public void onSeekComplete(android.media.MediaPlayer mp) {
+ genericSeekCompleteListener();
+ }
+ };
+
+ private final void genericSeekCompleteListener() {
+ if (status == PlayerStatus.SEEKING) {
+ setStatus(statusBeforeSeek);
+ }
+ }
+
+ private final com.aocate.media.MediaPlayer.OnInfoListener audioInfoListener = new com.aocate.media.MediaPlayer.OnInfoListener() {
+ @Override
+ public boolean onInfo(com.aocate.media.MediaPlayer mp, int what,
+ int extra) {
+ return genericInfoListener(what);
+ }
+ };
+
+ private final android.media.MediaPlayer.OnInfoListener videoInfoListener = new android.media.MediaPlayer.OnInfoListener() {
+ @Override
+ public boolean onInfo(android.media.MediaPlayer mp, int what, int extra) {
+ return genericInfoListener(what);
+ }
+ };
+
+ private boolean genericInfoListener(int what) {
+ switch (what) {
+ case MediaPlayer.MEDIA_INFO_BUFFERING_START:
+ sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_START, 0);
+ return true;
+ case MediaPlayer.MEDIA_INFO_BUFFERING_END:
+ sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_END, 0);
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private final com.aocate.media.MediaPlayer.OnErrorListener audioErrorListener = new com.aocate.media.MediaPlayer.OnErrorListener() {
+ @Override
+ public boolean onError(com.aocate.media.MediaPlayer mp, int what,
+ int extra) {
+ return genericOnError(mp, what, extra);
+ }
+ };
+
+ private final android.media.MediaPlayer.OnErrorListener videoErrorListener = new android.media.MediaPlayer.OnErrorListener() {
+ @Override
+ public boolean onError(android.media.MediaPlayer mp, int what, int extra) {
+ return genericOnError(mp, what, extra);
+ }
+ };
+
+ private boolean genericOnError(Object inObj, int what, int extra) {
+ final String TAG = "PlaybackService.onErrorListener";
+ Log.w(TAG, "An error has occured: " + what + " " + extra);
+ IPlayer mp = DuckType.coerce(inObj).to(IPlayer.class);
+ if (mp.isPlaying()) {
+ pause(true, true);
+ }
+ sendNotificationBroadcast(NOTIFICATION_TYPE_ERROR, what);
+ setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING);
+ stopSelf();
+ return true;
+ }
+
+ private final com.aocate.media.MediaPlayer.OnCompletionListener audioCompletionListener = new com.aocate.media.MediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(com.aocate.media.MediaPlayer mp) {
+ genericOnCompletion();
+ }
+ };
+
+ private final android.media.MediaPlayer.OnCompletionListener videoCompletionListener = new android.media.MediaPlayer.OnCompletionListener() {
+ @Override
+ public void onCompletion(android.media.MediaPlayer mp) {
+ genericOnCompletion();
+ }
+ };
+
+ private void genericOnCompletion() {
+ endPlayback(true);
+ }
+
+ private final com.aocate.media.MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener = new com.aocate.media.MediaPlayer.OnBufferingUpdateListener() {
+ @Override
+ public void onBufferingUpdate(com.aocate.media.MediaPlayer mp,
+ int percent) {
+ genericOnBufferingUpdate(percent);
+ }
+ };
+
+ private final android.media.MediaPlayer.OnBufferingUpdateListener videoBufferingUpdateListener = new android.media.MediaPlayer.OnBufferingUpdateListener() {
+ @Override
+ public void onBufferingUpdate(android.media.MediaPlayer mp, int percent) {
+ genericOnBufferingUpdate(percent);
+ }
+ };
+
+ private void genericOnBufferingUpdate(int percent) {
+ sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent);
+ }
+
+ private void endPlayback(boolean playNextEpisode) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Playback ended");
+ audioManager.abandonAudioFocus(audioFocusChangeListener);
+
+ // Save state
+ cancelPositionSaver();
+
+ boolean isInQueue = false;
+ FeedItem nextItem = null;
+
+ if (media instanceof FeedMedia) {
+ FeedItem item = ((FeedMedia) media).getItem();
+ DBWriter.markItemRead(PlaybackService.this, item, true, true);
+ nextItem = DBTasks.getQueueSuccessorOfItem(this, item.getId(), queue);
+ isInQueue = media instanceof FeedMedia
+ && QueueAccess.ItemListAccess(queue).contains(((FeedMedia) media).getItem().getId());
+ if (isInQueue) {
+ DBWriter.removeQueueItem(PlaybackService.this, item.getId(), true);
+ }
+ DBWriter.addItemToPlaybackHistory(PlaybackService.this, (FeedMedia) media);
+ DBWriter.setFeedMedia(PlaybackService.this, (FeedMedia) media);
+ long autoDeleteMediaId = ((FeedComponent) media).getId();
+ if (shouldStream) {
+ autoDeleteMediaId = -1;
+ }
+ }
+
+ // Load next episode if previous episode was in the queue and if there
+ // is an episode in the queue left.
+ // Start playback immediately if continuous playback is enabled
+ boolean loadNextItem = isInQueue && nextItem != null;
+ playNextEpisode = playNextEpisode && loadNextItem
+ && UserPreferences.isFollowQueue();
+ if (loadNextItem) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Loading next item in queue");
+ media = nextItem.getMedia();
+ }
+ final boolean prepareImmediately;
+ if (playNextEpisode) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Playback of next episode will start immediately.");
+ prepareImmediately = startWhenPrepared = true;
+ } else {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "No more episodes available to play");
+ media = null;
+ prepareImmediately = startWhenPrepared = false;
+ stopForeground(true);
+ stopWidgetUpdater();
+ }
+
+ int notificationCode = 0;
+ if (media != null) {
+ shouldStream = !media.localFileAvailable();
+ if (media.getMediaType() == MediaType.AUDIO) {
+ notificationCode = EXTRA_CODE_AUDIO;
+ playingVideo = false;
+ } else if (media.getMediaType() == MediaType.VIDEO) {
+ notificationCode = EXTRA_CODE_VIDEO;
+ }
+ }
+ writePlaybackPreferences();
+ if (media != null) {
+ resetVideoSurface();
+ refreshRemoteControlClientState();
+ initMediaplayer(prepareImmediately);
+
+ sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD,
+ notificationCode);
+ } else {
+ sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0);
+ stopSelf();
+ }
+ }
public void setSleepTimer(long waitingTime) {
if (AppConfig.DEBUG)
@@ -903,26 +957,26 @@ public class PlaybackService extends Service {
/**
* Saves the current position and pauses playback. Note that, if audiofocus
* is abandoned, the lockscreen controls will also disapear.
- *
+ *
* @param abandonFocus
* is true if the service should release audio focus
- * @param reset
+ * @param reinit
* is true if service should reinit after pausing if the media
* file is being streamed
*/
public void pause(boolean abandonFocus, boolean reinit) {
- if (player != null && player.isPlaying()) {
+ if (player.isPlaying()) {
if (AppConfig.DEBUG)
Log.d(TAG, "Pausing playback.");
player.pause();
+ cancelPositionSaver();
+ saveCurrentPosition();
+ setStatus(PlayerStatus.PAUSED);
if (abandonFocus) {
audioManager.abandonAudioFocus(audioFocusChangeListener);
pausedBecauseOfTransientAudiofocusLoss = false;
disableSleepTimer();
}
- cancelPositionSaver();
- saveCurrentPosition();
- setStatus(PlayerStatus.PAUSED);
stopWidgetUpdater();
stopForeground(true);
if (shouldStream && reinit) {
@@ -960,8 +1014,8 @@ public class PlaybackService extends Service {
/** Resets the media player and moves into INITIALIZED state. */
public void reinit() {
player.reset();
- prepareImmediately = false;
- initMediaplayer();
+ player = createMediaPlayer(player);
+ initMediaplayer(false);
}
@SuppressLint("NewApi")
@@ -981,7 +1035,7 @@ public class PlaybackService extends Service {
player.start();
if (status != PlayerStatus.PAUSED) {
- player.seekTo(media.getPosition());
+ player.seekTo((int) media.getPosition());
}
setStatus(PlayerStatus.PLAYING);
setupPositionSaver();
@@ -1164,7 +1218,7 @@ public class PlaybackService extends Service {
/**
* Seek a specific position from the current position
- *
+ *
* @param delta
* offset from current position (positive or negative)
* */
@@ -1328,8 +1382,10 @@ public class PlaybackService extends Service {
i.putExtra("album", media.getFeedTitle());
i.putExtra("track", media.getEpisodeTitle());
i.putExtra("playing", isPlaying);
- i.putExtra("ListSize", manager.getQueueSize(false));
- i.putExtra("duration", media.getDuration());
+ if (queue != null) {
+ i.putExtra("ListSize", queue.size());
+ }
+ i.putExtra("duration", media.getDuration());
i.putExtra("position", media.getPosition());
sendBroadcast(i);
}
@@ -1338,7 +1394,7 @@ public class PlaybackService extends Service {
* Pauses playback when the headset is disconnected and the preference is
* set
*/
- private final BroadcastReceiver headsetDisconnected = new BroadcastReceiver() {
+ private BroadcastReceiver headsetDisconnected = new BroadcastReceiver() {
private static final String TAG = "headsetDisconnected";
private static final int UNPLUGGED = 0;
@@ -1361,7 +1417,7 @@ public class PlaybackService extends Service {
}
};
- private final BroadcastReceiver audioBecomingNoisy = new BroadcastReceiver() {
+ private BroadcastReceiver audioBecomingNoisy = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -1381,7 +1437,7 @@ public class PlaybackService extends Service {
}
}
- private final BroadcastReceiver shutdownReceiver = new BroadcastReceiver() {
+ private BroadcastReceiver shutdownReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -1394,7 +1450,7 @@ public class PlaybackService extends Service {
};
- private final BroadcastReceiver skipCurrentEpisodeReceiver = new BroadcastReceiver() {
+ private BroadcastReceiver skipCurrentEpisodeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ACTION_SKIP_CURRENT_EPISODE)) {
@@ -1408,7 +1464,7 @@ public class PlaybackService extends Service {
}
}
}
- };
+ };
/** Periodically saves the position of the media file */
class PositionSaver implements Runnable {
@@ -1523,53 +1579,6 @@ public class PlaybackService extends Service {
postStatusUpdateIntent();
}
- public boolean canSetSpeed() {
- if (media != null && media.getMediaType() == MediaType.AUDIO) {
- return ((AudioPlayer) player).canSetSpeed();
- }
- return false;
- }
-
- public boolean canSetPitch() {
- if (media != null && media.getMediaType() == MediaType.AUDIO) {
- return ((AudioPlayer) player).canSetPitch();
- }
- return false;
- }
-
- public void setSpeed(double speed) {
- if (media != null && media.getMediaType() == MediaType.AUDIO) {
- AudioPlayer audioPlayer = (AudioPlayer) player;
- if (audioPlayer.canSetSpeed()) {
- audioPlayer.setPlaybackSpeed((float) speed);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Playback speed was set to " + speed);
- sendNotificationBroadcast(
- NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE, 0);
- }
- }
- }
-
- public void setPitch(double pitch) {
- if (media != null && media.getMediaType() == MediaType.AUDIO) {
- AudioPlayer audioPlayer = (AudioPlayer) player;
- if (audioPlayer.canSetPitch()) {
- audioPlayer.setPlaybackPitch((float) pitch);
- }
- }
- }
-
- public double getCurrentPlaybackSpeed() {
- if (media.getMediaType() == MediaType.AUDIO
- && player instanceof AudioPlayer) {
- AudioPlayer audioPlayer = (AudioPlayer) player;
- if (audioPlayer.canSetSpeed()) {
- return audioPlayer.getCurrentSpeedMultiplier();
- }
- }
- return -1;
- }
-
/**
* call getDuration() on mediaplayer or return INVALID_TIME if player is in
* an invalid state. This method should be used instead of calling
@@ -1624,6 +1633,52 @@ public class PlaybackService extends Service {
editor.commit();
}
+ public boolean canSetSpeed() {
+ if (media != null && media.getMediaType() == MediaType.AUDIO) {
+ return player.canSetSpeed();
+ }
+ return false;
+ }
+
+ public boolean canSetPitch() {
+ if (media != null && media.getMediaType() == MediaType.AUDIO) {
+ return player.canSetPitch();
+ }
+ return false;
+ }
+
+ public void setSpeed(double speed) {
+ if (media != null && media.getMediaType() == MediaType.AUDIO) {
+ AudioPlayer audioPlayer = (AudioPlayer) player;
+ if (audioPlayer.canSetSpeed()) {
+ audioPlayer.setPlaybackSpeed((float) speed);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Playback speed was set to " + speed);
+ sendNotificationBroadcast(
+ NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE, 0);
+ }
+ }
+ }
+
+ public void setPitch(double pitch) {
+ if (media != null && media.getMediaType() == MediaType.AUDIO) {
+ if (player.canSetPitch()) {
+ player.setPlaybackPitch((float) pitch);
+ }
+ }
+ }
+
+ public double getCurrentPlaybackSpeed() {
+ if (media.getMediaType() == MediaType.AUDIO
+ && player instanceof AudioPlayer) {
+ AudioPlayer audioPlayer = (AudioPlayer) player;
+ if (audioPlayer.canSetSpeed()) {
+ return audioPlayer.getCurrentSpeedMultiplier();
+ }
+ }
+ return -1;
+ }
+
private static class InitTask extends AsyncTask<Playable, Void, Playable> {
private Playable playable;
public PlayableException exception;
@@ -1656,4 +1711,16 @@ public class PlaybackService extends Service {
}
}
+
+ private void loadQueue() {
+ dbLoaderExecutor.submit(new QueueLoaderTask());
+ }
+
+ private class QueueLoaderTask implements Runnable {
+ @Override
+ public void run() {
+ List<FeedItem> queueRef = DBReader.getQueue(PlaybackService.this);
+ queue = queueRef;
+ }
+ }
}
diff --git a/src/de/danoeh/antennapod/service/download/DownloadRequest.java b/src/de/danoeh/antennapod/service/download/DownloadRequest.java
new file mode 100644
index 000000000..1f4e32e1b
--- /dev/null
+++ b/src/de/danoeh/antennapod/service/download/DownloadRequest.java
@@ -0,0 +1,177 @@
+package de.danoeh.antennapod.service.download;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class DownloadRequest implements Parcelable {
+
+ private final String destination;
+ private final String source;
+ private final String title;
+ private final long feedfileId;
+ private final int feedfileType;
+
+ protected int progressPercent;
+ protected long soFar;
+ protected long size;
+ protected int statusMsg;
+
+ public DownloadRequest(String destination, String source, String title,
+ long feedfileId, int feedfileType) {
+ if (destination == null) {
+ throw new IllegalArgumentException("Destination must not be null");
+ }
+ if (source == null) {
+ throw new IllegalArgumentException("Source must not be null");
+ }
+ if (title == null) {
+ throw new IllegalArgumentException("Title must not be null");
+ }
+
+ this.destination = destination;
+ this.source = source;
+ this.title = title;
+ this.feedfileId = feedfileId;
+ this.feedfileType = feedfileType;
+ }
+
+ private DownloadRequest(Parcel in) {
+ destination = in.readString();
+ source = in.readString();
+ title = in.readString();
+ feedfileId = in.readLong();
+ feedfileType = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(destination);
+ dest.writeString(source);
+ dest.writeString(title);
+ dest.writeLong(feedfileId);
+ dest.writeInt(feedfileType);
+ }
+
+ public static final Parcelable.Creator<DownloadRequest> CREATOR = new Parcelable.Creator<DownloadRequest>() {
+ public DownloadRequest createFromParcel(Parcel in) {
+ return new DownloadRequest(in);
+ }
+
+ public DownloadRequest[] newArray(int size) {
+ return new DownloadRequest[size];
+ }
+ };
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result
+ + ((destination == null) ? 0 : destination.hashCode());
+ result = prime * result + (int) (feedfileId ^ (feedfileId >>> 32));
+ result = prime * result + feedfileType;
+ result = prime * result + progressPercent;
+ result = prime * result + (int) (size ^ (size >>> 32));
+ result = prime * result + (int) (soFar ^ (soFar >>> 32));
+ result = prime * result + ((source == null) ? 0 : source.hashCode());
+ result = prime * result + statusMsg;
+ result = prime * result + ((title == null) ? 0 : title.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ DownloadRequest other = (DownloadRequest) obj;
+ if (destination == null) {
+ if (other.destination != null)
+ return false;
+ } else if (!destination.equals(other.destination))
+ return false;
+ if (feedfileId != other.feedfileId)
+ return false;
+ if (feedfileType != other.feedfileType)
+ return false;
+ if (progressPercent != other.progressPercent)
+ return false;
+ if (size != other.size)
+ return false;
+ if (soFar != other.soFar)
+ return false;
+ if (source == null) {
+ if (other.source != null)
+ return false;
+ } else if (!source.equals(other.source))
+ return false;
+ if (statusMsg != other.statusMsg)
+ return false;
+ if (title == null) {
+ if (other.title != null)
+ return false;
+ } else if (!title.equals(other.title))
+ return false;
+ return true;
+ }
+
+ public String getDestination() {
+ return destination;
+ }
+
+ public String getSource() {
+ return source;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public long getFeedfileId() {
+ return feedfileId;
+ }
+
+ public int getFeedfileType() {
+ return feedfileType;
+ }
+
+ public int getProgressPercent() {
+ return progressPercent;
+ }
+
+ public void setProgressPercent(int progressPercent) {
+ this.progressPercent = progressPercent;
+ }
+
+ public long getSoFar() {
+ return soFar;
+ }
+
+ public void setSoFar(long soFar) {
+ this.soFar = soFar;
+ }
+
+ public long getSize() {
+ return size;
+ }
+
+ public void setSize(long size) {
+ this.size = size;
+ }
+
+ public int getStatusMsg() {
+ return statusMsg;
+ }
+
+ public void setStatusMsg(int statusMsg) {
+ this.statusMsg = statusMsg;
+ }
+}
diff --git a/src/de/danoeh/antennapod/service/download/DownloadService.java b/src/de/danoeh/antennapod/service/download/DownloadService.java
index e1230e170..c84a6f913 100644
--- a/src/de/danoeh/antennapod/service/download/DownloadService.java
+++ b/src/de/danoeh/antennapod/service/download/DownloadService.java
@@ -1,28 +1,17 @@
-/**
- * Registers a DownloadReceiver and waits for all Downloads
- * to complete, then stops
- * */
-
package de.danoeh.antennapod.service.download;
import java.io.File;
import java.io.IOException;
-import java.lang.Thread.UncaughtExceptionHandler;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Date;
import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.RejectedExecutionHandler;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
import javax.xml.parsers.ParserConfigurationException;
+import de.danoeh.antennapod.storage.*;
import org.xml.sax.SAXException;
import android.annotation.SuppressLint;
@@ -41,8 +30,6 @@ import android.os.AsyncTask;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.webkit.URLUtil;
@@ -50,904 +37,881 @@ import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.DownloadActivity;
import de.danoeh.antennapod.activity.DownloadLogActivity;
-import de.danoeh.antennapod.asynctask.DownloadStatus;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.Feed;
-import de.danoeh.antennapod.feed.FeedFile;
import de.danoeh.antennapod.feed.FeedImage;
import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.feed.FeedMedia;
-import de.danoeh.antennapod.storage.DownloadRequestException;
-import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.syndication.handler.FeedHandler;
import de.danoeh.antennapod.syndication.handler.UnsupportedFeedtypeException;
import de.danoeh.antennapod.util.ChapterUtils;
import de.danoeh.antennapod.util.DownloadError;
import de.danoeh.antennapod.util.InvalidFeedException;
+/**
+ * Manages the download of feedfiles in the app. Downloads can be enqueued viathe startService intent.
+ * The argument of the intent is an instance of DownloadRequest in the EXTRA_REQUEST field of
+ * the intent.
+ * After the downloads have finished, the downloaded object will be passed on to a specific handler, depending on the
+ * type of the feedfile.
+ */
public class DownloadService extends Service {
- private static final String TAG = "DownloadService";
-
- public static String ACTION_ALL_FEED_DOWNLOADS_COMPLETED = "action.de.danoeh.antennapod.storage.all_feed_downloads_completed";
-
- public static final String ACTION_ENQUEUE_DOWNLOAD = "action.de.danoeh.antennapod.service.enqueueDownload";
- public static final String ACTION_CANCEL_DOWNLOAD = "action.de.danoeh.antennapod.service.cancelDownload";
- public static final String ACTION_CANCEL_ALL_DOWNLOADS = "action.de.danoeh.antennapod.service.cancelAllDownloads";
-
- /** Extra for ACTION_CANCEL_DOWNLOAD */
- public static final String EXTRA_DOWNLOAD_URL = "downloadUrl";
-
- /**
- * Sent by the DownloadService when the content of the downloads list
- * changes.
- */
- public static final String ACTION_DOWNLOADS_CONTENT_CHANGED = "action.de.danoeh.antennapod.service.downloadsContentChanged";
-
- public static final String EXTRA_DOWNLOAD_ID = "extra.de.danoeh.antennapod.service.download_id";
-
- /** Extra for ACTION_ENQUEUE_DOWNLOAD intent. */
- public static final String EXTRA_REQUEST = "request";
-
- private CopyOnWriteArrayList<DownloadStatus> completedDownloads;
-
- private ExecutorService syncExecutor;
- private ExecutorService downloadExecutor;
- /** Number of threads of downloadExecutor. */
- private static final int NUM_PARALLEL_DOWNLOADS = 4;
-
- private DownloadRequester requester;
- private FeedManager manager;
- private NotificationCompat.Builder notificationCompatBuilder;
- private Notification.BigTextStyle notificationBuilder;
- private int NOTIFICATION_ID = 2;
- private int REPORT_ID = 3;
-
- private List<Downloader> downloads;
-
- /** Number of completed downloads which are currently being handled. */
- private volatile int downloadsBeingHandled;
-
- private volatile boolean shutdownInitiated = false;
- /** True if service is running. */
- public static boolean isRunning = false;
-
- private Handler handler;
-
- private NotificationUpdater notificationUpdater;
- private ScheduledFuture notificationUpdaterFuture;
- private static final int SCHED_EX_POOL_SIZE = 1;
- private ScheduledThreadPoolExecutor schedExecutor;
-
- private final IBinder mBinder = new LocalBinder();
-
- public class LocalBinder extends Binder {
- public DownloadService getService() {
- return DownloadService.this;
- }
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (intent.getParcelableExtra(EXTRA_REQUEST) != null) {
- onDownloadQueued(intent);
- }
- return Service.START_NOT_STICKY;
- }
-
- @SuppressLint("NewApi")
- @Override
- public void onCreate() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Service started");
- isRunning = true;
- handler = new Handler();
- completedDownloads = new CopyOnWriteArrayList<DownloadStatus>(
- new ArrayList<DownloadStatus>());
- downloads = new ArrayList<Downloader>();
- registerReceiver(downloadQueued, new IntentFilter(
- ACTION_ENQUEUE_DOWNLOAD));
-
- IntentFilter cancelDownloadReceiverFilter = new IntentFilter();
- cancelDownloadReceiverFilter.addAction(ACTION_CANCEL_ALL_DOWNLOADS);
- cancelDownloadReceiverFilter.addAction(ACTION_CANCEL_DOWNLOAD);
- registerReceiver(cancelDownloadReceiver, cancelDownloadReceiverFilter);
- syncExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
-
- @Override
- public Thread newThread(Runnable r) {
- Thread t = new Thread(r);
- t.setPriority(Thread.MIN_PRIORITY);
- t.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
-
- @Override
- public void uncaughtException(Thread thread, Throwable ex) {
- Log.e(TAG, "Thread exited with uncaught exception");
- ex.printStackTrace();
- downloadsBeingHandled -= 1;
- queryDownloads();
- }
- });
- return t;
- }
- });
- downloadExecutor = Executors.newFixedThreadPool(NUM_PARALLEL_DOWNLOADS,
- new ThreadFactory() {
-
- @Override
- public Thread newThread(Runnable r) {
- Thread t = new Thread(r);
- t.setPriority(Thread.MIN_PRIORITY);
- return t;
- }
- });
- schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE,
- new ThreadFactory() {
-
- @Override
- public Thread newThread(Runnable r) {
- Thread t = new Thread(r);
- t.setPriority(Thread.MIN_PRIORITY);
- return t;
- }
- }, new RejectedExecutionHandler() {
-
- @Override
- public void rejectedExecution(Runnable r,
- ThreadPoolExecutor executor) {
- Log.w(TAG, "SchedEx rejected submission of new task");
- }
- });
- setupNotificationBuilders();
- manager = FeedManager.getInstance();
- requester = DownloadRequester.getInstance();
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
-
- @Override
- public void onDestroy() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Service shutting down");
- isRunning = false;
- unregisterReceiver(cancelDownloadReceiver);
- unregisterReceiver(downloadQueued);
- }
-
- @SuppressLint("NewApi")
- private void setupNotificationBuilders() {
- PendingIntent pIntent = PendingIntent.getActivity(this, 0, new Intent(
- this, DownloadActivity.class),
- PendingIntent.FLAG_UPDATE_CURRENT);
-
- Bitmap icon = BitmapFactory.decodeResource(getResources(),
- R.drawable.stat_notify_sync);
-
- if (android.os.Build.VERSION.SDK_INT >= 16) {
- notificationBuilder = new Notification.BigTextStyle(
- new Notification.Builder(this).setOngoing(true)
- .setContentIntent(pIntent).setLargeIcon(icon)
- .setSmallIcon(R.drawable.stat_notify_sync));
- } else {
- notificationCompatBuilder = new NotificationCompat.Builder(this)
- .setOngoing(true).setContentIntent(pIntent)
- .setLargeIcon(icon)
- .setSmallIcon(R.drawable.stat_notify_sync);
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "Notification set up");
- }
-
- /**
- * Updates the contents of the service's notifications. Should be called
- * before setupNotificationBuilders.
- */
- @SuppressLint("NewApi")
- private Notification updateNotifications() {
- String contentTitle = getString(R.string.download_notification_title);
- String downloadsLeft = requester.getNumberOfDownloads()
- + getString(R.string.downloads_left);
- if (android.os.Build.VERSION.SDK_INT >= 16) {
-
- if (notificationBuilder != null) {
-
- StringBuilder bigText = new StringBuilder("");
- for (int i = 0; i < downloads.size(); i++) {
- Downloader downloader = downloads.get(i);
- if (downloader.getStatus() != null) {
- FeedFile f = downloader.getStatus().getFeedFile();
- if (f.getClass() == Feed.class) {
- Feed feed = (Feed) f;
- if (feed.getTitle() != null) {
- if (i > 0) {
- bigText.append("\n");
- }
- bigText.append("\u2022 " + feed.getTitle());
- }
- } else if (f.getClass() == FeedMedia.class) {
- FeedMedia media = (FeedMedia) f;
- if (media.getItem().getTitle() != null) {
- if (i > 0) {
- bigText.append("\n");
- }
- bigText.append("\u2022 "
- + media.getItem().getTitle()
- + " ("
- + downloader.getStatus()
- .getProgressPercent() + "%)");
- }
- }
- }
- }
- notificationBuilder.setSummaryText(downloadsLeft);
- notificationBuilder.setBigContentTitle(contentTitle);
- if (bigText != null) {
- notificationBuilder.bigText(bigText.toString());
- }
- return notificationBuilder.build();
- }
- } else {
- if (notificationCompatBuilder != null) {
- notificationCompatBuilder.setContentTitle(contentTitle);
- notificationCompatBuilder.setContentText(downloadsLeft);
- return notificationCompatBuilder.getNotification();
- }
- }
- return null;
- }
-
- private Downloader getDownloader(String downloadUrl) {
- for (Downloader downloader : downloads) {
- if (downloader.getStatus().getFeedFile().getDownload_url()
- .equals(downloadUrl)) {
- return downloader;
- }
- }
- return null;
- }
-
- private BroadcastReceiver cancelDownloadReceiver = new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(ACTION_CANCEL_DOWNLOAD)) {
- String url = intent.getStringExtra(EXTRA_DOWNLOAD_URL);
- if (url == null) {
- throw new IllegalArgumentException(
- "ACTION_CANCEL_DOWNLOAD intent needs download url extra");
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "Cancelling download with url " + url);
- Downloader d = getDownloader(url);
- if (d != null) {
- d.cancel();
- removeDownload(d);
- } else {
- Log.e(TAG, "Could not cancel download with url " + url);
- }
-
- } else if (intent.getAction().equals(ACTION_CANCEL_ALL_DOWNLOADS)) {
- for (Downloader d : downloads) {
- d.cancel();
- DownloadRequester.getInstance().removeDownload(
- d.getStatus().getFeedFile());
- d.getStatus().getFeedFile().setFile_url(null);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Cancelled all downloads");
- }
- downloads.clear();
- sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
-
- }
- queryDownloads();
- }
-
- };
-
- private void onDownloadQueued(Intent intent) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Received enqueue request");
- Request request = intent.getParcelableExtra(EXTRA_REQUEST);
- if (request == null) {
- throw new IllegalArgumentException(
- "ACTION_ENQUEUE_DOWNLOAD intent needs request extra");
- }
- if (shutdownInitiated) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Cancelling shutdown; new download was queued");
- shutdownInitiated = false;
- }
-
- DownloadRequester requester = DownloadRequester.getInstance();
- FeedFile feedfile = requester.getDownload(request.source);
- if (feedfile != null) {
-
- DownloadStatus status = new DownloadStatus(feedfile,
- feedfile.getHumanReadableIdentifier());
- Downloader downloader = getDownloader(status);
- if (downloader != null) {
- downloads.add(downloader);
- downloadExecutor.submit(downloader);
- sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
- }
- } else {
- Log.e(TAG,
- "Could not find feedfile in download requester when trying to enqueue new download");
- }
- queryDownloads();
- }
-
- private BroadcastReceiver downloadQueued = new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- onDownloadQueued(intent);
- }
-
- };
-
- private Downloader getDownloader(DownloadStatus status) {
- if (URLUtil.isHttpUrl(status.getFeedFile().getDownload_url())) {
- return new HttpDownloader(new DownloaderCallback() {
-
- @Override
- public void onDownloadCompleted(final Downloader downloader) {
- handler.post(new Runnable() {
-
- @Override
- public void run() {
- DownloadService.this
- .onDownloadCompleted(downloader);
- }
- });
- }
- }, status);
- }
- Log.e(TAG, "Could not find appropriate downloader for "
- + status.getFeedFile().getDownload_url());
- return null;
- }
-
- @SuppressLint("NewApi")
- public void onDownloadCompleted(final Downloader downloader) {
- final AsyncTask<Void, Void, Void> handlerTask = new AsyncTask<Void, Void, Void>() {
- boolean successful;
-
- @Override
- protected void onPostExecute(Void result) {
- super.onPostExecute(result);
- if (!successful) {
- queryDownloads();
- }
- }
-
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- removeDownload(downloader);
- }
-
- @Override
- protected Void doInBackground(Void... params) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Received 'Download Complete' - message.");
- downloadsBeingHandled += 1;
- DownloadStatus status = downloader.getStatus();
- status.setCompletionDate(new Date());
- successful = status.isSuccessful();
-
- FeedFile download = status.getFeedFile();
- if (download != null) {
- if (successful) {
- if (download.getClass() == Feed.class) {
- handleCompletedFeedDownload(status);
- } else if (download.getClass() == FeedImage.class) {
- handleCompletedImageDownload(status);
- } else if (download.getClass() == FeedMedia.class) {
- handleCompletedFeedMediaDownload(status);
- }
- } else {
- download.setFile_url(null);
- download.setDownloaded(false);
- if (!successful && !status.isCancelled()) {
- Log.e(TAG, "Download failed");
- saveDownloadStatus(status);
- }
- sendDownloadHandledIntent();
- downloadsBeingHandled -= 1;
- }
- }
- return null;
- }
- };
- if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
- handlerTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
- } else {
- handlerTask.execute();
- }
- }
-
- /**
- * Remove download from the DownloadRequester list and from the
- * DownloadService list.
- */
- private void removeDownload(final Downloader d) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Removing downloader: "
- + d.getStatus().getFeedFile().getDownload_url());
- boolean rc = downloads.remove(d);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Result of downloads.remove: " + rc);
- DownloadRequester.getInstance().removeDownload(
- d.getStatus().getFeedFile());
- sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
- }
-
- /**
- * Adds a new DownloadStatus object to the list of completed downloads and
- * saves it in the database
- *
- * @param status
- * the download that is going to be saved
- */
- private void saveDownloadStatus(DownloadStatus status) {
- completedDownloads.add(status);
- manager.addDownloadStatus(this, status);
- }
-
- private void sendDownloadHandledIntent() {
- EventDistributor.getInstance().sendDownloadHandledBroadcast();
- }
-
- /**
- * Creates a notification at the end of the service lifecycle to notify the
- * user about the number of completed downloads. A report will only be
- * created if the number of successfully downloaded feeds is bigger than 1
- * or if there is at least one failed download which is not an image or if
- * there is at least one downloaded media file.
- */
- private void updateReport() {
- // check if report should be created
- boolean createReport = false;
- int successfulDownloads = 0;
- int failedDownloads = 0;
-
- // a download report is created if at least one download has failed
- // (excluding failed image downloads)
- for (DownloadStatus status : completedDownloads) {
- if (status.isSuccessful()) {
- successfulDownloads++;
- } else if (!status.isCancelled()) {
- if (status.getFeedFile().getClass() != FeedImage.class) {
- createReport = true;
- }
- failedDownloads++;
- }
- }
-
- if (createReport) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Creating report");
- // create notification object
- Notification notification = new NotificationCompat.Builder(this)
- .setTicker(
- getString(de.danoeh.antennapod.R.string.download_report_title))
- .setContentTitle(
- getString(de.danoeh.antennapod.R.string.download_report_title))
- .setContentText(
- String.format(
- getString(R.string.download_report_content),
- successfulDownloads, failedDownloads))
- .setSmallIcon(R.drawable.stat_notify_sync)
- .setLargeIcon(
- BitmapFactory.decodeResource(getResources(),
- R.drawable.stat_notify_sync))
- .setContentIntent(
- PendingIntent.getActivity(this, 0, new Intent(this,
- DownloadLogActivity.class), 0))
- .setAutoCancel(true).getNotification();
- NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- nm.notify(REPORT_ID, notification);
- } else {
- if (AppConfig.DEBUG)
- Log.d(TAG, "No report is created");
- }
- completedDownloads.clear();
- }
-
- /** Check if there's something else to download, otherwise stop */
- void queryDownloads() {
- int numOfDownloads = downloads.size();
- if (AppConfig.DEBUG) {
- Log.d(TAG, numOfDownloads + " downloads left");
- Log.d(TAG, "Downloads being handled: " + downloadsBeingHandled);
- Log.d(TAG, "ShutdownInitiated: " + shutdownInitiated);
- }
-
- if (numOfDownloads == 0 && downloadsBeingHandled <= 0) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Starting shutdown");
- shutdownInitiated = true;
- updateReport();
- cancelNotificationUpdater();
- stopForeground(true);
- } else {
- setupNotificationUpdater();
- startForeground(NOTIFICATION_ID, updateNotifications());
- }
- }
-
- /** Is called whenever a Feed is downloaded */
- private void handleCompletedFeedDownload(DownloadStatus status) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Handling completed Feed Download");
- syncExecutor.execute(new FeedSyncThread(status));
-
- }
-
- /** Is called whenever a Feed-Image is downloaded */
- private void handleCompletedImageDownload(DownloadStatus status) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Handling completed Image Download");
- syncExecutor.execute(new ImageHandlerThread(status));
- }
-
- /** Is called whenever a FeedMedia is downloaded. */
- private void handleCompletedFeedMediaDownload(DownloadStatus status) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Handling completed FeedMedia Download");
- syncExecutor.execute(new MediaHandlerThread(status));
- }
-
- /**
- * Takes a single Feed, parses the corresponding file and refreshes
- * information in the manager
- */
- class FeedSyncThread implements Runnable {
- private static final String TAG = "FeedSyncThread";
-
- private Feed feed;
- private DownloadStatus status;
-
- private int reason;
- private boolean successful;
-
- public FeedSyncThread(DownloadStatus status) {
- this.feed = (Feed) status.getFeedFile();
- this.status = status;
- }
-
- public void run() {
- Feed savedFeed = null;
- reason = 0;
- String reasonDetailed = null;
- successful = true;
- final FeedManager manager = FeedManager.getInstance();
- FeedHandler feedHandler = new FeedHandler();
- feed.setDownloaded(true);
-
- try {
- feed = feedHandler.parseFeed(feed);
- if (AppConfig.DEBUG)
- Log.d(TAG, feed.getTitle() + " parsed");
- if (checkFeedData(feed) == false) {
- throw new InvalidFeedException();
- }
- // Save information of feed in DB
- savedFeed = manager.updateFeed(DownloadService.this, feed);
- // Download Feed Image if provided and not downloaded
- if (savedFeed.getImage() != null
- && savedFeed.getImage().isDownloaded() == false) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Feed has image; Downloading....");
- savedFeed.getImage().setFeed(savedFeed);
- final Feed savedFeedRef = savedFeed;
- handler.post(new Runnable() {
-
- @Override
- public void run() {
- try {
- requester.downloadImage(DownloadService.this,
- savedFeedRef.getImage());
- } catch (DownloadRequestException e) {
- e.printStackTrace();
- manager.addDownloadStatus(
- DownloadService.this,
- new DownloadStatus(
- savedFeedRef.getImage(),
- savedFeedRef
- .getImage()
- .getHumanReadableIdentifier(),
- DownloadError.ERROR_REQUEST_ERROR,
- false, e.getMessage()));
- }
- }
- });
-
- }
-
- } catch (SAXException e) {
- successful = false;
- e.printStackTrace();
- reason = DownloadError.ERROR_PARSER_EXCEPTION;
- reasonDetailed = e.getMessage();
- } catch (IOException e) {
- successful = false;
- e.printStackTrace();
- reason = DownloadError.ERROR_PARSER_EXCEPTION;
- reasonDetailed = e.getMessage();
- } catch (ParserConfigurationException e) {
- successful = false;
- e.printStackTrace();
- reason = DownloadError.ERROR_PARSER_EXCEPTION;
- reasonDetailed = e.getMessage();
- } catch (UnsupportedFeedtypeException e) {
- e.printStackTrace();
- successful = false;
- reason = DownloadError.ERROR_UNSUPPORTED_TYPE;
- reasonDetailed = e.getMessage();
- } catch (InvalidFeedException e) {
- e.printStackTrace();
- successful = false;
- reason = DownloadError.ERROR_PARSER_EXCEPTION;
- reasonDetailed = e.getMessage();
- }
-
- // cleanup();
- if (savedFeed == null) {
- savedFeed = feed;
- }
-
- saveDownloadStatus(new DownloadStatus(savedFeed,
- savedFeed.getHumanReadableIdentifier(), reason, successful,
- reasonDetailed));
- sendDownloadHandledIntent();
- downloadsBeingHandled -= 1;
- handler.post(new Runnable() {
-
- @Override
- public void run() {
- queryDownloads();
-
- }
- });
- }
-
- /** Checks if the feed was parsed correctly. */
- private boolean checkFeedData(Feed feed) {
- if (feed.getTitle() == null) {
- Log.e(TAG, "Feed has no title.");
- return false;
- }
- if (!hasValidFeedItems(feed)) {
- Log.e(TAG, "Feed has invalid items");
- return false;
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "Feed appears to be valid.");
- return true;
-
- }
-
- private boolean hasValidFeedItems(Feed feed) {
- for (FeedItem item : feed.getItemsArray()) {
- if (item.getTitle() == null) {
- Log.e(TAG, "Item has no title");
- return false;
- }
- if (item.getPubDate() == null) {
- Log.e(TAG,
- "Item has no pubDate. Using current time as pubDate");
- if (item.getTitle() != null) {
- Log.e(TAG, "Title of invalid item: " + item.getTitle());
- }
- item.setPubDate(new Date());
- }
- }
- return true;
- }
-
- /** Delete files that aren't needed anymore */
- private void cleanup() {
- if (feed.getFile_url() != null) {
- if (new File(feed.getFile_url()).delete())
- if (AppConfig.DEBUG)
- Log.d(TAG, "Successfully deleted cache file.");
- else
- Log.e(TAG, "Failed to delete cache file.");
- feed.setFile_url(null);
- } else if (AppConfig.DEBUG) {
- Log.d(TAG, "Didn't delete cache file: File url is not set.");
- }
- }
-
- }
-
- /** Handles a completed image download. */
- class ImageHandlerThread implements Runnable {
- private FeedImage image;
- private DownloadStatus status;
-
- public ImageHandlerThread(DownloadStatus status) {
- this.image = (FeedImage) status.getFeedFile();
- this.status = status;
- }
-
- @Override
- public void run() {
- image.setDownloaded(true);
-
- saveDownloadStatus(status);
- sendDownloadHandledIntent();
- manager.setFeedImage(DownloadService.this, image);
- if (image.getFeed() != null) {
- manager.setFeed(DownloadService.this, image.getFeed());
- } else {
- Log.e(TAG,
- "Image has no feed, image might not be saved correctly!");
- }
- downloadsBeingHandled -= 1;
- handler.post(new Runnable() {
-
- @Override
- public void run() {
- queryDownloads();
-
- }
- });
- }
- }
-
- /** Handles a completed media download. */
- class MediaHandlerThread implements Runnable {
- private FeedMedia media;
- private DownloadStatus status;
-
- public MediaHandlerThread(DownloadStatus status) {
- super();
- this.media = (FeedMedia) status.getFeedFile();
- this.status = status;
- }
-
- @Override
- public void run() {
- boolean chaptersRead = false;
-
- media.setDownloaded(true);
- // Get duration
- MediaPlayer mediaplayer = new MediaPlayer();
- try {
- mediaplayer.setDataSource(media.getFile_url());
- mediaplayer.prepare();
- media.setDuration(mediaplayer.getDuration());
- if (AppConfig.DEBUG)
- Log.d(TAG, "Duration of file is " + media.getDuration());
- mediaplayer.reset();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- mediaplayer.release();
- }
-
- if (media.getItem().getChapters() == null) {
- ChapterUtils.loadChaptersFromFileUrl(media);
- if (media.getItem().getChapters() != null) {
- chaptersRead = true;
- }
- }
-
- saveDownloadStatus(status);
- sendDownloadHandledIntent();
- if (chaptersRead) {
- manager.setFeedItem(DownloadService.this, media.getItem());
- }
- manager.setFeedMedia(DownloadService.this, media);
-
- if (!FeedManager.getInstance().isInQueue(media.getItem())) {
- FeedManager.getInstance().addQueueItem(DownloadService.this,
- media.getItem());
- }
-
- downloadsBeingHandled -= 1;
- handler.post(new Runnable() {
-
- @Override
- public void run() {
- queryDownloads();
-
- }
- });
- }
- }
-
- /** Is used to request a new download. */
- public static class Request implements Parcelable {
- private String destination;
- private String source;
-
- public Request(String destination, String source) {
- super();
- this.destination = destination;
- this.source = source;
- }
-
- private Request(Parcel in) {
- destination = in.readString();
- source = in.readString();
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeString(destination);
- dest.writeString(source);
- }
-
- public static final Parcelable.Creator<Request> CREATOR = new Parcelable.Creator<Request>() {
- public Request createFromParcel(Parcel in) {
- return new Request(in);
- }
-
- public Request[] newArray(int size) {
- return new Request[size];
- }
- };
-
- public String getDestination() {
- return destination;
- }
-
- public String getSource() {
- return source;
- }
-
- }
-
- /** Schedules the notification updater task if it hasn't been scheduled yet. */
- private void setupNotificationUpdater() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Setting up notification updater");
- if (notificationUpdater == null) {
- notificationUpdater = new NotificationUpdater();
- notificationUpdaterFuture = schedExecutor.scheduleAtFixedRate(
- notificationUpdater, 5L, 5L, TimeUnit.SECONDS);
- }
- }
-
- private void cancelNotificationUpdater() {
- boolean result = false;
- if (notificationUpdaterFuture != null) {
- result = notificationUpdaterFuture.cancel(true);
- }
- notificationUpdater = null;
- notificationUpdaterFuture = null;
- Log.d(TAG, "NotificationUpdater cancelled. Result: " + result);
- }
-
- private class NotificationUpdater implements Runnable {
- public void run() {
- handler.post(new Runnable() {
- @Override
- public void run() {
- Notification n = updateNotifications();
- if (n != null) {
- NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- nm.notify(NOTIFICATION_ID, n);
- }
- }
- });
- }
- }
-
- public List<Downloader> getDownloads() {
- return downloads;
- }
+ private static final String TAG = "DownloadService";
+
+ /**
+ * Cancels one download. The intent MUST have an EXTRA_DOWNLOAD_URL extra that contains the download URL of the
+ * object whose download should be cancelled.
+ */
+ public static final String ACTION_CANCEL_DOWNLOAD = "action.de.danoeh.antennapod.service.cancelDownload";
+
+ /**
+ * Cancels all running downloads.
+ */
+ public static final String ACTION_CANCEL_ALL_DOWNLOADS = "action.de.danoeh.antennapod.service.cancelAllDownloads";
+
+ /**
+ * Extra for ACTION_CANCEL_DOWNLOAD
+ */
+ public static final String EXTRA_DOWNLOAD_URL = "downloadUrl";
+
+ /**
+ * Sent by the DownloadService when the content of the downloads list
+ * changes.
+ */
+ public static final String ACTION_DOWNLOADS_CONTENT_CHANGED = "action.de.danoeh.antennapod.service.downloadsContentChanged";
+
+ /**
+ * Extra for ACTION_ENQUEUE_DOWNLOAD intent.
+ */
+ public static final String EXTRA_REQUEST = "request";
+
+ /**
+ * Stores DownloadStatus objects of completed downloads for creating a report at the end of the lifecylce.
+ */
+ private List<DownloadStatus> completedDownloads;
+
+ private ExecutorService syncExecutor;
+ private CompletionService<Downloader> downloadExecutor;
+ /**
+ * Number of threads of downloadExecutor.
+ */
+ private static final int NUM_PARALLEL_DOWNLOADS = 4;
+
+ private DownloadRequester requester;
+
+
+ private NotificationCompat.Builder notificationCompatBuilder;
+ private Notification.BigTextStyle notificationBuilder;
+ private int NOTIFICATION_ID = 2;
+ private int REPORT_ID = 3;
+
+ /**
+ * Currently running downloads.
+ */
+ private List<Downloader> downloads;
+
+ /**
+ * Number of running downloads.
+ */
+ private AtomicInteger numberOfDownloads;
+
+ /**
+ * True if service is running.
+ */
+ public static boolean isRunning = false;
+
+ private Handler handler;
+
+ private NotificationUpdater notificationUpdater;
+ private ScheduledFuture notificationUpdaterFuture;
+ private static final int SCHED_EX_POOL_SIZE = 1;
+ private ScheduledThreadPoolExecutor schedExecutor;
+
+ private final IBinder mBinder = new LocalBinder();
+
+ public class LocalBinder extends Binder {
+ public DownloadService getService() {
+ return DownloadService.this;
+ }
+ }
+
+ private Thread downloadCompletionThread = new Thread() {
+ private static final String TAG = "downloadCompletionThread";
+
+ @Override
+ public void run() {
+ if (AppConfig.DEBUG) Log.d(TAG, "downloadCompletionThread was started");
+ while (!isInterrupted()) {
+ try {
+ Downloader downloader = downloadExecutor.take().get();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Received 'Download Complete' - message.");
+ removeDownload(downloader);
+ DownloadStatus status = downloader.getResult();
+ boolean successful = status.isSuccessful();
+
+ final int type = status.getFeedfileType();
+ if (successful) {
+ if (type == Feed.FEEDFILETYPE_FEED) {
+ handleCompletedFeedDownload(downloader
+ .getDownloadRequest());
+ } else if (type == FeedImage.FEEDFILETYPE_FEEDIMAGE) {
+ handleCompletedImageDownload(status, downloader.getDownloadRequest());
+ } else if (type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
+ handleCompletedFeedMediaDownload(status, downloader.getDownloadRequest());
+ }
+ } else {
+ numberOfDownloads.decrementAndGet();
+ if (!successful && !status.isCancelled()) {
+ Log.e(TAG, "Download failed");
+ saveDownloadStatus(status);
+ }
+ sendDownloadHandledIntent();
+ queryDownloadsAsync();
+ }
+ } catch (InterruptedException e) {
+ if (AppConfig.DEBUG) Log.d(TAG, "DownloadCompletionThread was interrupted");
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ numberOfDownloads.decrementAndGet();
+ }
+ }
+ if (AppConfig.DEBUG) Log.d(TAG, "End of downloadCompletionThread");
+ }
+ };
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (intent.getParcelableExtra(EXTRA_REQUEST) != null) {
+ onDownloadQueued(intent);
+ } else if (numberOfDownloads.equals(0)) {
+ stopSelf();
+ }
+ return Service.START_NOT_STICKY;
+ }
+
+ @SuppressLint("NewApi")
+ @Override
+ public void onCreate() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Service started");
+ isRunning = true;
+ handler = new Handler();
+ completedDownloads = Collections.synchronizedList(new ArrayList<DownloadStatus>());
+ downloads = new ArrayList<Downloader>();
+ numberOfDownloads = new AtomicInteger(0);
+
+ IntentFilter cancelDownloadReceiverFilter = new IntentFilter();
+ cancelDownloadReceiverFilter.addAction(ACTION_CANCEL_ALL_DOWNLOADS);
+ cancelDownloadReceiverFilter.addAction(ACTION_CANCEL_DOWNLOAD);
+ registerReceiver(cancelDownloadReceiver, cancelDownloadReceiverFilter);
+ syncExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
+
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r);
+ t.setPriority(Thread.MIN_PRIORITY);
+ return t;
+ }
+ });
+ downloadExecutor = new ExecutorCompletionService<Downloader>(
+ Executors.newFixedThreadPool(NUM_PARALLEL_DOWNLOADS,
+ new ThreadFactory() {
+
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r);
+ t.setPriority(Thread.MIN_PRIORITY);
+ return t;
+ }
+ }));
+ schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE,
+ new ThreadFactory() {
+
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r);
+ t.setPriority(Thread.MIN_PRIORITY);
+ return t;
+ }
+ }, new RejectedExecutionHandler() {
+
+ @Override
+ public void rejectedExecution(Runnable r,
+ ThreadPoolExecutor executor) {
+ Log.w(TAG, "SchedEx rejected submission of new task");
+ }
+ }
+ );
+ downloadCompletionThread.start();
+ setupNotificationBuilders();
+ requester = DownloadRequester.getInstance();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ @Override
+ public void onDestroy() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Service shutting down");
+ isRunning = false;
+ updateReport();
+
+ stopForeground(true);
+ NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ nm.cancel(NOTIFICATION_ID);
+
+ downloadCompletionThread.interrupt();
+ syncExecutor.shutdown();
+ schedExecutor.shutdown();
+ cancelNotificationUpdater();
+ unregisterReceiver(cancelDownloadReceiver);
+ }
+
+ @SuppressLint("NewApi")
+ private void setupNotificationBuilders() {
+ PendingIntent pIntent = PendingIntent.getActivity(this, 0, new Intent(
+ this, DownloadActivity.class),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ Bitmap icon = BitmapFactory.decodeResource(getResources(),
+ R.drawable.stat_notify_sync);
+
+ if (android.os.Build.VERSION.SDK_INT >= 16) {
+ notificationBuilder = new Notification.BigTextStyle(
+ new Notification.Builder(this).setOngoing(true)
+ .setContentIntent(pIntent).setLargeIcon(icon)
+ .setSmallIcon(R.drawable.stat_notify_sync));
+ } else {
+ notificationCompatBuilder = new NotificationCompat.Builder(this)
+ .setOngoing(true).setContentIntent(pIntent)
+ .setLargeIcon(icon)
+ .setSmallIcon(R.drawable.stat_notify_sync);
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Notification set up");
+ }
+
+ /**
+ * Updates the contents of the service's notifications. Should be called
+ * before setupNotificationBuilders.
+ */
+ @SuppressLint("NewApi")
+ private Notification updateNotifications() {
+ String contentTitle = getString(R.string.download_notification_title);
+ String downloadsLeft = requester.getNumberOfDownloads()
+ + getString(R.string.downloads_left);
+ if (android.os.Build.VERSION.SDK_INT >= 16) {
+
+ if (notificationBuilder != null) {
+
+ StringBuilder bigText = new StringBuilder("");
+ for (int i = 0; i < downloads.size(); i++) {
+ Downloader downloader = downloads.get(i);
+ final DownloadRequest request = downloader
+ .getDownloadRequest();
+ if (request.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
+ if (request.getTitle() != null) {
+ if (i > 0) {
+ bigText.append("\n");
+ }
+ bigText.append("\u2022 " + request.getTitle());
+ }
+ } else if (request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
+ if (request.getTitle() != null) {
+ if (i > 0) {
+ bigText.append("\n");
+ }
+ bigText.append("\u2022 " + request.getTitle()
+ + " (" + request.getProgressPercent()
+ + "%)");
+ }
+ }
+
+ }
+ notificationBuilder.setSummaryText(downloadsLeft);
+ notificationBuilder.setBigContentTitle(contentTitle);
+ if (bigText != null) {
+ notificationBuilder.bigText(bigText.toString());
+ }
+ return notificationBuilder.build();
+ }
+ } else {
+ if (notificationCompatBuilder != null) {
+ notificationCompatBuilder.setContentTitle(contentTitle);
+ notificationCompatBuilder.setContentText(downloadsLeft);
+ return notificationCompatBuilder.getNotification();
+ }
+ }
+ return null;
+ }
+
+ private Downloader getDownloader(String downloadUrl) {
+ for (Downloader downloader : downloads) {
+ if (downloader.getDownloadRequest().getSource().equals(downloadUrl)) {
+ return downloader;
+ }
+ }
+ return null;
+ }
+
+ private BroadcastReceiver cancelDownloadReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(ACTION_CANCEL_DOWNLOAD)) {
+ String url = intent.getStringExtra(EXTRA_DOWNLOAD_URL);
+ if (url == null) {
+ throw new IllegalArgumentException(
+ "ACTION_CANCEL_DOWNLOAD intent needs download url extra");
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Cancelling download with url " + url);
+ Downloader d = getDownloader(url);
+ if (d != null) {
+ d.cancel();
+ } else {
+ Log.e(TAG, "Could not cancel download with url " + url);
+ }
+
+ } else if (intent.getAction().equals(ACTION_CANCEL_ALL_DOWNLOADS)) {
+ for (Downloader d : downloads) {
+ d.cancel();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Cancelled all downloads");
+ }
+ sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
+
+ }
+ queryDownloads();
+ }
+
+ };
+
+ private void onDownloadQueued(Intent intent) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Received enqueue request");
+ DownloadRequest request = intent.getParcelableExtra(EXTRA_REQUEST);
+ if (request == null) {
+ throw new IllegalArgumentException(
+ "ACTION_ENQUEUE_DOWNLOAD intent needs request extra");
+ }
+
+ Downloader downloader = getDownloader(request);
+ if (downloader != null) {
+ numberOfDownloads.incrementAndGet();
+ downloads.add(downloader);
+ downloadExecutor.submit(downloader);
+ sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
+ }
+
+ queryDownloads();
+ }
+
+ private Downloader getDownloader(DownloadRequest request) {
+ if (URLUtil.isHttpUrl(request.getSource())) {
+ return new HttpDownloader(request);
+ }
+ Log.e(TAG,
+ "Could not find appropriate downloader for "
+ + request.getSource());
+ return null;
+ }
+
+ @SuppressLint("NewApi")
+ public void onDownloadCompleted(final Downloader downloader) {
+ final AsyncTask<Void, Void, Void> handlerTask = new AsyncTask<Void, Void, Void>() {
+ boolean successful;
+
+ @Override
+ protected void onPostExecute(Void result) {
+ super.onPostExecute(result);
+ if (!successful) {
+ queryDownloads();
+ }
+ }
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ removeDownload(downloader);
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+
+
+ return null;
+ }
+ };
+ if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
+ handlerTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ } else {
+ handlerTask.execute();
+ }
+ }
+
+ /**
+ * Remove download from the DownloadRequester list and from the
+ * DownloadService list.
+ */
+ private void removeDownload(final Downloader d) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Removing downloader: "
+ + d.getDownloadRequest().getSource());
+ boolean rc = downloads.remove(d);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Result of downloads.remove: " + rc);
+ DownloadRequester.getInstance().removeDownload(d.getDownloadRequest());
+ sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
+ }
+
+ /**
+ * Adds a new DownloadStatus object to the list of completed downloads and
+ * saves it in the database
+ *
+ * @param status the download that is going to be saved
+ */
+ private void saveDownloadStatus(DownloadStatus status) {
+ completedDownloads.add(status);
+ DBWriter.addDownloadStatus(this, status);
+ }
+
+ private void sendDownloadHandledIntent() {
+ EventDistributor.getInstance().sendDownloadHandledBroadcast();
+ }
+
+ /**
+ * Creates a notification at the end of the service lifecycle to notify the
+ * user about the number of completed downloads. A report will only be
+ * created if the number of successfully downloaded feeds is bigger than 1
+ * or if there is at least one failed download which is not an image or if
+ * there is at least one downloaded media file.
+ */
+ private void updateReport() {
+ // check if report should be created
+ boolean createReport = false;
+ int successfulDownloads = 0;
+ int failedDownloads = 0;
+
+ // a download report is created if at least one download has failed
+ // (excluding failed image downloads)
+ for (DownloadStatus status : completedDownloads) {
+ if (status.isSuccessful()) {
+ successfulDownloads++;
+ } else if (!status.isCancelled()) {
+ if (status.getFeedfileType() != FeedImage.FEEDFILETYPE_FEEDIMAGE) {
+ createReport = true;
+ }
+ failedDownloads++;
+ }
+ }
+
+ if (createReport) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Creating report");
+ // create notification object
+ Notification notification = new NotificationCompat.Builder(this)
+ .setTicker(
+ getString(de.danoeh.antennapod.R.string.download_report_title))
+ .setContentTitle(
+ getString(de.danoeh.antennapod.R.string.download_report_title))
+ .setContentText(
+ String.format(
+ getString(R.string.download_report_content),
+ successfulDownloads, failedDownloads))
+ .setSmallIcon(R.drawable.stat_notify_sync)
+ .setLargeIcon(
+ BitmapFactory.decodeResource(getResources(),
+ R.drawable.stat_notify_sync))
+ .setContentIntent(
+ PendingIntent.getActivity(this, 0, new Intent(this,
+ DownloadLogActivity.class), 0))
+ .setAutoCancel(true).getNotification();
+ NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.notify(REPORT_ID, notification);
+ } else {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "No report is created");
+ }
+ completedDownloads.clear();
+ }
+
+ /**
+ * Calls query downloads on the services main thread. This method should be used instead of queryDownloads if it is
+ * used from a thread other than the main thread.
+ */
+ void queryDownloadsAsync() {
+ handler.post(new Runnable() {
+ public void run() {
+ queryDownloads();
+ ;
+ }
+ });
+ }
+
+ /**
+ * Check if there's something else to download, otherwise stop
+ */
+ void queryDownloads() {
+ if (AppConfig.DEBUG) {
+ Log.d(TAG, numberOfDownloads.get() + " downloads left");
+ }
+
+ if (numberOfDownloads.get() <= 0 && DownloadRequester.getInstance().hasNoDownloads()) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Number of downloads is " + numberOfDownloads.get() + ", attempting shutdown");
+ stopSelf();
+ } else {
+ setupNotificationUpdater();
+ startForeground(NOTIFICATION_ID, updateNotifications());
+ }
+ }
+
+ /**
+ * Is called whenever a Feed is downloaded
+ */
+ private void handleCompletedFeedDownload(DownloadRequest request) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Handling completed Feed Download");
+ syncExecutor.execute(new FeedSyncThread(request));
+
+ }
+
+ /**
+ * Is called whenever a Feed-Image is downloaded
+ */
+ private void handleCompletedImageDownload(DownloadStatus status, DownloadRequest request) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Handling completed Image Download");
+ syncExecutor.execute(new ImageHandlerThread(status, request));
+ }
+
+ /**
+ * Is called whenever a FeedMedia is downloaded.
+ */
+ private void handleCompletedFeedMediaDownload(DownloadStatus status, DownloadRequest request) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Handling completed FeedMedia Download");
+ syncExecutor.execute(new MediaHandlerThread(status, request));
+ }
+
+ /**
+ * Takes a single Feed, parses the corresponding file and refreshes
+ * information in the manager
+ */
+ class FeedSyncThread implements Runnable {
+ private static final String TAG = "FeedSyncThread";
+
+ private DownloadRequest request;
+
+ private DownloadError reason;
+ private boolean successful;
+
+ public FeedSyncThread(DownloadRequest request) {
+ if (request == null) {
+ throw new IllegalArgumentException("Request must not be null");
+ }
+
+ this.request = request;
+ }
+
+ public void run() {
+ Feed savedFeed = null;
+
+ Feed feed = new Feed(request.getSource(), new Date());
+ feed.setFile_url(request.getDestination());
+ feed.setDownloaded(true);
+
+ reason = null;
+ String reasonDetailed = null;
+ successful = true;
+ FeedHandler feedHandler = new FeedHandler();
+
+ try {
+ feed = feedHandler.parseFeed(feed);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, feed.getTitle() + " parsed");
+ if (checkFeedData(feed) == false) {
+ throw new InvalidFeedException();
+ }
+ // Save information of feed in DB
+ savedFeed = DBTasks.updateFeed(DownloadService.this, feed);
+ // Download Feed Image if provided and not downloaded
+ if (savedFeed.getImage() != null
+ && savedFeed.getImage().isDownloaded() == false) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Feed has image; Downloading....");
+ savedFeed.getImage().setFeed(savedFeed);
+ final Feed savedFeedRef = savedFeed;
+ try {
+ requester.downloadImage(DownloadService.this,
+ savedFeedRef.getImage());
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ DBWriter.addDownloadStatus(
+ DownloadService.this,
+ new DownloadStatus(
+ savedFeedRef.getImage(),
+ savedFeedRef
+ .getImage()
+ .getHumanReadableIdentifier(),
+ DownloadError.ERROR_REQUEST_ERROR,
+ false, e.getMessage()));
+ }
+ }
+
+ } catch (SAXException e) {
+ successful = false;
+ e.printStackTrace();
+ reason = DownloadError.ERROR_PARSER_EXCEPTION;
+ reasonDetailed = e.getMessage();
+ } catch (IOException e) {
+ successful = false;
+ e.printStackTrace();
+ reason = DownloadError.ERROR_PARSER_EXCEPTION;
+ reasonDetailed = e.getMessage();
+ } catch (ParserConfigurationException e) {
+ successful = false;
+ e.printStackTrace();
+ reason = DownloadError.ERROR_PARSER_EXCEPTION;
+ reasonDetailed = e.getMessage();
+ } catch (UnsupportedFeedtypeException e) {
+ e.printStackTrace();
+ successful = false;
+ reason = DownloadError.ERROR_UNSUPPORTED_TYPE;
+ reasonDetailed = e.getMessage();
+ } catch (InvalidFeedException e) {
+ e.printStackTrace();
+ successful = false;
+ reason = DownloadError.ERROR_PARSER_EXCEPTION;
+ reasonDetailed = e.getMessage();
+ }
+
+ // cleanup();
+ if (savedFeed == null) {
+ savedFeed = feed;
+ }
+
+ saveDownloadStatus(new DownloadStatus(savedFeed,
+ savedFeed.getHumanReadableIdentifier(), reason, successful,
+ reasonDetailed));
+ sendDownloadHandledIntent();
+ numberOfDownloads.decrementAndGet();
+ queryDownloadsAsync();
+ }
+
+ /**
+ * Checks if the feed was parsed correctly.
+ */
+ private boolean checkFeedData(Feed feed) {
+ if (feed.getTitle() == null) {
+ Log.e(TAG, "Feed has no title.");
+ return false;
+ }
+ if (!hasValidFeedItems(feed)) {
+ Log.e(TAG, "Feed has invalid items");
+ return false;
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Feed appears to be valid.");
+ return true;
+
+ }
+
+ private boolean hasValidFeedItems(Feed feed) {
+ for (FeedItem item : feed.getItems()) {
+ if (item.getTitle() == null) {
+ Log.e(TAG, "Item has no title");
+ return false;
+ }
+ if (item.getPubDate() == null) {
+ Log.e(TAG,
+ "Item has no pubDate. Using current time as pubDate");
+ if (item.getTitle() != null) {
+ Log.e(TAG, "Title of invalid item: " + item.getTitle());
+ }
+ item.setPubDate(new Date());
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Delete files that aren't needed anymore
+ */
+ private void cleanup(Feed feed) {
+ if (feed.getFile_url() != null) {
+ if (new File(feed.getFile_url()).delete())
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Successfully deleted cache file.");
+ else
+ Log.e(TAG, "Failed to delete cache file.");
+ feed.setFile_url(null);
+ } else if (AppConfig.DEBUG) {
+ Log.d(TAG, "Didn't delete cache file: File url is not set.");
+ }
+ }
+
+ }
+
+ /**
+ * Handles a completed image download.
+ */
+ class ImageHandlerThread implements Runnable {
+
+ private DownloadRequest request;
+ private DownloadStatus status;
+
+ public ImageHandlerThread(DownloadStatus status, DownloadRequest request) {
+ if (status == null) {
+ throw new IllegalArgumentException("Status must not be null");
+ }
+ if (request == null) {
+ throw new IllegalArgumentException("Request must not be null");
+ }
+ this.status = status;
+ this.request = request;
+ }
+
+ @Override
+ public void run() {
+ FeedImage image = DBReader.getFeedImage(DownloadService.this, request.getFeedfileId());
+ if (image == null) {
+ throw new IllegalStateException("Could not find downloaded image in database");
+ }
+
+ image.setFile_url(request.getDestination());
+ image.setDownloaded(true);
+
+ saveDownloadStatus(status);
+ sendDownloadHandledIntent();
+ DBWriter.setFeedImage(DownloadService.this, image);
+ numberOfDownloads.decrementAndGet();
+ queryDownloadsAsync();
+ }
+ }
+
+ /**
+ * Handles a completed media download.
+ */
+ class MediaHandlerThread implements Runnable {
+
+ private DownloadRequest request;
+ private DownloadStatus status;
+
+ public MediaHandlerThread(DownloadStatus status, DownloadRequest request) {
+ if (status == null) {
+ throw new IllegalArgumentException("Status must not be null");
+ }
+ if (request == null) {
+ throw new IllegalArgumentException("Request must not be null");
+ }
+
+ this.status = status;
+ this.request = request;
+ }
+
+ @Override
+ public void run() {
+ FeedMedia media = DBReader.getFeedMedia(DownloadService.this,
+ request.getFeedfileId());
+ if (media == null) {
+ throw new IllegalStateException(
+ "Could not find downloaded media object in database");
+ }
+ boolean chaptersRead = false;
+ media.setDownloaded(true);
+ media.setFile_url(request.getDestination());
+
+ // Get duration
+ MediaPlayer mediaplayer = new MediaPlayer();
+ try {
+ mediaplayer.setDataSource(media.getFile_url());
+ mediaplayer.prepare();
+ media.setDuration(mediaplayer.getDuration());
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Duration of file is " + media.getDuration());
+ mediaplayer.reset();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ mediaplayer.release();
+ }
+
+ if (media.getItem().getChapters() == null) {
+ ChapterUtils.loadChaptersFromFileUrl(media);
+ if (media.getItem().getChapters() != null) {
+ chaptersRead = true;
+ }
+ }
+
+ saveDownloadStatus(status);
+ sendDownloadHandledIntent();
+
+ try {
+ if (chaptersRead) {
+ DBWriter.setFeedItem(DownloadService.this, media.getItem()).get();
+ }
+ DBWriter.setFeedMedia(DownloadService.this, media).get();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ if (!DBTasks.isInQueue(DownloadService.this, media.getItem().getId())) {
+ DBWriter.addQueueItem(DownloadService.this, media.getItem().getId());
+ }
+
+ numberOfDownloads.decrementAndGet();
+ queryDownloadsAsync();
+ }
+ }
+
+ /**
+ * Schedules the notification updater task if it hasn't been scheduled yet.
+ */
+ private void setupNotificationUpdater() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Setting up notification updater");
+ if (notificationUpdater == null) {
+ notificationUpdater = new NotificationUpdater();
+ notificationUpdaterFuture = schedExecutor.scheduleAtFixedRate(
+ notificationUpdater, 5L, 5L, TimeUnit.SECONDS);
+ }
+ }
+
+ private void cancelNotificationUpdater() {
+ boolean result = false;
+ if (notificationUpdaterFuture != null) {
+ result = notificationUpdaterFuture.cancel(true);
+ }
+ notificationUpdater = null;
+ notificationUpdaterFuture = null;
+ Log.d(TAG, "NotificationUpdater cancelled. Result: " + result);
+ }
+
+ private class NotificationUpdater implements Runnable {
+ public void run() {
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ Notification n = updateNotifications();
+ if (n != null) {
+ NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.notify(NOTIFICATION_ID, n);
+ }
+ }
+ });
+ }
+ }
+
+ public List<Downloader> getDownloads() {
+ return downloads;
+ }
}
diff --git a/src/de/danoeh/antennapod/service/download/DownloadStatus.java b/src/de/danoeh/antennapod/service/download/DownloadStatus.java
new file mode 100644
index 000000000..62e54cbb4
--- /dev/null
+++ b/src/de/danoeh/antennapod/service/download/DownloadStatus.java
@@ -0,0 +1,181 @@
+package de.danoeh.antennapod.service.download;
+
+import java.util.Date;
+
+import de.danoeh.antennapod.feed.FeedFile;
+import de.danoeh.antennapod.util.DownloadError;
+
+/** Contains status attributes for one download */
+public class DownloadStatus {
+ /**
+ * Downloaders should use this constant for the size attribute if necessary
+ * so that the listadapters etc. can react properly.
+ */
+ public static final int SIZE_UNKNOWN = -1;
+
+ // ----------------------------------- ATTRIBUTES STORED IN DB
+ /** Unique id for storing the object in database. */
+ protected long id;
+ /**
+ * A human-readable string which is shown to the user so that he can
+ * identify the download. Should be the title of the item/feed/media or the
+ * URL if the download has no other title.
+ */
+ protected String title;
+ protected DownloadError reason;
+ /**
+ * A message which can be presented to the user to give more information.
+ * Should be null if Download was successful.
+ */
+ protected String reasonDetailed;
+ protected boolean successful;
+ protected Date completionDate;
+ protected long feedfileId;
+ /**
+ * Is used to determine the type of the feedfile even if the feedfile does
+ * not exist anymore. The value should be FEEDFILETYPE_FEED,
+ * FEEDFILETYPE_FEEDIMAGE or FEEDFILETYPE_FEEDMEDIA
+ */
+ protected int feedfileType;
+
+ // ------------------------------------ NOT STORED IN DB
+ protected boolean done;
+ protected boolean cancelled;
+
+ /** Constructor for restoring Download status entries from DB. */
+ public DownloadStatus(long id, String title, long feedfileId,
+ int feedfileType, boolean successful, DownloadError reason,
+ Date completionDate, String reasonDetailed) {
+ this.id = id;
+ this.title = title;
+ this.done = true;
+ this.feedfileId = feedfileId;
+ this.reason = reason;
+ this.successful = successful;
+ this.completionDate = completionDate;
+ this.reasonDetailed = reasonDetailed;
+ this.feedfileType = feedfileType;
+ }
+
+ public DownloadStatus(DownloadRequest request, DownloadError reason,
+ boolean successful, boolean cancelled, String reasonDetailed) {
+ if (request == null) {
+ throw new IllegalArgumentException("request must not be null");
+ }
+ this.title = request.getTitle();
+ this.feedfileId = request.getFeedfileId();
+ this.feedfileType = request.getFeedfileType();
+ this.reason = reason;
+ this.successful = successful;
+ this.cancelled = cancelled;
+ this.reasonDetailed = reasonDetailed;
+ this.completionDate = new Date();
+ }
+
+ /** Constructor for creating new completed downloads. */
+ public DownloadStatus(FeedFile feedfile, String title, DownloadError reason,
+ boolean successful, String reasonDetailed) {
+ if (feedfile == null) {
+ throw new IllegalArgumentException("feedfile must not be null");
+ }
+
+ this.title = title;
+ this.done = true;
+ this.feedfileId = feedfile.getId();
+ this.feedfileType = feedfile.getTypeAsInt();
+ this.reason = reason;
+ this.successful = successful;
+ this.completionDate = new Date();
+ this.reasonDetailed = reasonDetailed;
+ }
+
+ /** Constructor for creating new completed downloads. */
+ public DownloadStatus(long feedfileId, int feedfileType, String title,
+ DownloadError reason, boolean successful, String reasonDetailed) {
+ this.title = title;
+ this.done = true;
+ this.feedfileId = feedfileId;
+ this.feedfileType = feedfileType;
+ this.reason = reason;
+ this.successful = successful;
+ this.completionDate = new Date();
+ this.reasonDetailed = reasonDetailed;
+ }
+
+ @Override
+ public String toString() {
+ return "DownloadStatus [id=" + id + ", title=" + title + ", reason="
+ + reason + ", reasonDetailed=" + reasonDetailed
+ + ", successful=" + successful + ", completionDate="
+ + completionDate + ", feedfileId=" + feedfileId
+ + ", feedfileType=" + feedfileType + ", done=" + done
+ + ", cancelled=" + cancelled + "]";
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public DownloadError getReason() {
+ return reason;
+ }
+
+ public String getReasonDetailed() {
+ return reasonDetailed;
+ }
+
+ public boolean isSuccessful() {
+ return successful;
+ }
+
+ public Date getCompletionDate() {
+ return completionDate;
+ }
+
+ public long getFeedfileId() {
+ return feedfileId;
+ }
+
+ public int getFeedfileType() {
+ return feedfileType;
+ }
+
+ public boolean isDone() {
+ return done;
+ }
+
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ public void setSuccessful() {
+ this.successful = true;
+ this.reason = DownloadError.SUCCESS;
+ this.done = true;
+ }
+
+ public void setFailed(DownloadError reason, String reasonDetailed) {
+ this.successful = false;
+ this.reason = reason;
+ this.reasonDetailed = reasonDetailed;
+ }
+
+ public void setCancelled() {
+ this.successful = false;
+ this.reason = DownloadError.ERROR_DOWNLOAD_CANCELLED;
+ this.done = true;
+ this.cancelled = true;
+ }
+
+ public void setCompletionDate(Date completionDate) {
+ this.completionDate = completionDate;
+ }
+
+ public void setId(long id) {
+ this.id = id;
+ }
+} \ No newline at end of file
diff --git a/src/de/danoeh/antennapod/service/download/Downloader.java b/src/de/danoeh/antennapod/service/download/Downloader.java
index 9ed9d9a76..84731fe9f 100644
--- a/src/de/danoeh/antennapod/service/download/Downloader.java
+++ b/src/de/danoeh/antennapod/service/download/Downloader.java
@@ -1,49 +1,50 @@
package de.danoeh.antennapod.service.download;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.asynctask.DownloadStatus;
+
+import java.util.concurrent.Callable;
/** Downloads files */
-public abstract class Downloader extends Thread {
+public abstract class Downloader implements Callable<Downloader> {
private static final String TAG = "Downloader";
- private DownloaderCallback downloaderCallback;
- protected boolean finished;
+ protected volatile boolean finished;
protected volatile boolean cancelled;
- protected volatile DownloadStatus status;
+ protected DownloadRequest request;
+ protected DownloadStatus result;
- public Downloader(DownloaderCallback downloaderCallback,
- DownloadStatus status) {
+ public Downloader(DownloadRequest request) {
super();
- this.downloaderCallback = downloaderCallback;
- this.status = status;
- this.status.setStatusMsg(R.string.download_pending);
+ this.request = request;
+ this.request.setStatusMsg(R.string.download_pending);
this.cancelled = false;
+ this.result = new DownloadStatus(request, null, false, false, null);
}
- /**
- * This method must be called when the download was completed, failed, or
- * was cancelled
- */
- protected void finish() {
- if (!finished) {
- finished = true;
- downloaderCallback.onDownloadCompleted(this);
+ protected abstract void download();
+
+ public final Downloader call() {
+ download();
+ if (result == null) {
+ throw new IllegalStateException(
+ "Downloader hasn't created DownloadStatus object");
}
+ finished = true;
+ return this;
}
- protected abstract void download();
+ public DownloadRequest getDownloadRequest() {
+ return request;
+ }
- @Override
- public final void run() {
- download();
- finish();
+ public DownloadStatus getResult() {
+ return result;
}
- public DownloadStatus getStatus() {
- return status;
+ public boolean isFinished() {
+ return finished;
}
public void cancel() {
diff --git a/src/de/danoeh/antennapod/service/download/HttpDownloader.java b/src/de/danoeh/antennapod/service/download/HttpDownloader.java
index f8f26f6fd..c9671ceb3 100644
--- a/src/de/danoeh/antennapod/service/download/HttpDownloader.java
+++ b/src/de/danoeh/antennapod/service/download/HttpDownloader.java
@@ -26,7 +26,6 @@ import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.R;
-import de.danoeh.antennapod.asynctask.DownloadStatus;
import de.danoeh.antennapod.util.DownloadError;
import de.danoeh.antennapod.util.StorageUtils;
@@ -39,9 +38,8 @@ public class HttpDownloader extends Downloader {
private static final int CONNECTION_TIMEOUT = 30000;
private static final int SOCKET_TIMEOUT = 30000;
- public HttpDownloader(DownloaderCallback downloaderCallback,
- DownloadStatus status) {
- super(downloaderCallback, status);
+ public HttpDownloader(DownloadRequest request) {
+ super(request);
}
private DefaultHttpClient createHttpClient() {
@@ -66,8 +64,7 @@ public class HttpDownloader extends Downloader {
OutputStream out = null;
InputStream connection = null;
try {
- HttpGet httpGet = new HttpGet(status.getFeedFile()
- .getDownload_url());
+ HttpGet httpGet = new HttpGet(request.getSource());
httpClient = createHttpClient();
HttpResponse response = httpClient.execute(httpGet);
HttpEntity httpEntity = response.getEntity();
@@ -76,8 +73,7 @@ public class HttpDownloader extends Downloader {
Log.d(TAG, "Response code is " + responseCode);
if (responseCode == HttpURLConnection.HTTP_OK && httpEntity != null) {
if (StorageUtils.storageAvailable(PodcastApp.getInstance())) {
- File destination = new File(status.getFeedFile()
- .getFile_url());
+ File destination = new File(request.getDestination());
if (!destination.exists()) {
connection = AndroidHttpClient
.getUngzippedContent(httpEntity);
@@ -86,29 +82,30 @@ public class HttpDownloader extends Downloader {
destination));
byte[] buffer = new byte[BUFFER_SIZE];
int count = 0;
- status.setStatusMsg(R.string.download_running);
+ request.setStatusMsg(R.string.download_running);
if (AppConfig.DEBUG)
Log.d(TAG, "Getting size of download");
- status.setSize(httpEntity.getContentLength());
+ request.setSize(httpEntity.getContentLength());
if (AppConfig.DEBUG)
- Log.d(TAG, "Size is " + status.getSize());
- if (status.getSize() < 0) {
- status.setSize(DownloadStatus.SIZE_UNKNOWN);
+ Log.d(TAG, "Size is " + request.getSize());
+ if (request.getSize() < 0) {
+ request.setSize(DownloadStatus.SIZE_UNKNOWN);
}
long freeSpace = StorageUtils.getFreeSpaceAvailable();
if (AppConfig.DEBUG)
Log.d(TAG, "Free space is " + freeSpace);
- if (status.getSize() == DownloadStatus.SIZE_UNKNOWN
- || status.getSize() <= freeSpace) {
+ if (request.getSize() == DownloadStatus.SIZE_UNKNOWN
+ || request.getSize() <= freeSpace) {
if (AppConfig.DEBUG)
Log.d(TAG, "Starting download");
while (!cancelled
&& (count = in.read(buffer)) != -1) {
out.write(buffer, 0, count);
- status.setSoFar(status.getSoFar() + count);
- status.setProgressPercent((int) (((double) status
- .getSoFar() / (double) status.getSize()) * 100));
+ request.setSoFar(request.getSoFar() + count);
+ request.setProgressPercent((int) (((double) request
+ .getSoFar() / (double) request
+ .getSize()) * 100));
}
if (cancelled) {
onCancelled();
@@ -144,10 +141,8 @@ public class HttpDownloader extends Downloader {
} catch (NullPointerException e) {
// might be thrown by connection.getInputStream()
e.printStackTrace();
- onFail(DownloadError.ERROR_CONNECTION_ERROR, status.getFeedFile()
- .getDownload_url());
+ onFail(DownloadError.ERROR_CONNECTION_ERROR, request.getSource());
} finally {
- IOUtils.closeQuietly(connection);
IOUtils.closeQuietly(out);
if (httpClient != null) {
httpClient.getConnectionManager().shutdown();
@@ -158,36 +153,28 @@ public class HttpDownloader extends Downloader {
private void onSuccess() {
if (AppConfig.DEBUG)
Log.d(TAG, "Download was successful");
- status.setSuccessful(true);
- status.setDone(true);
+ result.setSuccessful();
}
- private void onFail(int reason, String reasonDetailed) {
+ private void onFail(DownloadError reason, String reasonDetailed) {
if (AppConfig.DEBUG) {
Log.d(TAG, "Download failed");
}
- status.setReason(reason);
- status.setReasonDetailed(reasonDetailed);
- status.setDone(true);
- status.setSuccessful(false);
+ result.setFailed(reason, reasonDetailed);
cleanup();
}
private void onCancelled() {
if (AppConfig.DEBUG)
Log.d(TAG, "Download was cancelled");
- status.setReason(DownloadError.ERROR_DOWNLOAD_CANCELLED);
- status.setDone(true);
- status.setSuccessful(false);
- status.setCancelled(true);
+ result.setCancelled();
cleanup();
}
/** Deletes unfinished downloads. */
private void cleanup() {
- if (status != null && status.getFeedFile() != null
- && status.getFeedFile().getFile_url() != null) {
- File dest = new File(status.getFeedFile().getFile_url());
+ if (request.getDestination() != null) {
+ File dest = new File(request.getDestination());
if (dest.exists()) {
boolean rc = dest.delete();
if (AppConfig.DEBUG)
diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java
new file mode 100644
index 000000000..c96051874
--- /dev/null
+++ b/src/de/danoeh/antennapod/storage/DBReader.java
@@ -0,0 +1,755 @@
+package de.danoeh.antennapod.storage;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.SQLException;
+import android.util.Log;
+import de.danoeh.antennapod.AppConfig;
+import de.danoeh.antennapod.feed.Chapter;
+import de.danoeh.antennapod.feed.Feed;
+import de.danoeh.antennapod.feed.FeedImage;
+import de.danoeh.antennapod.feed.FeedItem;
+import de.danoeh.antennapod.feed.FeedMedia;
+import de.danoeh.antennapod.feed.ID3Chapter;
+import de.danoeh.antennapod.feed.SimpleChapter;
+import de.danoeh.antennapod.feed.VorbisCommentChapter;
+import de.danoeh.antennapod.service.download.*;
+import de.danoeh.antennapod.util.DownloadError;
+import de.danoeh.antennapod.util.comparator.DownloadStatusComparator;
+import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator;
+
+/**
+ * Provides methods for reading data from the AntennaPod database.
+ * In general, all database calls in DBReader-methods are executed on the caller's thread.
+ * This means that the caller should make sure that DBReader-methods are not executed on the GUI-thread.
+ * This class will use the {@link de.danoeh.antennapod.feed.EventDistributor} to notify listeners about changes in the database.
+
+ */
+public final class DBReader {
+ private static final String TAG = "DBReader";
+
+ /**
+ * Maximum size of the list returned by {@link #getPlaybackHistory(android.content.Context)}.
+ */
+ public static final int PLAYBACK_HISTORY_SIZE = 50;
+
+ /**
+ * Maximum size of the list returned by {@link #getDownloadLog(android.content.Context)}.
+ */
+ public static final int DOWNLOAD_LOG_SIZE = 200;
+
+
+ private DBReader() {
+ }
+
+ /**
+ * Returns a list of Feeds, sorted alphabetically by their title.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return A list of Feeds, sorted alphabetically by their title. A Feed-object
+ * of the returned list does NOT have its list of FeedItems yet. The FeedItem-list
+ * can be loaded separately with {@link #getFeedItemList(android.content.Context, de.danoeh.antennapod.feed.Feed)}.
+ */
+ public static List<Feed> getFeedList(final Context context) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Extracting Feedlist");
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+
+ Cursor feedlistCursor = adapter.getAllFeedsCursor();
+ List<Feed> feeds = new ArrayList<Feed>(feedlistCursor.getCount());
+
+ if (feedlistCursor.moveToFirst()) {
+ do {
+ Feed feed = extractFeedFromCursorRow(adapter, feedlistCursor);
+ feeds.add(feed);
+ } while (feedlistCursor.moveToNext());
+ }
+ feedlistCursor.close();
+ return feeds;
+ }
+
+ /**
+ * Returns a list of 'expired Feeds', i.e. Feeds that have not been updated for a certain amount of time.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param expirationTime Time that is used for determining whether a feed is outdated or not.
+ * A Feed is considered expired if 'lastUpdate < (currentTime - expirationTime)' evaluates to true.
+ * @return A list of Feeds, sorted alphabetically by their title. A Feed-object
+ * of the returned list does NOT have its list of FeedItems yet. The FeedItem-list
+ * can be loaded separately with {@link #getFeedItemList(android.content.Context, de.danoeh.antennapod.feed.Feed)}.
+ */
+ static List<Feed> getExpiredFeedsList(final Context context, final long expirationTime) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, String.format("getExpiredFeedsList(%d)", expirationTime));
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+
+ Cursor feedlistCursor = adapter.getExpiredFeedsCursor(expirationTime);
+ List<Feed> feeds = new ArrayList<Feed>(feedlistCursor.getCount());
+
+ if (feedlistCursor.moveToFirst()) {
+ do {
+ Feed feed = extractFeedFromCursorRow(adapter, feedlistCursor);
+ feeds.add(feed);
+ } while (feedlistCursor.moveToNext());
+ }
+ feedlistCursor.close();
+ return feeds;
+ }
+
+ /**
+ * Takes a list of FeedItems and loads their corresponding Feed-objects from the database.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param items The FeedItems whose Feed-objects should be loaded.
+ */
+ public static void loadFeedDataOfFeedItemlist(Context context,
+ List<FeedItem> items) {
+ List<Feed> feeds = getFeedList(context);
+ for (FeedItem item : items) {
+ for (Feed feed : feeds) {
+ if (feed.getId() == item.getFeedId()) {
+ item.setFeed(feed);
+ break;
+ }
+ }
+ if (item.getFeed() == null) {
+ Log.w(TAG, "No match found for item with ID " + item.getId() + ". Feed ID was " + item.getFeedId());
+ }
+ }
+ }
+
+ /**
+ * Loads the list of FeedItems for a certain Feed-object. This method should NOT be used if the FeedItems are not
+ * used. In order to get information ABOUT the list of FeedItems, consider using {@link #getFeedStatisticsList(android.content.Context)} instead.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param feed The Feed whose items should be loaded
+ * @return A list with the FeedItems of the Feed. The Feed-attribute of the FeedItems will already be set correctly.
+ * The method does NOT change the items-attribute of the feed.
+ */
+ public static List<FeedItem> getFeedItemList(Context context,
+ final Feed feed) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Extracting Feeditems of feed " + feed.getTitle());
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+
+ Cursor itemlistCursor = adapter.getAllItemsOfFeedCursor(feed);
+ List<FeedItem> items = extractItemlistFromCursor(adapter,
+ itemlistCursor);
+ itemlistCursor.close();
+
+ Collections.sort(items, new FeedItemPubdateComparator());
+
+ adapter.close();
+
+ for (FeedItem item : items) {
+ item.setFeed(feed);
+ }
+
+ return items;
+ }
+
+ static List<FeedItem> extractItemlistFromCursor(Context context, Cursor itemlistCursor) {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ List<FeedItem> result = extractItemlistFromCursor(adapter, itemlistCursor);
+ adapter.close();
+ return result;
+ }
+
+ private static List<FeedItem> extractItemlistFromCursor(
+ PodDBAdapter adapter, Cursor itemlistCursor) {
+ ArrayList<String> itemIds = new ArrayList<String>();
+ List<FeedItem> items = new ArrayList<FeedItem>(
+ itemlistCursor.getCount());
+
+ if (itemlistCursor.moveToFirst()) {
+ do {
+ FeedItem item = new FeedItem();
+
+ item.setId(itemlistCursor.getLong(PodDBAdapter.IDX_FI_SMALL_ID));
+ item.setTitle(itemlistCursor
+ .getString(PodDBAdapter.IDX_FI_SMALL_TITLE));
+ item.setLink(itemlistCursor
+ .getString(PodDBAdapter.IDX_FI_SMALL_LINK));
+ item.setPubDate(new Date(itemlistCursor
+ .getLong(PodDBAdapter.IDX_FI_SMALL_PUBDATE)));
+ item.setPaymentLink(itemlistCursor
+ .getString(PodDBAdapter.IDX_FI_SMALL_PAYMENT_LINK));
+ item.setFeedId(itemlistCursor
+ .getLong(PodDBAdapter.IDX_FI_SMALL_FEED));
+ itemIds.add(String.valueOf(item.getId()));
+
+ item.setRead((itemlistCursor
+ .getInt(PodDBAdapter.IDX_FI_SMALL_READ) > 0));
+ item.setItemIdentifier(itemlistCursor
+ .getString(PodDBAdapter.IDX_FI_SMALL_ITEM_IDENTIFIER));
+
+ // extract chapters
+ boolean hasSimpleChapters = itemlistCursor
+ .getInt(PodDBAdapter.IDX_FI_SMALL_HAS_CHAPTERS) > 0;
+ if (hasSimpleChapters) {
+ Cursor chapterCursor = adapter
+ .getSimpleChaptersOfFeedItemCursor(item);
+ if (chapterCursor.moveToFirst()) {
+ item.setChapters(new ArrayList<Chapter>());
+ do {
+ int chapterType = chapterCursor
+ .getInt(PodDBAdapter.KEY_CHAPTER_TYPE_INDEX);
+ Chapter chapter = null;
+ long start = chapterCursor
+ .getLong(PodDBAdapter.KEY_CHAPTER_START_INDEX);
+ String title = chapterCursor
+ .getString(PodDBAdapter.KEY_TITLE_INDEX);
+ String link = chapterCursor
+ .getString(PodDBAdapter.KEY_CHAPTER_LINK_INDEX);
+
+ switch (chapterType) {
+ case SimpleChapter.CHAPTERTYPE_SIMPLECHAPTER:
+ chapter = new SimpleChapter(start, title, item,
+ link);
+ break;
+ case ID3Chapter.CHAPTERTYPE_ID3CHAPTER:
+ chapter = new ID3Chapter(start, title, item,
+ link);
+ break;
+ case VorbisCommentChapter.CHAPTERTYPE_VORBISCOMMENT_CHAPTER:
+ chapter = new VorbisCommentChapter(start,
+ title, item, link);
+ break;
+ }
+ chapter.setId(chapterCursor
+ .getLong(PodDBAdapter.KEY_ID_INDEX));
+ item.getChapters().add(chapter);
+ } while (chapterCursor.moveToNext());
+ }
+ chapterCursor.close();
+ }
+ items.add(item);
+ } while (itemlistCursor.moveToNext());
+ }
+
+ extractMediafromItemlist(adapter, items, itemIds);
+ return items;
+ }
+
+ private static void extractMediafromItemlist(PodDBAdapter adapter,
+ List<FeedItem> items, ArrayList<String> itemIds) {
+
+ List<FeedItem> itemsCopy = new ArrayList<FeedItem>(items);
+ Cursor cursor = adapter.getFeedMediaCursorByItemID(itemIds
+ .toArray(new String[itemIds.size()]));
+ if (cursor.moveToFirst()) {
+ do {
+ long itemId = cursor.getLong(PodDBAdapter.KEY_MEDIA_FEEDITEM_INDEX);
+ // find matching feed item
+ FeedItem item = getMatchingItemForMedia(itemId, itemsCopy);
+ if (item != null) {
+ item.setMedia(extractFeedMediaFromCursorRow(cursor));
+ item.getMedia().setItem(item);
+ }
+ } while (cursor.moveToNext());
+ cursor.close();
+ }
+ }
+
+ private static FeedMedia extractFeedMediaFromCursorRow(final Cursor cursor) {
+ long mediaId = cursor.getLong(PodDBAdapter.KEY_ID_INDEX);
+ Date playbackCompletionDate = null;
+ long playbackCompletionTime = cursor
+ .getLong(PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE_INDEX);
+ if (playbackCompletionTime > 0) {
+ playbackCompletionDate = new Date(
+ playbackCompletionTime);
+ }
+
+ return new FeedMedia(
+ mediaId,
+ null,
+ cursor.getInt(PodDBAdapter.KEY_DURATION_INDEX),
+ cursor.getInt(PodDBAdapter.KEY_POSITION_INDEX),
+ cursor.getLong(PodDBAdapter.KEY_SIZE_INDEX),
+ cursor.getString(PodDBAdapter.KEY_MIME_TYPE_INDEX),
+ cursor.getString(PodDBAdapter.KEY_FILE_URL_INDEX),
+ cursor.getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX),
+ cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0,
+ playbackCompletionDate);
+ }
+
+ private static Feed extractFeedFromCursorRow(PodDBAdapter adapter,
+ Cursor cursor) {
+ Date lastUpdate = new Date(
+ cursor.getLong(PodDBAdapter.KEY_LAST_UPDATE_INDEX));
+
+ final FeedImage image;
+ long imageIndex = cursor.getLong(PodDBAdapter.KEY_IMAGE_INDEX);
+ if (imageIndex != 0) {
+ image = getFeedImage(adapter, imageIndex);
+ } else {
+ image = null;
+ }
+ Feed feed = new Feed(cursor.getLong(PodDBAdapter.KEY_ID_INDEX),
+ lastUpdate,
+ cursor.getString(PodDBAdapter.KEY_TITLE_INDEX),
+ cursor.getString(PodDBAdapter.KEY_LINK_INDEX),
+ cursor.getString(PodDBAdapter.KEY_DESCRIPTION_INDEX),
+ cursor.getString(PodDBAdapter.KEY_PAYMENT_LINK_INDEX),
+ cursor.getString(PodDBAdapter.KEY_AUTHOR_INDEX),
+ cursor.getString(PodDBAdapter.KEY_LANGUAGE_INDEX),
+ cursor.getString(PodDBAdapter.KEY_TYPE_INDEX),
+ cursor.getString(PodDBAdapter.KEY_FEED_IDENTIFIER_INDEX),
+ image,
+ cursor.getString(PodDBAdapter.KEY_FILE_URL_INDEX),
+ cursor.getString(PodDBAdapter.KEY_DOWNLOAD_URL_INDEX),
+ cursor.getInt(PodDBAdapter.KEY_DOWNLOADED_INDEX) > 0);
+
+ if (image != null) {
+ image.setFeed(feed);
+ }
+ return feed;
+ }
+
+ private static FeedItem getMatchingItemForMedia(long itemId,
+ List<FeedItem> items) {
+ for (FeedItem item : items) {
+ if (item.getId() == itemId) {
+ return item;
+ }
+ }
+ return null;
+ }
+
+ static List<FeedItem> getQueue(Context context, PodDBAdapter adapter) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Extracting queue");
+
+ Cursor itemlistCursor = adapter.getQueueCursor();
+ List<FeedItem> items = extractItemlistFromCursor(adapter,
+ itemlistCursor);
+ itemlistCursor.close();
+ loadFeedDataOfFeedItemlist(context, items);
+
+ return items;
+ }
+
+ /**
+ * Loads the IDs of the FeedItems in the queue. This method should be preferred over
+ * {@link #getQueue(android.content.Context)} if the FeedItems of the queue are not needed.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return A list of IDs sorted by the same order as the queue. The caller can wrap the returned
+ * list in a {@link de.danoeh.antennapod.util.QueueAccess} object for easier access to the queue's properties.
+ */
+ public static List<Long> getQueueIDList(Context context) {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+
+ adapter.open();
+ List<Long> result = getQueueIDList(adapter);
+ adapter.close();
+
+ return result;
+ }
+
+ static List<Long> getQueueIDList(PodDBAdapter adapter) {
+ adapter.open();
+ Cursor queueCursor = adapter.getQueueIDCursor();
+
+ List<Long> queueIds = new ArrayList<Long>(queueCursor.getCount());
+ if (queueCursor.moveToFirst()) {
+ do {
+ queueIds.add(queueCursor.getLong(0));
+ } while (queueCursor.moveToNext());
+ }
+ return queueIds;
+ }
+
+
+ /**
+ * Loads a list of the FeedItems in the queue. If the FeedItems of the queue are not used directly, consider using
+ * {@link #getQueueIDList(android.content.Context)} instead.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return A list of FeedItems sorted by the same order as the queue. The caller can wrap the returned
+ * list in a {@link de.danoeh.antennapod.util.QueueAccess} object for easier access to the queue's properties.
+ */
+ public static List<FeedItem> getQueue(Context context) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Extracting queue");
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ List<FeedItem> items = getQueue(context, adapter);
+ adapter.close();
+ return items;
+ }
+
+ /**
+ * Loads a list of FeedItems whose episode has been downloaded.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return A list of FeedItems whose episdoe has been downloaded.
+ */
+ public static List<FeedItem> getDownloadedItems(Context context) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Extracting downloaded items");
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+
+ Cursor itemlistCursor = adapter.getDownloadedItemsCursor();
+ List<FeedItem> items = extractItemlistFromCursor(adapter,
+ itemlistCursor);
+ itemlistCursor.close();
+ loadFeedDataOfFeedItemlist(context, items);
+ Collections.sort(items, new FeedItemPubdateComparator());
+
+ adapter.close();
+ return items;
+
+ }
+
+ /**
+ * Loads a list of FeedItems whose 'read'-attribute is set to false.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return A list of FeedItems whose 'read'-attribute it set to false. If the FeedItems in the list are not used,
+ * consider using {@link #getUnreadItemIds(android.content.Context)} instead.
+ */
+ public static List<FeedItem> getUnreadItemsList(Context context) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Extracting unread items list");
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+
+ Cursor itemlistCursor = adapter.getUnreadItemsCursor();
+ List<FeedItem> items = extractItemlistFromCursor(adapter,
+ itemlistCursor);
+ itemlistCursor.close();
+
+ loadFeedDataOfFeedItemlist(context, items);
+
+ adapter.close();
+
+ return items;
+ }
+
+ /**
+ * Loads the IDs of the FeedItems whose 'read'-attribute is set to false.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return A list of IDs of the FeedItems whose 'read'-attribute is set to false. This method should be preferred
+ * over {@link #getUnreadItemsList(android.content.Context)} if the FeedItems in the UnreadItems list are not used.
+ */
+ public static long[] getUnreadItemIds(Context context) {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ Cursor cursor = adapter.getUnreadItemIdsCursor();
+ long[] itemIds = new long[cursor.getCount()];
+ int i = 0;
+ if (cursor.moveToFirst()) {
+ do {
+ itemIds[i] = cursor.getLong(PodDBAdapter.KEY_ID_INDEX);
+ i++;
+ } while (cursor.moveToNext());
+ }
+ return itemIds;
+ }
+
+ /**
+ * Loads the playback history from the database. A FeedItem is in the playback history if playback of the correpsonding episode
+ * has been completed at least once.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return The playback history. The FeedItems are sorted by their media's playbackCompletionDate in descending order.
+ * The size of the returned list is limited by {@link #PLAYBACK_HISTORY_SIZE}.
+ */
+ public static List<FeedItem> getPlaybackHistory(final Context context) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Loading playback history");
+ final int PLAYBACK_HISTORY_SIZE = 50;
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+
+ Cursor mediaCursor = adapter.getCompletedMediaCursor(PLAYBACK_HISTORY_SIZE);
+ String[] itemIds = new String[mediaCursor.getCount()];
+ for (int i = 0; i < itemIds.length && mediaCursor.moveToPosition(i); i++) {
+ itemIds[i] = Long.toString(mediaCursor.getLong(PodDBAdapter.KEY_MEDIA_FEEDITEM_INDEX));
+ }
+ mediaCursor.close();
+ Cursor itemCursor = adapter.getFeedItemCursor(itemIds);
+ List<FeedItem> items = extractItemlistFromCursor(adapter, itemCursor);
+ loadFeedDataOfFeedItemlist(context, items);
+ itemCursor.close();
+
+ adapter.close();
+ return items;
+ }
+
+ /**
+ * Loads the download log from the database.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return A list with DownloadStatus objects that represent the download log.
+ * The size of the returned list is limited by {@link #DOWNLOAD_LOG_SIZE}.
+ */
+ public static List<DownloadStatus> getDownloadLog(Context context) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Extracting DownloadLog");
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ Cursor logCursor = adapter.getDownloadLogCursor(DOWNLOAD_LOG_SIZE);
+ List<DownloadStatus> downloadLog = new ArrayList<DownloadStatus>(
+ logCursor.getCount());
+
+ if (logCursor.moveToFirst()) {
+ do {
+ long id = logCursor.getLong(PodDBAdapter.KEY_ID_INDEX);
+
+ long feedfileId = logCursor
+ .getLong(PodDBAdapter.KEY_FEEDFILE_INDEX);
+ int feedfileType = logCursor
+ .getInt(PodDBAdapter.KEY_FEEDFILETYPE_INDEX);
+ boolean successful = logCursor
+ .getInt(PodDBAdapter.KEY_SUCCESSFUL_INDEX) > 0;
+ int reason = logCursor.getInt(PodDBAdapter.KEY_REASON_INDEX);
+ String reasonDetailed = logCursor
+ .getString(PodDBAdapter.KEY_REASON_DETAILED_INDEX);
+ String title = logCursor
+ .getString(PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE_INDEX);
+ Date completionDate = new Date(
+ logCursor
+ .getLong(PodDBAdapter.KEY_COMPLETION_DATE_INDEX));
+ downloadLog.add(new DownloadStatus(id, title, feedfileId,
+ feedfileType, successful, DownloadError.fromCode(reason), completionDate,
+ reasonDetailed));
+
+ } while (logCursor.moveToNext());
+ }
+ logCursor.close();
+ Collections.sort(downloadLog, new DownloadStatusComparator());
+ return downloadLog;
+ }
+
+ /**
+ * Loads the FeedItemStatistics objects of all Feeds in the database. This method should be preferred over
+ * {@link #getFeedItemList(android.content.Context, de.danoeh.antennapod.feed.Feed)} if only metadata about
+ * the FeedItems is needed.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return A list of FeedItemStatistics objects sorted alphabetically by their Feed's title.
+ */
+ public static List<FeedItemStatistics> getFeedStatisticsList(final Context context) {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ List<FeedItemStatistics> result = new ArrayList<FeedItemStatistics>();
+ Cursor cursor = adapter.getFeedStatisticsCursor();
+ if (cursor.moveToFirst()) {
+ do {
+ result.add(new FeedItemStatistics(cursor.getLong(PodDBAdapter.IDX_FEEDSTATISTICS_FEED),
+ cursor.getInt(PodDBAdapter.IDX_FEEDSTATISTICS_NUM_ITEMS),
+ cursor.getInt(PodDBAdapter.IDX_FEEDSTATISTICS_NEW_ITEMS),
+ cursor.getInt(PodDBAdapter.IDX_FEEDSTATISTICS_IN_PROGRESS_EPISODES),
+ new Date(cursor.getLong(PodDBAdapter.IDX_FEEDSTATISTICS_LATEST_EPISODE))));
+ } while (cursor.moveToNext());
+ }
+
+ cursor.close();
+ adapter.close();
+ return result;
+ }
+
+ /**
+ * Loads a specific Feed from the database.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param feedId The ID of the Feed
+ * @return The Feed or null if the Feed could not be found. The Feeds FeedItems will also be loaded from the
+ * database and the items-attribute will be set correctly.
+ */
+ public static Feed getFeed(final Context context, final long feedId) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Loading feed with id " + feedId);
+ Feed feed = null;
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ Cursor feedCursor = adapter.getFeedCursor(feedId);
+ if (feedCursor.moveToFirst()) {
+ feed = extractFeedFromCursorRow(adapter, feedCursor);
+ feed.setItems(getFeedItemList(context, feed));
+ } else {
+ Log.e(TAG, "getFeed could not find feed with id " + feedId);
+ }
+ feedCursor.close();
+ adapter.close();
+ return feed;
+ }
+
+ static FeedItem getFeedItem(final Context context, final long itemId, PodDBAdapter adapter) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Loading feeditem with id " + itemId);
+ FeedItem item = null;
+
+ Cursor itemCursor = adapter.getFeedItemCursor(Long.toString(itemId));
+ if (itemCursor.moveToFirst()) {
+ List<FeedItem> list = extractItemlistFromCursor(adapter, itemCursor);
+ if (list.size() > 0) {
+ item = list.get(0);
+ loadFeedDataOfFeedItemlist(context, list);
+ }
+ }
+ return item;
+
+ }
+
+ /**
+ * Loads a specific FeedItem from the database.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param itemId The ID of the FeedItem
+ * @return The FeedItem or null if the FeedItem could not be found. All FeedComponent-attributes of the FeedItem will
+ * also be loaded from the database.
+ */
+ public static FeedItem getFeedItem(final Context context, final long itemId) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Loading feeditem with id " + itemId);
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ FeedItem item = getFeedItem(context, itemId, adapter);
+ adapter.close();
+ return item;
+
+ }
+
+ /**
+ * Loads additional information about a FeedItem, e.g. shownotes
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param item The FeedItem
+ */
+ public static void loadExtraInformationOfFeedItem(final Context context, final FeedItem item) {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ Cursor extraCursor = adapter.getExtraInformationOfItem(item);
+ if (extraCursor.moveToFirst()) {
+ String description = extraCursor
+ .getString(PodDBAdapter.IDX_FI_EXTRA_DESCRIPTION);
+ String contentEncoded = extraCursor
+ .getString(PodDBAdapter.IDX_FI_EXTRA_CONTENT_ENCODED);
+ item.setDescription(description);
+ item.setContentEncoded(contentEncoded);
+ }
+ adapter.close();
+ }
+
+ /**
+ * Returns the number of downloaded episodes.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return The number of downloaded episodes.
+ */
+ public static int getNumberOfDownloadedEpisodes(final Context context) {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ final int result = adapter.getNumberOfDownloadedEpisodes();
+ adapter.close();
+ return result;
+ }
+
+ /**
+ * Returns the number of unread items.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @return The number of unread items.
+ */
+ public static int getNumberOfUnreadItems(final Context context) {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ final int result = adapter.getNumberOfUnreadItems();
+ adapter.close();
+ return result;
+ }
+
+ /**
+ * Searches the DB for a FeedImage of the given id.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param imageId The id of the object
+ * @return The found object
+ */
+ public static FeedImage getFeedImage(final Context context, final long imageId) {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ FeedImage result = getFeedImage(adapter, imageId);
+ adapter.close();
+ return result;
+ }
+
+ /**
+ * Searches the DB for a FeedImage of the given id.
+ *
+ * @param id The id of the object
+ * @return The found object
+ */
+ static FeedImage getFeedImage(PodDBAdapter adapter, final long id) {
+ Cursor cursor = adapter.getImageOfFeedCursor(id);
+ if ((cursor.getCount() == 0) || !cursor.moveToFirst()) {
+ throw new SQLException("No FeedImage found at index: " + id);
+ }
+ FeedImage image = new FeedImage(id, cursor.getString(cursor
+ .getColumnIndex(PodDBAdapter.KEY_TITLE)),
+ cursor.getString(cursor
+ .getColumnIndex(PodDBAdapter.KEY_FILE_URL)),
+ cursor.getString(cursor
+ .getColumnIndex(PodDBAdapter.KEY_DOWNLOAD_URL)),
+ cursor.getInt(cursor
+ .getColumnIndex(PodDBAdapter.KEY_DOWNLOADED)) > 0);
+ cursor.close();
+ return image;
+ }
+
+ /**
+ * Searches the DB for a FeedMedia of the given id.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param mediaId The id of the object
+ * @return The found object
+ */
+ public static FeedMedia getFeedMedia(final Context context, final long mediaId) {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+
+ adapter.open();
+ Cursor mediaCursor = adapter.getSingleFeedMediaCursor(mediaId);
+
+ FeedMedia media = null;
+ if (mediaCursor.moveToFirst()) {
+ final long itemId = mediaCursor.getLong(PodDBAdapter.KEY_MEDIA_FEEDITEM_INDEX);
+ media = extractFeedMediaFromCursorRow(mediaCursor);
+ FeedItem item = getFeedItem(context, itemId);
+ if (media != null && item != null) {
+ media.setItem(item);
+ item.setMedia(media);
+ }
+ }
+
+ mediaCursor.close();
+ adapter.close();
+
+ return media;
+ }
+}
diff --git a/src/de/danoeh/antennapod/storage/DBTasks.java b/src/de/danoeh/antennapod/storage/DBTasks.java
new file mode 100644
index 000000000..b1efda658
--- /dev/null
+++ b/src/de/danoeh/antennapod/storage/DBTasks.java
@@ -0,0 +1,731 @@
+package de.danoeh.antennapod.storage;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.util.Log;
+import de.danoeh.antennapod.AppConfig;
+import de.danoeh.antennapod.feed.EventDistributor;
+import de.danoeh.antennapod.feed.Feed;
+import de.danoeh.antennapod.feed.FeedImage;
+import de.danoeh.antennapod.feed.FeedItem;
+import de.danoeh.antennapod.feed.FeedMedia;
+import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.service.PlaybackService;
+import de.danoeh.antennapod.service.download.DownloadStatus;
+import de.danoeh.antennapod.util.DownloadError;
+import de.danoeh.antennapod.util.NetworkUtils;
+import de.danoeh.antennapod.util.QueueAccess;
+import de.danoeh.antennapod.util.exception.MediaFileNotFoundException;
+
+/**
+ * Provides methods for doing common tasks that use DBReader and DBWriter.
+ */
+public final class DBTasks {
+ private static final String TAG = "DBTasks";
+
+ private DBTasks() {
+ }
+
+ /**
+ * Starts playback of a FeedMedia object's file. This method will build an Intent based on the given parameters to
+ * start the {@link PlaybackService}.
+ *
+ * @param context Used for sending starting Services and Activities.
+ * @param media The FeedMedia object.
+ * @param showPlayer If true, starts the appropriate player activity ({@link de.danoeh.antennapod.activity.AudioplayerActivity}
+ * or {@link de.danoeh.antennapod.activity.VideoplayerActivity}
+ * @param startWhenPrepared Parameter for the {@link PlaybackService} start intent. If true, playback will start as
+ * soon as the PlaybackService has finished loading the FeedMedia object's file.
+ * @param shouldStream Parameter for the {@link PlaybackService} start intent. If true, the FeedMedia object's file
+ * will be streamed, otherwise the downloaded file will be used. If the downloaded file cannot be
+ * found, the PlaybackService will shutdown and the database entry of the FeedMedia object will be
+ * corrected.
+ */
+ public static void playMedia(final Context context, final FeedMedia media,
+ boolean showPlayer, boolean startWhenPrepared, boolean shouldStream) {
+ try {
+ if (!shouldStream) {
+ if (media.fileExists() == false) {
+ throw new MediaFileNotFoundException(
+ "No episode was found at " + media.getFile_url(),
+ media);
+ }
+ }
+ // Start playback Service
+ Intent launchIntent = new Intent(context, PlaybackService.class);
+ launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
+ launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED,
+ startWhenPrepared);
+ launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM,
+ shouldStream);
+ launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY,
+ true);
+ context.startService(launchIntent);
+ if (showPlayer) {
+ // Launch media player
+ context.startActivity(PlaybackService.getPlayerActivityIntent(
+ context, media));
+ }
+ DBWriter.addQueueItemAt(context, media.getItem().getId(), 0, false);
+ } catch (MediaFileNotFoundException e) {
+ e.printStackTrace();
+ if (media.isPlaying()) {
+ context.sendBroadcast(new Intent(
+ PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
+ }
+ notifyMissingFeedMediaFile(context, media);
+ }
+ }
+
+ private static AtomicBoolean isRefreshing = new AtomicBoolean(false);
+
+ /**
+ * Refreshes a given list of Feeds in a separate Thread. This method might ignore subsequent calls if it is still
+ * enqueuing Feeds for download from a previous call
+ *
+ * @param context Might be used for accessing the database
+ * @param feeds List of Feeds that should be refreshed.
+ */
+ public static void refreshAllFeeds(final Context context,
+ final List<Feed> feeds) {
+ if (isRefreshing.compareAndSet(false, true)) {
+ new Thread() {
+ public void run() {
+ if (feeds != null) {
+ refreshFeeds(context, feeds);
+ } else {
+ refreshFeeds(context, DBReader.getFeedList(context));
+ }
+ isRefreshing.set(false);
+ }
+ }.start();
+ } else {
+ if (AppConfig.DEBUG)
+ Log.d(TAG,
+ "Ignoring request to refresh all feeds: Refresh lock is locked");
+ }
+ }
+
+ /**
+ * Refreshes expired Feeds in the list returned by the getExpiredFeedsList(Context, long) method in DBReader.
+ * The expiration date parameter is determined by the update interval specified in {@link UserPreferences}.
+ *
+ * @param context Used for DB access.
+ */
+ public static void refreshExpiredFeeds(final Context context) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Refreshing expired feeds");
+
+ new Thread() {
+ public void run() {
+ long millis = UserPreferences.getUpdateInterval();
+
+ if (millis > 0) {
+ long now = Calendar.getInstance().getTime().getTime();
+
+ // Allow a 10 minute window
+ millis -= 10 * 60 * 1000;
+ List<Feed> feedList = DBReader.getExpiredFeedsList(context,
+ now - millis);
+ if (feedList.size() > 0) {
+ refreshFeeds(context, feedList);
+ }
+ }
+ }
+ }.start();
+ }
+
+ private static void refreshFeeds(final Context context,
+ final List<Feed> feedList) {
+
+ for (Feed feed : feedList) {
+ try {
+ refreshFeed(context, feed);
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ DBWriter.addDownloadStatus(
+ context,
+ new DownloadStatus(feed, feed
+ .getHumanReadableIdentifier(),
+ DownloadError.ERROR_REQUEST_ERROR, false, e
+ .getMessage()));
+ }
+ }
+
+ }
+
+ /**
+ * Updates a specific Feed.
+ *
+ * @param context Used for requesting the download.
+ * @param feed The Feed object.
+ */
+ public static void refreshFeed(Context context, Feed feed)
+ throws DownloadRequestException {
+ DownloadRequester.getInstance().downloadFeed(context,
+ new Feed(feed.getDownload_url(), new Date(), feed.getTitle()));
+ }
+
+ /**
+ * Notifies the database about a missing FeedImage file. This method will attempt to re-download the file.
+ *
+ * @param context Used for requesting the download.
+ * @param image The FeedImage object.
+ */
+ public static void notifyInvalidImageFile(final Context context,
+ final FeedImage image) {
+ Log.i(TAG,
+ "The DB was notified about an invalid image download. It will now try to re-download the image file");
+ try {
+ DownloadRequester.getInstance().downloadImage(context, image);
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ Log.w(TAG, "Failed to download invalid feed image");
+ }
+ }
+
+ /**
+ * Notifies the database about a missing FeedMedia file. This method will correct the FeedMedia object's values in the
+ * DB and send a FeedUpdateBroadcast.
+ */
+ public static void notifyMissingFeedMediaFile(final Context context,
+ final FeedMedia media) {
+ Log.i(TAG,
+ "The feedmanager was notified about a missing episode. It will update its database now.");
+ media.setDownloaded(false);
+ media.setFile_url(null);
+ DBWriter.setFeedMedia(context, media);
+ EventDistributor.getInstance().sendFeedUpdateBroadcast();
+ }
+
+ /**
+ * Request the download of all objects in the queue. from a separate Thread.
+ *
+ * @param context Used for requesting the download an accessing the database.
+ */
+ public static void downloadAllItemsInQueue(final Context context) {
+ new Thread() {
+ public void run() {
+ List<FeedItem> queue = DBReader.getQueue(context);
+ if (!queue.isEmpty()) {
+ try {
+ downloadFeedItems(context,
+ queue.toArray(new FeedItem[queue.size()]));
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }.start();
+ }
+
+ /**
+ * Requests the download of a list of FeedItem objects.
+ *
+ * @param context Used for requesting the download and accessing the DB.
+ * @param items The FeedItem objects.
+ */
+ public static void downloadFeedItems(final Context context,
+ FeedItem... items) throws DownloadRequestException {
+ downloadFeedItems(true, context, items);
+ }
+
+ private static void downloadFeedItems(boolean performAutoCleanup,
+ final Context context, final FeedItem... items)
+ throws DownloadRequestException {
+ final DownloadRequester requester = DownloadRequester.getInstance();
+
+ if (performAutoCleanup) {
+ new Thread() {
+
+ @Override
+ public void run() {
+ performAutoCleanup(context,
+ getPerformAutoCleanupArgs(context, items.length));
+ }
+
+ }.start();
+ }
+ for (FeedItem item : items) {
+ if (item.getMedia() != null
+ && !requester.isDownloadingFile(item.getMedia())
+ && !item.getMedia().isDownloaded()) {
+ if (items.length > 1) {
+ try {
+ requester.downloadMedia(context, item.getMedia());
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ DBWriter.addDownloadStatus(context,
+ new DownloadStatus(item.getMedia(), item
+ .getMedia()
+ .getHumanReadableIdentifier(),
+ DownloadError.ERROR_REQUEST_ERROR,
+ false, e.getMessage()));
+ }
+ } else {
+ requester.downloadMedia(context, item.getMedia());
+ }
+ }
+ }
+ }
+
+ private static int getNumberOfUndownloadedEpisodes(
+ final List<FeedItem> queue, final List<FeedItem> unreadItems) {
+ int counter = 0;
+ for (FeedItem item : queue) {
+ if (item.hasMedia() && !item.getMedia().isDownloaded()
+ && !item.getMedia().isPlaying()) {
+ counter++;
+ }
+ }
+ for (FeedItem item : unreadItems) {
+ if (item.hasMedia() && !item.getMedia().isDownloaded()) {
+ counter++;
+ }
+ }
+ return counter;
+ }
+
+ /**
+ * Looks for undownloaded episodes in the queue or list of unread items and request a download if
+ * 1. Network is available
+ * 2. There is free space in the episode cache
+ * This method should NOT be executed on the GUI thread.
+ *
+ * @param context Used for accessing the DB.
+ */
+ public static void autodownloadUndownloadedItems(final Context context) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Performing auto-dl of undownloaded episodes");
+ if (NetworkUtils.autodownloadNetworkAvailable(context)
+ && UserPreferences.isEnableAutodownload()) {
+ final List<FeedItem> queue = DBReader.getQueue(context);
+ final List<FeedItem> unreadItems = DBReader
+ .getUnreadItemsList(context);
+
+ int undownloadedEpisodes = getNumberOfUndownloadedEpisodes(queue,
+ unreadItems);
+ int downloadedEpisodes = DBReader
+ .getNumberOfDownloadedEpisodes(context);
+ int deletedEpisodes = performAutoCleanup(context,
+ getPerformAutoCleanupArgs(context, undownloadedEpisodes));
+ int episodeSpaceLeft = undownloadedEpisodes;
+ boolean cacheIsUnlimited = UserPreferences.getEpisodeCacheSize() == UserPreferences
+ .getEpisodeCacheSizeUnlimited();
+
+ if (!cacheIsUnlimited
+ && UserPreferences.getEpisodeCacheSize() < downloadedEpisodes
+ + undownloadedEpisodes) {
+ episodeSpaceLeft = UserPreferences.getEpisodeCacheSize()
+ - (downloadedEpisodes - deletedEpisodes);
+ }
+
+ List<FeedItem> itemsToDownload = new ArrayList<FeedItem>();
+ if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) {
+ for (int i = 0; i < queue.size(); i++) { // ignore playing item
+ FeedItem item = queue.get(i);
+ if (item.hasMedia() && !item.getMedia().isDownloaded()
+ && !item.getMedia().isPlaying()) {
+ itemsToDownload.add(item);
+ episodeSpaceLeft--;
+ undownloadedEpisodes--;
+ if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) {
+ break;
+ }
+ }
+ }
+ }
+ if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) {
+ for (FeedItem item : unreadItems) {
+ if (item.hasMedia() && !item.getMedia().isDownloaded()) {
+ itemsToDownload.add(item);
+ episodeSpaceLeft--;
+ undownloadedEpisodes--;
+ if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) {
+ break;
+ }
+ }
+ }
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Enqueueing " + itemsToDownload.size()
+ + " items for download");
+
+ try {
+ downloadFeedItems(false, context,
+ itemsToDownload.toArray(new FeedItem[itemsToDownload
+ .size()]));
+ } catch (DownloadRequestException e) {
+ e.printStackTrace();
+ }
+
+ }
+ }
+
+ private static int getPerformAutoCleanupArgs(Context context,
+ final int episodeNumber) {
+ if (episodeNumber >= 0
+ && UserPreferences.getEpisodeCacheSize() != UserPreferences
+ .getEpisodeCacheSizeUnlimited()) {
+ int downloadedEpisodes = DBReader
+ .getNumberOfDownloadedEpisodes(context);
+ if (downloadedEpisodes + episodeNumber >= UserPreferences
+ .getEpisodeCacheSize()) {
+
+ return downloadedEpisodes + episodeNumber
+ - UserPreferences.getEpisodeCacheSize();
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Removed downloaded episodes outside of the queue if the episode cache is full. Episodes with a smaller
+ * 'playbackCompletionDate'-value will be deleted first.
+ * <p/>
+ * This method should NOT be executed on the GUI thread.
+ *
+ * @param context Used for accessing the DB.
+ */
+ public static void performAutoCleanup(final Context context) {
+ performAutoCleanup(context, getPerformAutoCleanupArgs(context, 0));
+ }
+
+ private static int performAutoCleanup(final Context context,
+ final int episodeNumber) {
+ List<FeedItem> candidates = DBReader.getDownloadedItems(context);
+ List<FeedItem> queue = DBReader.getQueue(context);
+ List<FeedItem> delete;
+ for (FeedItem item : candidates) {
+ if (item.hasMedia() && item.getMedia().isDownloaded()
+ && !queue.contains(item) && item.isRead()) {
+ candidates.add(item);
+ }
+
+ }
+
+ Collections.sort(candidates, new Comparator<FeedItem>() {
+ @Override
+ public int compare(FeedItem lhs, FeedItem rhs) {
+ Date l = lhs.getMedia().getPlaybackCompletionDate();
+ Date r = rhs.getMedia().getPlaybackCompletionDate();
+
+ if (l == null) {
+ l = new Date(0);
+ }
+ if (r == null) {
+ r = new Date(0);
+ }
+ return l.compareTo(r);
+ }
+ });
+
+ if (candidates.size() > episodeNumber) {
+ delete = candidates.subList(0, episodeNumber);
+ } else {
+ delete = candidates;
+ }
+
+ for (FeedItem item : delete) {
+ DBWriter.deleteFeedMediaOfItem(context, item.getId());
+ }
+
+ int counter = delete.size();
+
+ if (AppConfig.DEBUG)
+ Log.d(TAG, String.format(
+ "Auto-delete deleted %d episodes (%d requested)", counter,
+ episodeNumber));
+
+ return counter;
+ }
+
+ /**
+ * Adds all FeedItem objects whose 'read'-attribute is false to the queue in a separate thread.
+ */
+ public static void enqueueAllNewItems(final Context context) {
+ long[] unreadItems = DBReader.getUnreadItemIds(context);
+ DBWriter.addQueueItem(context, unreadItems);
+ }
+
+ /**
+ * Returns the successor of a FeedItem in the queue.
+ *
+ * @param context Used for accessing the DB.
+ * @param itemId ID of the FeedItem
+ * @param queue Used for determining the successor of the item. If this parameter is null, the method will load
+ * the queue from the database in the same thread.
+ * @return Successor of the FeedItem or null if the FeedItem is not in the queue or has no successor.
+ */
+ public static FeedItem getQueueSuccessorOfItem(Context context,
+ final long itemId, List<FeedItem> queue) {
+ FeedItem result = null;
+ if (queue == null) {
+ queue = DBReader.getQueue(context);
+ }
+ if (queue != null) {
+ Iterator<FeedItem> iterator = queue.iterator();
+ while (iterator.hasNext()) {
+ FeedItem item = iterator.next();
+ if (item.getId() == itemId) {
+ if (iterator.hasNext()) {
+ result = iterator.next();
+ }
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Loads the queue from the database and checks if the specified FeedItem is in the queue.
+ * This method should NOT be executed in the GUI thread.
+ *
+ * @param context Used for accessing the DB.
+ * @param feedItemId ID of the FeedItem
+ */
+ public static boolean isInQueue(Context context, final long feedItemId) {
+ List<Long> queue = DBReader.getQueueIDList(context);
+ return QueueAccess.IDListAccess(queue).contains(feedItemId);
+ }
+
+ private static Feed searchFeedByIdentifyingValue(Context context,
+ String identifier) {
+ List<Feed> feeds = DBReader.getFeedList(context);
+ for (Feed feed : feeds) {
+ if (feed.getIdentifyingValue().equals(identifier)) {
+ return feed;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get a FeedItem by its identifying value.
+ */
+ private static FeedItem searchFeedItemByIdentifyingValue(Feed feed,
+ String identifier) {
+ for (FeedItem item : feed.getItems()) {
+ if (item.getIdentifyingValue().equals(identifier)) {
+ return item;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Adds a new Feed to the database or updates the old version if it already exists. If another Feed with the same
+ * identifying value already exists, this method will add new FeedItems from the new Feed to the existing Feed.
+ * These FeedItems will be marked as unread.
+ * This method should NOT be executed on the GUI thread.
+ *
+ * @param context Used for accessing the DB.
+ * @param newFeed The new Feed object.
+ * @return The updated Feed from the database if it already existed, or the new Feed from the parameters otherwise.
+ */
+ public static synchronized Feed updateFeed(final Context context,
+ final Feed newFeed) {
+ // Look up feed in the feedslist
+ final Feed savedFeed = searchFeedByIdentifyingValue(context,
+ newFeed.getIdentifyingValue());
+ if (savedFeed == null) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG,
+ "Found no existing Feed with title "
+ + newFeed.getTitle() + ". Adding as new one.");
+ // Add a new Feed
+ try {
+ DBWriter.addNewFeed(context, newFeed).get();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ }
+ return newFeed;
+ } else {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Feed with title " + newFeed.getTitle()
+ + " already exists. Syncing new with existing one.");
+
+ savedFeed.setItems(DBReader.getFeedItemList(context, savedFeed));
+ if (savedFeed.compareWithOther(newFeed)) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG,
+ "Feed has updated attribute values. Updating old feed's attributes");
+ savedFeed.updateFromOther(newFeed);
+ }
+ // Look for new or updated Items
+ for (int idx = 0; idx < newFeed.getItems().size(); idx++) {
+ final FeedItem item = newFeed.getItems().get(idx);
+ FeedItem oldItem = searchFeedItemByIdentifyingValue(savedFeed,
+ item.getIdentifyingValue());
+ if (oldItem == null) {
+ // item is new
+ final int i = idx;
+ item.setFeed(savedFeed);
+ savedFeed.getItems().add(i, item);
+ DBWriter.markItemRead(context, item.getId(), false);
+ } else {
+ oldItem.updateFromOther(item);
+ }
+ }
+ // update attributes
+ savedFeed.setLastUpdate(newFeed.getLastUpdate());
+ savedFeed.setType(newFeed.getType());
+ try {
+ DBWriter.setCompleteFeed(context, savedFeed).get();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ }
+ new Thread() {
+ @Override
+ public void run() {
+ autodownloadUndownloadedItems(context);
+ }
+ }.start();
+ return savedFeed;
+ }
+ }
+
+ /**
+ * Searches the titles of FeedItems of a specific Feed for a given
+ * string.
+ *
+ * @param context Used for accessing the DB.
+ * @param feedID The id of the feed whose items should be searched.
+ * @param query The search string.
+ * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems.
+ */
+ public static FutureTask<List<FeedItem>> searchFeedItemTitle(final Context context,
+ final long feedID, final String query) {
+ return new FutureTask<List<FeedItem>>(new QueryTask<List<FeedItem>>(context) {
+ @Override
+ public void execute(PodDBAdapter adapter) {
+ Cursor searchResult = adapter.searchItemTitles(feedID,
+ query);
+ List<FeedItem> items = DBReader.extractItemlistFromCursor(context, searchResult);
+ DBReader.loadFeedDataOfFeedItemlist(context, items);
+ setResult(items);
+ searchResult.close();
+ }
+ });
+ }
+
+ /**
+ * Searches the descriptions of FeedItems of a specific Feed for a given
+ * string.
+ *
+ * @param context Used for accessing the DB.
+ * @param feedID The id of the feed whose items should be searched.
+ * @param query The search string
+ * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems.
+ */
+ public static FutureTask<List<FeedItem>> searchFeedItemDescription(final Context context,
+ final long feedID, final String query) {
+ return new FutureTask<List<FeedItem>>(new QueryTask<List<FeedItem>>(context) {
+ @Override
+ public void execute(PodDBAdapter adapter) {
+ Cursor searchResult = adapter.searchItemDescriptions(feedID,
+ query);
+ List<FeedItem> items = DBReader.extractItemlistFromCursor(context, searchResult);
+ DBReader.loadFeedDataOfFeedItemlist(context, items);
+ setResult(items);
+ searchResult.close();
+ }
+ });
+ }
+
+ /**
+ * Searches the contentEncoded-value of FeedItems of a specific Feed for a given
+ * string.
+ *
+ * @param context Used for accessing the DB.
+ * @param feedID The id of the feed whose items should be searched.
+ * @param query The search string
+ * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems.
+ */
+ public static FutureTask<List<FeedItem>> searchFeedItemContentEncoded(final Context context,
+ final long feedID, final String query) {
+ return new FutureTask<List<FeedItem>>(new QueryTask<List<FeedItem>>(context) {
+ @Override
+ public void execute(PodDBAdapter adapter) {
+ Cursor searchResult = adapter.searchItemContentEncoded(feedID,
+ query);
+ List<FeedItem> items = DBReader.extractItemlistFromCursor(context, searchResult);
+ DBReader.loadFeedDataOfFeedItemlist(context, items);
+ setResult(items);
+ searchResult.close();
+ }
+ });
+ }
+
+ /**
+ * Searches chapters of the FeedItems of a specific Feed for a given string.
+ *
+ * @param context Used for accessing the DB.
+ * @param feedID The id of the feed whose items should be searched.
+ * @param query The search string
+ * @return A FutureTask object that executes the search request and returns the search result as a List of FeedItems.
+ */
+ public static FutureTask<List<FeedItem>> searchFeedItemChapters(final Context context,
+ final long feedID, final String query) {
+ return new FutureTask<List<FeedItem>>(new QueryTask<List<FeedItem>>(context) {
+ @Override
+ public void execute(PodDBAdapter adapter) {
+ Cursor searchResult = adapter.searchItemChapters(feedID,
+ query);
+ List<FeedItem> items = DBReader.extractItemlistFromCursor(context, searchResult);
+ DBReader.loadFeedDataOfFeedItemlist(context, items);
+ setResult(items);
+ searchResult.close();
+ }
+ });
+ }
+
+ /**
+ * A runnable which should be used for database queries. The onCompletion
+ * method is executed on the database executor to handle Cursors correctly.
+ * This class automatically creates a PodDBAdapter object and closes it when
+ * it is no longer in use.
+ */
+ static abstract class QueryTask<T> implements Callable<T> {
+ private T result;
+ private Context context;
+
+ public QueryTask(Context context) {
+ this.context = context;
+ }
+
+ @Override
+ public T call() throws Exception {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ execute(adapter);
+ adapter.close();
+ return result;
+ }
+
+ public abstract void execute(PodDBAdapter adapter);
+
+ protected void setResult(T result) {
+ this.result = result;
+ }
+ }
+
+}
diff --git a/src/de/danoeh/antennapod/storage/DBWriter.java b/src/de/danoeh/antennapod/storage/DBWriter.java
new file mode 100644
index 000000000..aed8b93c9
--- /dev/null
+++ b/src/de/danoeh/antennapod/storage/DBWriter.java
@@ -0,0 +1,727 @@
+package de.danoeh.antennapod.storage;
+
+import java.io.File;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import de.danoeh.antennapod.AppConfig;
+import de.danoeh.antennapod.feed.*;
+import de.danoeh.antennapod.preferences.PlaybackPreferences;
+import de.danoeh.antennapod.service.PlaybackService;
+import de.danoeh.antennapod.service.download.DownloadStatus;
+import de.danoeh.antennapod.util.QueueAccess;
+
+/**
+ * Provides methods for writing data to AntennaPod's database.
+ * In general, DBWriter-methods will be executed on an internal ExecutorService.
+ * Some methods return a Future-object which the caller can use for waiting for the method's completion. The returned Future's
+ * will NOT contain any results.
+ * The caller can also use the {@link EventDistributor} in order to be notified about the method's completion asynchronously.
+ * This class will use the {@link EventDistributor} to notify listeners about changes in the database.
+ */
+public class DBWriter {
+ private static final String TAG = "DBWriter";
+
+ private static final ExecutorService dbExec;
+
+ static {
+ dbExec = Executors.newSingleThreadExecutor(new ThreadFactory() {
+
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r);
+ t.setPriority(Thread.MIN_PRIORITY);
+ return t;
+ }
+ });
+ }
+
+ private DBWriter() {
+ }
+
+ /**
+ * Deletes a downloaded FeedMedia file from the storage device.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param mediaId ID of the FeedMedia object whose downloaded file should be deleted.
+ */
+ public static Future<?> deleteFeedMediaOfItem(final Context context,
+ final long mediaId) {
+ return dbExec.submit(new Runnable() {
+ @Override
+ public void run() {
+
+ final FeedMedia media = DBReader.getFeedMedia(context, mediaId);
+ if (media != null) {
+ boolean result = false;
+ if (media.isDownloaded()) {
+ // delete downloaded media file
+ File mediaFile = new File(media.getFile_url());
+ if (mediaFile.exists()) {
+ result = mediaFile.delete();
+ }
+ media.setDownloaded(false);
+ media.setFile_url(null);
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setMedia(media);
+ adapter.close();
+
+ // If media is currently being played, change playback
+ // type to 'stream' and shutdown playback service
+ SharedPreferences prefs = PreferenceManager
+ .getDefaultSharedPreferences(context);
+ if (PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA) {
+ if (media.getId() == PlaybackPreferences
+ .getCurrentlyPlayingFeedMediaId()) {
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putBoolean(
+ PlaybackPreferences.PREF_CURRENT_EPISODE_IS_STREAM,
+ true);
+ editor.commit();
+ }
+ if (PlaybackPreferences
+ .getCurrentlyPlayingFeedMediaId() == media
+ .getId()) {
+ context.sendBroadcast(new Intent(
+ PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
+ }
+ }
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Deleting File. Result: " + result);
+ }
+ }
+ });
+ }
+
+ /**
+ * Deletes a Feed and all downloaded files of its components like images and downloaded episodes.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param feedId ID of the Feed that should be deleted.
+ */
+ public static Future<?> deleteFeed(final Context context, final long feedId) {
+ return dbExec.submit(new Runnable() {
+ @Override
+ public void run() {
+ DownloadRequester requester = DownloadRequester.getInstance();
+ SharedPreferences prefs = PreferenceManager
+ .getDefaultSharedPreferences(context
+ .getApplicationContext());
+ final Feed feed = DBReader.getFeed(context, feedId);
+ if (feed != null) {
+ if (PlaybackPreferences.getCurrentlyPlayingMedia() == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA
+ && PlaybackPreferences.getLastPlayedFeedId() == feed
+ .getId()) {
+ context.sendBroadcast(new Intent(
+ PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putLong(
+ PlaybackPreferences.PREF_CURRENTLY_PLAYING_FEED_ID,
+ -1);
+ editor.commit();
+ }
+
+ // delete image file
+ if (feed.getImage() != null) {
+ if (feed.getImage().isDownloaded()
+ && feed.getImage().getFile_url() != null) {
+ File imageFile = new File(feed.getImage()
+ .getFile_url());
+ imageFile.delete();
+ } else if (requester.isDownloadingFile(feed.getImage())) {
+ requester.cancelDownload(context, feed.getImage());
+ }
+ }
+ // delete stored media files and mark them as read
+ List<FeedItem> queue = DBReader.getQueue(context);
+ boolean queueWasModified = false;
+ if (feed.getItems() == null) {
+ DBReader.getFeedItemList(context, feed);
+ }
+
+ for (FeedItem item : feed.getItems()) {
+ queueWasModified |= queue.remove(item);
+ if (item.getMedia() != null
+ && item.getMedia().isDownloaded()) {
+ File mediaFile = new File(item.getMedia()
+ .getFile_url());
+ mediaFile.delete();
+ } else if (item.getMedia() != null
+ && requester.isDownloadingFile(item.getMedia())) {
+ requester.cancelDownload(context, item.getMedia());
+ }
+ }
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ if (queueWasModified) {
+ adapter.setQueue(queue);
+ }
+ adapter.removeFeed(feed);
+ adapter.close();
+ EventDistributor.getInstance().sendFeedUpdateBroadcast();
+ }
+ }
+ });
+ }
+
+ /**
+ * Deletes the entire playback history.
+ *
+ * @param context A context that is used for opening a database connection.
+ */
+ public static Future<?> clearPlaybackHistory(final Context context) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.clearPlaybackHistory();
+ adapter.close();
+ EventDistributor.getInstance()
+ .sendPlaybackHistoryUpdateBroadcast();
+ }
+ });
+ }
+
+ /**
+ * Adds a FeedMedia object to the playback history. A FeedMedia object is in the playback history if
+ * its playback completion date is set to a non-null value. This method will set the playback completion date to the
+ * current date regardless of the current value.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param media FeedMedia that should be added to the playback history.
+ */
+ public static Future<?> addItemToPlaybackHistory(final Context context,
+ final FeedMedia media) {
+ return dbExec.submit(new Runnable() {
+ @Override
+ public void run() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Adding new item to playback history");
+ media.setPlaybackCompletionDate(new Date());
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setMedia(media);
+ adapter.close();
+ EventDistributor.getInstance().sendPlaybackHistoryUpdateBroadcast();
+
+ }
+ });
+ }
+
+ private static void cleanupDownloadLog(final PodDBAdapter adapter) {
+ final long logSize = adapter.getDownloadLogSize();
+ if (logSize > DBReader.DOWNLOAD_LOG_SIZE) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Cleaning up download log");
+ adapter.removeDownloadLogItems(logSize - DBReader.DOWNLOAD_LOG_SIZE);
+ }
+ }
+
+ /**
+ * Adds a Download status object to the download log.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param status The DownloadStatus object.
+ */
+ public static Future<?> addDownloadStatus(final Context context,
+ final DownloadStatus status) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+
+ adapter.setDownloadStatus(status);
+ cleanupDownloadLog(adapter);
+ adapter.close();
+ EventDistributor.getInstance().sendDownloadLogUpdateBroadcast();
+ }
+ });
+
+ }
+
+ /**
+ * Inserts a FeedItem in the queue at the specified index. The 'read'-attribute of the FeedItem will be set to
+ * true. If the FeedItem is already in the queue, the queue will not be modified.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param itemId ID of the FeedItem that should be added to the queue.
+ * @param index Destination index. Must be in range 0..queue.size()
+ * @param performAutoDownload True if an auto-download process should be started after the operation
+ * @throws IndexOutOfBoundsException if index < 0 || index >= queue.size()
+ */
+ public static Future<?> addQueueItemAt(final Context context, final long itemId,
+ final int index, final boolean performAutoDownload) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ final PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ final List<FeedItem> queue = DBReader
+ .getQueue(context, adapter);
+ FeedItem item = null;
+
+ if (queue != null) {
+ boolean queueModified = false;
+ boolean unreadItemsModified = false;
+
+ if (!itemListContains(queue, itemId)) {
+ item = DBReader.getFeedItem(context, itemId);
+ if (item != null) {
+ queue.add(index, item);
+ queueModified = true;
+ if (!item.isRead()) {
+ item.setRead(true);
+ unreadItemsModified = true;
+ }
+ }
+ }
+ if (queueModified) {
+ adapter.setQueue(queue);
+ EventDistributor.getInstance()
+ .sendQueueUpdateBroadcast();
+ }
+ if (unreadItemsModified && item != null) {
+ adapter.setSingleFeedItem(item);
+ EventDistributor.getInstance()
+ .sendUnreadItemsUpdateBroadcast();
+ }
+ }
+ adapter.close();
+ if (performAutoDownload) {
+
+ new Thread() {
+ @Override
+ public void run() {
+ DBTasks.autodownloadUndownloadedItems(context);
+
+ }
+ }.start();
+ }
+
+ }
+ });
+
+ }
+
+ /**
+ * Appends FeedItem objects to the end of the queue. The 'read'-attribute of all items will be set to true.
+ * If a FeedItem is already in the queue, the FeedItem will not change its position in the queue.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param itemIds IDs of the FeedItem objects that should be added to the queue.
+ */
+ public static Future<?> addQueueItem(final Context context,
+ final long... itemIds) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ if (itemIds.length > 0) {
+ final PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ final List<FeedItem> queue = DBReader.getQueue(context,
+ adapter);
+
+ if (queue != null) {
+ boolean queueModified = false;
+ boolean unreadItemsModified = false;
+ List<FeedItem> itemsToSave = new LinkedList<FeedItem>();
+ for (int i = 0; i < itemIds.length; i++) {
+ if (!itemListContains(queue, itemIds[i])) {
+ final FeedItem item = DBReader.getFeedItem(
+ context, itemIds[i]);
+
+ if (item != null) {
+ queue.add(item);
+ queueModified = true;
+ if (!item.isRead()) {
+ item.setRead(true);
+ itemsToSave.add(item);
+ unreadItemsModified = true;
+ }
+ }
+ }
+ }
+ if (queueModified) {
+ adapter.setQueue(queue);
+ EventDistributor.getInstance()
+ .sendQueueUpdateBroadcast();
+ }
+ if (unreadItemsModified) {
+ adapter.setFeedItemlist(itemsToSave);
+ EventDistributor.getInstance()
+ .sendUnreadItemsUpdateBroadcast();
+ }
+ }
+ adapter.close();
+ new Thread() {
+ @Override
+ public void run() {
+ DBTasks.autodownloadUndownloadedItems(context);
+
+ }
+ }.start();
+ }
+ }
+ });
+
+ }
+
+ /**
+ * Removes all FeedItem objects from the queue.
+ *
+ * @param context A context that is used for opening a database connection.
+ */
+ public static Future<?> clearQueue(final Context context) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.clearQueue();
+ adapter.close();
+
+ EventDistributor.getInstance().sendQueueUpdateBroadcast();
+ }
+ });
+ }
+
+ /**
+ * Removes a FeedItem object from the queue.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param itemId ID of the FeedItem that should be removed.
+ * @param performAutoDownload true if an auto-download process should be started after the operation.
+ */
+ public static Future<?> removeQueueItem(final Context context,
+ final long itemId, final boolean performAutoDownload) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ final PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ final List<FeedItem> queue = DBReader
+ .getQueue(context, adapter);
+ FeedItem item = null;
+
+ if (queue != null) {
+ boolean queueModified = false;
+ QueueAccess queueAccess = QueueAccess.ItemListAccess(queue);
+ if (queueAccess.contains(itemId)) {
+ item = DBReader.getFeedItem(context, itemId);
+ if (item != null) {
+ queueModified = queueAccess.remove(itemId);
+ }
+ }
+ if (queueModified) {
+ adapter.setQueue(queue);
+ EventDistributor.getInstance()
+ .sendQueueUpdateBroadcast();
+ } else {
+ Log.w(TAG, "Queue was not modified by call to removeQueueItem");
+ }
+ } else {
+ Log.e(TAG, "removeQueueItem: Could not load queue");
+ }
+ adapter.close();
+ if (performAutoDownload) {
+
+ new Thread() {
+ @Override
+ public void run() {
+ DBTasks.autodownloadUndownloadedItems(context);
+
+ }
+ }.start();
+ }
+ }
+ });
+
+ }
+
+ /**
+ * Changes the position of a FeedItem in the queue.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param from Source index. Must be in range 0..queue.size()-1.
+ * @param to Destination index. Must be in range 0..queue.size()-1.
+ * @param broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to
+ * false if the caller wants to avoid unexpected updates of the GUI.
+ * @throws IndexOutOfBoundsException if (to < 0 || to >= queue.size()) || (from < 0 || from >= queue.size())
+ */
+ public static Future<?> moveQueueItem(final Context context, final int from,
+ final int to, final boolean broadcastUpdate) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ final PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ final List<FeedItem> queue = DBReader
+ .getQueue(context, adapter);
+
+ if (queue != null) {
+ if (from >= 0 && from < queue.size() && to >= 0
+ && to < queue.size()) {
+
+ final FeedItem item = queue.remove(from);
+ queue.add(to, item);
+
+ adapter.setQueue(queue);
+ if (broadcastUpdate) {
+ EventDistributor.getInstance()
+ .sendQueueUpdateBroadcast();
+ }
+
+ }
+ } else {
+ Log.e(TAG, "moveQueueItem: Could not load queue");
+ }
+ adapter.close();
+ }
+ });
+ }
+
+ /**
+ * Sets the 'read'-attribute of a FeedItem to the specified value.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param item The FeedItem object
+ * @param read New value of the 'read'-attribute
+ * @param resetMediaPosition true if this method should also reset the position of the FeedItem's FeedMedia object.
+ * If the FeedItem has no FeedMedia object, this parameter will be ignored.
+ */
+ public static Future<?> markItemRead(Context context, FeedItem item, boolean read, boolean resetMediaPosition) {
+ long mediaId = (item.hasMedia()) ? item.getMedia().getId() : 0;
+ return markItemRead(context, item.getId(), read, mediaId, resetMediaPosition);
+ }
+
+ /**
+ * Sets the 'read'-attribute of a FeedItem to the specified value.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param itemId ID of the FeedItem
+ * @param read New value of the 'read'-attribute
+ */
+ public static Future<?> markItemRead(final Context context, final long itemId,
+ final boolean read) {
+ return markItemRead(context, itemId, read, 0, false);
+ }
+
+ private static Future<?> markItemRead(final Context context, final long itemId,
+ final boolean read, final long mediaId,
+ final boolean resetMediaPosition) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ final PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setFeedItemRead(read, itemId, mediaId,
+ resetMediaPosition);
+ adapter.close();
+
+ EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
+ }
+ });
+ }
+
+ /**
+ * Sets the 'read'-attribute of all FeedItems of a specific Feed to true.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param feedId ID of the Feed.
+ */
+ public static Future<?> markFeedRead(final Context context, final long feedId) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ final PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ Cursor itemCursor = adapter.getAllItemsOfFeedCursor(feedId);
+ long[] itemIds = new long[itemCursor.getCount()];
+ itemCursor.moveToFirst();
+ for (int i = 0; i < itemIds.length; i++) {
+ itemIds[i] = itemCursor.getLong(PodDBAdapter.KEY_ID_INDEX);
+ }
+ itemCursor.close();
+ adapter.setFeedItemRead(true, itemIds);
+ adapter.close();
+
+ EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
+ }
+ });
+
+ }
+
+ /**
+ * Sets the 'read'-attribute of all FeedItems to true.
+ *
+ * @param context A context that is used for opening a database connection.
+ */
+ public static Future<?> markAllItemsRead(final Context context) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ final PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ Cursor itemCursor = adapter.getUnreadItemsCursor();
+ long[] itemIds = new long[itemCursor.getCount()];
+ itemCursor.moveToFirst();
+ for (int i = 0; i < itemIds.length; i++) {
+ itemIds[i] = itemCursor.getLong(PodDBAdapter.KEY_ID_INDEX);
+ }
+ itemCursor.close();
+ adapter.setFeedItemRead(true, itemIds);
+ adapter.close();
+
+ EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
+ }
+ });
+
+ }
+
+ static Future<?> addNewFeed(final Context context, final Feed feed) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ final PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ EventDistributor.getInstance().sendFeedUpdateBroadcast();
+ }
+ });
+ }
+
+ static Future<?> setCompleteFeed(final Context context, final Feed feed) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ EventDistributor.getInstance().sendFeedUpdateBroadcast();
+ }
+ });
+
+ }
+
+ /**
+ * Saves a FeedMedia object in the database. This method will save all attributes of the FeedMedia object. The
+ * contents of FeedComponent-attributes (e.g. the FeedMedia's 'item'-attribute) will not be saved.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param media The FeedMedia object.
+ */
+ public static Future<?> setFeedMedia(final Context context,
+ final FeedMedia media) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setMedia(media);
+ adapter.close();
+ }
+ });
+ }
+
+ /**
+ * Saves the 'position' and 'duration' attributes of a FeedMedia object
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param media The FeedMedia object.
+ */
+ public static Future<?> setFeedMediaPlaybackInformation(final Context context, final FeedMedia media) {
+ return dbExec.submit(new Runnable() {
+ @Override
+ public void run() {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setFeedMediaPlaybackInformation(media);
+ adapter.close();
+ }
+ });
+ }
+
+ /**
+ * Saves a FeedItem object in the database. This method will save all attributes of the FeedItem object including
+ * the content of FeedComponent-attributes.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param item The FeedItem object.
+ */
+ public static Future<?> setFeedItem(final Context context,
+ final FeedItem item) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setSingleFeedItem(item);
+ adapter.close();
+ }
+ });
+ }
+
+ /**
+ * Saves a FeedImage object in the database. This method will save all attributes of the FeedImage object. The
+ * contents of FeedComponent-attributes (e.g. the FeedImages's 'feed'-attribute) will not be saved.
+ *
+ * @param context A context that is used for opening a database connection.
+ * @param image The FeedImage object.
+ */
+ public static Future<?> setFeedImage(final Context context,
+ final FeedImage image) {
+ return dbExec.submit(new Runnable() {
+
+ @Override
+ public void run() {
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setImage(image);
+ adapter.close();
+ }
+ });
+ }
+
+ private static boolean itemListContains(List<FeedItem> items, long itemId) {
+ for (FeedItem item : items) {
+ if (item.getId() == itemId) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/de/danoeh/antennapod/storage/DownloadRequester.java b/src/de/danoeh/antennapod/storage/DownloadRequester.java
index 29bd764dd..246b8bdfd 100644
--- a/src/de/danoeh/antennapod/storage/DownloadRequester.java
+++ b/src/de/danoeh/antennapod/storage/DownloadRequester.java
@@ -5,6 +5,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
import android.content.Context;
import android.content.Intent;
@@ -17,280 +18,303 @@ import de.danoeh.antennapod.feed.FeedFile;
import de.danoeh.antennapod.feed.FeedImage;
import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.service.download.DownloadRequest;
import de.danoeh.antennapod.service.download.DownloadService;
import de.danoeh.antennapod.util.FileNameGenerator;
import de.danoeh.antennapod.util.URLChecker;
public class DownloadRequester {
- private static final String TAG = "DownloadRequester";
-
- public static String IMAGE_DOWNLOADPATH = "images/";
- public static String FEED_DOWNLOADPATH = "cache/";
- public static String MEDIA_DOWNLOADPATH = "media/";
-
- private static DownloadRequester downloader;
-
- Map<String, FeedFile> downloads;
-
- private DownloadRequester() {
- downloads = new ConcurrentHashMap<String, FeedFile>();
- }
-
- public static DownloadRequester getInstance() {
- if (downloader == null) {
- downloader = new DownloadRequester();
- }
- return downloader;
- }
-
- private void download(Context context, FeedFile item, File dest,
- boolean overwriteIfExists) {
- if (!isDownloadingFile(item)) {
- if (!isFilenameAvailable(dest.toString()) || dest.exists()) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Filename already used.");
- if (isFilenameAvailable(dest.toString()) && overwriteIfExists) {
- boolean result = dest.delete();
- if (AppConfig.DEBUG)
- Log.d(TAG, "Deleting file. Result: " + result);
- } else {
- // find different name
- File newDest = null;
- for (int i = 1; i < Integer.MAX_VALUE; i++) {
- String newName = FilenameUtils.getBaseName(dest
- .getName())
- + "-"
- + i
- + "."
- + FilenameUtils.getExtension(dest.getName());
- if (AppConfig.DEBUG)
- Log.d(TAG, "Testing filename " + newName);
- newDest = new File(dest.getParent(), newName);
- if (!newDest.exists()
- && isFilenameAvailable(newDest.toString())) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "File doesn't exist yet. Using "
- + newName);
- break;
- }
- }
- if (newDest != null) {
- dest = newDest;
- }
- }
- }
- if (AppConfig.DEBUG)
- Log.d(TAG,
- "Requesting download of url " + item.getDownload_url());
- item.setDownload_url(URLChecker.prepareURL(item.getDownload_url()));
- item.setFile_url(dest.toString());
- downloads.put(item.getDownload_url(), item);
-
- DownloadService.Request request = new DownloadService.Request(
- item.getFile_url(), item.getDownload_url());
-
- if (!DownloadService.isRunning) {
- Intent launchIntent = new Intent(context, DownloadService.class);
- launchIntent.putExtra(DownloadService.EXTRA_REQUEST, request);
- context.startService(launchIntent);
- } else {
- Intent queueIntent = new Intent(
- DownloadService.ACTION_ENQUEUE_DOWNLOAD);
- queueIntent.putExtra(DownloadService.EXTRA_REQUEST, request);
- context.sendBroadcast(queueIntent);
- }
- EventDistributor.getInstance().sendDownloadQueuedBroadcast();
- } else {
- Log.e(TAG, "URL " + item.getDownload_url()
- + " is already being downloaded");
- }
- }
-
- /**
- * Returns true if a filename is available and false if it has already been
- * taken by another requested download.
- */
- private boolean isFilenameAvailable(String path) {
- for (String key : downloads.keySet()) {
- FeedFile f = downloads.get(key);
- if (f.getFile_url() != null && f.getFile_url().equals(path)) {
- if (AppConfig.DEBUG)
- Log.d(TAG, path
- + " is already used by another requested download");
- return false;
- }
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, path + " is available as a download destination");
- return true;
- }
-
- public void downloadFeed(Context context, Feed feed)
- throws DownloadRequestException {
- if (feedFileValid(feed)) {
- download(context, feed, new File(getFeedfilePath(context),
- getFeedfileName(feed)), true);
- }
- }
-
- public void downloadImage(Context context, FeedImage image)
- throws DownloadRequestException {
- if (feedFileValid(image)) {
- download(context, image, new File(getImagefilePath(context),
- getImagefileName(image)), true);
- }
- }
-
- public void downloadMedia(Context context, FeedMedia feedmedia)
- throws DownloadRequestException {
- if (feedFileValid(feedmedia)) {
- download(context, feedmedia,
- new File(getMediafilePath(context, feedmedia),
- getMediafilename(feedmedia)), false);
- }
- }
-
- /**
- * Throws a DownloadRequestException if the feedfile or the download url of
- * the feedfile is null.
- *
- * @throws DownloadRequestException
- */
- private boolean feedFileValid(FeedFile f) throws DownloadRequestException {
- if (f == null) {
- throw new DownloadRequestException("Feedfile was null");
- } else if (f.getDownload_url() == null) {
- throw new DownloadRequestException("File has no download URL");
- } else {
- return true;
- }
- }
-
- /**
- * Cancels a running download.
- * */
- public void cancelDownload(final Context context, final FeedFile f) {
- cancelDownload(context, f.getDownload_url());
- }
-
- /**
- * Cancels a running download.
- * */
- public void cancelDownload(final Context context, final String downloadUrl) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Cancelling download with url " + downloadUrl);
- Intent cancelIntent = new Intent(DownloadService.ACTION_CANCEL_DOWNLOAD);
- cancelIntent.putExtra(DownloadService.EXTRA_DOWNLOAD_URL, downloadUrl);
- context.sendBroadcast(cancelIntent);
- }
-
- /** Cancels all running downloads */
- public void cancelAllDownloads(Context context) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Cancelling all running downloads");
- context.sendBroadcast(new Intent(
- DownloadService.ACTION_CANCEL_ALL_DOWNLOADS));
- }
-
- /** Returns true if there is at least one Feed in the downloads queue. */
- public boolean isDownloadingFeeds() {
- for (FeedFile f : downloads.values()) {
- if (f.getClass() == Feed.class) {
- return true;
- }
- }
- return false;
- }
-
- /** Checks if feedfile is in the downloads list */
- public boolean isDownloadingFile(FeedFile item) {
- if (item.getDownload_url() != null) {
- return downloads.containsKey(item.getDownload_url());
- }
- return false;
- }
-
- public FeedFile getDownload(String downloadUrl) {
- return downloads.get(downloadUrl);
- }
-
- /** Checks if feedfile with the given download url is in the downloads list */
- public boolean isDownloadingFile(String downloadUrl) {
- return downloads.get(downloadUrl) != null;
- }
-
- public boolean hasNoDownloads() {
- return downloads.isEmpty();
- }
-
- public FeedFile getDownloadAt(int index) {
- return downloads.get(index);
- }
-
- /** Remove an object from the downloads-list of the requester. */
- public void removeDownload(FeedFile f) {
- if (downloads.remove(f.getDownload_url()) == null) {
- Log.e(TAG,
- "Could not remove object with url " + f.getDownload_url());
- }
- }
-
- /** Get the number of uncompleted Downloads */
- public int getNumberOfDownloads() {
- return downloads.size();
- }
-
- public String getFeedfilePath(Context context)
- throws DownloadRequestException {
- return getExternalFilesDirOrThrowException(context, FEED_DOWNLOADPATH)
- .toString() + "/";
- }
-
- public String getFeedfileName(Feed feed) {
- String filename = feed.getDownload_url();
- if (feed.getTitle() != null && !feed.getTitle().isEmpty()) {
- filename = feed.getTitle();
- }
- return "feed-" + FileNameGenerator.generateFileName(filename);
- }
-
- public String getImagefilePath(Context context)
- throws DownloadRequestException {
- return getExternalFilesDirOrThrowException(context, IMAGE_DOWNLOADPATH)
- .toString() + "/";
- }
-
- public String getImagefileName(FeedImage image) {
- String filename = image.getDownload_url();
- if (image.getFeed() != null && image.getFeed().getTitle() != null) {
- filename = image.getFeed().getTitle();
- }
- return "image-" + FileNameGenerator.generateFileName(filename);
- }
-
- public String getMediafilePath(Context context, FeedMedia media)
- throws DownloadRequestException {
- File externalStorage = getExternalFilesDirOrThrowException(
- context,
- MEDIA_DOWNLOADPATH
- + FileNameGenerator.generateFileName(media.getItem()
- .getFeed().getTitle()) + "/");
- return externalStorage.toString();
- }
-
- private File getExternalFilesDirOrThrowException(Context context,
- String type) throws DownloadRequestException {
- File result = UserPreferences.getDataFolder(context, type);
- if (result == null) {
- throw new DownloadRequestException(
- "Failed to access external storage");
- }
- return result;
- }
-
- public String getMediafilename(FeedMedia media) {
- return URLUtil.guessFileName(media.getDownload_url(), null,
- media.getMime_type());
- }
-
+ private static final String TAG = "DownloadRequester";
+
+ public static String IMAGE_DOWNLOADPATH = "images/";
+ public static String FEED_DOWNLOADPATH = "cache/";
+ public static String MEDIA_DOWNLOADPATH = "media/";
+
+ private static DownloadRequester downloader;
+
+ Map<String, DownloadRequest> downloads;
+
+ private DownloadRequester() {
+ downloads = new ConcurrentHashMap<String, DownloadRequest>();
+ }
+
+ public static DownloadRequester getInstance() {
+ if (downloader == null) {
+ downloader = new DownloadRequester();
+ }
+ return downloader;
+ }
+
+ private void download(Context context, FeedFile item, File dest,
+ boolean overwriteIfExists) {
+ if (!isDownloadingFile(item)) {
+ if (!isFilenameAvailable(dest.toString()) || dest.exists()) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Filename already used.");
+ if (isFilenameAvailable(dest.toString()) && overwriteIfExists) {
+ boolean result = dest.delete();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Deleting file. Result: " + result);
+ } else {
+ // find different name
+ File newDest = null;
+ for (int i = 1; i < Integer.MAX_VALUE; i++) {
+ String newName = FilenameUtils.getBaseName(dest
+ .getName())
+ + "-"
+ + i
+ + FilenameUtils.EXTENSION_SEPARATOR
+ + FilenameUtils.getExtension(dest.getName());
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Testing filename " + newName);
+ newDest = new File(dest.getParent(), newName);
+ if (!newDest.exists()
+ && isFilenameAvailable(newDest.toString())) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "File doesn't exist yet. Using "
+ + newName);
+ break;
+ }
+ }
+ if (newDest != null) {
+ dest = newDest;
+ }
+ }
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG,
+ "Requesting download of url " + item.getDownload_url());
+ item.setDownload_url(URLChecker.prepareURL(item.getDownload_url()));
+
+ DownloadRequest request = new DownloadRequest(dest.toString(),
+ item.getDownload_url(), item.getHumanReadableIdentifier(),
+ item.getId(), item.getTypeAsInt());
+
+ downloads.put(request.getSource(), request);
+
+ Intent launchIntent = new Intent(context, DownloadService.class);
+ launchIntent.putExtra(DownloadService.EXTRA_REQUEST, request);
+ context.startService(launchIntent);
+ EventDistributor.getInstance().sendDownloadQueuedBroadcast();
+ } else {
+ Log.e(TAG, "URL " + item.getDownload_url()
+ + " is already being downloaded");
+ }
+ }
+
+ /**
+ * Returns true if a filename is available and false if it has already been
+ * taken by another requested download.
+ */
+ private boolean isFilenameAvailable(String path) {
+ for (String key : downloads.keySet()) {
+ DownloadRequest r = downloads.get(key);
+ if (StringUtils.equals(r.getDestination(), path)) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, path
+ + " is already used by another requested download");
+ return false;
+ }
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG, path + " is available as a download destination");
+ return true;
+ }
+
+ public void downloadFeed(Context context, Feed feed)
+ throws DownloadRequestException {
+ if (feedFileValid(feed)) {
+ download(context, feed, new File(getFeedfilePath(context),
+ getFeedfileName(feed)), true);
+ }
+ }
+
+ public void downloadImage(Context context, FeedImage image)
+ throws DownloadRequestException {
+ if (feedFileValid(image)) {
+ download(context, image, new File(getImagefilePath(context),
+ getImagefileName(image)), true);
+ }
+ }
+
+ public void downloadMedia(Context context, FeedMedia feedmedia)
+ throws DownloadRequestException {
+ if (feedFileValid(feedmedia)) {
+ download(context, feedmedia,
+ new File(getMediafilePath(context, feedmedia),
+ getMediafilename(feedmedia)), false);
+ }
+ }
+
+ /**
+ * Throws a DownloadRequestException if the feedfile or the download url of
+ * the feedfile is null.
+ *
+ * @throws DownloadRequestException
+ */
+ private boolean feedFileValid(FeedFile f) throws DownloadRequestException {
+ if (f == null) {
+ throw new DownloadRequestException("Feedfile was null");
+ } else if (f.getDownload_url() == null) {
+ throw new DownloadRequestException("File has no download URL");
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Cancels a running download.
+ */
+ public void cancelDownload(final Context context, final FeedFile f) {
+ cancelDownload(context, f.getDownload_url());
+ }
+
+ /**
+ * Cancels a running download.
+ */
+ public void cancelDownload(final Context context, final String downloadUrl) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Cancelling download with url " + downloadUrl);
+ Intent cancelIntent = new Intent(DownloadService.ACTION_CANCEL_DOWNLOAD);
+ cancelIntent.putExtra(DownloadService.EXTRA_DOWNLOAD_URL, downloadUrl);
+ context.sendBroadcast(cancelIntent);
+ }
+
+ /**
+ * Cancels all running downloads
+ */
+ public void cancelAllDownloads(Context context) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Cancelling all running downloads");
+ context.sendBroadcast(new Intent(
+ DownloadService.ACTION_CANCEL_ALL_DOWNLOADS));
+ }
+
+ /**
+ * Returns true if there is at least one Feed in the downloads queue.
+ */
+ public boolean isDownloadingFeeds() {
+ for (DownloadRequest r : downloads.values()) {
+ if (r.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if feedfile is in the downloads list
+ */
+ public boolean isDownloadingFile(FeedFile item) {
+ if (item.getDownload_url() != null) {
+ return downloads.containsKey(item.getDownload_url());
+ }
+ return false;
+ }
+
+ public DownloadRequest getDownload(String downloadUrl) {
+ return downloads.get(downloadUrl);
+ }
+
+ /**
+ * Checks if feedfile with the given download url is in the downloads list
+ */
+ public boolean isDownloadingFile(String downloadUrl) {
+ return downloads.get(downloadUrl) != null;
+ }
+
+ public boolean hasNoDownloads() {
+ return downloads.isEmpty();
+ }
+
+ /**
+ * Remove an object from the downloads-list of the requester.
+ */
+ public void removeDownload(DownloadRequest r) {
+ if (downloads.remove(r.getSource()) == null) {
+ Log.e(TAG,
+ "Could not remove object with url " + r.getSource());
+ }
+ }
+
+ /**
+ * Get the number of uncompleted Downloads
+ */
+ public int getNumberOfDownloads() {
+ return downloads.size();
+ }
+
+ public String getFeedfilePath(Context context)
+ throws DownloadRequestException {
+ return getExternalFilesDirOrThrowException(context, FEED_DOWNLOADPATH)
+ .toString() + "/";
+ }
+
+ public String getFeedfileName(Feed feed) {
+ String filename = feed.getDownload_url();
+ if (feed.getTitle() != null && !feed.getTitle().isEmpty()) {
+ filename = feed.getTitle();
+ }
+ return "feed-" + FileNameGenerator.generateFileName(filename);
+ }
+
+ public String getImagefilePath(Context context)
+ throws DownloadRequestException {
+ return getExternalFilesDirOrThrowException(context, IMAGE_DOWNLOADPATH)
+ .toString() + "/";
+ }
+
+ public String getImagefileName(FeedImage image) {
+ String filename = image.getDownload_url();
+ if (image.getFeed() != null && image.getFeed().getTitle() != null) {
+ filename = image.getFeed().getTitle();
+ }
+ return "image-" + FileNameGenerator.generateFileName(filename);
+ }
+
+ public String getMediafilePath(Context context, FeedMedia media)
+ throws DownloadRequestException {
+ File externalStorage = getExternalFilesDirOrThrowException(
+ context,
+ MEDIA_DOWNLOADPATH
+ + FileNameGenerator.generateFileName(media.getItem()
+ .getFeed().getTitle()) + "/");
+ return externalStorage.toString();
+ }
+
+ private File getExternalFilesDirOrThrowException(Context context,
+ String type) throws DownloadRequestException {
+ File result = UserPreferences.getDataFolder(context, type);
+ if (result == null) {
+ throw new DownloadRequestException(
+ "Failed to access external storage");
+ }
+ return result;
+ }
+
+ public String getMediafilename(FeedMedia media) {
+ String filename;
+ String titleBaseFilename = "";
+
+ // Try to generate the filename by the item title
+ if (media.getItem() != null && media.getItem().getTitle() != null) {
+ String title = media.getItem().getTitle();
+ // Delete reserved characters
+ titleBaseFilename = title.replaceAll("[\\\\/%\\?\\*:|<>\"\\p{Cntrl}]", "");
+ titleBaseFilename = titleBaseFilename.trim();
+ }
+
+ String URLBaseFilename = URLUtil.guessFileName(media.getDownload_url(),
+ null, media.getMime_type());;
+
+ if (titleBaseFilename != "") {
+ // Append extension
+ filename = titleBaseFilename + FilenameUtils.EXTENSION_SEPARATOR +
+ FilenameUtils.getExtension(URLBaseFilename);
+ } else {
+ // Fall back on URL file name
+ filename = URLBaseFilename;
+ }
+ return filename;
+ }
}
diff --git a/src/de/danoeh/antennapod/storage/FeedItemStatistics.java b/src/de/danoeh/antennapod/storage/FeedItemStatistics.java
new file mode 100644
index 000000000..17e838761
--- /dev/null
+++ b/src/de/danoeh/antennapod/storage/FeedItemStatistics.java
@@ -0,0 +1,42 @@
+package de.danoeh.antennapod.storage;
+
+import java.util.Date;
+
+/**
+ * Contains information about a feed's items.
+ */
+public class FeedItemStatistics {
+ private long feedID;
+ private int numberOfItems;
+ private int numberOfNewItems;
+ private int numberOfInProgressItems;
+ private Date lastUpdate;
+
+ public FeedItemStatistics(long feedID, int numberOfItems, int numberOfNewItems, int numberOfInProgressItems, Date lastUpdate) {
+ this.feedID = feedID;
+ this.numberOfItems = numberOfItems;
+ this.numberOfNewItems = numberOfNewItems;
+ this.numberOfInProgressItems = numberOfInProgressItems;
+ this.lastUpdate = lastUpdate;
+ }
+
+ public long getFeedID() {
+ return feedID;
+ }
+
+ public int getNumberOfItems() {
+ return numberOfItems;
+ }
+
+ public int getNumberOfNewItems() {
+ return numberOfNewItems;
+ }
+
+ public int getNumberOfInProgressItems() {
+ return numberOfInProgressItems;
+ }
+
+ public Date getLastUpdate() {
+ return lastUpdate;
+ }
+}
diff --git a/src/de/danoeh/antennapod/storage/FeedSearcher.java b/src/de/danoeh/antennapod/storage/FeedSearcher.java
new file mode 100644
index 000000000..e7aa93f83
--- /dev/null
+++ b/src/de/danoeh/antennapod/storage/FeedSearcher.java
@@ -0,0 +1,57 @@
+package de.danoeh.antennapod.storage;
+
+import android.content.Context;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.feed.FeedItem;
+import de.danoeh.antennapod.feed.SearchResult;
+import de.danoeh.antennapod.util.comparator.SearchResultValueComparator;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+
+/**
+ * Performs search on Feeds and FeedItems
+ */
+public class FeedSearcher {
+ private static final String TAG = "FeedSearcher";
+
+
+ /**
+ * Performs a search in all feeds or one specific feed.
+ */
+ public static List<SearchResult> performSearch(final Context context,
+ final String query, final long selectedFeed) {
+ final int values[] = {0, 0, 1, 2};
+ final String[] subtitles = {context.getString(R.string.found_in_shownotes_label),
+ context.getString(R.string.found_in_shownotes_label),
+ context.getString(R.string.found_in_chapters_label),
+ context.getString(R.string.found_in_title_label)};
+
+ List<SearchResult> result = new ArrayList<SearchResult>();
+
+ FutureTask<List<FeedItem>>[] tasks = new FutureTask[4];
+ (tasks[0] = DBTasks.searchFeedItemContentEncoded(context, selectedFeed, query)).run();
+ (tasks[1] = DBTasks.searchFeedItemDescription(context, selectedFeed, query)).run();
+ (tasks[2] = DBTasks.searchFeedItemChapters(context, selectedFeed, query)).run();
+ (tasks[3] = DBTasks.searchFeedItemTitle(context, selectedFeed, query)).run();
+ try {
+ for (int i = 0; i < tasks.length; i++) {
+ FutureTask task = tasks[i];
+ List<FeedItem> items = (List<FeedItem>) task.get();
+ for (FeedItem item : items) {
+ result.add(new SearchResult(item, values[i], subtitles[i]));
+ }
+
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ }
+ Collections.sort(result, new SearchResultValueComparator());
+ return result;
+ }
+}
diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java
index 420264840..78b4c6daa 100644
--- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java
+++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java
@@ -10,776 +10,1083 @@ import android.database.DatabaseUtils;
import android.database.MergeCursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteStatement;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
-import de.danoeh.antennapod.asynctask.DownloadStatus;
import de.danoeh.antennapod.feed.Chapter;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedImage;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.feed.FeedMedia;
+import de.danoeh.antennapod.service.download.DownloadStatus;
+
+// TODO Remove media column from feeditem table
/**
* Implements methods for accessing the database
- * */
+ */
public class PodDBAdapter {
- private static final String TAG = "PodDBAdapter";
- private static final int DATABASE_VERSION = 8;
- private static final String DATABASE_NAME = "Antennapod.db";
-
- /** Maximum number of arguments for IN-operator. */
- public static final int IN_OPERATOR_MAXIMUM = 800;
-
- // ----------- Column indices
- // ----------- General indices
- public static final int KEY_ID_INDEX = 0;
- public static final int KEY_TITLE_INDEX = 1;
- public static final int KEY_FILE_URL_INDEX = 2;
- public static final int KEY_DOWNLOAD_URL_INDEX = 3;
- public static final int KEY_DOWNLOADED_INDEX = 4;
- public static final int KEY_LINK_INDEX = 5;
- public static final int KEY_DESCRIPTION_INDEX = 6;
- public static final int KEY_PAYMENT_LINK_INDEX = 7;
- // ----------- Feed indices
- public static final int KEY_LAST_UPDATE_INDEX = 8;
- public static final int KEY_LANGUAGE_INDEX = 9;
- public static final int KEY_AUTHOR_INDEX = 10;
- public static final int KEY_IMAGE_INDEX = 11;
- public static final int KEY_TYPE_INDEX = 12;
- public static final int KEY_FEED_IDENTIFIER_INDEX = 13;
- // ----------- FeedItem indices
- public static final int KEY_CONTENT_ENCODED_INDEX = 2;
- public static final int KEY_PUBDATE_INDEX = 3;
- public static final int KEY_READ_INDEX = 4;
- public static final int KEY_MEDIA_INDEX = 8;
- public static final int KEY_FEED_INDEX = 9;
- public static final int KEY_HAS_SIMPLECHAPTERS_INDEX = 10;
- public static final int KEY_ITEM_IDENTIFIER_INDEX = 11;
- // ---------- FeedMedia indices
- public static final int KEY_DURATION_INDEX = 1;
- public static final int KEY_POSITION_INDEX = 5;
- public static final int KEY_SIZE_INDEX = 6;
- public static final int KEY_MIME_TYPE_INDEX = 7;
- public static final int KEY_PLAYBACK_COMPLETION_DATE_INDEX = 8;
- // --------- Download log indices
- public static final int KEY_FEEDFILE_INDEX = 1;
- public static final int KEY_FEEDFILETYPE_INDEX = 2;
- public static final int KEY_REASON_INDEX = 3;
- public static final int KEY_SUCCESSFUL_INDEX = 4;
- public static final int KEY_COMPLETION_DATE_INDEX = 5;
- public static final int KEY_REASON_DETAILED_INDEX = 6;
- public static final int KEY_DOWNLOADSTATUS_TITLE_INDEX = 7;
- // --------- Queue indices
- public static final int KEY_FEEDITEM_INDEX = 1;
- public static final int KEY_QUEUE_FEED_INDEX = 2;
- // --------- Chapters indices
- public static final int KEY_CHAPTER_START_INDEX = 2;
- public static final int KEY_CHAPTER_FEEDITEM_INDEX = 3;
- public static final int KEY_CHAPTER_LINK_INDEX = 4;
- public static final int KEY_CHAPTER_TYPE_INDEX = 5;
-
- // Key-constants
- public static final String KEY_ID = "id";
- public static final String KEY_TITLE = "title";
- public static final String KEY_NAME = "name";
- public static final String KEY_LINK = "link";
- public static final String KEY_DESCRIPTION = "description";
- public static final String KEY_FILE_URL = "file_url";
- public static final String KEY_DOWNLOAD_URL = "download_url";
- public static final String KEY_PUBDATE = "pubDate";
- public static final String KEY_READ = "read";
- public static final String KEY_DURATION = "duration";
- public static final String KEY_POSITION = "position";
- public static final String KEY_SIZE = "filesize";
- public static final String KEY_MIME_TYPE = "mime_type";
- public static final String KEY_IMAGE = "image";
- public static final String KEY_FEED = "feed";
- public static final String KEY_MEDIA = "media";
- public static final String KEY_DOWNLOADED = "downloaded";
- public static final String KEY_LASTUPDATE = "last_update";
- public static final String KEY_FEEDFILE = "feedfile";
- public static final String KEY_REASON = "reason";
- public static final String KEY_SUCCESSFUL = "successful";
- public static final String KEY_FEEDFILETYPE = "feedfile_type";
- public static final String KEY_COMPLETION_DATE = "completion_date";
- public static final String KEY_FEEDITEM = "feeditem";
- public static final String KEY_CONTENT_ENCODED = "content_encoded";
- public static final String KEY_PAYMENT_LINK = "payment_link";
- public static final String KEY_START = "start";
- public static final String KEY_LANGUAGE = "language";
- public static final String KEY_AUTHOR = "author";
- public static final String KEY_HAS_CHAPTERS = "has_simple_chapters";
- public static final String KEY_TYPE = "type";
- public static final String KEY_ITEM_IDENTIFIER = "item_identifier";
- public static final String KEY_FEED_IDENTIFIER = "feed_identifier";
- public static final String KEY_REASON_DETAILED = "reason_detailed";
- public static final String KEY_DOWNLOADSTATUS_TITLE = "title";
- public static final String KEY_CHAPTER_TYPE = "type";
- public static final String KEY_PLAYBACK_COMPLETION_DATE = "playback_completion_date";
-
- // Table names
- public static final String TABLE_NAME_FEEDS = "Feeds";
- public static final String TABLE_NAME_FEED_ITEMS = "FeedItems";
- public static final String TABLE_NAME_FEED_IMAGES = "FeedImages";
- public static final String TABLE_NAME_FEED_MEDIA = "FeedMedia";
- public static final String TABLE_NAME_DOWNLOAD_LOG = "DownloadLog";
- public static final String TABLE_NAME_QUEUE = "Queue";
- public static final String TABLE_NAME_SIMPLECHAPTERS = "SimpleChapters";
-
- // SQL Statements for creating new tables
- private static final String TABLE_PRIMARY_KEY = KEY_ID
- + " INTEGER PRIMARY KEY AUTOINCREMENT ,";
-
- private static final String CREATE_TABLE_FEEDS = "CREATE TABLE "
- + TABLE_NAME_FEEDS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
- + " TEXT," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL + " TEXT,"
- + KEY_DOWNLOADED + " INTEGER," + KEY_LINK + " TEXT,"
- + KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT,"
- + KEY_LASTUPDATE + " TEXT," + KEY_LANGUAGE + " TEXT," + KEY_AUTHOR
- + " TEXT," + KEY_IMAGE + " INTEGER," + KEY_TYPE + " TEXT,"
- + KEY_FEED_IDENTIFIER + " TEXT)";;
-
- private static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE "
- + TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
- + " TEXT," + KEY_CONTENT_ENCODED + " TEXT," + KEY_PUBDATE
- + " INTEGER," + KEY_READ + " INTEGER," + KEY_LINK + " TEXT,"
- + KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT,"
- + KEY_MEDIA + " INTEGER," + KEY_FEED + " INTEGER,"
- + KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT)";
-
- private static final String CREATE_TABLE_FEED_IMAGES = "CREATE TABLE "
- + TABLE_NAME_FEED_IMAGES + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
- + " TEXT," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL + " TEXT,"
- + KEY_DOWNLOADED + " INTEGER)";
-
- private static final String CREATE_TABLE_FEED_MEDIA = "CREATE TABLE "
- + TABLE_NAME_FEED_MEDIA + " (" + TABLE_PRIMARY_KEY + KEY_DURATION
- + " INTEGER," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL
- + " TEXT," + KEY_DOWNLOADED + " INTEGER," + KEY_POSITION
- + " INTEGER," + KEY_SIZE + " INTEGER," + KEY_MIME_TYPE + " TEXT,"
- + KEY_PLAYBACK_COMPLETION_DATE + " INTEGER)";
-
- private static final String CREATE_TABLE_DOWNLOAD_LOG = "CREATE TABLE "
- + TABLE_NAME_DOWNLOAD_LOG + " (" + TABLE_PRIMARY_KEY + KEY_FEEDFILE
- + " INTEGER," + KEY_FEEDFILETYPE + " INTEGER," + KEY_REASON
- + " INTEGER," + KEY_SUCCESSFUL + " INTEGER," + KEY_COMPLETION_DATE
- + " INTEGER," + KEY_REASON_DETAILED + " TEXT,"
- + KEY_DOWNLOADSTATUS_TITLE + " TEXT)";
-
- private static final String CREATE_TABLE_QUEUE = "CREATE TABLE "
- + TABLE_NAME_QUEUE + "(" + KEY_ID + " INTEGER PRIMARY KEY,"
- + KEY_FEEDITEM + " INTEGER," + KEY_FEED + " INTEGER)";
-
- private static final String CREATE_TABLE_SIMPLECHAPTERS = "CREATE TABLE "
- + TABLE_NAME_SIMPLECHAPTERS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
- + " TEXT," + KEY_START + " INTEGER," + KEY_FEEDITEM + " INTEGER,"
- + KEY_LINK + " TEXT," + KEY_CHAPTER_TYPE + " INTEGER)";
-
- private SQLiteDatabase db;
- private final Context context;
- private PodDBHelper helper;
-
- /**
- * Select all columns from the feeditems-table except description and
- * content-encoded.
- */
- private static final String[] SEL_FI_SMALL = { KEY_ID, KEY_TITLE,
- KEY_PUBDATE, KEY_READ, KEY_LINK, KEY_PAYMENT_LINK, KEY_MEDIA,
- KEY_FEED, KEY_HAS_CHAPTERS, KEY_ITEM_IDENTIFIER };
-
- // column indices for SEL_FI_SMALL
-
- public static final int IDX_FI_SMALL_ID = 0;
- public static final int IDX_FI_SMALL_TITLE = 1;
- public static final int IDX_FI_SMALL_PUBDATE = 2;
- public static final int IDX_FI_SMALL_READ = 3;
- public static final int IDX_FI_SMALL_LINK = 4;
- public static final int IDX_FI_SMALL_PAYMENT_LINK = 5;
- public static final int IDX_FI_SMALL_MEDIA = 6;
- public static final int IDX_FI_SMALL_FEED = 7;
- public static final int IDX_FI_SMALL_HAS_CHAPTERS = 8;
- public static final int IDX_FI_SMALL_ITEM_IDENTIFIER = 9;
-
- /** Select id, description and content-encoded column from feeditems. */
- public static final String[] SEL_FI_EXTRA = { KEY_ID, KEY_DESCRIPTION,
- KEY_CONTENT_ENCODED, KEY_FEED };
-
- // column indices for SEL_FI_EXTRA
-
- public static final int IDX_FI_EXTRA_ID = 0;
- public static final int IDX_FI_EXTRA_DESCRIPTION = 1;
- public static final int IDX_FI_EXTRA_CONTENT_ENCODED = 2;
- public static final int IDX_FI_EXTRA_FEED = 3;
-
- public PodDBAdapter(Context c) {
- this.context = c;
- helper = new PodDBHelper(context, DATABASE_NAME, null, DATABASE_VERSION);
- }
-
- public PodDBAdapter open() {
- if (db == null || !db.isOpen() || db.isReadOnly()) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Opening DB");
- try {
- db = helper.getWritableDatabase();
- } catch (SQLException ex) {
- ex.printStackTrace();
- db = helper.getReadableDatabase();
- }
- }
- return this;
- }
-
- public void close() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Closing DB");
- db.close();
- }
-
- /**
- * Inserts or updates a feed entry
- *
- * @return the id of the entry
- * */
- public long setFeed(Feed feed) {
- ContentValues values = new ContentValues();
- values.put(KEY_TITLE, feed.getTitle());
- values.put(KEY_LINK, feed.getLink());
- values.put(KEY_DESCRIPTION, feed.getDescription());
- values.put(KEY_PAYMENT_LINK, feed.getPaymentLink());
- values.put(KEY_AUTHOR, feed.getAuthor());
- values.put(KEY_LANGUAGE, feed.getLanguage());
- if (feed.getImage() != null) {
- if (feed.getImage().getId() == 0) {
- setImage(feed.getImage());
- }
- values.put(KEY_IMAGE, feed.getImage().getId());
- }
-
- values.put(KEY_FILE_URL, feed.getFile_url());
- values.put(KEY_DOWNLOAD_URL, feed.getDownload_url());
- values.put(KEY_DOWNLOADED, feed.isDownloaded());
- values.put(KEY_LASTUPDATE, feed.getLastUpdate().getTime());
- values.put(KEY_TYPE, feed.getType());
- values.put(KEY_FEED_IDENTIFIER, feed.getFeedIdentifier());
- if (feed.getId() == 0) {
- // Create new entry
- if (AppConfig.DEBUG)
- Log.d(this.toString(), "Inserting new Feed into db");
- feed.setId(db.insert(TABLE_NAME_FEEDS, null, values));
- } else {
- if (AppConfig.DEBUG)
- Log.d(this.toString(), "Updating existing Feed in db");
- db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?",
- new String[] { Long.toString(feed.getId()) });
- }
- return feed.getId();
- }
-
- /**
- * Inserts or updates an image entry
- *
- * @return the id of the entry
- * */
- public long setImage(FeedImage image) {
- ContentValues values = new ContentValues();
- values.put(KEY_TITLE, image.getTitle());
- values.put(KEY_DOWNLOAD_URL, image.getDownload_url());
- values.put(KEY_DOWNLOADED, image.isDownloaded());
- values.put(KEY_FILE_URL, image.getFile_url());
- if (image.getId() == 0) {
- image.setId(db.insert(TABLE_NAME_FEED_IMAGES, null, values));
- } else {
- db.update(TABLE_NAME_FEED_IMAGES, values, KEY_ID + "=?",
- new String[] { String.valueOf(image.getId()) });
- }
- return image.getId();
- }
-
- /**
- * Inserts or updates an image entry
- *
- * @return the id of the entry
- */
- public long setMedia(FeedMedia media) {
- ContentValues values = new ContentValues();
- values.put(KEY_DURATION, media.getDuration());
- values.put(KEY_POSITION, media.getPosition());
- values.put(KEY_SIZE, media.getSize());
- values.put(KEY_MIME_TYPE, media.getMime_type());
- values.put(KEY_DOWNLOAD_URL, media.getDownload_url());
- values.put(KEY_DOWNLOADED, media.isDownloaded());
- values.put(KEY_FILE_URL, media.getFile_url());
- if (media.getPlaybackCompletionDate() != null) {
- values.put(KEY_PLAYBACK_COMPLETION_DATE, media
- .getPlaybackCompletionDate().getTime());
- } else {
- values.put(KEY_PLAYBACK_COMPLETION_DATE, 0);
- }
- if (media.getId() == 0) {
- media.setId(db.insert(TABLE_NAME_FEED_MEDIA, null, values));
- } else {
- db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?",
- new String[] { String.valueOf(media.getId()) });
- }
- return media.getId();
- }
-
- /**
- * Insert all FeedItems of a feed and the feed object itself in a single
- * transaction
- */
- public void setCompleteFeed(Feed feed) {
- db.beginTransaction();
- setFeed(feed);
- for (FeedItem item : feed.getItemsArray()) {
- setFeedItem(item);
- }
- db.setTransactionSuccessful();
- db.endTransaction();
- }
-
- public long setSingleFeedItem(FeedItem item) {
- db.beginTransaction();
- long result = setFeedItem(item);
- db.setTransactionSuccessful();
- db.endTransaction();
- return result;
- }
-
- /**
- * Inserts or updates a feeditem entry
- *
- * @return the id of the entry
- */
- private long setFeedItem(FeedItem item) {
- ContentValues values = new ContentValues();
- values.put(KEY_TITLE, item.getTitle());
- values.put(KEY_LINK, item.getLink());
- if (item.getDescription() != null) {
- values.put(KEY_DESCRIPTION, item.getDescription());
- }
- if (item.getContentEncoded() != null) {
- values.put(KEY_CONTENT_ENCODED, item.getContentEncoded());
- }
- values.put(KEY_PUBDATE, item.getPubDate().getTime());
- values.put(KEY_PAYMENT_LINK, item.getPaymentLink());
- if (item.getMedia() != null) {
- if (item.getMedia().getId() == 0) {
- setMedia(item.getMedia());
- }
- values.put(KEY_MEDIA, item.getMedia().getId());
- }
- if (item.getFeed().getId() == 0) {
- setFeed(item.getFeed());
- }
- values.put(KEY_FEED, item.getFeed().getId());
- values.put(KEY_READ, item.isRead());
- values.put(KEY_HAS_CHAPTERS, item.getChapters() != null);
- values.put(KEY_ITEM_IDENTIFIER, item.getItemIdentifier());
- if (item.getId() == 0) {
- item.setId(db.insert(TABLE_NAME_FEED_ITEMS, null, values));
- } else {
- db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?",
- new String[] { String.valueOf(item.getId()) });
- }
- if (item.getChapters() != null) {
- setChapters(item);
- }
- return item.getId();
- }
-
- public void setChapters(FeedItem item) {
- ContentValues values = new ContentValues();
- for (Chapter chapter : item.getChapters()) {
- values.put(KEY_TITLE, chapter.getTitle());
- values.put(KEY_START, chapter.getStart());
- values.put(KEY_FEEDITEM, item.getId());
- values.put(KEY_LINK, chapter.getLink());
- values.put(KEY_CHAPTER_TYPE, chapter.getChapterType());
- if (chapter.getId() == 0) {
- chapter.setId(db
- .insert(TABLE_NAME_SIMPLECHAPTERS, null, values));
- } else {
- db.update(TABLE_NAME_SIMPLECHAPTERS, values, KEY_ID + "=?",
- new String[] { String.valueOf(chapter.getId()) });
- }
- }
- }
-
- /**
- * Inserts or updates a download status.
- * */
- public long setDownloadStatus(DownloadStatus status) {
- ContentValues values = new ContentValues();
- if (status.getFeedFile() != null) {
- values.put(KEY_FEEDFILE, status.getFeedFile().getId());
- if (status.getFeedFile().getClass() == Feed.class) {
- values.put(KEY_FEEDFILETYPE, Feed.FEEDFILETYPE_FEED);
- } else if (status.getFeedFile().getClass() == FeedImage.class) {
- values.put(KEY_FEEDFILETYPE, FeedImage.FEEDFILETYPE_FEEDIMAGE);
- } else if (status.getFeedFile().getClass() == FeedMedia.class) {
- values.put(KEY_FEEDFILETYPE, FeedMedia.FEEDFILETYPE_FEEDMEDIA);
- }
- }
- values.put(KEY_REASON, status.getReason());
- values.put(KEY_SUCCESSFUL, status.isSuccessful());
- values.put(KEY_COMPLETION_DATE, status.getCompletionDate().getTime());
- values.put(KEY_REASON_DETAILED, status.getReasonDetailed());
- values.put(KEY_DOWNLOADSTATUS_TITLE, status.getTitle());
- if (status.getId() == 0) {
- status.setId(db.insert(TABLE_NAME_DOWNLOAD_LOG, null, values));
- } else {
- db.update(TABLE_NAME_DOWNLOAD_LOG, values, KEY_ID + "=?",
- new String[] { String.valueOf(status.getId()) });
- }
-
- return status.getId();
- }
-
- public void setQueue(List<FeedItem> queue) {
- ContentValues values = new ContentValues();
- db.beginTransaction();
- db.delete(TABLE_NAME_QUEUE, null, null);
- for (int i = 0; i < queue.size(); i++) {
- FeedItem item = queue.get(i);
- values.put(KEY_ID, i);
- values.put(KEY_FEEDITEM, item.getId());
- values.put(KEY_FEED, item.getFeed().getId());
- db.insertWithOnConflict(TABLE_NAME_QUEUE, null, values,
- SQLiteDatabase.CONFLICT_REPLACE);
- }
- db.setTransactionSuccessful();
- db.endTransaction();
- }
-
- public void removeFeedMedia(FeedMedia media) {
- db.delete(TABLE_NAME_FEED_MEDIA, KEY_ID + "=?",
- new String[] { String.valueOf(media.getId()) });
- }
-
- public void removeChaptersOfItem(FeedItem item) {
- db.delete(TABLE_NAME_SIMPLECHAPTERS, KEY_FEEDITEM + "=?",
- new String[] { String.valueOf(item.getId()) });
- }
-
- public void removeFeedImage(FeedImage image) {
- db.delete(TABLE_NAME_FEED_IMAGES, KEY_ID + "=?",
- new String[] { String.valueOf(image.getId()) });
- }
-
- /** Remove a FeedItem and its FeedMedia entry. */
- public void removeFeedItem(FeedItem item) {
- if (item.getMedia() != null) {
- removeFeedMedia(item.getMedia());
- }
- if (item.getChapters() != null) {
- removeChaptersOfItem(item);
- }
- db.delete(TABLE_NAME_FEED_ITEMS, KEY_ID + "=?",
- new String[] { String.valueOf(item.getId()) });
- }
-
- /** Remove a feed with all its FeedItems and Media entries. */
- public void removeFeed(Feed feed) {
- db.beginTransaction();
- if (feed.getImage() != null) {
- removeFeedImage(feed.getImage());
- }
- for (FeedItem item : feed.getItemsArray()) {
- removeFeedItem(item);
- }
- db.delete(TABLE_NAME_FEEDS, KEY_ID + "=?",
- new String[] { String.valueOf(feed.getId()) });
- db.setTransactionSuccessful();
- db.endTransaction();
- }
-
- public void removeDownloadStatus(DownloadStatus remove) {
- db.delete(TABLE_NAME_DOWNLOAD_LOG, KEY_ID + "=?",
- new String[] { String.valueOf(remove.getId()) });
- }
-
- /**
- * Get all Feeds from the Feed Table.
- *
- * @return The cursor of the query
- * */
- public final Cursor getAllFeedsCursor() {
- open();
- Cursor c = db.query(TABLE_NAME_FEEDS, null, null, null, null, null,
- null);
- return c;
- }
-
- /**
- * Returns a cursor with all FeedItems of a Feed. Uses SEL_FI_SMALL
- *
- * @param feed
- * The feed you want to get the FeedItems from.
- * @return The cursor of the query
- * */
- public final Cursor getAllItemsOfFeedCursor(final Feed feed) {
- open();
- Cursor c = db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_FEED
- + "=?", new String[] { String.valueOf(feed.getId()) }, null,
- null, null);
- return c;
- }
-
- /** Return a cursor with the SEL_FI_EXTRA selection of a single feeditem. */
- public final Cursor getExtraInformationOfItem(final FeedItem item) {
- open();
- Cursor c = db
- .query(TABLE_NAME_FEED_ITEMS, SEL_FI_EXTRA, KEY_ID + "=?",
- new String[] { String.valueOf(item.getId()) }, null,
- null, null);
- return c;
- }
-
- /**
- * Returns a cursor for a DB query in the FeedMedia table for a given ID.
- *
- * @param item
- * The item you want to get the FeedMedia from
- * @return The cursor of the query
- * */
- public final Cursor getFeedMediaOfItemCursor(final FeedItem item) {
- open();
- Cursor c = db.query(TABLE_NAME_FEED_MEDIA, null, KEY_ID + "=?",
- new String[] { String.valueOf(item.getMedia().getId()) }, null,
- null, null);
- return c;
- }
-
- /**
- * Returns a cursor for a DB query in the FeedImages table for a given ID.
- *
- * @param id
- * ID of the FeedImage
- * @return The cursor of the query
- * */
- public final Cursor getImageOfFeedCursor(final long id) {
- open();
- Cursor c = db.query(TABLE_NAME_FEED_IMAGES, null, KEY_ID + "=?",
- new String[] { String.valueOf(id) }, null, null, null);
- return c;
- }
-
- public final Cursor getSimpleChaptersOfFeedItemCursor(final FeedItem item) {
- open();
- Cursor c = db.query(TABLE_NAME_SIMPLECHAPTERS, null, KEY_FEEDITEM
- + "=?", new String[] { String.valueOf(item.getId()) }, null,
- null, null);
- return c;
- }
-
- public final Cursor getDownloadLogCursor() {
- open();
- Cursor c = db.query(TABLE_NAME_DOWNLOAD_LOG, null, null, null, null,
- null, null);
- return c;
- }
-
- public final Cursor getQueueCursor() {
- open();
- Cursor c = db.query(TABLE_NAME_QUEUE, null, null, null, null, null,
- null);
- return c;
- }
-
- public final Cursor getFeedMediaCursor(String... mediaIds) {
- int length = mediaIds.length;
- if (length > IN_OPERATOR_MAXIMUM) {
- Log.w(TAG, "Length of id array is larger than "
- + IN_OPERATOR_MAXIMUM + ". Creating multiple cursors");
- int numCursors = (int) (((double) length) / (IN_OPERATOR_MAXIMUM)) + 1;
- Cursor[] cursors = new Cursor[numCursors];
- for (int i = 0; i < numCursors; i++) {
- int neededLength = 0;
- String[] parts = null;
- final int elementsLeft = length - i * IN_OPERATOR_MAXIMUM;
-
- if (elementsLeft >= IN_OPERATOR_MAXIMUM) {
- neededLength = IN_OPERATOR_MAXIMUM;
- parts = Arrays.copyOfRange(mediaIds, i
- * IN_OPERATOR_MAXIMUM, (i + 1)
- * IN_OPERATOR_MAXIMUM);
- } else {
- neededLength = elementsLeft;
- parts = Arrays.copyOfRange(mediaIds, i
- * IN_OPERATOR_MAXIMUM, (i * IN_OPERATOR_MAXIMUM)
- + neededLength);
- }
-
- cursors[i] = db.rawQuery("SELECT * FROM "
- + TABLE_NAME_FEED_MEDIA + " WHERE " + KEY_ID + " IN "
- + buildInOperator(neededLength), parts);
- }
- return new MergeCursor(cursors);
- } else {
- return db.query(TABLE_NAME_FEED_MEDIA, null, KEY_ID + " IN "
- + buildInOperator(length), mediaIds, null, null, null);
- }
- }
-
- /** Builds an IN-operator argument depending on the number of items. */
- private String buildInOperator(int size) {
- StringBuffer buffer = new StringBuffer("(");
- for (int i = 0; i <= size; i++) {
- buffer.append("?,");
- }
- buffer.append("?)");
- return buffer.toString();
- }
-
- /**
- * Searches the DB for a FeedImage of the given id.
- *
- * @param id
- * The id of the object
- * @return The found object
- * */
- public final FeedImage getFeedImage(final long id) throws SQLException {
- Cursor cursor = this.getImageOfFeedCursor(id);
- if ((cursor.getCount() == 0) || !cursor.moveToFirst()) {
- throw new SQLException("No FeedImage found at index: " + id);
- }
- FeedImage image = new FeedImage(id, cursor.getString(cursor
- .getColumnIndex(KEY_TITLE)), cursor.getString(cursor
- .getColumnIndex(KEY_FILE_URL)), cursor.getString(cursor
- .getColumnIndex(KEY_DOWNLOAD_URL)), cursor.getInt(cursor
- .getColumnIndex(KEY_DOWNLOADED)) > 0);
- cursor.close();
- return image;
- }
-
- /**
- * Uses DatabaseUtils to escape a search query and removes ' at the
- * beginning and the end of the string returned by the escape method.
- */
- private String prepareSearchQuery(String query) {
- StringBuilder builder = new StringBuilder();
- DatabaseUtils.appendEscapedSQLString(builder, query);
- builder.deleteCharAt(0);
- builder.deleteCharAt(builder.length() - 1);
- return builder.toString();
- }
-
- /**
- * Searches for the given query in the description of all items or the items
- * of a specified feed.
- *
- * @return A cursor with all search results in SEL_FI_EXTRA selection.
- * */
- public Cursor searchItemDescriptions(Feed feed, String query) {
- if (feed != null) {
- // search items in specific feed
- return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_EXTRA, KEY_FEED
- + "=? AND " + KEY_DESCRIPTION + " LIKE '%"
- + prepareSearchQuery(query) + "%'",
- new String[] { String.valueOf(feed.getId()) }, null, null,
- null);
- } else {
- // search through all items
- return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_EXTRA,
- KEY_DESCRIPTION + " LIKE '%" + prepareSearchQuery(query)
- + "%'", null, null, null, null);
- }
- }
-
- /**
- * Searches for the given query in the content-encoded field of all items or
- * the items of a specified feed.
- *
- * @return A cursor with all search results in SEL_FI_EXTRA selection.
- * */
- public Cursor searchItemContentEncoded(Feed feed, String query) {
- if (feed != null) {
- // search items in specific feed
- return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_EXTRA, KEY_FEED
- + "=? AND " + KEY_CONTENT_ENCODED + " LIKE '%"
- + prepareSearchQuery(query) + "%'",
- new String[] { String.valueOf(feed.getId()) }, null, null,
- null);
- } else {
- // search through all items
- return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_EXTRA,
- KEY_CONTENT_ENCODED + " LIKE '%"
- + prepareSearchQuery(query) + "%'", null, null,
- null, null);
- }
- }
-
- /** Helper class for opening the Antennapod database. */
- private static class PodDBHelper extends SQLiteOpenHelper {
- /**
- * Constructor.
- *
- * @param context
- * Context to use
- * @param name
- * Name of the database
- * @param factory
- * to use for creating cursor objects
- * @param version
- * number of the database
- * */
- public PodDBHelper(final Context context, final String name,
- final CursorFactory factory, final int version) {
- super(context, name, factory, version);
- }
-
- @Override
- public void onCreate(final SQLiteDatabase db) {
- db.execSQL(CREATE_TABLE_FEEDS);
- db.execSQL(CREATE_TABLE_FEED_ITEMS);
- db.execSQL(CREATE_TABLE_FEED_IMAGES);
- db.execSQL(CREATE_TABLE_FEED_MEDIA);
- db.execSQL(CREATE_TABLE_DOWNLOAD_LOG);
- db.execSQL(CREATE_TABLE_QUEUE);
- db.execSQL(CREATE_TABLE_SIMPLECHAPTERS);
- }
-
- @Override
- public void onUpgrade(final SQLiteDatabase db, final int oldVersion,
- final int newVersion) {
- Log.w("DBAdapter", "Upgrading from version " + oldVersion + " to "
- + newVersion + ".");
- if (oldVersion <= 1) {
- db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS + " ADD COLUMN "
- + KEY_TYPE + " TEXT");
- }
- if (oldVersion <= 2) {
- db.execSQL("ALTER TABLE " + TABLE_NAME_SIMPLECHAPTERS
- + " ADD COLUMN " + KEY_LINK + " TEXT");
- }
- if (oldVersion <= 3) {
- db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_ITEMS
- + " ADD COLUMN " + KEY_ITEM_IDENTIFIER + " TEXT");
- }
- if (oldVersion <= 4) {
- db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS + " ADD COLUMN "
- + KEY_FEED_IDENTIFIER + " TEXT");
- }
- if (oldVersion <= 5) {
- db.execSQL("ALTER TABLE " + TABLE_NAME_DOWNLOAD_LOG
- + " ADD COLUMN " + KEY_REASON_DETAILED + " TEXT");
- db.execSQL("ALTER TABLE " + TABLE_NAME_DOWNLOAD_LOG
- + " ADD COLUMN " + KEY_DOWNLOADSTATUS_TITLE + " TEXT");
- }
- if (oldVersion <= 6) {
- db.execSQL("ALTER TABLE " + TABLE_NAME_SIMPLECHAPTERS
- + " ADD COLUMN " + KEY_CHAPTER_TYPE + " INTEGER");
- }
- if (oldVersion <= 7) {
- db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_MEDIA
- + " ADD COLUMN " + KEY_PLAYBACK_COMPLETION_DATE
- + " INTEGER");
- }
- }
- }
+ private static final String TAG = "PodDBAdapter";
+ private static final int DATABASE_VERSION = 9;
+ public static final String DATABASE_NAME = "Antennapod.db";
+
+ /**
+ * Maximum number of arguments for IN-operator.
+ */
+ public static final int IN_OPERATOR_MAXIMUM = 800;
+
+ /**
+ * Maximum number of entries per search request.
+ */
+ public static final int SEARCH_LIMIT = 30;
+
+ // ----------- Column indices
+ // ----------- General indices
+ public static final int KEY_ID_INDEX = 0;
+ public static final int KEY_TITLE_INDEX = 1;
+ public static final int KEY_FILE_URL_INDEX = 2;
+ public static final int KEY_DOWNLOAD_URL_INDEX = 3;
+ public static final int KEY_DOWNLOADED_INDEX = 4;
+ public static final int KEY_LINK_INDEX = 5;
+ public static final int KEY_DESCRIPTION_INDEX = 6;
+ public static final int KEY_PAYMENT_LINK_INDEX = 7;
+ // ----------- Feed indices
+ public static final int KEY_LAST_UPDATE_INDEX = 8;
+ public static final int KEY_LANGUAGE_INDEX = 9;
+ public static final int KEY_AUTHOR_INDEX = 10;
+ public static final int KEY_IMAGE_INDEX = 11;
+ public static final int KEY_TYPE_INDEX = 12;
+ public static final int KEY_FEED_IDENTIFIER_INDEX = 13;
+ // ----------- FeedItem indices
+ public static final int KEY_CONTENT_ENCODED_INDEX = 2;
+ public static final int KEY_PUBDATE_INDEX = 3;
+ public static final int KEY_READ_INDEX = 4;
+ public static final int KEY_MEDIA_INDEX = 8;
+ public static final int KEY_FEED_INDEX = 9;
+ public static final int KEY_HAS_SIMPLECHAPTERS_INDEX = 10;
+ public static final int KEY_ITEM_IDENTIFIER_INDEX = 11;
+ // ---------- FeedMedia indices
+ public static final int KEY_DURATION_INDEX = 1;
+ public static final int KEY_POSITION_INDEX = 5;
+ public static final int KEY_SIZE_INDEX = 6;
+ public static final int KEY_MIME_TYPE_INDEX = 7;
+ public static final int KEY_PLAYBACK_COMPLETION_DATE_INDEX = 8;
+ public static final int KEY_MEDIA_FEEDITEM_INDEX = 9;
+ // --------- Download log indices
+ public static final int KEY_FEEDFILE_INDEX = 1;
+ public static final int KEY_FEEDFILETYPE_INDEX = 2;
+ public static final int KEY_REASON_INDEX = 3;
+ public static final int KEY_SUCCESSFUL_INDEX = 4;
+ public static final int KEY_COMPLETION_DATE_INDEX = 5;
+ public static final int KEY_REASON_DETAILED_INDEX = 6;
+ public static final int KEY_DOWNLOADSTATUS_TITLE_INDEX = 7;
+ // --------- Queue indices
+ public static final int KEY_FEEDITEM_INDEX = 1;
+ public static final int KEY_QUEUE_FEED_INDEX = 2;
+ // --------- Chapters indices
+ public static final int KEY_CHAPTER_START_INDEX = 2;
+ public static final int KEY_CHAPTER_FEEDITEM_INDEX = 3;
+ public static final int KEY_CHAPTER_LINK_INDEX = 4;
+ public static final int KEY_CHAPTER_TYPE_INDEX = 5;
+
+ // Key-constants
+ public static final String KEY_ID = "id";
+ public static final String KEY_TITLE = "title";
+ public static final String KEY_NAME = "name";
+ public static final String KEY_LINK = "link";
+ public static final String KEY_DESCRIPTION = "description";
+ public static final String KEY_FILE_URL = "file_url";
+ public static final String KEY_DOWNLOAD_URL = "download_url";
+ public static final String KEY_PUBDATE = "pubDate";
+ public static final String KEY_READ = "read";
+ public static final String KEY_DURATION = "duration";
+ public static final String KEY_POSITION = "position";
+ public static final String KEY_SIZE = "filesize";
+ public static final String KEY_MIME_TYPE = "mime_type";
+ public static final String KEY_IMAGE = "image";
+ public static final String KEY_FEED = "feed";
+ public static final String KEY_MEDIA = "media";
+ public static final String KEY_DOWNLOADED = "downloaded";
+ public static final String KEY_LASTUPDATE = "last_update";
+ public static final String KEY_FEEDFILE = "feedfile";
+ public static final String KEY_REASON = "reason";
+ public static final String KEY_SUCCESSFUL = "successful";
+ public static final String KEY_FEEDFILETYPE = "feedfile_type";
+ public static final String KEY_COMPLETION_DATE = "completion_date";
+ public static final String KEY_FEEDITEM = "feeditem";
+ public static final String KEY_CONTENT_ENCODED = "content_encoded";
+ public static final String KEY_PAYMENT_LINK = "payment_link";
+ public static final String KEY_START = "start";
+ public static final String KEY_LANGUAGE = "language";
+ public static final String KEY_AUTHOR = "author";
+ public static final String KEY_HAS_CHAPTERS = "has_simple_chapters";
+ public static final String KEY_TYPE = "type";
+ public static final String KEY_ITEM_IDENTIFIER = "item_identifier";
+ public static final String KEY_FEED_IDENTIFIER = "feed_identifier";
+ public static final String KEY_REASON_DETAILED = "reason_detailed";
+ public static final String KEY_DOWNLOADSTATUS_TITLE = "title";
+ public static final String KEY_CHAPTER_TYPE = "type";
+ public static final String KEY_PLAYBACK_COMPLETION_DATE = "playback_completion_date";
+
+ // Table names
+ public static final String TABLE_NAME_FEEDS = "Feeds";
+ public static final String TABLE_NAME_FEED_ITEMS = "FeedItems";
+ public static final String TABLE_NAME_FEED_IMAGES = "FeedImages";
+ public static final String TABLE_NAME_FEED_MEDIA = "FeedMedia";
+ public static final String TABLE_NAME_DOWNLOAD_LOG = "DownloadLog";
+ public static final String TABLE_NAME_QUEUE = "Queue";
+ public static final String TABLE_NAME_SIMPLECHAPTERS = "SimpleChapters";
+
+ // SQL Statements for creating new tables
+ private static final String TABLE_PRIMARY_KEY = KEY_ID
+ + " INTEGER PRIMARY KEY AUTOINCREMENT ,";
+
+ private static final String CREATE_TABLE_FEEDS = "CREATE TABLE "
+ + TABLE_NAME_FEEDS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
+ + " TEXT," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL + " TEXT,"
+ + KEY_DOWNLOADED + " INTEGER," + KEY_LINK + " TEXT,"
+ + KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT,"
+ + KEY_LASTUPDATE + " TEXT," + KEY_LANGUAGE + " TEXT," + KEY_AUTHOR
+ + " TEXT," + KEY_IMAGE + " INTEGER," + KEY_TYPE + " TEXT,"
+ + KEY_FEED_IDENTIFIER + " TEXT)";
+ ;
+
+ private static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE "
+ + TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
+ + " TEXT," + KEY_CONTENT_ENCODED + " TEXT," + KEY_PUBDATE
+ + " INTEGER," + KEY_READ + " INTEGER," + KEY_LINK + " TEXT,"
+ + KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT,"
+ + KEY_MEDIA + " INTEGER," + KEY_FEED + " INTEGER,"
+ + KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT)";
+
+ private static final String CREATE_TABLE_FEED_IMAGES = "CREATE TABLE "
+ + TABLE_NAME_FEED_IMAGES + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
+ + " TEXT," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL + " TEXT,"
+ + KEY_DOWNLOADED + " INTEGER)";
+
+ private static final String CREATE_TABLE_FEED_MEDIA = "CREATE TABLE "
+ + TABLE_NAME_FEED_MEDIA + " (" + TABLE_PRIMARY_KEY + KEY_DURATION
+ + " INTEGER," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL
+ + " TEXT," + KEY_DOWNLOADED + " INTEGER," + KEY_POSITION
+ + " INTEGER," + KEY_SIZE + " INTEGER," + KEY_MIME_TYPE + " TEXT,"
+ + KEY_PLAYBACK_COMPLETION_DATE + " INTEGER,"
+ + KEY_FEEDITEM + " INTEGER)";
+
+ private static final String CREATE_TABLE_DOWNLOAD_LOG = "CREATE TABLE "
+ + TABLE_NAME_DOWNLOAD_LOG + " (" + TABLE_PRIMARY_KEY + KEY_FEEDFILE
+ + " INTEGER," + KEY_FEEDFILETYPE + " INTEGER," + KEY_REASON
+ + " INTEGER," + KEY_SUCCESSFUL + " INTEGER," + KEY_COMPLETION_DATE
+ + " INTEGER," + KEY_REASON_DETAILED + " TEXT,"
+ + KEY_DOWNLOADSTATUS_TITLE + " TEXT)";
+
+ private static final String CREATE_TABLE_QUEUE = "CREATE TABLE "
+ + TABLE_NAME_QUEUE + "(" + KEY_ID + " INTEGER PRIMARY KEY,"
+ + KEY_FEEDITEM + " INTEGER," + KEY_FEED + " INTEGER)";
+
+ private static final String CREATE_TABLE_SIMPLECHAPTERS = "CREATE TABLE "
+ + TABLE_NAME_SIMPLECHAPTERS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
+ + " TEXT," + KEY_START + " INTEGER," + KEY_FEEDITEM + " INTEGER,"
+ + KEY_LINK + " TEXT," + KEY_CHAPTER_TYPE + " INTEGER)";
+
+ private SQLiteDatabase db;
+ private final Context context;
+ private PodDBHelper helper;
+
+ /**
+ * Select all columns from the feeditems-table except description and
+ * content-encoded.
+ */
+ private static final String[] SEL_FI_SMALL = {
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ID,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_TITLE,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_READ,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_LINK,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_PAYMENT_LINK, KEY_MEDIA,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_FEED,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_HAS_CHAPTERS,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER};
+
+ /**
+ * Contains SEL_FI_SMALL as comma-separated list. Useful for raw queries.
+ */
+ private static final String SEL_FI_SMALL_STR;
+
+ static {
+ String selFiSmall = Arrays.toString(SEL_FI_SMALL);
+ SEL_FI_SMALL_STR = selFiSmall.substring(1, selFiSmall.length() - 1);
+ }
+
+ // column indices for SEL_FI_SMALL
+
+ public static final int IDX_FI_SMALL_ID = 0;
+ public static final int IDX_FI_SMALL_TITLE = 1;
+ public static final int IDX_FI_SMALL_PUBDATE = 2;
+ public static final int IDX_FI_SMALL_READ = 3;
+ public static final int IDX_FI_SMALL_LINK = 4;
+ public static final int IDX_FI_SMALL_PAYMENT_LINK = 5;
+ public static final int IDX_FI_SMALL_MEDIA = 6;
+ public static final int IDX_FI_SMALL_FEED = 7;
+ public static final int IDX_FI_SMALL_HAS_CHAPTERS = 8;
+ public static final int IDX_FI_SMALL_ITEM_IDENTIFIER = 9;
+
+ /**
+ * Select id, description and content-encoded column from feeditems.
+ */
+ public static final String[] SEL_FI_EXTRA = {KEY_ID, KEY_DESCRIPTION,
+ KEY_CONTENT_ENCODED, KEY_FEED};
+
+ // column indices for SEL_FI_EXTRA
+
+ public static final int IDX_FI_EXTRA_ID = 0;
+ public static final int IDX_FI_EXTRA_DESCRIPTION = 1;
+ public static final int IDX_FI_EXTRA_CONTENT_ENCODED = 2;
+ public static final int IDX_FI_EXTRA_FEED = 3;
+
+ static PodDBHelper dbHelperSingleton;
+
+ private static synchronized PodDBHelper getDbHelperSingleton(Context appContext) {
+ if (dbHelperSingleton == null) {
+ dbHelperSingleton = new PodDBHelper(appContext, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+ return dbHelperSingleton;
+ }
+
+ public PodDBAdapter(Context c) {
+ this.context = c;
+ helper = getDbHelperSingleton(c.getApplicationContext());
+ }
+
+ public PodDBAdapter open() {
+ if (db == null || !db.isOpen() || db.isReadOnly()) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Opening DB");
+ try {
+ db = helper.getWritableDatabase();
+ } catch (SQLException ex) {
+ ex.printStackTrace();
+ db = helper.getReadableDatabase();
+ }
+ }
+ return this;
+ }
+
+ public void close() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Closing DB");
+ //db.close();
+ }
+
+ /**
+ * Inserts or updates a feed entry
+ *
+ * @return the id of the entry
+ */
+ public long setFeed(Feed feed) {
+ ContentValues values = new ContentValues();
+ values.put(KEY_TITLE, feed.getTitle());
+ values.put(KEY_LINK, feed.getLink());
+ values.put(KEY_DESCRIPTION, feed.getDescription());
+ values.put(KEY_PAYMENT_LINK, feed.getPaymentLink());
+ values.put(KEY_AUTHOR, feed.getAuthor());
+ values.put(KEY_LANGUAGE, feed.getLanguage());
+ if (feed.getImage() != null) {
+ if (feed.getImage().getId() == 0) {
+ setImage(feed.getImage());
+ }
+ values.put(KEY_IMAGE, feed.getImage().getId());
+ }
+
+ values.put(KEY_FILE_URL, feed.getFile_url());
+ values.put(KEY_DOWNLOAD_URL, feed.getDownload_url());
+ values.put(KEY_DOWNLOADED, feed.isDownloaded());
+ values.put(KEY_LASTUPDATE, feed.getLastUpdate().getTime());
+ values.put(KEY_TYPE, feed.getType());
+ values.put(KEY_FEED_IDENTIFIER, feed.getFeedIdentifier());
+ if (feed.getId() == 0) {
+ // Create new entry
+ if (AppConfig.DEBUG)
+ Log.d(this.toString(), "Inserting new Feed into db");
+ feed.setId(db.insert(TABLE_NAME_FEEDS, null, values));
+ } else {
+ if (AppConfig.DEBUG)
+ Log.d(this.toString(), "Updating existing Feed in db");
+ db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?",
+ new String[]{String.valueOf(feed.getId())});
+ }
+ return feed.getId();
+ }
+
+ /**
+ * Inserts or updates an image entry
+ *
+ * @return the id of the entry
+ */
+ public long setImage(FeedImage image) {
+ db.beginTransaction();
+ ContentValues values = new ContentValues();
+ values.put(KEY_TITLE, image.getTitle());
+ values.put(KEY_DOWNLOAD_URL, image.getDownload_url());
+ values.put(KEY_DOWNLOADED, image.isDownloaded());
+ values.put(KEY_FILE_URL, image.getFile_url());
+ if (image.getId() == 0) {
+ image.setId(db.insert(TABLE_NAME_FEED_IMAGES, null, values));
+ } else {
+ db.update(TABLE_NAME_FEED_IMAGES, values, KEY_ID + "=?",
+ new String[]{String.valueOf(image.getId())});
+ }
+ if (image.getFeed() != null && image.getFeed().getId() != 0) {
+ values.clear();
+ values.put(KEY_IMAGE, image.getId());
+ db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(image.getFeed().getId())});
+ }
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ return image.getId();
+ }
+
+ /**
+ * Inserts or updates an image entry
+ *
+ * @return the id of the entry
+ */
+ public long setMedia(FeedMedia media) {
+ ContentValues values = new ContentValues();
+ values.put(KEY_DURATION, media.getDuration());
+ values.put(KEY_POSITION, media.getPosition());
+ values.put(KEY_SIZE, media.getSize());
+ values.put(KEY_MIME_TYPE, media.getMime_type());
+ values.put(KEY_DOWNLOAD_URL, media.getDownload_url());
+ values.put(KEY_DOWNLOADED, media.isDownloaded());
+ values.put(KEY_FILE_URL, media.getFile_url());
+
+ if (media.getPlaybackCompletionDate() != null) {
+ values.put(KEY_PLAYBACK_COMPLETION_DATE, media
+ .getPlaybackCompletionDate().getTime());
+ } else {
+ values.put(KEY_PLAYBACK_COMPLETION_DATE, 0);
+ }
+ if (media.getItem() != null) {
+ values.put(KEY_FEEDITEM, media.getItem().getId());
+ }
+ if (media.getId() == 0) {
+ media.setId(db.insert(TABLE_NAME_FEED_MEDIA, null, values));
+ } else {
+ db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?",
+ new String[]{String.valueOf(media.getId())});
+ }
+ return media.getId();
+ }
+
+ public void setFeedMediaPlaybackInformation(FeedMedia media) {
+ if (media.getId() != 0) {
+ ContentValues values = new ContentValues();
+ values.put(KEY_POSITION, media.getPosition());
+ values.put(KEY_DURATION, media.getDuration());
+ db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?",
+ new String[]{String.valueOf(media.getId())});
+ } else {
+ Log.e(TAG, "setFeedMediaPlaybackInformation: ID of media was 0");
+ }
+ }
+
+ /**
+ * Insert all FeedItems of a feed and the feed object itself in a single
+ * transaction
+ */
+ public void setCompleteFeed(Feed feed) {
+ db.beginTransaction();
+ setFeed(feed);
+ if (feed.getItems() != null) {
+ for (FeedItem item : feed.getItems()) {
+ setFeedItem(item, false);
+ }
+ }
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+
+ public void setFeedItemlist(List<FeedItem> items) {
+ db.beginTransaction();
+ for (FeedItem item : items) {
+ setFeedItem(item, true);
+ }
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+
+ public long setSingleFeedItem(FeedItem item) {
+ db.beginTransaction();
+ long result = setFeedItem(item, true);
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ return result;
+ }
+
+ /**
+ * Inserts or updates a feeditem entry
+ * @param item The FeedItem
+ * @param saveFeed true if the Feed of the item should also be saved. This should be set to
+ * false if the method is executed on a list of FeedItems of the same Feed.
+ * @return the id of the entry
+ */
+ private long setFeedItem(FeedItem item, boolean saveFeed) {
+ ContentValues values = new ContentValues();
+ values.put(KEY_TITLE, item.getTitle());
+ values.put(KEY_LINK, item.getLink());
+ if (item.getDescription() != null) {
+ values.put(KEY_DESCRIPTION, item.getDescription());
+ }
+ if (item.getContentEncoded() != null) {
+ values.put(KEY_CONTENT_ENCODED, item.getContentEncoded());
+ }
+ values.put(KEY_PUBDATE, item.getPubDate().getTime());
+ values.put(KEY_PAYMENT_LINK, item.getPaymentLink());
+ if (saveFeed && item.getFeed() != null) {
+ setFeed(item.getFeed());
+ }
+ values.put(KEY_FEED, item.getFeed().getId());
+ values.put(KEY_READ, item.isRead());
+ values.put(KEY_HAS_CHAPTERS, item.getChapters() != null);
+ values.put(KEY_ITEM_IDENTIFIER, item.getItemIdentifier());
+ if (item.getId() == 0) {
+ item.setId(db.insert(TABLE_NAME_FEED_ITEMS, null, values));
+ } else {
+ db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?",
+ new String[]{String.valueOf(item.getId())});
+ }
+ if (item.getMedia() != null) {
+ setMedia(item.getMedia());
+ }
+ if (item.getChapters() != null) {
+ setChapters(item);
+ }
+ return item.getId();
+ }
+
+ public void setFeedItemRead(boolean read, long itemId, long mediaId,
+ boolean resetMediaPosition) {
+ db.beginTransaction();
+ ContentValues values = new ContentValues();
+
+ values.put(KEY_READ, read);
+ db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(itemId)});
+
+ if (resetMediaPosition) {
+ values.clear();
+ values.put(KEY_POSITION, 0);
+ db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", new String[]{String.valueOf(mediaId)});
+ }
+
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+
+ public void setFeedItemRead(boolean read, long... itemIds) {
+ db.beginTransaction();
+ ContentValues values = new ContentValues();
+ for (long id : itemIds) {
+ values.clear();
+ values.put(KEY_READ, read);
+ db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[]{String.valueOf(id)});
+ }
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+
+ public void setChapters(FeedItem item) {
+ ContentValues values = new ContentValues();
+ for (Chapter chapter : item.getChapters()) {
+ values.put(KEY_TITLE, chapter.getTitle());
+ values.put(KEY_START, chapter.getStart());
+ values.put(KEY_FEEDITEM, item.getId());
+ values.put(KEY_LINK, chapter.getLink());
+ values.put(KEY_CHAPTER_TYPE, chapter.getChapterType());
+ if (chapter.getId() == 0) {
+ chapter.setId(db
+ .insert(TABLE_NAME_SIMPLECHAPTERS, null, values));
+ } else {
+ db.update(TABLE_NAME_SIMPLECHAPTERS, values, KEY_ID + "=?",
+ new String[]{String.valueOf(chapter.getId())});
+ }
+ }
+ }
+
+ /**
+ * Inserts or updates a download status.
+ */
+ public long setDownloadStatus(DownloadStatus status) {
+ ContentValues values = new ContentValues();
+ values.put(KEY_FEEDFILE, status.getFeedfileId());
+ values.put(KEY_FEEDFILETYPE, status.getFeedfileType());
+ values.put(KEY_REASON, status.getReason().getCode());
+ values.put(KEY_SUCCESSFUL, status.isSuccessful());
+ values.put(KEY_COMPLETION_DATE, status.getCompletionDate().getTime());
+ values.put(KEY_REASON_DETAILED, status.getReasonDetailed());
+ values.put(KEY_DOWNLOADSTATUS_TITLE, status.getTitle());
+ if (status.getId() == 0) {
+ status.setId(db.insert(TABLE_NAME_DOWNLOAD_LOG, null, values));
+ } else {
+ db.update(TABLE_NAME_DOWNLOAD_LOG, values, KEY_ID + "=?",
+ new String[]{String.valueOf(status.getId())});
+ }
+
+ return status.getId();
+ }
+
+ public long getDownloadLogSize() {
+ Cursor result = db.rawQuery("SELECT COUNT(?) AS ? FROM ?",
+ new String[]{KEY_ID, KEY_ID, TABLE_NAME_DOWNLOAD_LOG});
+ long count = result.getLong(KEY_ID_INDEX);
+ result.close();
+ return count;
+ }
+
+ public void removeDownloadLogItems(long count) {
+ if (count > 0) {
+ db.rawQuery("DELETE FROM ? ORDER BY ? ASC LIMIT ?",
+ new String[]{TABLE_NAME_DOWNLOAD_LOG,
+ KEY_COMPLETION_DATE, String.valueOf(count)});
+ }
+ }
+
+ public void setQueue(List<FeedItem> queue) {
+ ContentValues values = new ContentValues();
+ db.beginTransaction();
+ db.delete(TABLE_NAME_QUEUE, null, null);
+ for (int i = 0; i < queue.size(); i++) {
+ FeedItem item = queue.get(i);
+ values.put(KEY_ID, i);
+ values.put(KEY_FEEDITEM, item.getId());
+ values.put(KEY_FEED, item.getFeed().getId());
+ db.insertWithOnConflict(TABLE_NAME_QUEUE, null, values,
+ SQLiteDatabase.CONFLICT_REPLACE);
+ }
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+
+ public void clearQueue() {
+ db.delete(TABLE_NAME_QUEUE, null, null);
+ }
+
+ public void removeFeedMedia(FeedMedia media) {
+ db.delete(TABLE_NAME_FEED_MEDIA, KEY_ID + "=?",
+ new String[]{String.valueOf(media.getId())});
+ }
+
+ public void removeChaptersOfItem(FeedItem item) {
+ db.delete(TABLE_NAME_SIMPLECHAPTERS, KEY_FEEDITEM + "=?",
+ new String[]{String.valueOf(item.getId())});
+ }
+
+ public void removeFeedImage(FeedImage image) {
+ db.delete(TABLE_NAME_FEED_IMAGES, KEY_ID + "=?",
+ new String[]{String.valueOf(image.getId())});
+ }
+
+ /**
+ * Remove a FeedItem and its FeedMedia entry.
+ */
+ public void removeFeedItem(FeedItem item) {
+ if (item.getMedia() != null) {
+ removeFeedMedia(item.getMedia());
+ }
+ if (item.getChapters() != null) {
+ removeChaptersOfItem(item);
+ }
+ db.delete(TABLE_NAME_FEED_ITEMS, KEY_ID + "=?",
+ new String[]{String.valueOf(item.getId())});
+ }
+
+ /**
+ * Remove a feed with all its FeedItems and Media entries.
+ */
+ public void removeFeed(Feed feed) {
+ db.beginTransaction();
+ if (feed.getImage() != null) {
+ removeFeedImage(feed.getImage());
+ }
+ if (feed.getItems() != null) {
+ for (FeedItem item : feed.getItems()) {
+ removeFeedItem(item);
+ }
+ }
+ db.delete(TABLE_NAME_FEEDS, KEY_ID + "=?",
+ new String[]{String.valueOf(feed.getId())});
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+
+ public void removeDownloadStatus(DownloadStatus remove) {
+ db.delete(TABLE_NAME_DOWNLOAD_LOG, KEY_ID + "=?",
+ new String[]{String.valueOf(remove.getId())});
+ }
+
+ public void clearPlaybackHistory() {
+ ContentValues values = new ContentValues();
+ values.put(KEY_PLAYBACK_COMPLETION_DATE, 0);
+ db.update(TABLE_NAME_FEED_MEDIA, values, null, null);
+ }
+
+ /**
+ * Get all Feeds from the Feed Table.
+ *
+ * @return The cursor of the query
+ */
+ public final Cursor getAllFeedsCursor() {
+ Cursor c = db.query(TABLE_NAME_FEEDS, null, null, null, null, null,
+ KEY_TITLE + " ASC");
+ return c;
+ }
+
+ public final Cursor getExpiredFeedsCursor(long expirationTime) {
+ Cursor c = db.query(TABLE_NAME_FEEDS, null, "?<?", new String[]{
+ KEY_LASTUPDATE, String.valueOf(System.currentTimeMillis() - expirationTime)}, null, null,
+ null);
+ return c;
+ }
+
+ /**
+ * Returns a cursor with all FeedItems of a Feed. Uses SEL_FI_SMALL
+ *
+ * @param feed The feed you want to get the FeedItems from.
+ * @return The cursor of the query
+ */
+ public final Cursor getAllItemsOfFeedCursor(final Feed feed) {
+ return getAllItemsOfFeedCursor(feed.getId());
+ }
+
+ public final Cursor getAllItemsOfFeedCursor(final long feedId) {
+ Cursor c = db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_FEED
+ + "=?", new String[]{String.valueOf(feedId)}, null, null,
+ null);
+ return c;
+ }
+
+ /**
+ * Return a cursor with the SEL_FI_EXTRA selection of a single feeditem.
+ */
+ public final Cursor getExtraInformationOfItem(final FeedItem item) {
+ Cursor c = db
+ .query(TABLE_NAME_FEED_ITEMS, SEL_FI_EXTRA, KEY_ID + "=?",
+ new String[]{String.valueOf(item.getId())}, null,
+ null, null);
+ return c;
+ }
+
+ /**
+ * Returns a cursor for a DB query in the FeedMedia table for a given ID.
+ *
+ * @param item The item you want to get the FeedMedia from
+ * @return The cursor of the query
+ */
+ public final Cursor getFeedMediaOfItemCursor(final FeedItem item) {
+ Cursor c = db.query(TABLE_NAME_FEED_MEDIA, null, KEY_ID + "=?",
+ new String[]{String.valueOf(item.getMedia().getId())}, null,
+ null, null);
+ return c;
+ }
+
+ /**
+ * Returns a cursor for a DB query in the FeedImages table for a given ID.
+ *
+ * @param id ID of the FeedImage
+ * @return The cursor of the query
+ */
+ public final Cursor getImageOfFeedCursor(final long id) {
+ Cursor c = db.query(TABLE_NAME_FEED_IMAGES, null, KEY_ID + "=?",
+ new String[]{String.valueOf(id)}, null, null, null);
+ return c;
+ }
+
+ public final Cursor getSimpleChaptersOfFeedItemCursor(final FeedItem item) {
+ Cursor c = db.query(TABLE_NAME_SIMPLECHAPTERS, null, KEY_FEEDITEM
+ + "=?", new String[]{String.valueOf(item.getId())}, null,
+ null, null);
+ return c;
+ }
+
+ public final Cursor getDownloadLogCursor(final int limit) {
+ Cursor c = db.query(TABLE_NAME_DOWNLOAD_LOG, null, null, null, null,
+ null, KEY_COMPLETION_DATE + " DESC LIMIT " + limit);
+ return c;
+ }
+
+ /**
+ * Returns a cursor which contains all feed items in the queue. The returned
+ * cursor uses the SEL_FI_SMALL selection.
+ */
+ public final Cursor getQueueCursor() {
+ Object[] args = (Object[]) new String[]{
+ SEL_FI_SMALL_STR + "," + TABLE_NAME_QUEUE + "." + KEY_ID,
+ TABLE_NAME_FEED_ITEMS, TABLE_NAME_QUEUE,
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ID,
+ TABLE_NAME_QUEUE + "." + KEY_FEEDITEM,
+ TABLE_NAME_QUEUE + "." + KEY_ID};
+ String query = String.format(
+ "SELECT %s FROM %s INNER JOIN %s ON %s=%s ORDER BY %s", args);
+ Cursor c = db.rawQuery(query, null);
+ /*
+ * Cursor c = db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL,
+ * "INNER JOIN ? ON ?=?", new String[] { TABLE_NAME_QUEUE,
+ * TABLE_NAME_FEED_ITEMS + "." + KEY_ID, TABLE_NAME_QUEUE + "." +
+ * KEY_FEEDITEM }, null, null, TABLE_NAME_QUEUE + "." + KEY_FEEDITEM);
+ */
+ return c;
+ }
+
+ public Cursor getQueueIDCursor() {
+ Cursor c = db.query(TABLE_NAME_QUEUE, new String[]{KEY_FEEDITEM}, null, null, null, null, KEY_ID + " ASC", null);
+ return c;
+ }
+
+ /**
+ * Returns a cursor which contains all feed items in the unread items list.
+ * The returned cursor uses the SEL_FI_SMALL selection.
+ */
+ public final Cursor getUnreadItemsCursor() {
+ Cursor c = db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_READ
+ + "=0", null, null, null, KEY_PUBDATE + " DESC");
+ return c;
+ }
+
+ public final Cursor getUnreadItemIdsCursor() {
+ Cursor c = db.query(TABLE_NAME_FEED_ITEMS, new String[]{KEY_ID},
+ KEY_READ + "=0", null, null, null, KEY_PUBDATE + " DESC");
+ return c;
+
+ }
+
+ public Cursor getDownloadedItemsCursor() {
+ final String query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS
+ + " INNER JOIN " + TABLE_NAME_FEED_MEDIA + " ON "
+ + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_ID + " WHERE "
+ + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + ">0";
+ Cursor c = db.rawQuery(query, null);
+ return c;
+ }
+
+ /**
+ * Returns a cursor which contains feed media objects with a playback
+ * completion date in descending order.
+ *
+ * @param limit The maximum row count of the returned cursor. Must be an
+ * integer >= 0.
+ * @throws IllegalArgumentException if limit < 0
+ */
+ public final Cursor getCompletedMediaCursor(int limit) {
+ if (limit < 0) {
+ throw new IllegalArgumentException("Limit must be >= 0");
+ }
+ Cursor c = db.query(TABLE_NAME_FEED_MEDIA, null,
+ KEY_PLAYBACK_COMPLETION_DATE + " > 0", null, null,
+ null, KEY_PLAYBACK_COMPLETION_DATE + " DESC LIMIT " + limit);
+ return c;
+ }
+
+ public final Cursor getSingleFeedMediaCursor(long id) {
+ return db.query(TABLE_NAME_FEED_MEDIA, null, KEY_ID + "=?", new String[]{String.valueOf(id)}, null, null, null);
+ }
+
+ public final Cursor getFeedMediaCursorByItemID(String... mediaIds) {
+ int length = mediaIds.length;
+ if (length > IN_OPERATOR_MAXIMUM) {
+ Log.w(TAG, "Length of id array is larger than "
+ + IN_OPERATOR_MAXIMUM + ". Creating multiple cursors");
+ int numCursors = (int) (((double) length) / (IN_OPERATOR_MAXIMUM)) + 1;
+ Cursor[] cursors = new Cursor[numCursors];
+ for (int i = 0; i < numCursors; i++) {
+ int neededLength = 0;
+ String[] parts = null;
+ final int elementsLeft = length - i * IN_OPERATOR_MAXIMUM;
+
+ if (elementsLeft >= IN_OPERATOR_MAXIMUM) {
+ neededLength = IN_OPERATOR_MAXIMUM;
+ parts = Arrays.copyOfRange(mediaIds, i
+ * IN_OPERATOR_MAXIMUM, (i + 1)
+ * IN_OPERATOR_MAXIMUM);
+ } else {
+ neededLength = elementsLeft;
+ parts = Arrays.copyOfRange(mediaIds, i
+ * IN_OPERATOR_MAXIMUM, (i * IN_OPERATOR_MAXIMUM)
+ + neededLength);
+ }
+
+ cursors[i] = db.rawQuery("SELECT * FROM "
+ + TABLE_NAME_FEED_MEDIA + " WHERE " + KEY_FEEDITEM + " IN "
+ + buildInOperator(neededLength), parts);
+ }
+ return new MergeCursor(cursors);
+ } else {
+ return db.query(TABLE_NAME_FEED_MEDIA, null, KEY_FEEDITEM + " IN "
+ + buildInOperator(length), mediaIds, null, null, null);
+ }
+ }
+
+ /**
+ * Builds an IN-operator argument depending on the number of items.
+ */
+ private String buildInOperator(int size) {
+ if (size == 1) {
+ return "(?)";
+ }
+ StringBuffer buffer = new StringBuffer("(");
+ for (int i = 0; i < size - 1; i++) {
+ buffer.append("?,");
+ }
+ buffer.append("?)");
+ return buffer.toString();
+ }
+
+ public final Cursor getFeedCursor(final long id) {
+ Cursor c = db.query(TABLE_NAME_FEEDS, null, KEY_ID + "=" + id, null,
+ null, null, null);
+ return c;
+ }
+
+ public final Cursor getFeedItemCursor(final String... ids) {
+ if (ids.length > IN_OPERATOR_MAXIMUM) {
+ throw new IllegalArgumentException(
+ "number of IDs must not be larger than "
+ + IN_OPERATOR_MAXIMUM);
+ }
+
+ return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_ID + " IN "
+ + buildInOperator(ids.length), ids, null, null, null);
+
+ }
+
+ public final int getNumberOfUnreadItems() {
+ final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_ITEMS +
+ " WHERE " + KEY_READ + " = 0";
+ Cursor c = db.rawQuery(query, null);
+ int result = 0;
+ if (c.moveToFirst()) {
+ result = c.getInt(0);
+ }
+ c.close();
+ return result;
+ }
+
+ public final int getNumberOfDownloadedEpisodes() {
+ final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_MEDIA +
+ " WHERE " + KEY_DOWNLOADED + " > 0";
+
+ Cursor c = db.rawQuery(query, null);
+ int result = 0;
+ if (c.moveToFirst()) {
+ result = c.getInt(0);
+ }
+ c.close();
+ return result;
+ }
+
+ /**
+ * Uses DatabaseUtils to escape a search query and removes ' at the
+ * beginning and the end of the string returned by the escape method.
+ */
+ private String prepareSearchQuery(String query) {
+ StringBuilder builder = new StringBuilder();
+ DatabaseUtils.appendEscapedSQLString(builder, query);
+ builder.deleteCharAt(0);
+ builder.deleteCharAt(builder.length() - 1);
+ return builder.toString();
+ }
+
+ /**
+ * Searches for the given query in the description of all items or the items
+ * of a specified feed.
+ *
+ * @return A cursor with all search results in SEL_FI_EXTRA selection.
+ */
+ public Cursor searchItemDescriptions(long feedID, String query) {
+ if (feedID != 0) {
+ // search items in specific feed
+ return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_FEED
+ + "=? AND " + KEY_DESCRIPTION + " LIKE '%"
+ + prepareSearchQuery(query) + "%'",
+ new String[]{String.valueOf(feedID)}, null, null,
+ null);
+ } else {
+ // search through all items
+ return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL,
+ KEY_DESCRIPTION + " LIKE '%" + prepareSearchQuery(query)
+ + "%'", null, null, null, null);
+ }
+ }
+
+ /**
+ * Searches for the given query in the content-encoded field of all items or
+ * the items of a specified feed.
+ *
+ * @return A cursor with all search results in SEL_FI_EXTRA selection.
+ */
+ public Cursor searchItemContentEncoded(long feedID, String query) {
+ if (feedID != 0) {
+ // search items in specific feed
+ return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_FEED
+ + "=? AND " + KEY_CONTENT_ENCODED + " LIKE '%"
+ + prepareSearchQuery(query) + "%'",
+ new String[]{String.valueOf(feedID)}, null, null,
+ null);
+ } else {
+ // search through all items
+ return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL,
+ KEY_CONTENT_ENCODED + " LIKE '%"
+ + prepareSearchQuery(query) + "%'", null, null,
+ null, null);
+ }
+ }
+
+ public Cursor searchItemTitles(long feedID, String query) {
+ if (feedID != 0) {
+ // search items in specific feed
+ return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL, KEY_FEED
+ + "=? AND " + KEY_TITLE + " LIKE '%"
+ + prepareSearchQuery(query) + "%'",
+ new String[]{String.valueOf(feedID)}, null, null,
+ null);
+ } else {
+ // search through all items
+ return db.query(TABLE_NAME_FEED_ITEMS, SEL_FI_SMALL,
+ KEY_TITLE + " LIKE '%"
+ + prepareSearchQuery(query) + "%'", null, null,
+ null, null);
+ }
+ }
+
+ public Cursor searchItemChapters(long feedID, String searchQuery) {
+ final String query;
+ if (feedID != 0) {
+ query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS + " INNER JOIN " +
+ TABLE_NAME_SIMPLECHAPTERS + " ON " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_FEEDITEM + "=" +
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ID + " WHERE " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" +
+ feedID + " AND " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_TITLE + " LIKE '%"
+ + prepareSearchQuery(searchQuery) + "%'";
+ } else {
+ query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS + " INNER JOIN " +
+ TABLE_NAME_SIMPLECHAPTERS + " ON " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_FEEDITEM + "=" +
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ID + " WHERE " + TABLE_NAME_SIMPLECHAPTERS + "." + KEY_TITLE + " LIKE '%"
+ + prepareSearchQuery(searchQuery) + "%'";
+ }
+ return db.rawQuery(query, null);
+ }
+
+
+ public static final int IDX_FEEDSTATISTICS_FEED = 0;
+ public static final int IDX_FEEDSTATISTICS_NUM_ITEMS = 1;
+ public static final int IDX_FEEDSTATISTICS_NEW_ITEMS = 2;
+ public static final int IDX_FEEDSTATISTICS_LATEST_EPISODE = 3;
+ public static final int IDX_FEEDSTATISTICS_IN_PROGRESS_EPISODES = 4;
+
+ /**
+ * Select number of items, new items, the date of the latest episode and the number of episodes in progress. The result
+ * is sorted by the title of the feed.
+ */
+ private static final String FEED_STATISTICS_QUERY = "SELECT feed, num_items, new_items, latest_episode, in_progress FROM " +
+ "(SELECT feed,count(*) AS num_items," +
+ " COUNT(CASE WHEN read=0 THEN 1 END) AS new_items," +
+ " MAX(pubDate) AS latest_episode," +
+ " COUNT(CASE WHEN position>0 THEN 1 END) AS in_progress," +
+ " COUNT(CASE WHEN downloaded=1 THEN 1 END) AS episodes_downloaded " +
+ " FROM FeedItems INNER JOIN FeedMedia ON FeedItems.id=FeedMedia.feeditem GROUP BY FeedItems.feed)" +
+ " INNER JOIN Feeds ON Feeds.id = feed ORDER BY Feeds.title;";
+
+ public Cursor getFeedStatisticsCursor() {
+ return db.rawQuery(FEED_STATISTICS_QUERY, null);
+ }
+
+ /**
+ * Helper class for opening the Antennapod database.
+ */
+ private static class PodDBHelper extends SQLiteOpenHelper {
+ /**
+ * Constructor.
+ *
+ * @param context Context to use
+ * @param name Name of the database
+ * @param factory to use for creating cursor objects
+ * @param version number of the database
+ */
+ public PodDBHelper(final Context context, final String name,
+ final CursorFactory factory, final int version) {
+ super(context, name, factory, version);
+ }
+
+ @Override
+ public void onCreate(final SQLiteDatabase db) {
+ db.execSQL(CREATE_TABLE_FEEDS);
+ db.execSQL(CREATE_TABLE_FEED_ITEMS);
+ db.execSQL(CREATE_TABLE_FEED_IMAGES);
+ db.execSQL(CREATE_TABLE_FEED_MEDIA);
+ db.execSQL(CREATE_TABLE_DOWNLOAD_LOG);
+ db.execSQL(CREATE_TABLE_QUEUE);
+ db.execSQL(CREATE_TABLE_SIMPLECHAPTERS);
+ }
+
+ @Override
+ public void onUpgrade(final SQLiteDatabase db, final int oldVersion,
+ final int newVersion) {
+ Log.w("DBAdapter", "Upgrading from version " + oldVersion + " to "
+ + newVersion + ".");
+ if (oldVersion <= 1) {
+ db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS + " ADD COLUMN "
+ + KEY_TYPE + " TEXT");
+ }
+ if (oldVersion <= 2) {
+ db.execSQL("ALTER TABLE " + TABLE_NAME_SIMPLECHAPTERS
+ + " ADD COLUMN " + KEY_LINK + " TEXT");
+ }
+ if (oldVersion <= 3) {
+ db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_ITEMS
+ + " ADD COLUMN " + KEY_ITEM_IDENTIFIER + " TEXT");
+ }
+ if (oldVersion <= 4) {
+ db.execSQL("ALTER TABLE " + TABLE_NAME_FEEDS + " ADD COLUMN "
+ + KEY_FEED_IDENTIFIER + " TEXT");
+ }
+ if (oldVersion <= 5) {
+ db.execSQL("ALTER TABLE " + TABLE_NAME_DOWNLOAD_LOG
+ + " ADD COLUMN " + KEY_REASON_DETAILED + " TEXT");
+ db.execSQL("ALTER TABLE " + TABLE_NAME_DOWNLOAD_LOG
+ + " ADD COLUMN " + KEY_DOWNLOADSTATUS_TITLE + " TEXT");
+ }
+ if (oldVersion <= 6) {
+ db.execSQL("ALTER TABLE " + TABLE_NAME_SIMPLECHAPTERS
+ + " ADD COLUMN " + KEY_CHAPTER_TYPE + " INTEGER");
+ }
+ if (oldVersion <= 7) {
+ db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_MEDIA
+ + " ADD COLUMN " + KEY_PLAYBACK_COMPLETION_DATE
+ + " INTEGER");
+ }
+ if (oldVersion <= 8) {
+ final int KEY_ID_POSITION = 0;
+ final int KEY_MEDIA_POSITION = 1;
+ // Add feeditem column to feedmedia table
+ db.execSQL("ALTER TABLE " + TABLE_NAME_FEED_MEDIA
+ + " ADD COLUMN " + KEY_FEEDITEM
+ + " INTEGER");
+ Cursor feeditemCursor = db.query(TABLE_NAME_FEED_ITEMS, new String[]{KEY_ID, KEY_MEDIA}, "? > 0", new String[]{KEY_MEDIA}, null, null, null);
+ if (feeditemCursor.moveToFirst()) {
+ db.beginTransaction();
+ ContentValues contentValues = new ContentValues();
+ do {
+ long mediaId = feeditemCursor.getLong(KEY_MEDIA_POSITION);
+ contentValues.put(KEY_FEEDITEM, feeditemCursor.getLong(KEY_ID_POSITION));
+ db.update(TABLE_NAME_FEED_MEDIA, contentValues, KEY_ID + "=?", new String[]{String.valueOf(mediaId)});
+ contentValues.clear();
+ } while (feeditemCursor.moveToNext());
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+ feeditemCursor.close();
+ }
+ }
+ }
}
diff --git a/src/de/danoeh/antennapod/util/ConnectionTester.java b/src/de/danoeh/antennapod/util/ConnectionTester.java
index 2fd22d356..5d940d9e1 100644
--- a/src/de/danoeh/antennapod/util/ConnectionTester.java
+++ b/src/de/danoeh/antennapod/util/ConnectionTester.java
@@ -14,7 +14,7 @@ public class ConnectionTester implements Runnable {
private static final String TAG = "ConnectionTester";
private String strUrl;
private Callback callback;
- private int reason;
+ private DownloadError reason;
private Handler handler;
@@ -68,10 +68,10 @@ public class ConnectionTester implements Runnable {
public static abstract class Callback {
public abstract void onConnectionSuccessful();
- public abstract void onConnectionFailure(int reason);
+ public abstract void onConnectionFailure(DownloadError reason);
}
- public int getReason() {
+ public DownloadError getReason() {
return reason;
}
diff --git a/src/de/danoeh/antennapod/util/DownloadError.java b/src/de/danoeh/antennapod/util/DownloadError.java
index 4723a521c..c37a14584 100644
--- a/src/de/danoeh/antennapod/util/DownloadError.java
+++ b/src/de/danoeh/antennapod/util/DownloadError.java
@@ -4,54 +4,46 @@ import android.content.Context;
import de.danoeh.antennapod.R;
/** Utility class for Download Errors. */
-public class DownloadError {
- public static final int ERROR_PARSER_EXCEPTION = 1;
- public static final int ERROR_UNSUPPORTED_TYPE = 2;
- public static final int ERROR_CONNECTION_ERROR = 3;
- public static final int ERROR_MALFORMED_URL = 4;
- public static final int ERROR_IO_ERROR = 5;
- public static final int ERROR_FILE_EXISTS = 6;
- public static final int ERROR_DOWNLOAD_CANCELLED = 7;
- public static final int ERROR_DEVICE_NOT_FOUND = 8;
- public static final int ERROR_HTTP_DATA_ERROR = 9;
- public static final int ERROR_NOT_ENOUGH_SPACE = 10;
- public static final int ERROR_UNKNOWN_HOST = 11;
- public static final int ERROR_REQUEST_ERROR = 12;
-
- /** Get a human-readable string for a specific error code. */
- public static String getErrorString(Context context, int code) {
- int resId;
- switch(code) {
- case ERROR_NOT_ENOUGH_SPACE:
- resId = R.string.download_error_insufficient_space;
- break;
- case ERROR_DEVICE_NOT_FOUND:
- resId = R.string.download_error_device_not_found;
- break;
- case ERROR_IO_ERROR:
- resId = R.string.download_error_io_error;
- break;
- case ERROR_HTTP_DATA_ERROR:
- resId = R.string.download_error_http_data_error;
- break;
- case ERROR_PARSER_EXCEPTION:
- resId = R.string.download_error_parser_exception;
- break;
- case ERROR_UNSUPPORTED_TYPE:
- resId = R.string.download_error_unsupported_type;
- break;
- case ERROR_CONNECTION_ERROR:
- resId = R.string.download_error_connection_error;
- break;
- case ERROR_UNKNOWN_HOST:
- resId = R.string.download_error_unknown_host;
- break;
- case ERROR_REQUEST_ERROR:
- resId = R.string.download_error_request_error;
- break;
- default:
- resId = R.string.download_error_error_unknown;
+public enum DownloadError {
+ SUCCESS(0, R.string.download_successful),
+ ERROR_PARSER_EXCEPTION(1, R.string.download_error_parser_exception),
+ ERROR_UNSUPPORTED_TYPE(2, R.string.download_error_unsupported_type),
+ ERROR_CONNECTION_ERROR(3, R.string.download_error_connection_error),
+ ERROR_MALFORMED_URL(4, R.string.download_error_error_unknown),
+ ERROR_IO_ERROR(5, R.string.download_error_io_error),
+ ERROR_FILE_EXISTS(6, R.string.download_error_error_unknown),
+ ERROR_DOWNLOAD_CANCELLED(7, R.string.download_error_error_unknown),
+ ERROR_DEVICE_NOT_FOUND(8, R.string.download_error_device_not_found),
+ ERROR_HTTP_DATA_ERROR(9, R.string.download_error_http_data_error),
+ ERROR_NOT_ENOUGH_SPACE(10, R.string.download_error_insufficient_space),
+ ERROR_UNKNOWN_HOST(11, R.string.download_error_unknown_host),
+ ERROR_REQUEST_ERROR(12, R.string.download_error_request_error);
+
+ private final int code;
+ private final int resId;
+
+ private DownloadError(int code, int resId) {
+ this.code = code;
+ this.resId = resId;
+ }
+
+ /** Return DownloadError from its associated code. */
+ public static DownloadError fromCode(int code) {
+ for (DownloadError reason : values()) {
+ if (reason.getCode() == code) {
+ return reason;
+ }
}
+ throw new IllegalArgumentException("unknown code: " + code);
+ }
+
+ /** Get machine-readable code. */
+ public int getCode() {
+ return code;
+ }
+
+ /** Get a human-readable string. */
+ public String getErrorString(Context context) {
return context.getString(resId);
}
diff --git a/src/de/danoeh/antennapod/util/QueueAccess.java b/src/de/danoeh/antennapod/util/QueueAccess.java
new file mode 100644
index 000000000..7a1c7fef2
--- /dev/null
+++ b/src/de/danoeh/antennapod/util/QueueAccess.java
@@ -0,0 +1,93 @@
+package de.danoeh.antennapod.util;
+
+import de.danoeh.antennapod.feed.FeedItem;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Provides methods for accessing the queue. It is possible to load only a part of the information about the queue that
+ * is stored in the database (e.g. sometimes the user just has to test if a specific item is contained in the List.
+ * QueueAccess provides an interface for accessing the queue without having to care about the type of the queue
+ * representation.
+ */
+public abstract class QueueAccess {
+ /**
+ * Returns true if the item is in the queue, false otherwise.
+ */
+ public abstract boolean contains(long id);
+
+ /**
+ * Removes the item from the queue.
+ *
+ * @return true if the queue was modified by this operation.
+ */
+ public abstract boolean remove(long id);
+
+ private QueueAccess() {
+
+ }
+
+ public static QueueAccess IDListAccess(final List<Long> ids) {
+ return new QueueAccess() {
+ @Override
+ public boolean contains(long id) {
+ return (ids != null) && ids.contains(id);
+ }
+
+ @Override
+ public boolean remove(long id) {
+ return ids.remove(id);
+ }
+
+
+ };
+ }
+
+ public static QueueAccess ItemListAccess(final List<FeedItem> items) {
+ return new QueueAccess() {
+ @Override
+ public boolean contains(long id) {
+ if (items == null) {
+ return false;
+ }
+ for (FeedItem item : items) {
+ if (item.getId() == id) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean remove(long id) {
+ Iterator<FeedItem> it = items.iterator();
+ FeedItem item;
+ while (it.hasNext()) {
+ item = it.next();
+ if (item.getId() == id) {
+ it.remove();
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+ }
+
+ public static QueueAccess NotInQueueAccess() {
+ return new QueueAccess() {
+ @Override
+ public boolean contains(long id) {
+ return false;
+ }
+
+ @Override
+ public boolean remove(long id) {
+ return false;
+ }
+ };
+
+ }
+
+}
diff --git a/src/de/danoeh/antennapod/util/ShownotesProvider.java b/src/de/danoeh/antennapod/util/ShownotesProvider.java
new file mode 100644
index 000000000..d273e0b8f
--- /dev/null
+++ b/src/de/danoeh/antennapod/util/ShownotesProvider.java
@@ -0,0 +1,17 @@
+package de.danoeh.antennapod.util;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+
+/**
+ * Created by daniel on 04.08.13.
+ */
+public interface ShownotesProvider {
+ /**
+ * Loads shownotes. If the shownotes have to be loaded from a file or from a
+ * database, it should be done in a separate thread. After the shownotes
+ * have been loaded, callback.onShownotesLoaded should be called.
+ */
+ public Callable<String> loadShownotes();
+
+}
diff --git a/src/de/danoeh/antennapod/util/UndoBarController.java b/src/de/danoeh/antennapod/util/UndoBarController.java
index e726717a1..a0240e7ce 100644
--- a/src/de/danoeh/antennapod/util/UndoBarController.java
+++ b/src/de/danoeh/antennapod/util/UndoBarController.java
@@ -16,15 +16,17 @@
package de.danoeh.antennapod.util;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Parcelable;
import android.text.TextUtils;
import android.view.View;
-import android.view.ViewPropertyAnimator;
import android.widget.TextView;
+import com.nineoldandroids.animation.Animator;
+import com.nineoldandroids.animation.AnimatorListenerAdapter;
+import com.nineoldandroids.view.ViewHelper;
+import com.nineoldandroids.view.ViewPropertyAnimator;
+import static com.nineoldandroids.view.ViewPropertyAnimator.animate;
import de.danoeh.antennapod.R;
@@ -46,7 +48,7 @@ public class UndoBarController {
public UndoBarController(View undoBarView, UndoListener undoListener) {
mBarView = undoBarView;
- mBarAnimator = mBarView.animate();
+ mBarAnimator = animate(mBarView);
mUndoListener = undoListener;
mMessageView = (TextView) mBarView.findViewById(R.id.undobar_message);
@@ -73,7 +75,7 @@ public class UndoBarController {
mBarView.setVisibility(View.VISIBLE);
if (immediate) {
- mBarView.setAlpha(1);
+ ViewHelper.setAlpha(mBarView, 1);
} else {
mBarAnimator.cancel();
mBarAnimator
@@ -89,7 +91,7 @@ public class UndoBarController {
mHideHandler.removeCallbacks(mHideRunnable);
if (immediate) {
mBarView.setVisibility(View.GONE);
- mBarView.setAlpha(0);
+ ViewHelper.setAlpha(mBarView, 0);
mUndoMessage = null;
mUndoToken = null;
diff --git a/src/de/danoeh/antennapod/util/comparator/DownloadStatusComparator.java b/src/de/danoeh/antennapod/util/comparator/DownloadStatusComparator.java
index 12f800565..2cfe52364 100644
--- a/src/de/danoeh/antennapod/util/comparator/DownloadStatusComparator.java
+++ b/src/de/danoeh/antennapod/util/comparator/DownloadStatusComparator.java
@@ -2,7 +2,7 @@ package de.danoeh.antennapod.util.comparator;
import java.util.Comparator;
-import de.danoeh.antennapod.asynctask.DownloadStatus;
+import de.danoeh.antennapod.service.download.*;
/** Compares the completion date of two Downloadstatus objects. */
public class DownloadStatusComparator implements Comparator<DownloadStatus> {
diff --git a/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java b/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java
index e1cafe85d..f897f886c 100644
--- a/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java
+++ b/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java
@@ -2,18 +2,24 @@ package de.danoeh.antennapod.util.id3reader;
import java.io.IOException;
import java.io.InputStream;
+import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
+import android.util.Log;
+
+import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.feed.Chapter;
import de.danoeh.antennapod.feed.ID3Chapter;
import de.danoeh.antennapod.util.id3reader.model.FrameHeader;
import de.danoeh.antennapod.util.id3reader.model.TagHeader;
public class ChapterReader extends ID3Reader {
+ private static final String TAG = "ID3ChapterReader";
private static final String FRAME_ID_CHAPTER = "CHAP";
private static final String FRAME_ID_TITLE = "TIT2";
+ private static final String FRAME_ID_LINK = "WXXX";
private List<Chapter> chapters;
private ID3Chapter currentChapter;
@@ -33,27 +39,45 @@ public class ChapterReader extends ID3Reader {
if (currentChapter != null) {
if (!hasId3Chapter(currentChapter)) {
chapters.add(currentChapter);
- System.out.println("Found chapter: " + currentChapter);
+ if (AppConfig.DEBUG) Log.d(TAG, "Found chapter: " + currentChapter);
currentChapter = null;
}
}
- String elementId = readISOString(input, Integer.MAX_VALUE);
+ StringBuffer elementId = new StringBuffer();
+ readISOString(elementId, input, Integer.MAX_VALUE);
char[] startTimeSource = readBytes(input, 4);
long startTime = ((int) startTimeSource[0] << 24)
| ((int) startTimeSource[1] << 16)
| ((int) startTimeSource[2] << 8) | startTimeSource[3];
- currentChapter = new ID3Chapter(elementId, startTime);
+ currentChapter = new ID3Chapter(elementId.toString(), startTime);
skipBytes(input, 12);
return ID3Reader.ACTION_DONT_SKIP;
} else if (header.getId().equals(FRAME_ID_TITLE)) {
if (currentChapter != null && currentChapter.getTitle() == null) {
+ StringBuffer title = new StringBuffer();
+ readString(title, input, header.getSize());
currentChapter
- .setTitle(readString(input, header.getSize()));
- System.out.println("Found title: " + currentChapter.getTitle());
+ .setTitle(title.toString());
+ if (AppConfig.DEBUG) Log.d(TAG, "Found title: " + currentChapter.getTitle());
return ID3Reader.ACTION_DONT_SKIP;
}
- }
+ } else if (header.getId().equals(FRAME_ID_LINK)) {
+ if (currentChapter != null) {
+ // skip description
+ int descriptionLength = readString(null, input, header.getSize());
+ StringBuffer link = new StringBuffer();
+ readISOString(link, input, header.getSize() - descriptionLength);
+ String decodedLink = URLDecoder.decode(link.toString(), "UTF-8");
+
+ currentChapter.setLink(decodedLink);
+
+ if (AppConfig.DEBUG) Log.d(TAG, "Found link: " + currentChapter.getLink());
+ return ID3Reader.ACTION_DONT_SKIP;
+ }
+ } else if (header.getId().equals("APIC")) {
+ Log.d(TAG, header.toString());
+ }
return super.onStartFrameHeader(header, input);
}
diff --git a/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java b/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java
index dff6d77e8..92f817363 100644
--- a/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java
+++ b/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java
@@ -24,7 +24,11 @@ public class ID3Reader {
protected int readerPosition;
- private static final byte ENCODING_UNICODE = 1;
+ private static final byte ENCODING_UTF16_WITH_BOM = 1;
+ private static final byte ENCODING_UTF16_WITHOUT_BOM = 2;
+ private static final byte ENCODING_UTF8 = 3;
+
+ private TagHeader tagHeader;
public ID3Reader() {
}
@@ -34,7 +38,7 @@ public class ID3Reader {
int rc;
readerPosition = 0;
char[] tagHeaderSource = readBytes(input, HEADER_LENGTH);
- TagHeader tagHeader = createTagHeader(tagHeaderSource);
+ tagHeader = createTagHeader(tagHeaderSource);
if (tagHeader == null) {
onNoTagHeaderFound();
} else {
@@ -124,12 +128,12 @@ public class ID3Reader {
+ HEADER_LENGTH);
}
if (hasTag) {
- String id = null;
- id = new String(source, 0, ID3_LENGTH);
+ String id = new String(source, 0, ID3_LENGTH);
char version = (char) ((source[3] << 8) | source[4]);
byte flags = (byte) source[5];
int size = (source[6] << 24) | (source[7] << 16) | (source[8] << 8)
| source[9];
+ size = unsynchsafe(size);
return new TagHeader(id, size, version, flags);
} else {
return null;
@@ -142,48 +146,89 @@ public class ID3Reader {
throw new ID3ReaderException("Length of header must be "
+ HEADER_LENGTH);
}
- String id = null;
- id = new String(source, 0, FRAME_ID_LENGTH);
- int size = (((int) source[4]) << 24) | (((int) source[5]) << 16)
- | (((int) source[6]) << 8) | source[7];
+ String id = new String(source, 0, FRAME_ID_LENGTH);
+
+ int size = (((int) source[4]) << 24) | (((int) source[5]) << 16)
+ | (((int) source[6]) << 8) | source[7];
+ if (tagHeader != null && tagHeader.getVersion() >= 0x0400) {
+ size = unsynchsafe(size);
+ }
char flags = (char) ((source[8] << 8) | source[9]);
return new FrameHeader(id, size, flags);
}
- protected String readString(InputStream input, int max) throws IOException,
+ private int unsynchsafe(int in) {
+ int out = 0;
+ int mask = 0x7F000000;
+
+ while (mask != 0) {
+ out >>= 1;
+ out |= in & mask;
+ mask >>= 8;
+ }
+
+ return out;
+ }
+
+ protected int readString(StringBuffer buffer, InputStream input, int max) throws IOException,
ID3ReaderException {
if (max > 0) {
char[] encoding = readBytes(input, 1);
max--;
- if (encoding[0] == ENCODING_UNICODE) {
- return readUnicodeString(input, max);
- } else {
- return readISOString(input, max);
+ if (encoding[0] == ENCODING_UTF16_WITH_BOM || encoding[0] == ENCODING_UTF16_WITHOUT_BOM) {
+ return readUnicodeString(buffer, input, max, Charset.forName("UTF-16")) + 1; // take encoding byte into account
+ } else if (encoding[0] == ENCODING_UTF8) {
+ return readUnicodeString(buffer, input, max, Charset.forName("UTF-8")) + 1; // take encoding byte into account
+ } else {
+ return readISOString(buffer, input, max) + 1; // take encoding byte into account
}
} else {
- return "";
+ if (buffer != null) {
+ buffer.append("");
+ }
+ return 0;
}
}
- protected String readISOString(InputStream input, int max)
+ protected int readISOString(StringBuffer buffer, InputStream input, int max)
throws IOException, ID3ReaderException {
int bytesRead = 0;
- StringBuilder builder = new StringBuilder();
char c;
while (++bytesRead <= max && (c = (char) input.read()) > 0) {
- builder.append(c);
+ if (buffer != null) {
+ buffer.append(c);
+ }
}
- return builder.toString();
+ return bytesRead;
}
- private String readUnicodeString(InputStream input, int max)
+ private int readUnicodeString(StringBuffer strBuffer, InputStream input, int max, Charset charset)
throws IOException, ID3ReaderException {
byte[] buffer = new byte[max];
- IOUtils.readFully(input, buffer);
- Charset charset = Charset.forName("UTF-16");
- return charset.newDecoder().decode(ByteBuffer.wrap(buffer)).toString();
+ int c, cZero = -1;
+ int i = 0;
+ for (; i < max; i++) {
+ c = input.read();
+ if (c == -1) {
+ break;
+ } else if (c == 0) {
+ if (cZero == 0) {
+ // termination character found
+ break;
+ } else {
+ cZero = 0;
+ }
+ } else {
+ buffer[i] = (byte) c;
+ cZero = -1;
+ }
+ }
+ if (strBuffer != null) {
+ strBuffer.append(charset.newDecoder().decode(ByteBuffer.wrap(buffer)).toString());
+ }
+ return i;
}
public int onStartTagHeader(TagHeader header) {
diff --git a/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java b/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java
index 2c0d8e5ba..df73393a5 100644
--- a/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java
+++ b/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java
@@ -11,8 +11,7 @@ public class FrameHeader extends Header {
@Override
public String toString() {
- return "FrameHeader [flags=" + Integer.toString(flags) + ", id=" + id + ", size=" + size
- + "]";
- }
+ return String.format("FrameHeader [flags=%s, id=%s, size=%s]", Integer.toBinaryString(flags), id, Integer.toBinaryString(size));
+ }
}
diff --git a/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java b/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java
index 472124bf7..aa029f672 100644
--- a/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java
+++ b/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java
@@ -7,12 +7,16 @@ import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.feed.FeedItem;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.service.PlaybackService;
+import de.danoeh.antennapod.storage.DBTasks;
+import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.storage.DownloadRequester;
+import de.danoeh.antennapod.util.QueueAccess;
import de.danoeh.antennapod.util.ShareUtils;
+import java.util.List;
+
/** Handles interactions with the FeedItemMenu. */
public class FeedItemMenuHandler {
private FeedItemMenuHandler() {
@@ -21,7 +25,7 @@ public class FeedItemMenuHandler {
/**
* Used by the MenuHandler to access different types of menus through one
- * interface (for example android.view.Menu and com.actionbarsherlock.Menu)
+ * interface
*/
public interface MenuInterface {
/**
@@ -45,11 +49,12 @@ public class FeedItemMenuHandler {
* True if MenuItems that let the user share information about
* the FeedItem and visit its website should be set visible. This
* parameter should be set to false if the menu space is limited.
+ * @param queueAccess
+ * Used for testing if the queue contains the selected item
* @return Always returns true
* */
public static boolean onPrepareMenu(MenuInterface mi,
- FeedItem selectedItem, boolean showExtendedMenu) {
- FeedManager manager = FeedManager.getInstance();
+ FeedItem selectedItem, boolean showExtendedMenu, QueueAccess queueAccess) {
DownloadRequester requester = DownloadRequester.getInstance();
boolean hasMedia = selectedItem.getMedia() != null;
boolean downloaded = hasMedia && selectedItem.getMedia().isDownloaded();
@@ -79,7 +84,7 @@ public class FeedItemMenuHandler {
mi.setItemVisibility(R.id.cancel_download_item, false);
}
- boolean isInQueue = manager.isInQueue(selectedItem);
+ boolean isInQueue = queueAccess.contains(selectedItem.getId());
if (!isInQueue || isPlaying) {
mi.setItemVisibility(R.id.remove_from_queue_item, false);
}
@@ -111,39 +116,38 @@ public class FeedItemMenuHandler {
public static boolean onMenuItemClicked(Context context, int menuItemId,
FeedItem selectedItem) throws DownloadRequestException {
DownloadRequester requester = DownloadRequester.getInstance();
- FeedManager manager = FeedManager.getInstance();
switch (menuItemId) {
case R.id.skip_episode_item:
context.sendBroadcast(new Intent(
PlaybackService.ACTION_SKIP_CURRENT_EPISODE));
break;
case R.id.download_item:
- manager.downloadFeedItem(context, selectedItem);
+ DBTasks.downloadFeedItems(context, selectedItem);
break;
case R.id.play_item:
- manager.playMedia(context, selectedItem.getMedia(), true, true,
+ DBTasks.playMedia(context, selectedItem.getMedia(), true, true,
false);
break;
case R.id.remove_item:
- manager.deleteFeedMedia(context, selectedItem.getMedia());
+ DBWriter.deleteFeedMediaOfItem(context, selectedItem.getId());
break;
case R.id.cancel_download_item:
requester.cancelDownload(context, selectedItem.getMedia());
break;
case R.id.mark_read_item:
- manager.markItemRead(context, selectedItem, true, true);
+ DBWriter.markItemRead(context, selectedItem, true, true);
break;
case R.id.mark_unread_item:
- manager.markItemRead(context, selectedItem, false, true);
+ DBWriter.markItemRead(context, selectedItem, false, true);
break;
case R.id.add_to_queue_item:
- manager.addQueueItem(context, selectedItem);
+ DBWriter.addQueueItem(context, selectedItem.getId());
break;
case R.id.remove_from_queue_item:
- manager.removeQueueItem(context, selectedItem, true);
+ DBWriter.removeQueueItem(context, selectedItem.getId(), true);
break;
case R.id.stream_item:
- manager.playMedia(context, selectedItem.getMedia(), true, true,
+ DBTasks.playMedia(context, selectedItem.getMedia(), true, true,
true);
break;
case R.id.visit_website_item:
diff --git a/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java b/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java
index af8538e83..843607617 100644
--- a/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java
+++ b/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java
@@ -5,17 +5,17 @@ import android.content.Intent;
import android.net.Uri;
import android.util.Log;
-import com.actionbarsherlock.view.Menu;
-import com.actionbarsherlock.view.MenuInflater;
-import com.actionbarsherlock.view.MenuItem;
-
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.FeedInfoActivity;
import de.danoeh.antennapod.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.feed.Feed;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.service.download.DownloadService;
+import de.danoeh.antennapod.storage.DBTasks;
+import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.storage.DownloadRequester;
import de.danoeh.antennapod.util.ShareUtils;
@@ -56,7 +56,6 @@ public class FeedMenuHandler {
*/
public static boolean onOptionsItemClicked(Context context, MenuItem item,
Feed selectedFeed) throws DownloadRequestException {
- FeedManager manager = FeedManager.getInstance();
switch (item.getItemId()) {
case R.id.show_info_item:
Intent startIntent = new Intent(context, FeedInfoActivity.class);
@@ -65,10 +64,10 @@ public class FeedMenuHandler {
context.startActivity(startIntent);
break;
case R.id.refresh_item:
- manager.refreshFeed(context, selectedFeed);
+ DBTasks.refreshFeed(context, selectedFeed);
break;
case R.id.mark_all_read_item:
- manager.markFeedRead(context, selectedFeed);
+ DBWriter.markFeedRead(context, selectedFeed.getId());
break;
case R.id.visit_website_item:
Uri uri = Uri.parse(selectedFeed.getLink());
diff --git a/src/de/danoeh/antennapod/util/playback/ExternalMedia.java b/src/de/danoeh/antennapod/util/playback/ExternalMedia.java
index c0a92904b..1ada0ec03 100644
--- a/src/de/danoeh/antennapod/util/playback/ExternalMedia.java
+++ b/src/de/danoeh/antennapod/util/playback/ExternalMedia.java
@@ -2,6 +2,7 @@ package de.danoeh.antennapod.util.playback;
import java.io.InputStream;
import java.util.List;
+import java.util.concurrent.Callable;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
@@ -95,8 +96,13 @@ public class ExternalMedia implements Playable {
}
@Override
- public void loadShownotes(ShownoteLoaderCallback callback) {
- callback.onShownotesLoaded(null);
+ public Callable<String> loadShownotes() {
+ return new Callable<String>() {
+ @Override
+ public String call() throws Exception {
+ return "";
+ }
+ };
}
@Override
diff --git a/src/de/danoeh/antennapod/util/playback/Playable.java b/src/de/danoeh/antennapod/util/playback/Playable.java
index 0404379e2..98d5fbb36 100644
--- a/src/de/danoeh/antennapod/util/playback/Playable.java
+++ b/src/de/danoeh/antennapod/util/playback/Playable.java
@@ -3,7 +3,11 @@ package de.danoeh.antennapod.util.playback;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.List;
+import java.util.concurrent.FutureTask;
+import android.content.Context;
+import de.danoeh.antennapod.storage.DBReader;
+import de.danoeh.antennapod.util.ShownotesProvider;
import org.apache.commons.io.IOUtils;
import android.content.SharedPreferences;
@@ -12,262 +16,263 @@ import android.os.Parcelable;
import android.util.Log;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.feed.Chapter;
-import de.danoeh.antennapod.feed.Feed;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.feed.MediaType;
-/** Interface for objects that can be played by the PlaybackService. */
+/**
+ * Interface for objects that can be played by the PlaybackService.
+ */
public interface Playable extends Parcelable,
- ImageLoader.ImageWorkerTaskResource {
-
- /**
- * Save information about the playable in a preference so that it can be
- * restored later via PlayableUtils.createInstanceFromPreferences.
- * Implementations must NOT call commit() after they have written the values
- * to the preferences file.
- */
- public void writeToPreferences(SharedPreferences.Editor prefEditor);
-
- /**
- * This method is called from a separate thread by the PlaybackService.
- * Playable objects should load their metadata in this method. This method
- * should execute as quickly as possible and NOT load chapter marks if no
- * local file is available.
- */
- public void loadMetadata() throws PlayableException;
-
- /**
- * This method is called from a separate thread by the PlaybackService.
- * Playable objects should load their chapter marks in this method if no
- * local file was available when loadMetadata() was called.
- */
- public void loadChapterMarks();
-
- /** Returns the title of the episode that this playable represents */
- public String getEpisodeTitle();
-
- /**
- * Loads shownotes. If the shownotes have to be loaded from a file or from a
- * database, it should be done in a separate thread. After the shownotes
- * have been loaded, callback.onShownotesLoaded should be called.
- */
- public void loadShownotes(ShownoteLoaderCallback callback);
-
- /**
- * Returns a list of chapter marks or null if this Playable has no chapters.
- */
- public List<Chapter> getChapters();
-
- /** Returns a link to a website that is meant to be shown in a browser */
- public String getWebsiteLink();
-
- public String getPaymentLink();
-
- /** Returns the title of the feed this Playable belongs to. */
- public String getFeedTitle();
-
- /**
- * Returns a unique identifier, for example a file url or an ID from a
- * database.
- */
- public Object getIdentifier();
-
- /** Return duration of object or 0 if duration is unknown. */
- public int getDuration();
-
- /** Return position of object or 0 if position is unknown. */
- public int getPosition();
-
- /**
- * Returns the type of media. This method should return the correct value
- * BEFORE loadMetadata() is called.
- */
- public MediaType getMediaType();
-
- /**
- * Returns an url to a local file that can be played or null if this file
- * does not exist.
- */
- public String getLocalMediaUrl();
-
- /**
- * Returns an url to a file that can be streamed by the player or null if
- * this url is not known.
- */
- public String getStreamUrl();
-
- /**
- * Returns true if a local file that can be played is available. getFileUrl
- * MUST return a non-null string if this method returns true.
- */
- public boolean localFileAvailable();
-
- /**
- * Returns true if a streamable file is available. getStreamUrl MUST return
- * a non-null string if this method returns true.
- */
- public boolean streamAvailable();
-
- /**
- * Saves the current position of this object. Implementations can use the
- * provided SharedPreference to save this information and retrieve it later
- * via PlayableUtils.createInstanceFromPreferences.
- */
- public void saveCurrentPosition(SharedPreferences pref, int newPosition);
-
- public void setPosition(int newPosition);
-
- public void setDuration(int newDuration);
-
- /** Is called by the PlaybackService when playback starts. */
- public void onPlaybackStart();
-
- /** Is called by the PlaybackService when playback is completed. */
- public void onPlaybackCompleted();
-
- /**
- * Returns an integer that must be unique among all Playable classes. The
- * return value is later used by PlayableUtils to determine the type of the
- * Playable object that is restored.
- */
- public int getPlayableType();
-
- public void setChapters(List<Chapter> chapters);
-
- /** Provides utility methods for Playable objects. */
- public static class PlayableUtils {
- private static final String TAG = "PlayableUtils";
-
- /**
- * Restores a playable object from a sharedPreferences file.
- *
- * @param type
- * An integer that represents the type of the Playable object
- * that is restored.
- * @param pref
- * The SharedPreferences file from which the Playable object
- * is restored
- * @return The restored Playable object
- */
- public static Playable createInstanceFromPreferences(int type,
- SharedPreferences pref) {
- // ADD new Playable types here:
- switch (type) {
- case FeedMedia.PLAYABLE_TYPE_FEEDMEDIA:
- long feedId = pref.getLong(FeedMedia.PREF_FEED_ID, -1);
- long mediaId = pref.getLong(FeedMedia.PREF_MEDIA_ID, -1);
- if (feedId != -1 && mediaId != -1) {
- Feed feed = FeedManager.getInstance().getFeed(feedId);
- if (feed != null) {
- return FeedManager.getInstance().getFeedMedia(mediaId,
- feed);
- }
- }
- break;
- case ExternalMedia.PLAYABLE_TYPE_EXTERNAL_MEDIA:
- String source = pref.getString(ExternalMedia.PREF_SOURCE_URL,
- null);
- String mediaType = pref.getString(
- ExternalMedia.PREF_MEDIA_TYPE, null);
- if (source != null && mediaType != null) {
- int position = pref.getInt(ExternalMedia.PREF_POSITION, 0);
- return new ExternalMedia(source,
- MediaType.valueOf(mediaType), position);
- }
- break;
- }
- Log.e(TAG, "Could not restore Playable object from preferences");
- return null;
- }
- }
-
- public static class PlayableException extends Exception {
- private static final long serialVersionUID = 1L;
-
- public PlayableException() {
- super();
- }
-
- public PlayableException(String detailMessage, Throwable throwable) {
- super(detailMessage, throwable);
- }
-
- public PlayableException(String detailMessage) {
- super(detailMessage);
- }
-
- public PlayableException(Throwable throwable) {
- super(throwable);
- }
-
- }
-
- public static interface ShownoteLoaderCallback {
- void onShownotesLoaded(String shownotes);
- }
-
- /** Uses local file as image resource if it is available. */
- public static class DefaultPlayableImageLoader implements
- ImageLoader.ImageWorkerTaskResource {
- private Playable playable;
-
- public DefaultPlayableImageLoader(Playable playable) {
- if (playable == null) {
- throw new IllegalArgumentException("Playable must not be null");
- }
- this.playable = playable;
- }
-
- @Override
- public InputStream openImageInputStream() {
- if (playable.localFileAvailable()
- && playable.getLocalMediaUrl() != null) {
- MediaMetadataRetriever mmr = new MediaMetadataRetriever();
- try {
- mmr.setDataSource(playable.getLocalMediaUrl());
- } catch (IllegalArgumentException e) {
- e.printStackTrace();
- return null;
- }
- byte[] imgData = mmr.getEmbeddedPicture();
- if (imgData != null) {
- return new PublicByteArrayInputStream(imgData);
-
- }
- }
- return null;
- }
-
- @Override
- public String getImageLoaderCacheKey() {
- return playable.getLocalMediaUrl();
- }
-
- @Override
- public InputStream reopenImageInputStream(InputStream input) {
- if (input instanceof PublicByteArrayInputStream) {
- IOUtils.closeQuietly(input);
- byte[] imgData = ((PublicByteArrayInputStream) input)
- .getByteArray();
- if (imgData != null) {
- ByteArrayInputStream out = new ByteArrayInputStream(imgData);
- return out;
- }
-
- }
- return null;
- }
-
- private static class PublicByteArrayInputStream extends
- ByteArrayInputStream {
- public PublicByteArrayInputStream(byte[] buf) {
- super(buf);
- }
-
- public byte[] getByteArray() {
- return buf;
- }
- }
- }
+ ImageLoader.ImageWorkerTaskResource, ShownotesProvider {
+
+ /**
+ * Save information about the playable in a preference so that it can be
+ * restored later via PlayableUtils.createInstanceFromPreferences.
+ * Implementations must NOT call commit() after they have written the values
+ * to the preferences file.
+ */
+ public void writeToPreferences(SharedPreferences.Editor prefEditor);
+
+ /**
+ * This method is called from a separate thread by the PlaybackService.
+ * Playable objects should load their metadata in this method. This method
+ * should execute as quickly as possible and NOT load chapter marks if no
+ * local file is available.
+ */
+ public void loadMetadata() throws PlayableException;
+
+ /**
+ * This method is called from a separate thread by the PlaybackService.
+ * Playable objects should load their chapter marks in this method if no
+ * local file was available when loadMetadata() was called.
+ */
+ public void loadChapterMarks();
+
+ /**
+ * Returns the title of the episode that this playable represents
+ */
+ public String getEpisodeTitle();
+
+ /**
+ * Returns a list of chapter marks or null if this Playable has no chapters.
+ */
+ public List<Chapter> getChapters();
+
+ /**
+ * Returns a link to a website that is meant to be shown in a browser
+ */
+ public String getWebsiteLink();
+
+ public String getPaymentLink();
+
+ /**
+ * Returns the title of the feed this Playable belongs to.
+ */
+ public String getFeedTitle();
+
+ /**
+ * Returns a unique identifier, for example a file url or an ID from a
+ * database.
+ */
+ public Object getIdentifier();
+
+ /**
+ * Return duration of object or 0 if duration is unknown.
+ */
+ public int getDuration();
+
+ /**
+ * Return position of object or 0 if position is unknown.
+ */
+ public int getPosition();
+
+ /**
+ * Returns the type of media. This method should return the correct value
+ * BEFORE loadMetadata() is called.
+ */
+ public MediaType getMediaType();
+
+ /**
+ * Returns an url to a local file that can be played or null if this file
+ * does not exist.
+ */
+ public String getLocalMediaUrl();
+
+ /**
+ * Returns an url to a file that can be streamed by the player or null if
+ * this url is not known.
+ */
+ public String getStreamUrl();
+
+ /**
+ * Returns true if a local file that can be played is available. getFileUrl
+ * MUST return a non-null string if this method returns true.
+ */
+ public boolean localFileAvailable();
+
+ /**
+ * Returns true if a streamable file is available. getStreamUrl MUST return
+ * a non-null string if this method returns true.
+ */
+ public boolean streamAvailable();
+
+ /**
+ * Saves the current position of this object. Implementations can use the
+ * provided SharedPreference to save this information and retrieve it later
+ * via PlayableUtils.createInstanceFromPreferences.
+ */
+ public void saveCurrentPosition(SharedPreferences pref, int newPosition);
+
+ public void setPosition(int newPosition);
+
+ public void setDuration(int newDuration);
+
+ /**
+ * Is called by the PlaybackService when playback starts.
+ */
+ public void onPlaybackStart();
+
+ /**
+ * Is called by the PlaybackService when playback is completed.
+ */
+ public void onPlaybackCompleted();
+
+ /**
+ * Returns an integer that must be unique among all Playable classes. The
+ * return value is later used by PlayableUtils to determine the type of the
+ * Playable object that is restored.
+ */
+ public int getPlayableType();
+
+ public void setChapters(List<Chapter> chapters);
+
+ /**
+ * Provides utility methods for Playable objects.
+ */
+ public static class PlayableUtils {
+ private static final String TAG = "PlayableUtils";
+
+ /**
+ * Restores a playable object from a sharedPreferences file. This method might load data from the database,
+ * depending on the type of playable that was restored.
+ *
+ * @param type An integer that represents the type of the Playable object
+ * that is restored.
+ * @param pref The SharedPreferences file from which the Playable object
+ * is restored
+ * @return The restored Playable object
+ */
+ public static Playable createInstanceFromPreferences(Context context, int type,
+ SharedPreferences pref) {
+ // ADD new Playable types here:
+ switch (type) {
+ case FeedMedia.PLAYABLE_TYPE_FEEDMEDIA:
+ long mediaId = pref.getLong(FeedMedia.PREF_MEDIA_ID, -1);
+ if (mediaId != -1) {
+ return DBReader.getFeedMedia(context, mediaId);
+ }
+ break;
+ case ExternalMedia.PLAYABLE_TYPE_EXTERNAL_MEDIA:
+ String source = pref.getString(ExternalMedia.PREF_SOURCE_URL,
+ null);
+ String mediaType = pref.getString(
+ ExternalMedia.PREF_MEDIA_TYPE, null);
+ if (source != null && mediaType != null) {
+ int position = pref.getInt(ExternalMedia.PREF_POSITION, 0);
+ return new ExternalMedia(source,
+ MediaType.valueOf(mediaType), position);
+ }
+ break;
+ }
+ Log.e(TAG, "Could not restore Playable object from preferences");
+ return null;
+ }
+ }
+
+ public static class PlayableException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public PlayableException() {
+ super();
+ }
+
+ public PlayableException(String detailMessage, Throwable throwable) {
+ super(detailMessage, throwable);
+ }
+
+ public PlayableException(String detailMessage) {
+ super(detailMessage);
+ }
+
+ public PlayableException(Throwable throwable) {
+ super(throwable);
+ }
+
+ }
+
+ /**
+ * Uses local file as image resource if it is available.
+ */
+ public static class DefaultPlayableImageLoader implements
+ ImageLoader.ImageWorkerTaskResource {
+ private Playable playable;
+
+ public DefaultPlayableImageLoader(Playable playable) {
+ if (playable == null) {
+ throw new IllegalArgumentException("Playable must not be null");
+ }
+ this.playable = playable;
+ }
+
+ @Override
+ public InputStream openImageInputStream() {
+ if (playable.localFileAvailable()
+ && playable.getLocalMediaUrl() != null) {
+ MediaMetadataRetriever mmr = new MediaMetadataRetriever();
+ try {
+ mmr.setDataSource(playable.getLocalMediaUrl());
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ return null;
+ }
+ byte[] imgData = mmr.getEmbeddedPicture();
+ if (imgData != null) {
+ return new PublicByteArrayInputStream(imgData);
+
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String getImageLoaderCacheKey() {
+ return playable.getLocalMediaUrl();
+ }
+
+ @Override
+ public InputStream reopenImageInputStream(InputStream input) {
+ if (input instanceof PublicByteArrayInputStream) {
+ IOUtils.closeQuietly(input);
+ byte[] imgData = ((PublicByteArrayInputStream) input)
+ .getByteArray();
+ if (imgData != null) {
+ ByteArrayInputStream out = new ByteArrayInputStream(imgData);
+ return out;
+ }
+
+ }
+ return null;
+ }
+
+ private static class PublicByteArrayInputStream extends
+ ByteArrayInputStream {
+ public PublicByteArrayInputStream(byte[] buf) {
+ super(buf);
+ }
+
+ public byte[] getByteArray() {
+ return buf;
+ }
+ }
+ }
}
diff --git a/src/de/danoeh/antennapod/util/playback/PlaybackController.java b/src/de/danoeh/antennapod/util/playback/PlaybackController.java
index 0060c756f..22c78cc07 100644
--- a/src/de/danoeh/antennapod/util/playback/PlaybackController.java
+++ b/src/de/danoeh/antennapod/util/playback/PlaybackController.java
@@ -16,6 +16,7 @@ import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
+import android.os.AsyncTask;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.util.Log;
@@ -28,11 +29,11 @@ import android.widget.TextView;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.feed.Chapter;
-import de.danoeh.antennapod.feed.FeedManager;
import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.preferences.PlaybackPreferences;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.service.PlayerStatus;
+import de.danoeh.antennapod.storage.DBTasks;
import de.danoeh.antennapod.util.Converter;
import de.danoeh.antennapod.util.playback.Playable.PlayableUtils;
@@ -41,683 +42,703 @@ import de.danoeh.antennapod.util.playback.Playable.PlayableUtils;
* control playback instead of communicating with the PlaybackService directly.
*/
public abstract class PlaybackController {
- private static final String TAG = "PlaybackController";
+ private static final String TAG = "PlaybackController";
- static final int DEFAULT_SEEK_DELTA = 30000;
+ public static final int DEFAULT_SEEK_DELTA = 30000;
public static final int INVALID_TIME = -1;
- private Activity activity;
-
- private PlaybackService playbackService;
- private Playable media;
- private PlayerStatus status;
-
- private ScheduledThreadPoolExecutor schedExecutor;
- private static final int SCHED_EX_POOLSIZE = 1;
-
- protected MediaPositionObserver positionObserver;
- protected ScheduledFuture positionObserverFuture;
-
- private boolean mediaInfoLoaded = false;
- private boolean released = false;
-
- /**
- * True if controller should reinit playback service if 'pause' button is
- * pressed.
- */
- private boolean reinitOnPause;
-
- public PlaybackController(Activity activity, boolean reinitOnPause) {
- this.activity = activity;
- this.reinitOnPause = reinitOnPause;
- schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOLSIZE,
- new ThreadFactory() {
-
- @Override
- public Thread newThread(Runnable r) {
- Thread t = new Thread(r);
- t.setPriority(Thread.MIN_PRIORITY);
- return t;
- }
- }, new RejectedExecutionHandler() {
-
- @Override
- public void rejectedExecution(Runnable r,
- ThreadPoolExecutor executor) {
- Log.w(TAG,
- "Rejected execution of runnable in schedExecutor");
- }
- });
- }
-
- /**
- * Creates a new connection to the playbackService. Should be called in the
- * activity's onResume() method.
- */
- public void init() {
- activity.registerReceiver(statusUpdate, new IntentFilter(
- PlaybackService.ACTION_PLAYER_STATUS_CHANGED));
-
- activity.registerReceiver(notificationReceiver, new IntentFilter(
- PlaybackService.ACTION_PLAYER_NOTIFICATION));
-
- activity.registerReceiver(shutdownReceiver, new IntentFilter(
- PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
-
- if (!released) {
- bindToService();
- } else {
- throw new IllegalStateException(
- "Can't call init() after release() has been called");
- }
- }
-
- /**
- * Should be called if the PlaybackController is no longer needed, for
- * example in the activity's onStop() method.
- */
- public void release() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Releasing PlaybackController");
-
- try {
- activity.unregisterReceiver(statusUpdate);
- } catch (IllegalArgumentException e) {
- // ignore
- }
-
- try {
- activity.unregisterReceiver(notificationReceiver);
- } catch (IllegalArgumentException e) {
- // ignore
- }
-
- try {
- activity.unbindService(mConnection);
- } catch (IllegalArgumentException e) {
- // ignore
- }
-
- try {
- activity.unregisterReceiver(shutdownReceiver);
- } catch (IllegalArgumentException e) {
- // ignore
- }
- cancelPositionObserver();
- schedExecutor.shutdownNow();
- media = null;
- released = true;
-
- }
-
- /** Should be called in the activity's onPause() method. */
- public void pause() {
- mediaInfoLoaded = false;
- if (playbackService != null && playbackService.isPlayingVideo()) {
- playbackService.pause(true, true);
- }
- }
-
- /**
- * Tries to establish a connection to the PlaybackService. If it isn't
- * running, the PlaybackService will be started with the last played media
- * as the arguments of the launch intent.
- */
- private void bindToService() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Trying to connect to service");
- Intent serviceIntent = getPlayLastPlayedMediaIntent();
- boolean bound = false;
- if (!PlaybackService.isRunning) {
- if (serviceIntent != null) {
- activity.startService(serviceIntent);
- bound = activity.bindService(serviceIntent, mConnection, 0);
- } else {
- status = PlayerStatus.STOPPED;
- setupGUI();
- handleStatus();
- }
- } else {
- if (AppConfig.DEBUG)
- Log.d(TAG,
- "PlaybackService is running, trying to connect without start command.");
- bound = activity.bindService(new Intent(activity,
- PlaybackService.class), mConnection, 0);
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "Result for service binding: " + bound);
- }
-
- /**
- * Returns an intent that starts the PlaybackService and plays the last
- * played media or null if no last played media could be found.
- */
- private Intent getPlayLastPlayedMediaIntent() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Trying to restore last played media");
- SharedPreferences prefs = PreferenceManager
- .getDefaultSharedPreferences(activity.getApplicationContext());
- long currentlyPlayingMedia = PlaybackPreferences
- .getCurrentlyPlayingMedia();
- if (currentlyPlayingMedia != PlaybackPreferences.NO_MEDIA_PLAYING) {
- Playable media = PlayableUtils.createInstanceFromPreferences(
- (int) currentlyPlayingMedia, prefs);
- if (media != null) {
- Intent serviceIntent = new Intent(activity,
- PlaybackService.class);
- serviceIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
- serviceIntent.putExtra(
- PlaybackService.EXTRA_START_WHEN_PREPARED, false);
- serviceIntent.putExtra(
- PlaybackService.EXTRA_PREPARE_IMMEDIATELY, false);
- boolean fileExists = media.localFileAvailable();
- boolean lastIsStream = PlaybackPreferences
- .getCurrentEpisodeIsStream();
- if (!fileExists && !lastIsStream && media instanceof FeedMedia) {
- FeedManager.getInstance().notifyMissingFeedMediaFile(
- activity, (FeedMedia) media);
- }
- serviceIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM,
- lastIsStream || !fileExists);
- return serviceIntent;
- }
- }
- if (AppConfig.DEBUG)
- Log.d(TAG, "No last played media found");
- return null;
- }
-
- public abstract void setupGUI();
-
- private void setupPositionObserver() {
- if ((positionObserverFuture != null && positionObserverFuture
- .isCancelled())
- || (positionObserverFuture != null && positionObserverFuture
- .isDone()) || positionObserverFuture == null) {
-
- if (AppConfig.DEBUG)
- Log.d(TAG, "Setting up position observer");
- positionObserver = new MediaPositionObserver();
- positionObserverFuture = schedExecutor.scheduleWithFixedDelay(
- positionObserver, MediaPositionObserver.WAITING_INTERVALL,
- MediaPositionObserver.WAITING_INTERVALL,
- TimeUnit.MILLISECONDS);
- }
- }
-
- private void cancelPositionObserver() {
- if (positionObserverFuture != null) {
- boolean result = positionObserverFuture.cancel(true);
- if (AppConfig.DEBUG)
- Log.d(TAG, "PositionObserver cancelled. Result: " + result);
- }
- }
-
- public abstract void onPositionObserverUpdate();
-
- private ServiceConnection mConnection = new ServiceConnection() {
- public void onServiceConnected(ComponentName className, IBinder service) {
- playbackService = ((PlaybackService.LocalBinder) service)
- .getService();
-
- queryService();
- if (AppConfig.DEBUG)
- Log.d(TAG, "Connection to Service established");
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- playbackService = null;
- if (AppConfig.DEBUG)
- Log.d(TAG, "Disconnected from Service");
-
- }
- };
-
- protected BroadcastReceiver statusUpdate = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Received statusUpdate Intent.");
- if (isConnectedToPlaybackService()) {
- status = playbackService.getStatus();
- handleStatus();
- } else {
- Log.w(TAG,
- "Couldn't receive status update: playbackService was null");
- bindToService();
- }
- }
- };
-
- protected BroadcastReceiver notificationReceiver = new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (isConnectedToPlaybackService()) {
- int type = intent.getIntExtra(
- PlaybackService.EXTRA_NOTIFICATION_TYPE, -1);
- int code = intent.getIntExtra(
- PlaybackService.EXTRA_NOTIFICATION_CODE, -1);
- if (code != -1 && type != -1) {
- switch (type) {
- case PlaybackService.NOTIFICATION_TYPE_ERROR:
- handleError(code);
- break;
- case PlaybackService.NOTIFICATION_TYPE_BUFFER_UPDATE:
- float progress = ((float) code) / 100;
- onBufferUpdate(progress);
- break;
- case PlaybackService.NOTIFICATION_TYPE_RELOAD:
- cancelPositionObserver();
- mediaInfoLoaded = false;
- onReloadNotification(intent.getIntExtra(
- PlaybackService.EXTRA_NOTIFICATION_CODE, -1));
- queryService();
-
- break;
- case PlaybackService.NOTIFICATION_TYPE_SLEEPTIMER_UPDATE:
- onSleepTimerUpdate();
- break;
- case PlaybackService.NOTIFICATION_TYPE_BUFFER_START:
- onBufferStart();
- break;
- case PlaybackService.NOTIFICATION_TYPE_BUFFER_END:
- onBufferEnd();
- break;
- case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_END:
- onPlaybackEnd();
- case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE:
- onPlaybackSpeedChange();
- break;
- }
-
- } else {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Bad arguments. Won't handle intent");
- }
- } else {
- bindToService();
- }
- }
-
- };
-
- private BroadcastReceiver shutdownReceiver = new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (isConnectedToPlaybackService()) {
- if (intent.getAction().equals(
- PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)) {
- release();
- onShutdownNotification();
- }
- }
- }
- };
-
- public abstract void onPlaybackSpeedChange();
-
- public abstract void onShutdownNotification();
-
- /** Called when the currently displayed information should be refreshed. */
- public abstract void onReloadNotification(int code);
-
- public abstract void onBufferStart();
-
- public abstract void onBufferEnd();
-
- public abstract void onBufferUpdate(float progress);
-
- public abstract void onSleepTimerUpdate();
-
- public abstract void handleError(int code);
-
- public abstract void onPlaybackEnd();
-
- /**
- * Is called whenever the PlaybackService changes it's status. This method
- * should be used to update the GUI or start/cancel background threads.
- */
- private void handleStatus() {
- TypedArray res = activity.obtainStyledAttributes(new int[] {
- R.attr.av_play, R.attr.av_pause });
- final int playResource = res.getResourceId(0, R.drawable.av_play);
- final int pauseResource = res.getResourceId(1, R.drawable.av_pause);
- res.recycle();
-
- switch (status) {
-
- case ERROR:
- postStatusMsg(R.string.player_error_msg);
- break;
- case PAUSED:
- clearStatusMsg();
- checkMediaInfoLoaded();
- cancelPositionObserver();
- updatePlayButtonAppearance(playResource);
- break;
- case PLAYING:
- clearStatusMsg();
- checkMediaInfoLoaded();
- setupPositionObserver();
- updatePlayButtonAppearance(pauseResource);
- break;
- case PREPARING:
- postStatusMsg(R.string.player_preparing_msg);
- checkMediaInfoLoaded();
- if (playbackService != null) {
- if (playbackService.isStartWhenPrepared()) {
- updatePlayButtonAppearance(pauseResource);
- } else {
- updatePlayButtonAppearance(playResource);
- }
- }
- break;
- case STOPPED:
- postStatusMsg(R.string.player_stopped_msg);
- break;
- case PREPARED:
- checkMediaInfoLoaded();
- postStatusMsg(R.string.player_ready_msg);
- updatePlayButtonAppearance(playResource);
- break;
- case SEEKING:
- postStatusMsg(R.string.player_seeking_msg);
- break;
- case AWAITING_VIDEO_SURFACE:
- onAwaitingVideoSurface();
- break;
- case INITIALIZED:
- checkMediaInfoLoaded();
- clearStatusMsg();
- updatePlayButtonAppearance(playResource);
- break;
- }
- }
-
- private void checkMediaInfoLoaded() {
- if (!mediaInfoLoaded) {
- loadMediaInfo();
- }
- mediaInfoLoaded = true;
- }
-
- private void updatePlayButtonAppearance(int resource) {
- ImageButton butPlay = getPlayButton();
- butPlay.setImageResource(resource);
- }
-
- public abstract ImageButton getPlayButton();
-
- public abstract void postStatusMsg(int msg);
-
- public abstract void clearStatusMsg();
-
- public abstract void loadMediaInfo();
-
- public abstract void onAwaitingVideoSurface();
-
- /**
- * Called when connection to playback service has been established or
- * information has to be refreshed
- */
- void queryService() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Querying service info");
- if (playbackService != null) {
- status = playbackService.getStatus();
- media = playbackService.getMedia();
- if (media == null) {
- Log.w(TAG,
- "PlaybackService has no media object. Trying to restore last played media.");
- Intent serviceIntent = getPlayLastPlayedMediaIntent();
- if (serviceIntent != null) {
- activity.startService(serviceIntent);
- }
- }
- onServiceQueried();
-
- setupGUI();
- handleStatus();
- // make sure that new media is loaded if it's available
- mediaInfoLoaded = false;
-
- } else {
- Log.e(TAG,
- "queryService() was called without an existing connection to playbackservice");
- }
- }
-
- public abstract void onServiceQueried();
-
- /**
- * Should be used by classes which implement the OnSeekBarChanged interface.
- */
- public float onSeekBarProgressChanged(SeekBar seekBar, int progress,
- boolean fromUser, TextView txtvPosition) {
- if (fromUser && playbackService != null) {
- float prog = progress / ((float) seekBar.getMax());
- int duration = media.getDuration();
- txtvPosition.setText(Converter
- .getDurationStringLong((int) (prog * duration)));
- return prog;
- }
- return 0;
-
- }
-
- /**
- * Should be used by classes which implement the OnSeekBarChanged interface.
- */
- public void onSeekBarStartTrackingTouch(SeekBar seekBar) {
- // interrupt position Observer, restart later
- cancelPositionObserver();
- }
-
- /**
- * Should be used by classes which implement the OnSeekBarChanged interface.
- */
- public void onSeekBarStopTrackingTouch(SeekBar seekBar, float prog) {
- if (playbackService != null) {
- playbackService.seek((int) (prog * media.getDuration()));
- setupPositionObserver();
- }
- }
-
- public OnClickListener newOnPlayButtonClickListener() {
- return new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (playbackService != null) {
- switch (status) {
- case PLAYING:
- playbackService.pause(true, reinitOnPause);
- break;
- case PAUSED:
- case PREPARED:
- playbackService.play();
- break;
- case PREPARING:
- playbackService.setStartWhenPrepared(!playbackService
- .isStartWhenPrepared());
- if (reinitOnPause
- && playbackService.isStartWhenPrepared() == false) {
- playbackService.reinit();
- }
- break;
- case INITIALIZED:
- playbackService.setStartWhenPrepared(true);
- playbackService.prepare();
- break;
- }
- } else {
- Log.w(TAG,
- "Play/Pause button was pressed, but playbackservice was null!");
- }
- }
-
- };
- }
-
- public OnClickListener newOnRevButtonClickListener() {
- return new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (status == PlayerStatus.PLAYING) {
- playbackService.seekDelta(-DEFAULT_SEEK_DELTA);
- }
- }
- };
- }
-
- public OnClickListener newOnFFButtonClickListener() {
- return new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (status == PlayerStatus.PLAYING) {
- playbackService.seekDelta(DEFAULT_SEEK_DELTA);
- }
- }
- };
- }
-
- public boolean serviceAvailable() {
- return playbackService != null;
- }
-
- public int getPosition() {
- if (playbackService != null) {
- return playbackService.getCurrentPositionSafe();
- } else {
- return PlaybackService.INVALID_TIME;
- }
- }
-
- public int getDuration() {
- if (playbackService != null) {
- return playbackService.getDurationSafe();
- } else {
- return PlaybackService.INVALID_TIME;
- }
- }
-
- public Playable getMedia() {
- return media;
- }
-
- public boolean sleepTimerActive() {
- return playbackService != null && playbackService.sleepTimerActive();
- }
-
- public boolean sleepTimerNotActive() {
- return playbackService != null && !playbackService.sleepTimerActive();
- }
-
- public void disableSleepTimer() {
- if (playbackService != null) {
- playbackService.disableSleepTimer();
- }
- }
-
- public long getSleepTimerTimeLeft() {
- if (playbackService != null) {
- return playbackService.getSleepTimerTimeLeft();
- } else {
- return INVALID_TIME;
- }
- }
-
- public void setSleepTimer(long time) {
- if (playbackService != null) {
- playbackService.setSleepTimer(time);
- }
- }
-
- public void seekToChapter(Chapter chapter) {
- if (playbackService != null) {
- playbackService.seekToChapter(chapter);
- }
- }
-
- public void setVideoSurface(SurfaceHolder holder) {
- if (playbackService != null) {
- playbackService.setVideoSurface(holder);
- }
- }
-
- public PlayerStatus getStatus() {
- return status;
- }
-
- public boolean isPlayingVideo() {
- if (playbackService != null) {
- return PlaybackService.isPlayingVideo();
- }
- return false;
- }
-
- public boolean canSetPlaybackSpeed() {
- return playbackService != null && playbackService.canSetSpeed();
- }
-
- public void setPlaybackSpeed(double speed) {
- if (playbackService != null) {
- playbackService.setSpeed(speed);
- }
- }
-
- public double getCurrentPlaybackSpeedMultiplier() {
- if (canSetPlaybackSpeed()) {
- return playbackService.getCurrentPlaybackSpeed();
- } else {
- return -1;
- }
- }
-
- /**
- * Returns true if PlaybackController can communicate with the playback
- * service.
- */
- public boolean isConnectedToPlaybackService() {
- return playbackService != null;
- }
-
- public void notifyVideoSurfaceAbandoned() {
- if (playbackService != null) {
- playbackService.notifyVideoSurfaceAbandoned();
- }
- }
-
- /** Move service into INITIALIZED state if it's paused to save bandwidth */
- public void reinitServiceIfPaused() {
- if (playbackService != null
- && playbackService.isShouldStream()
- && (playbackService.getStatus() == PlayerStatus.PAUSED || (playbackService
- .getStatus() == PlayerStatus.PREPARING && playbackService
- .isStartWhenPrepared() == false))) {
- playbackService.reinit();
- }
- }
-
- /** Refreshes the current position of the media file that is playing. */
- public class MediaPositionObserver implements Runnable {
-
- public static final int WAITING_INTERVALL = 1000;
-
- @Override
- public void run() {
- if (playbackService != null && playbackService.getPlayer() != null
- && playbackService.getPlayer().isPlaying()) {
- activity.runOnUiThread(new Runnable() {
-
- @Override
- public void run() {
- onPositionObserverUpdate();
- }
- });
- }
- }
- }
+ private Activity activity;
+
+ private PlaybackService playbackService;
+ private Playable media;
+ private PlayerStatus status;
+
+ private ScheduledThreadPoolExecutor schedExecutor;
+ private static final int SCHED_EX_POOLSIZE = 1;
+
+ protected MediaPositionObserver positionObserver;
+ protected ScheduledFuture positionObserverFuture;
+
+ private boolean mediaInfoLoaded = false;
+ private boolean released = false;
+
+ /**
+ * True if controller should reinit playback service if 'pause' button is
+ * pressed.
+ */
+ private boolean reinitOnPause;
+
+ public PlaybackController(Activity activity, boolean reinitOnPause) {
+ this.activity = activity;
+ this.reinitOnPause = reinitOnPause;
+ schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOLSIZE,
+ new ThreadFactory() {
+
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r);
+ t.setPriority(Thread.MIN_PRIORITY);
+ return t;
+ }
+ }, new RejectedExecutionHandler() {
+
+ @Override
+ public void rejectedExecution(Runnable r,
+ ThreadPoolExecutor executor) {
+ Log.w(TAG,
+ "Rejected execution of runnable in schedExecutor");
+ }
+ }
+ );
+ }
+
+ /**
+ * Creates a new connection to the playbackService. Should be called in the
+ * activity's onResume() method.
+ */
+ public void init() {
+ activity.registerReceiver(statusUpdate, new IntentFilter(
+ PlaybackService.ACTION_PLAYER_STATUS_CHANGED));
+
+ activity.registerReceiver(notificationReceiver, new IntentFilter(
+ PlaybackService.ACTION_PLAYER_NOTIFICATION));
+
+ activity.registerReceiver(shutdownReceiver, new IntentFilter(
+ PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE));
+
+ if (!released) {
+ bindToService();
+ } else {
+ throw new IllegalStateException(
+ "Can't call init() after release() has been called");
+ }
+ }
+
+ /**
+ * Should be called if the PlaybackController is no longer needed, for
+ * example in the activity's onStop() method.
+ */
+ public void release() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Releasing PlaybackController");
+
+ try {
+ activity.unregisterReceiver(statusUpdate);
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+
+ try {
+ activity.unregisterReceiver(notificationReceiver);
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+
+ try {
+ activity.unbindService(mConnection);
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+
+ try {
+ activity.unregisterReceiver(shutdownReceiver);
+ } catch (IllegalArgumentException e) {
+ // ignore
+ }
+ cancelPositionObserver();
+ schedExecutor.shutdownNow();
+ media = null;
+ released = true;
+
+ }
+
+ /**
+ * Should be called in the activity's onPause() method.
+ */
+ public void pause() {
+ mediaInfoLoaded = false;
+ if (playbackService != null && playbackService.isPlayingVideo()) {
+ playbackService.pause(true, true);
+ }
+ }
+
+ /**
+ * Tries to establish a connection to the PlaybackService. If it isn't
+ * running, the PlaybackService will be started with the last played media
+ * as the arguments of the launch intent.
+ */
+ private void bindToService() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Trying to connect to service");
+ AsyncTask<Void, Void, Intent> intentLoader = new AsyncTask<Void, Void, Intent>() {
+ @Override
+ protected Intent doInBackground(Void... voids) {
+ return getPlayLastPlayedMediaIntent();
+ }
+
+ @Override
+ protected void onPostExecute(Intent serviceIntent) {
+ boolean bound = false;
+ if (!PlaybackService.isRunning) {
+ if (serviceIntent != null) {
+ activity.startService(serviceIntent);
+ bound = activity.bindService(serviceIntent, mConnection, 0);
+ } else {
+ status = PlayerStatus.STOPPED;
+ setupGUI();
+ handleStatus();
+ }
+ } else {
+ if (AppConfig.DEBUG)
+ Log.d(TAG,
+ "PlaybackService is running, trying to connect without start command.");
+ bound = activity.bindService(new Intent(activity,
+ PlaybackService.class), mConnection, 0);
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Result for service binding: " + bound);
+ }
+ };
+ intentLoader.execute();
+ }
+
+ /**
+ * Returns an intent that starts the PlaybackService and plays the last
+ * played media or null if no last played media could be found.
+ */
+ private Intent getPlayLastPlayedMediaIntent() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Trying to restore last played media");
+ SharedPreferences prefs = PreferenceManager
+ .getDefaultSharedPreferences(activity.getApplicationContext());
+ long currentlyPlayingMedia = PlaybackPreferences
+ .getCurrentlyPlayingMedia();
+ if (currentlyPlayingMedia != PlaybackPreferences.NO_MEDIA_PLAYING) {
+ Playable media = PlayableUtils.createInstanceFromPreferences(activity,
+ (int) currentlyPlayingMedia, prefs);
+ if (media != null) {
+ Intent serviceIntent = new Intent(activity,
+ PlaybackService.class);
+ serviceIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
+ serviceIntent.putExtra(
+ PlaybackService.EXTRA_START_WHEN_PREPARED, false);
+ serviceIntent.putExtra(
+ PlaybackService.EXTRA_PREPARE_IMMEDIATELY, false);
+ boolean fileExists = media.localFileAvailable();
+ boolean lastIsStream = PlaybackPreferences
+ .getCurrentEpisodeIsStream();
+ if (!fileExists && !lastIsStream && media instanceof FeedMedia) {
+ DBTasks.notifyMissingFeedMediaFile(
+ activity, (FeedMedia) media);
+ }
+ serviceIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM,
+ lastIsStream || !fileExists);
+ return serviceIntent;
+ }
+ }
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "No last played media found");
+ return null;
+ }
+
+ public abstract void setupGUI();
+
+ private void setupPositionObserver() {
+ if ((positionObserverFuture != null && positionObserverFuture
+ .isCancelled())
+ || (positionObserverFuture != null && positionObserverFuture
+ .isDone()) || positionObserverFuture == null) {
+
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Setting up position observer");
+ positionObserver = new MediaPositionObserver();
+ positionObserverFuture = schedExecutor.scheduleWithFixedDelay(
+ positionObserver, MediaPositionObserver.WAITING_INTERVALL,
+ MediaPositionObserver.WAITING_INTERVALL,
+ TimeUnit.MILLISECONDS);
+ }
+ }
+
+ private void cancelPositionObserver() {
+ if (positionObserverFuture != null) {
+ boolean result = positionObserverFuture.cancel(true);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "PositionObserver cancelled. Result: " + result);
+ }
+ }
+
+ public abstract void onPositionObserverUpdate();
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ playbackService = ((PlaybackService.LocalBinder) service)
+ .getService();
+
+ queryService();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Connection to Service established");
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ playbackService = null;
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Disconnected from Service");
+
+ }
+ };
+
+ protected BroadcastReceiver statusUpdate = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Received statusUpdate Intent.");
+ if (isConnectedToPlaybackService()) {
+ status = playbackService.getStatus();
+ handleStatus();
+ } else {
+ Log.w(TAG,
+ "Couldn't receive status update: playbackService was null");
+ bindToService();
+ }
+ }
+ };
+
+ protected BroadcastReceiver notificationReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (isConnectedToPlaybackService()) {
+ int type = intent.getIntExtra(
+ PlaybackService.EXTRA_NOTIFICATION_TYPE, -1);
+ int code = intent.getIntExtra(
+ PlaybackService.EXTRA_NOTIFICATION_CODE, -1);
+ if (code != -1 && type != -1) {
+ switch (type) {
+ case PlaybackService.NOTIFICATION_TYPE_ERROR:
+ handleError(code);
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_BUFFER_UPDATE:
+ float progress = ((float) code) / 100;
+ onBufferUpdate(progress);
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_RELOAD:
+ cancelPositionObserver();
+ mediaInfoLoaded = false;
+ onReloadNotification(intent.getIntExtra(
+ PlaybackService.EXTRA_NOTIFICATION_CODE, -1));
+ queryService();
+
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_SLEEPTIMER_UPDATE:
+ onSleepTimerUpdate();
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_BUFFER_START:
+ onBufferStart();
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_BUFFER_END:
+ onBufferEnd();
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_END:
+ onPlaybackEnd();
+ break;
+ case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE:
+ onPlaybackSpeedChange();
+ break;
+ }
+
+ } else {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Bad arguments. Won't handle intent");
+ }
+ } else {
+ bindToService();
+ }
+ }
+
+ };
+
+ private BroadcastReceiver shutdownReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (isConnectedToPlaybackService()) {
+ if (intent.getAction().equals(
+ PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)) {
+ release();
+ onShutdownNotification();
+ }
+ }
+ }
+ };
+
+ public abstract void onPlaybackSpeedChange();
+
+ public abstract void onShutdownNotification();
+
+ /**
+ * Called when the currently displayed information should be refreshed.
+ */
+ public abstract void onReloadNotification(int code);
+
+ public abstract void onBufferStart();
+
+ public abstract void onBufferEnd();
+
+ public abstract void onBufferUpdate(float progress);
+
+ public abstract void onSleepTimerUpdate();
+
+ public abstract void handleError(int code);
+
+ public abstract void onPlaybackEnd();
+
+ /**
+ * Is called whenever the PlaybackService changes it's status. This method
+ * should be used to update the GUI or start/cancel background threads.
+ */
+ private void handleStatus() {
+ TypedArray res = activity.obtainStyledAttributes(new int[]{
+ R.attr.av_play, R.attr.av_pause});
+ final int playResource = res.getResourceId(0, R.drawable.av_play);
+ final int pauseResource = res.getResourceId(1, R.drawable.av_pause);
+ res.recycle();
+
+ switch (status) {
+
+ case ERROR:
+ postStatusMsg(R.string.player_error_msg);
+ break;
+ case PAUSED:
+ clearStatusMsg();
+ checkMediaInfoLoaded();
+ cancelPositionObserver();
+ updatePlayButtonAppearance(playResource);
+ break;
+ case PLAYING:
+ clearStatusMsg();
+ checkMediaInfoLoaded();
+ setupPositionObserver();
+ updatePlayButtonAppearance(pauseResource);
+ break;
+ case PREPARING:
+ postStatusMsg(R.string.player_preparing_msg);
+ checkMediaInfoLoaded();
+ if (playbackService != null) {
+ if (playbackService.isStartWhenPrepared()) {
+ updatePlayButtonAppearance(pauseResource);
+ } else {
+ updatePlayButtonAppearance(playResource);
+ }
+ }
+ break;
+ case STOPPED:
+ postStatusMsg(R.string.player_stopped_msg);
+ break;
+ case PREPARED:
+ checkMediaInfoLoaded();
+ postStatusMsg(R.string.player_ready_msg);
+ updatePlayButtonAppearance(playResource);
+ break;
+ case SEEKING:
+ postStatusMsg(R.string.player_seeking_msg);
+ break;
+ case AWAITING_VIDEO_SURFACE:
+ onAwaitingVideoSurface();
+ break;
+ case INITIALIZED:
+ checkMediaInfoLoaded();
+ clearStatusMsg();
+ updatePlayButtonAppearance(playResource);
+ break;
+ }
+ }
+
+ private void checkMediaInfoLoaded() {
+ if (!mediaInfoLoaded) {
+ loadMediaInfo();
+ }
+ mediaInfoLoaded = true;
+ }
+
+ private void updatePlayButtonAppearance(int resource) {
+ ImageButton butPlay = getPlayButton();
+ butPlay.setImageResource(resource);
+ }
+
+ public abstract ImageButton getPlayButton();
+
+ public abstract void postStatusMsg(int msg);
+
+ public abstract void clearStatusMsg();
+
+ public abstract void loadMediaInfo();
+
+ public abstract void onAwaitingVideoSurface();
+
+ /**
+ * Called when connection to playback service has been established or
+ * information has to be refreshed
+ */
+ void queryService() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Querying service info");
+ if (playbackService != null) {
+ status = playbackService.getStatus();
+ media = playbackService.getMedia();
+ if (media == null) {
+ Log.w(TAG,
+ "PlaybackService has no media object. Trying to restore last played media.");
+ Intent serviceIntent = getPlayLastPlayedMediaIntent();
+ if (serviceIntent != null) {
+ activity.startService(serviceIntent);
+ }
+ }
+ onServiceQueried();
+
+ setupGUI();
+ handleStatus();
+ // make sure that new media is loaded if it's available
+ mediaInfoLoaded = false;
+
+ } else {
+ Log.e(TAG,
+ "queryService() was called without an existing connection to playbackservice");
+ }
+ }
+
+ public abstract void onServiceQueried();
+
+ /**
+ * Should be used by classes which implement the OnSeekBarChanged interface.
+ */
+ public float onSeekBarProgressChanged(SeekBar seekBar, int progress,
+ boolean fromUser, TextView txtvPosition) {
+ if (fromUser && playbackService != null) {
+ float prog = progress / ((float) seekBar.getMax());
+ int duration = media.getDuration();
+ txtvPosition.setText(Converter
+ .getDurationStringLong((int) (prog * duration)));
+ return prog;
+ }
+ return 0;
+
+ }
+
+ /**
+ * Should be used by classes which implement the OnSeekBarChanged interface.
+ */
+ public void onSeekBarStartTrackingTouch(SeekBar seekBar) {
+ // interrupt position Observer, restart later
+ cancelPositionObserver();
+ }
+
+ /**
+ * Should be used by classes which implement the OnSeekBarChanged interface.
+ */
+ public void onSeekBarStopTrackingTouch(SeekBar seekBar, float prog) {
+ if (playbackService != null) {
+ playbackService.seek((int) (prog * media.getDuration()));
+ setupPositionObserver();
+ }
+ }
+
+ public OnClickListener newOnPlayButtonClickListener() {
+ return new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (playbackService != null) {
+ switch (status) {
+ case PLAYING:
+ playbackService.pause(true, reinitOnPause);
+ break;
+ case PAUSED:
+ case PREPARED:
+ playbackService.play();
+ break;
+ case PREPARING:
+ playbackService.setStartWhenPrepared(!playbackService
+ .isStartWhenPrepared());
+ if (reinitOnPause
+ && playbackService.isStartWhenPrepared() == false) {
+ playbackService.reinit();
+ }
+ break;
+ case INITIALIZED:
+ playbackService.setStartWhenPrepared(true);
+ playbackService.prepare();
+ break;
+ }
+ } else {
+ Log.w(TAG,
+ "Play/Pause button was pressed, but playbackservice was null!");
+ }
+ }
+
+ };
+ }
+
+ public OnClickListener newOnRevButtonClickListener() {
+ return new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (status == PlayerStatus.PLAYING) {
+ playbackService.seekDelta(-DEFAULT_SEEK_DELTA);
+ }
+ }
+ };
+ }
+
+ public OnClickListener newOnFFButtonClickListener() {
+ return new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (status == PlayerStatus.PLAYING) {
+ playbackService.seekDelta(DEFAULT_SEEK_DELTA);
+ }
+ }
+ };
+ }
+
+ public boolean serviceAvailable() {
+ return playbackService != null;
+ }
+
+ public int getPosition() {
+ if (playbackService != null) {
+ return playbackService.getCurrentPositionSafe();
+ } else {
+ return PlaybackService.INVALID_TIME;
+ }
+ }
+
+ public int getDuration() {
+ if (playbackService != null) {
+ return playbackService.getDurationSafe();
+ } else {
+ return PlaybackService.INVALID_TIME;
+ }
+ }
+
+ public Playable getMedia() {
+ return media;
+ }
+
+ public boolean sleepTimerActive() {
+ return playbackService != null && playbackService.sleepTimerActive();
+ }
+
+ public boolean sleepTimerNotActive() {
+ return playbackService != null && !playbackService.sleepTimerActive();
+ }
+
+ public void disableSleepTimer() {
+ if (playbackService != null) {
+ playbackService.disableSleepTimer();
+ }
+ }
+
+ public long getSleepTimerTimeLeft() {
+ if (playbackService != null) {
+ return playbackService.getSleepTimerTimeLeft();
+ } else {
+ return INVALID_TIME;
+ }
+ }
+
+ public void setSleepTimer(long time) {
+ if (playbackService != null) {
+ playbackService.setSleepTimer(time);
+ }
+ }
+
+ public void seekToChapter(Chapter chapter) {
+ if (playbackService != null) {
+ playbackService.seekToChapter(chapter);
+ }
+ }
+
+ public void setVideoSurface(SurfaceHolder holder) {
+ if (playbackService != null) {
+ playbackService.setVideoSurface(holder);
+ }
+ }
+
+ public PlayerStatus getStatus() {
+ return status;
+ }
+
+ public boolean isPlayingVideo() {
+ if (playbackService != null) {
+ return PlaybackService.isPlayingVideo();
+ }
+ return false;
+ }
+
+ public boolean canSetPlaybackSpeed() {
+ return playbackService != null && playbackService.canSetSpeed();
+ }
+
+ public void setPlaybackSpeed(double speed) {
+ if (playbackService != null) {
+ playbackService.setSpeed(speed);
+ }
+ }
+
+ public double getCurrentPlaybackSpeedMultiplier() {
+ if (canSetPlaybackSpeed()) {
+ return playbackService.getCurrentPlaybackSpeed();
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Returns true if PlaybackController can communicate with the playback
+ * service.
+ */
+ public boolean isConnectedToPlaybackService() {
+ return playbackService != null;
+ }
+
+ public void notifyVideoSurfaceAbandoned() {
+ if (playbackService != null) {
+ playbackService.notifyVideoSurfaceAbandoned();
+ }
+ }
+
+ /**
+ * Move service into INITIALIZED state if it's paused to save bandwidth
+ */
+ public void reinitServiceIfPaused() {
+ if (playbackService != null
+ && playbackService.isShouldStream()
+ && (playbackService.getStatus() == PlayerStatus.PAUSED || (playbackService
+ .getStatus() == PlayerStatus.PREPARING && playbackService
+ .isStartWhenPrepared() == false))) {
+ playbackService.reinit();
+ }
+ }
+
+ /**
+ * Refreshes the current position of the media file that is playing.
+ */
+ public class MediaPositionObserver implements Runnable {
+
+ public static final int WAITING_INTERVALL = 1000;
+
+ @Override
+ public void run() {
+ if (playbackService != null && playbackService.getPlayer() != null
+ && playbackService.getPlayer().isPlaying()) {
+ activity.runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ onPositionObserverUpdate();
+ }
+ });
+ }
+ }
+ }
}
diff --git a/src/instrumentationTest/de/test/antennapod/AntennaPodTestRunner.java b/src/instrumentationTest/de/test/antennapod/AntennaPodTestRunner.java
new file mode 100644
index 000000000..7aaa14909
--- /dev/null
+++ b/src/instrumentationTest/de/test/antennapod/AntennaPodTestRunner.java
@@ -0,0 +1,18 @@
+package instrumentationTest.de.test.antennapod;
+
+import android.test.InstrumentationTestRunner;
+import android.test.suitebuilder.TestSuiteBuilder;
+import android.util.Log;
+
+import instrumentationTest.de.test.antennapod.service.download.HttpDownloaderTest;
+import instrumentationTest.de.test.antennapod.util.FilenameGeneratorTest;
+import junit.framework.TestSuite;
+
+public class AntennaPodTestRunner extends InstrumentationTestRunner {
+
+ @Override
+ public TestSuite getAllTests() {
+ return new TestSuiteBuilder(AntennaPodTestRunner.class).includeAllPackagesUnderHere().build();
+ //return new TestSuiteBuilder(AntennaPodTestRunner.class).includeAllPackagesUnderHere().excludePackages("instrumentationTest.de.test.antennapod.syndication.handler").build();
+ }
+}
diff --git a/src/instrumentationTest/de/test/antennapod/service/download/HttpDownloaderTest.java b/src/instrumentationTest/de/test/antennapod/service/download/HttpDownloaderTest.java
new file mode 100644
index 000000000..2cd5734d5
--- /dev/null
+++ b/src/instrumentationTest/de/test/antennapod/service/download/HttpDownloaderTest.java
@@ -0,0 +1,144 @@
+package instrumentationTest.de.test.antennapod.service.download;
+
+import java.io.File;
+
+import de.danoeh.antennapod.feed.FeedFile;
+import de.danoeh.antennapod.service.download.*;
+
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+public class HttpDownloaderTest extends AndroidTestCase {
+ private static final String TAG = "HttpDownloaderTest";
+ private static final String DOWNLOAD_DIR = "testdownloads";
+
+ private static boolean successful = true;
+
+ public HttpDownloaderTest() {
+ super();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ File externalDir = getContext().getExternalFilesDir(DOWNLOAD_DIR);
+ assertNotNull(externalDir);
+ File[] contents = externalDir.listFiles();
+ for (File f : contents) {
+ assertTrue(f.delete());
+ }
+ }
+
+ private FeedFileImpl setupFeedFile(String downloadUrl, String title) {
+ FeedFileImpl feedfile = new FeedFileImpl(downloadUrl);
+ String fileUrl = new File(getContext().getExternalFilesDir(DOWNLOAD_DIR).getAbsolutePath(), title).getAbsolutePath();
+ File file = new File(fileUrl);
+ Log.d(TAG, "Deleting file: " + file.delete());
+ feedfile.setFile_url(fileUrl);
+ return feedfile;
+ }
+
+ private void download(String url, String title, boolean expectedResult) {
+ FeedFile feedFile = setupFeedFile(url, title);
+ DownloadRequest request = new DownloadRequest(feedFile.getFile_url(), url, title, 0, feedFile.getTypeAsInt());
+ Downloader downloader = new HttpDownloader(request);
+ downloader.call();
+ DownloadStatus status = downloader.getResult();
+ assertNotNull(status);
+ assertTrue(status.isSuccessful() == expectedResult);
+ assertTrue(status.isDone());
+ assertTrue(new File(feedFile.getFile_url()).exists());
+ }
+
+ public void testRandomUrls() {
+ final String[] urls = {
+ "http://radiobox.omroep.nl/programme/read_programme_podcast/9168/read.rss",
+ "http://content.zdf.de/podcast/zdf_heute/heute_a.xml",
+ "http://rss.sciam.com/sciam/60secsciencepodcast",
+ "http://rss.sciam.com/sciam/60-second-mind",
+ "http://rss.sciam.com/sciam/60-second-space",
+ "http://rss.sciam.com/sciam/60-second-health",
+ "http://rss.sciam.com/sciam/60-second-tech",
+ "http://risky.biz/feeds/risky-business",
+ "http://risky.biz/feeds/rb2",
+ "http://podcast.hr-online.de/lateline/podcast.xml",
+ "http://bitlove.org/nsemak/mikrodilettanten/feed",
+ "http://bitlove.org/moepmoeporg/riotburnz/feed",
+ "http://bitlove.org/moepmoeporg/schachcast/feed",
+ "http://bitlove.org/moepmoeporg/sundaymoaning/feed",
+ "http://bitlove.org/motofunk/anekdotkast/feed",
+ "http://bitlove.org/motofunk/motofunk/feed",
+ "http://bitlove.org/nerdinand/zch/feed",
+ "http://podcast.homerj.de/podcasts.xml",
+ "http://www.dradio.de/rss/podcast/sendungen/wissenschaftundbildung/",
+ "http://www.dradio.de/rss/podcast/sendungen/wirtschaftundverbraucher/",
+ "http://www.dradio.de/rss/podcast/sendungen/literatur/",
+ "http://www.dradio.de/rss/podcast/sendungen/sport/",
+ "http://www.dradio.de/rss/podcast/sendungen/wirtschaftundgesellschaft/",
+ "http://www.dradio.de/rss/podcast/sendungen/filmederwoche/",
+ "http://www.blacksweetstories.com/feed/podcast/",
+ "http://feeds.5by5.tv/buildanalyze",
+ "http://bitlove.org/ranzzeit/ranz/feed"
+ };
+ for (int i = 0; i < urls.length; i++) {
+ download(urls[i], Integer.toString(i), true);
+ }
+ }
+
+ public void testRedirect() {
+ download("http://httpbin.org/redirect/4", "testRedirect", true);
+ }
+
+ public void testRelativeRedirect() {
+ download("http://httpbin.org/relative-redirect/4", "testRelativeRedirect", true);
+ }
+
+ public void testGzip() {
+ download("http://httpbin.org/gzip", "testGzip", true);
+ }
+
+ public void test404() {
+ download("http://httpbin.org/status/404", "test404", false);
+ }
+
+ public void testCancel() {
+ final String url = "http://httpbin.org/delay/3";
+ FeedFileImpl feedFile = setupFeedFile(url, "delay");
+ final Downloader downloader = new HttpDownloader(new DownloadRequest(feedFile.getFile_url(), url, "delay", 0, feedFile.getTypeAsInt()));
+ Thread t = new Thread() {
+ @Override
+ public void run() {
+ downloader.call();
+ }
+ };
+ downloader.cancel();
+ try {
+ t.join();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ DownloadStatus result = downloader.getResult();
+ assertTrue(result.isDone());
+ assertFalse(result.isSuccessful());
+ assertTrue(result.isCancelled());
+ assertFalse(new File(feedFile.getFile_url()).exists());
+ }
+
+ private static class FeedFileImpl extends FeedFile {
+ public FeedFileImpl(String download_url) {
+ super(null, download_url, false);
+ }
+
+
+ @Override
+ public String getHumanReadableIdentifier() {
+ return download_url;
+ }
+
+ @Override
+ public int getTypeAsInt() {
+ return 0;
+ }
+ }
+
+}
diff --git a/src/instrumentationTest/de/test/antennapod/storage/DBReaderTest.java b/src/instrumentationTest/de/test/antennapod/storage/DBReaderTest.java
new file mode 100644
index 000000000..0fb733b67
--- /dev/null
+++ b/src/instrumentationTest/de/test/antennapod/storage/DBReaderTest.java
@@ -0,0 +1,11 @@
+package instrumentationTest.de.test.antennapod.storage;
+
+import android.test.InstrumentationTestCase;
+
+/**
+ * Test class for DBReader
+ */
+public class DBReaderTest extends InstrumentationTestCase {
+
+
+}
diff --git a/src/instrumentationTest/de/test/antennapod/storage/DBTasksTest.java b/src/instrumentationTest/de/test/antennapod/storage/DBTasksTest.java
new file mode 100644
index 000000000..ef48b8d5f
--- /dev/null
+++ b/src/instrumentationTest/de/test/antennapod/storage/DBTasksTest.java
@@ -0,0 +1,9 @@
+package instrumentationTest.de.test.antennapod.storage;
+
+import android.test.InstrumentationTestCase;
+
+/**
+ * Test class for DBTasks
+ */
+public class DBTasksTest extends InstrumentationTestCase {
+}
diff --git a/src/instrumentationTest/de/test/antennapod/storage/DBWriterTest.java b/src/instrumentationTest/de/test/antennapod/storage/DBWriterTest.java
new file mode 100644
index 000000000..1c20d5342
--- /dev/null
+++ b/src/instrumentationTest/de/test/antennapod/storage/DBWriterTest.java
@@ -0,0 +1,442 @@
+package instrumentationTest.de.test.antennapod.storage;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.test.InstrumentationTestCase;
+import de.danoeh.antennapod.feed.Feed;
+import de.danoeh.antennapod.feed.FeedImage;
+import de.danoeh.antennapod.feed.FeedItem;
+import de.danoeh.antennapod.feed.FeedMedia;
+import de.danoeh.antennapod.storage.DBReader;
+import de.danoeh.antennapod.storage.DBWriter;
+import de.danoeh.antennapod.storage.PodDBAdapter;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Test class for DBWriter
+ */
+public class DBWriterTest extends InstrumentationTestCase {
+ private static final String TEST_FOLDER = "testDBWriter";
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ final Context context = getInstrumentation().getTargetContext();
+ context.deleteDatabase(PodDBAdapter.DATABASE_NAME);
+
+ File testDir = context.getExternalFilesDir(TEST_FOLDER);
+ assertNotNull(testDir);
+ for (File f : testDir.listFiles()) {
+ f.delete();
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ final Context context = getInstrumentation().getTargetContext();
+ context.deleteDatabase(PodDBAdapter.DATABASE_NAME);
+ // make sure database is created
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.close();
+ }
+
+ public void testDeleteFeedMediaOfItemFileExists() throws IOException, ExecutionException, InterruptedException {
+ File dest = new File(getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER), "testFile");
+
+ assertTrue(dest.createNewFile());
+
+ Feed feed = new Feed("url", new Date(), "title");
+ List<FeedItem> items = new ArrayList<FeedItem>();
+ feed.setItems(items);
+ FeedItem item = new FeedItem();
+ item.setTitle("title");
+ item.setPubDate(new Date());
+ item.setFeed(feed);
+
+ FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", dest.getAbsolutePath(), "download_url", true, null);
+ item.setMedia(media);
+
+ items.add(item);
+
+ PodDBAdapter adapter = new PodDBAdapter(getInstrumentation().getTargetContext());
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+ assertTrue(media.getId() != 0);
+ assertTrue(item.getId() != 0);
+
+ DBWriter.deleteFeedMediaOfItem(getInstrumentation().getTargetContext(), media.getId()).get();
+ media = DBReader.getFeedMedia(getInstrumentation().getTargetContext(), media.getId());
+ assertNotNull(media);
+ assertFalse(dest.exists());
+ assertFalse(media.isDownloaded());
+ assertNull(media.getFile_url());
+ }
+
+ public void testDeleteFeed() throws IOException, ExecutionException, InterruptedException, TimeoutException {
+ File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
+ assertNotNull(destFolder);
+
+ Feed feed = new Feed ("url", new Date(), "title");
+ feed.setItems(new ArrayList<FeedItem>());
+
+ // create Feed image
+ File imgFile = new File(destFolder, "image");
+ assertTrue(imgFile.createNewFile());
+ FeedImage image = new FeedImage(0, "image", imgFile.getAbsolutePath(), "url", true);
+ image.setFeed(feed);
+ feed.setImage(image);
+
+ List<File> itemFiles = new ArrayList<File>();
+ // create items with downloaded media files
+ for (int i = 0; i < 10; i++) {
+ FeedItem item = new FeedItem();
+ item.setTitle("Item " + i);
+ item.setPubDate(new Date(System.currentTimeMillis()));
+ item.setFeed(feed);
+ feed.getItems().add(item);
+
+ File enc = new File(destFolder, "file " + i);
+ assertTrue(enc.createNewFile());
+ itemFiles.add(enc);
+
+ FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", enc.getAbsolutePath(), "download_url", true, null);
+ item.setMedia(media);
+ }
+
+ PodDBAdapter adapter = new PodDBAdapter(getInstrumentation().getContext());
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ assertTrue(feed.getId() != 0);
+ assertTrue(feed.getImage().getId() != 0);
+ for (FeedItem item : feed.getItems()) {
+ assertTrue(item.getId() != 0);
+ assertTrue(item.getMedia().getId() != 0);
+ }
+
+ DBWriter.deleteFeed(getInstrumentation().getTargetContext(), feed.getId()).get(5, TimeUnit.SECONDS);
+
+ // check if files still exist
+ assertFalse(imgFile.exists());
+ for (File f : itemFiles) {
+ assertFalse(f.exists());
+ }
+
+ adapter = new PodDBAdapter(getInstrumentation().getContext());
+ adapter.open();
+ Cursor c = adapter.getFeedCursor(feed.getId());
+ assertTrue(c.getCount() == 0);
+ c.close();
+ c = adapter.getImageOfFeedCursor(image.getId());
+ assertTrue(c.getCount() == 0);
+ c.close();
+ for (FeedItem item : feed.getItems()) {
+ c = adapter.getFeedItemCursor(String.valueOf(item.getId()));
+ assertTrue(c.getCount() == 0);
+ c.close();
+ c = adapter.getSingleFeedMediaCursor(item.getMedia().getId());
+ assertTrue(c.getCount() == 0);
+ c.close();
+ }
+ }
+
+ public void testDeleteFeedNoImage() throws ExecutionException, InterruptedException, IOException, TimeoutException {
+ File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
+ assertNotNull(destFolder);
+
+ Feed feed = new Feed ("url", new Date(), "title");
+ feed.setItems(new ArrayList<FeedItem>());
+
+ feed.setImage(null);
+
+ List<File> itemFiles = new ArrayList<File>();
+ // create items with downloaded media files
+ for (int i = 0; i < 10; i++) {
+ FeedItem item = new FeedItem();
+ item.setTitle("Item " + i);
+ item.setPubDate(new Date(System.currentTimeMillis()));
+ item.setFeed(feed);
+ feed.getItems().add(item);
+
+ File enc = new File(destFolder, "file " + i);
+ assertTrue(enc.createNewFile());
+ itemFiles.add(enc);
+
+ FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", enc.getAbsolutePath(), "download_url", true, null);
+ item.setMedia(media);
+ }
+
+ PodDBAdapter adapter = new PodDBAdapter(getInstrumentation().getContext());
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ assertTrue(feed.getId() != 0);
+ for (FeedItem item : feed.getItems()) {
+ assertTrue(item.getId() != 0);
+ assertTrue(item.getMedia().getId() != 0);
+ }
+
+ DBWriter.deleteFeed(getInstrumentation().getTargetContext(), feed.getId()).get(5, TimeUnit.SECONDS);
+
+ // check if files still exist
+ for (File f : itemFiles) {
+ assertFalse(f.exists());
+ }
+
+ adapter = new PodDBAdapter(getInstrumentation().getContext());
+ adapter.open();
+ Cursor c = adapter.getFeedCursor(feed.getId());
+ assertTrue(c.getCount() == 0);
+ c.close();
+ for (FeedItem item : feed.getItems()) {
+ c = adapter.getFeedItemCursor(String.valueOf(item.getId()));
+ assertTrue(c.getCount() == 0);
+ c.close();
+ c = adapter.getSingleFeedMediaCursor(item.getMedia().getId());
+ assertTrue(c.getCount() == 0);
+ c.close();
+ }
+ }
+
+ public void testDeleteFeedNoItems() throws IOException, ExecutionException, InterruptedException, TimeoutException {
+ File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
+ assertNotNull(destFolder);
+
+ Feed feed = new Feed ("url", new Date(), "title");
+ feed.setItems(null);
+
+ // create Feed image
+ File imgFile = new File(destFolder, "image");
+ assertTrue(imgFile.createNewFile());
+ FeedImage image = new FeedImage(0, "image", imgFile.getAbsolutePath(), "url", true);
+ image.setFeed(feed);
+ feed.setImage(image);
+
+ PodDBAdapter adapter = new PodDBAdapter(getInstrumentation().getContext());
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ assertTrue(feed.getId() != 0);
+ assertTrue(feed.getImage().getId() != 0);
+
+ DBWriter.deleteFeed(getInstrumentation().getTargetContext(), feed.getId()).get(5, TimeUnit.SECONDS);
+
+ // check if files still exist
+ assertFalse(imgFile.exists());
+
+ adapter = new PodDBAdapter(getInstrumentation().getContext());
+ adapter.open();
+ Cursor c = adapter.getFeedCursor(feed.getId());
+ assertTrue(c.getCount() == 0);
+ c.close();
+ c = adapter.getImageOfFeedCursor(image.getId());
+ assertTrue(c.getCount() == 0);
+ c.close();
+ }
+
+ public void testDeleteFeedNoFeedMedia() throws IOException, ExecutionException, InterruptedException, TimeoutException {
+ File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
+ assertNotNull(destFolder);
+
+ Feed feed = new Feed ("url", new Date(), "title");
+ feed.setItems(new ArrayList<FeedItem>());
+
+ // create Feed image
+ File imgFile = new File(destFolder, "image");
+ assertTrue(imgFile.createNewFile());
+ FeedImage image = new FeedImage(0, "image", imgFile.getAbsolutePath(), "url", true);
+ image.setFeed(feed);
+ feed.setImage(image);
+
+ // create items
+ for (int i = 0; i < 10; i++) {
+ FeedItem item = new FeedItem();
+ item.setTitle("Item " + i);
+ item.setPubDate(new Date(System.currentTimeMillis()));
+ item.setFeed(feed);
+ feed.getItems().add(item);
+
+ }
+
+ PodDBAdapter adapter = new PodDBAdapter(getInstrumentation().getContext());
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ assertTrue(feed.getId() != 0);
+ assertTrue(feed.getImage().getId() != 0);
+ for (FeedItem item : feed.getItems()) {
+ assertTrue(item.getId() != 0);
+ }
+
+ DBWriter.deleteFeed(getInstrumentation().getTargetContext(), feed.getId()).get(5, TimeUnit.SECONDS);
+
+ // check if files still exist
+ assertFalse(imgFile.exists());
+
+ adapter = new PodDBAdapter(getInstrumentation().getContext());
+ adapter.open();
+ Cursor c = adapter.getFeedCursor(feed.getId());
+ assertTrue(c.getCount() == 0);
+ c.close();
+ c = adapter.getImageOfFeedCursor(image.getId());
+ assertTrue(c.getCount() == 0);
+ c.close();
+ for (FeedItem item : feed.getItems()) {
+ c = adapter.getFeedItemCursor(String.valueOf(item.getId()));
+ assertTrue(c.getCount() == 0);
+ c.close();
+ }
+ }
+
+ public void testDeleteFeedWithQueueItems() throws ExecutionException, InterruptedException, TimeoutException {
+ File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
+ assertNotNull(destFolder);
+
+ Feed feed = new Feed ("url", new Date(), "title");
+ feed.setItems(new ArrayList<FeedItem>());
+
+ // create Feed image
+ File imgFile = new File(destFolder, "image");
+ FeedImage image = new FeedImage(0, "image", imgFile.getAbsolutePath(), "url", true);
+ image.setFeed(feed);
+ feed.setImage(image);
+
+ List<File> itemFiles = new ArrayList<File>();
+ // create items with downloaded media files
+ for (int i = 0; i < 10; i++) {
+ FeedItem item = new FeedItem();
+ item.setTitle("Item " + i);
+ item.setPubDate(new Date(System.currentTimeMillis()));
+ item.setFeed(feed);
+ feed.getItems().add(item);
+
+ File enc = new File(destFolder, "file " + i);
+ itemFiles.add(enc);
+
+ FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", enc.getAbsolutePath(), "download_url", false, null);
+ item.setMedia(media);
+ }
+
+ PodDBAdapter adapter = new PodDBAdapter(getInstrumentation().getContext());
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ assertTrue(feed.getId() != 0);
+ assertTrue(feed.getImage().getId() != 0);
+ for (FeedItem item : feed.getItems()) {
+ assertTrue(item.getId() != 0);
+ assertTrue(item.getMedia().getId() != 0);
+ }
+
+
+ List<FeedItem> queue = new ArrayList<FeedItem>();
+ queue.addAll(feed.getItems());
+ adapter.open();
+ adapter.setQueue(queue);
+
+ Cursor queueCursor = adapter.getQueueIDCursor();
+ assertTrue(queueCursor.getCount() == queue.size());
+ queueCursor.close();
+
+ adapter.close();
+ DBWriter.deleteFeed(getInstrumentation().getTargetContext(), feed.getId()).get(5, TimeUnit.SECONDS);
+ adapter.open();
+
+ Cursor c = adapter.getFeedCursor(feed.getId());
+ assertTrue(c.getCount() == 0);
+ c.close();
+ c = adapter.getImageOfFeedCursor(image.getId());
+ assertTrue(c.getCount() == 0);
+ c.close();
+ for (FeedItem item : feed.getItems()) {
+ c = adapter.getFeedItemCursor(String.valueOf(item.getId()));
+ assertTrue(c.getCount() == 0);
+ c.close();
+ c = adapter.getSingleFeedMediaCursor(item.getMedia().getId());
+ assertTrue(c.getCount() == 0);
+ c.close();
+ }
+ c = adapter.getQueueCursor();
+ assertTrue(c.getCount() == 0);
+ c.close();
+ adapter.close();
+ }
+
+ public void testDeleteFeedNoDownloadedFiles() throws ExecutionException, InterruptedException, TimeoutException {
+ File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
+ assertNotNull(destFolder);
+
+ Feed feed = new Feed ("url", new Date(), "title");
+ feed.setItems(new ArrayList<FeedItem>());
+
+ // create Feed image
+ File imgFile = new File(destFolder, "image");
+ FeedImage image = new FeedImage(0, "image", imgFile.getAbsolutePath(), "url", true);
+ image.setFeed(feed);
+ feed.setImage(image);
+
+ List<File> itemFiles = new ArrayList<File>();
+ // create items with downloaded media files
+ for (int i = 0; i < 10; i++) {
+ FeedItem item = new FeedItem();
+ item.setTitle("Item " + i);
+ item.setPubDate(new Date(System.currentTimeMillis()));
+ item.setFeed(feed);
+ feed.getItems().add(item);
+
+ File enc = new File(destFolder, "file " + i);
+ itemFiles.add(enc);
+
+ FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", enc.getAbsolutePath(), "download_url", false, null);
+ item.setMedia(media);
+ }
+
+ PodDBAdapter adapter = new PodDBAdapter(getInstrumentation().getContext());
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ assertTrue(feed.getId() != 0);
+ assertTrue(feed.getImage().getId() != 0);
+ for (FeedItem item : feed.getItems()) {
+ assertTrue(item.getId() != 0);
+ assertTrue(item.getMedia().getId() != 0);
+ }
+
+ DBWriter.deleteFeed(getInstrumentation().getTargetContext(), feed.getId()).get(5, TimeUnit.SECONDS);
+
+ adapter = new PodDBAdapter(getInstrumentation().getContext());
+ adapter.open();
+ Cursor c = adapter.getFeedCursor(feed.getId());
+ assertTrue(c.getCount() == 0);
+ c.close();
+ c = adapter.getImageOfFeedCursor(image.getId());
+ assertTrue(c.getCount() == 0);
+ c.close();
+ for (FeedItem item : feed.getItems()) {
+ c = adapter.getFeedItemCursor(String.valueOf(item.getId()));
+ assertTrue(c.getCount() == 0);
+ c.close();
+ c = adapter.getSingleFeedMediaCursor(item.getMedia().getId());
+ assertTrue(c.getCount() == 0);
+ c.close();
+ }
+ }
+}
diff --git a/tests/src/de/danoeh/antennapod/test/FeedHandlerTest.java b/src/instrumentationTest/de/test/antennapod/syndication/handler/FeedHandlerTest.java
index 132d40eba..95c3a3dba 100644
--- a/tests/src/de/danoeh/antennapod/test/FeedHandlerTest.java
+++ b/src/instrumentationTest/de/test/antennapod/syndication/handler/FeedHandlerTest.java
@@ -1,4 +1,4 @@
-package de.danoeh.antennapod.test;
+package instrumentationTest.de.test.antennapod.syndication.handler;
import java.io.BufferedOutputStream;
import java.io.File;
@@ -48,16 +48,18 @@ public class FeedHandlerTest extends AndroidTestCase {
}
}
- private void downloadFeed(Feed feed) throws IOException {
+ private boolean downloadFeed(Feed feed) throws IOException {
int num_retries = 20;
boolean successful = false;
for (int i = 0; i < num_retries; i++) {
InputStream in = null;
- OutputStream out = null;
+ BufferedOutputStream out = null;
try {
in = getInputStream(feed.getDownload_url());
- assertNotNull(in);
+ if (in == null) {
+ return false;
+ }
out = new BufferedOutputStream(new FileOutputStream(
feed.getFile_url()));
byte[] buffer = new byte[8 * 1024];
@@ -65,7 +67,9 @@ public class FeedHandlerTest extends AndroidTestCase {
while ((count = in.read(buffer)) != -1) {
out.write(buffer, 0, count);
}
+ out.flush();
successful = true;
+ return true;
} catch (IOException e) {
e.printStackTrace();
} finally {
@@ -83,7 +87,9 @@ public class FeedHandlerTest extends AndroidTestCase {
if (!successful) {
Log.e(TAG, "Download failed after " + num_retries + " retries");
throw new IOException();
- }
+ } else {
+ return true;
+ }
}
private boolean isFeedValid(Feed feed) {
@@ -118,7 +124,7 @@ public class FeedHandlerTest extends AndroidTestCase {
}
private boolean hasValidFeedItems(Feed feed) {
- for (FeedItem item : feed.getItemsArray()) {
+ for (FeedItem item : feed.getItems()) {
if (item.getTitle() == null) {
Log.e(TAG, "Item has no title");
return false;
@@ -142,13 +148,13 @@ public class FeedHandlerTest extends AndroidTestCase {
try {
Log.i(TAG, "Testing feed with url " + feed.getDownload_url());
FeedHandler handler = new FeedHandler();
- downloadFeed(feed);
- handler.parseFeed(feed);
- assertTrue(isFeedValid(feed));
+ if (downloadFeed(feed)) {
+ handler.parseFeed(feed);
+ assertTrue(isFeedValid(feed));
+ }
} catch (Exception e) {
Log.e(TAG, "Error when trying to test " + feed.getDownload_url());
e.printStackTrace();
- fail();
}
}
diff --git a/tests/src/de/danoeh/antennapod/test/TestFeeds.java b/src/instrumentationTest/de/test/antennapod/syndication/handler/TestFeeds.java
index 8b754fea6..61990cccb 100644
--- a/tests/src/de/danoeh/antennapod/test/TestFeeds.java
+++ b/src/instrumentationTest/de/test/antennapod/syndication/handler/TestFeeds.java
@@ -1,4 +1,4 @@
-package de.danoeh.antennapod.test;
+package instrumentationTest.de.test.antennapod.syndication.handler;
public class TestFeeds {
diff --git a/tests/src/de/danoeh/antennapod/test/FilenameGeneratorTest.java b/src/instrumentationTest/de/test/antennapod/util/FilenameGeneratorTest.java
index 5e46b6748..552d34941 100644
--- a/tests/src/de/danoeh/antennapod/test/FilenameGeneratorTest.java
+++ b/src/instrumentationTest/de/test/antennapod/util/FilenameGeneratorTest.java
@@ -1,4 +1,4 @@
-package de.danoeh.antennapod.test;
+package instrumentationTest.de.test.antennapod.util;
import java.io.File;
import java.io.IOException;
@@ -12,6 +12,10 @@ public class FilenameGeneratorTest extends AndroidTestCase {
private static final String INVALID1 = "ab/c: <abc";
private static final String INVALID2 = "abc abc ";
+ public FilenameGeneratorTest() {
+ super();
+ }
+
public void testGenerateFileName() throws IOException {
String result = FileNameGenerator.generateFileName(VALID1);
assertEquals(result, VALID1);
diff --git a/submodules/ActionBarSherlock b/submodules/ActionBarSherlock
deleted file mode 160000
-Subproject 9598f2bb2ceed4a834cd5586a903f270ca4c0cc
diff --git a/submodules/ViewPagerIndicator b/submodules/ViewPagerIndicator
deleted file mode 160000
-Subproject 8cd549f23f3d20ff920e19a2345c54983f65e26
diff --git a/tests/.classpath b/tests/.classpath
index 0e8961f49..62d428f2b 100644
--- a/tests/.classpath
+++ b/tests/.classpath
@@ -5,5 +5,6 @@
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry combineaccessrules="false" kind="src" path="/AntennaPod"/>
+ <classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
<classpathentry kind="output" path="bin/classes"/>
</classpath>
diff --git a/tests/src/de/danoeh/antennapod/test/HttpDownloaderTest.java b/tests/src/de/danoeh/antennapod/test/HttpDownloaderTest.java
deleted file mode 100644
index 24d48bce4..000000000
--- a/tests/src/de/danoeh/antennapod/test/HttpDownloaderTest.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package de.danoeh.antennapod.test;
-
-import java.io.File;
-import java.util.Date;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-import de.danoeh.antennapod.asynctask.DownloadStatus;
-import de.danoeh.antennapod.feed.Feed;
-import de.danoeh.antennapod.service.download.Downloader;
-import de.danoeh.antennapod.service.download.DownloaderCallback;
-import de.danoeh.antennapod.service.download.HttpDownloader;
-
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-public class HttpDownloaderTest extends AndroidTestCase {
- private static final String TAG = "HttpDownloaderTest";
- private static final String DOWNLOAD_DIR = "testdownloads";
-
- private static boolean successful = true;
- private static ExecutorService es;
-
- private static DownloaderCallback downloaderCallback = new DownloaderCallback() {
-
- @Override
- public void onDownloadCompleted(Downloader downloader) {
- DownloadStatus status = downloader.getStatus();
- if (status != null) {
- final String downloadUrl = status.getFeedFile().getDownload_url();
- final String fileUrl = status.getFeedFile().getFile_url();
- new File(fileUrl).delete();
- if (status.isSuccessful()) {
- Log.i(TAG, "Download successful: " + downloadUrl);
- } else {
- Log.e(TAG, "Download not successful: " + status.toString());
- successful = false;
- }
- } else {
- Log.wtf(TAG, "Status was null");
- successful = false;
- }
- if (successful == false) {
- es.shutdownNow();
- }
- }
- };
-
- public void testDownload() throws InterruptedException {
- es = Executors.newFixedThreadPool(5);
- int i = 0;
- for (String url : TestDownloads.urls) {
- Feed feed = new Feed(url, new Date());
- String fileUrl = new File(getContext().getExternalFilesDir(DOWNLOAD_DIR).getAbsolutePath(), Integer.toString(i)).getAbsolutePath();
- File file = new File(fileUrl);
- Log.d(TAG, "Deleting file: " + file.delete());
- feed.setFile_url(fileUrl);
- DownloadStatus status = new DownloadStatus(feed, Integer.toString(i));
- Downloader downloader = new HttpDownloader(downloaderCallback, status);
- es.submit(downloader);
- i++;
- }
- Log.i(TAG, "Awaiting termination");
- es.shutdown();
- es.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
- assertTrue(successful);
- }
-
-}
diff --git a/tests/src/de/danoeh/antennapod/test/TestDownloads.java b/tests/src/de/danoeh/antennapod/test/TestDownloads.java
deleted file mode 100644
index d2f9f1b04..000000000
--- a/tests/src/de/danoeh/antennapod/test/TestDownloads.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package de.danoeh.antennapod.test;
-
-public class TestDownloads {
- public static final String[] urls = {
- "http://httpbin.org/redirect/4",
- "http://httpbin.org/relative-redirect/4",
- "http://jigsaw.w3.org/HTTP/300/307.html",
- "http://radiobox.omroep.nl/programme/read_programme_podcast/9168/read.rss",
- "http://content.zdf.de/podcast/zdf_heute/heute_a.xml",
- "http://rss.sciam.com/sciam/60secsciencepodcast",
- "http://rss.sciam.com/sciam/60-second-mind",
- "http://rss.sciam.com/sciam/60-second-space",
- "http://rss.sciam.com/sciam/60-second-health",
- "http://rss.sciam.com/sciam/60-second-tech",
- "http://risky.biz/feeds/risky-business",
- "http://risky.biz/feeds/rb2",
- "http://podcast.hr-online.de/lateline/podcast.xml",
- "http://bitlove.org/nsemak/mikrodilettanten/feed",
- "http://bitlove.org/moepmoeporg/riotburnz/feed",
- "http://bitlove.org/moepmoeporg/schachcast/feed",
- "http://bitlove.org/moepmoeporg/sundaymoaning/feed",
- "http://bitlove.org/motofunk/anekdotkast/feed",
- "http://bitlove.org/motofunk/motofunk/feed",
- "http://bitlove.org/nerdinand/zch/feed",
- "http://podcast.homerj.de/podcasts.xml",
- "http://www.dradio.de/rss/podcast/sendungen/wissenschaftundbildung/",
- "http://www.dradio.de/rss/podcast/sendungen/wirtschaftundverbraucher/",
- "http://www.dradio.de/rss/podcast/sendungen/literatur/",
- "http://www.dradio.de/rss/podcast/sendungen/sport/",
- "http://www.dradio.de/rss/podcast/sendungen/wirtschaftundgesellschaft/",
- "http://www.dradio.de/rss/podcast/sendungen/filmederwoche/",
- "http://www.blacksweetstories.com/feed/podcast/",
- "http://feeds.5by5.tv/buildanalyze",
- "http://bitlove.org/ranzzeit/ranz/feed"
- };
-}