summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--AndroidManifest.xml3
-rw-r--r--build.gradle44
-rw-r--r--pom.xml10
-rw-r--r--proguard-mvn.cfg66
-rw-r--r--proguard.cfg15
-rw-r--r--res/layout-land/audioplayer_activity.xml17
-rw-r--r--res/layout/audioplayer_activity.xml13
-rw-r--r--res/values/arrays.xml55
-rw-r--r--res/values/strings.xml18
-rw-r--r--res/xml/preferences.xml5
-rw-r--r--src/de/danoeh/antennapod/activity/AudioplayerActivity.java70
-rw-r--r--src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java8
-rw-r--r--src/de/danoeh/antennapod/activity/DownloadActivity.java4
-rw-r--r--src/de/danoeh/antennapod/activity/DownloadLogActivity.java2
-rw-r--r--src/de/danoeh/antennapod/activity/FeedItemlistActivity.java4
-rw-r--r--src/de/danoeh/antennapod/activity/ItemviewActivity.java2
-rw-r--r--src/de/danoeh/antennapod/activity/MainActivity.java11
-rw-r--r--src/de/danoeh/antennapod/activity/MediaplayerActivity.java13
-rw-r--r--src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java2
-rw-r--r--src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java2
-rw-r--r--src/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java4
-rw-r--r--src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java9
-rw-r--r--src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java9
-rw-r--r--src/de/danoeh/antennapod/activity/PreferenceActivity.java12
-rw-r--r--src/de/danoeh/antennapod/activity/SearchActivity.java51
-rw-r--r--src/de/danoeh/antennapod/adapter/ChapterListAdapter.java47
-rw-r--r--src/de/danoeh/antennapod/asynctask/BitmapDecodeWorkerTask.java2
-rw-r--r--src/de/danoeh/antennapod/asynctask/ImageLoader.java2
-rw-r--r--src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java16
-rw-r--r--src/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java3
-rw-r--r--src/de/danoeh/antennapod/asynctask/OpmlImportWorker.java7
-rw-r--r--src/de/danoeh/antennapod/dialog/VariableSpeedDialog.java100
-rw-r--r--src/de/danoeh/antennapod/feed/EventDistributor.java2
-rw-r--r--src/de/danoeh/antennapod/feed/Feed.java19
-rw-r--r--src/de/danoeh/antennapod/feed/FeedItem.java33
-rw-r--r--src/de/danoeh/antennapod/feed/FeedMedia.java20
-rw-r--r--src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java6
-rw-r--r--src/de/danoeh/antennapod/fragment/FeedlistFragment.java28
-rw-r--r--src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java16
-rw-r--r--src/de/danoeh/antennapod/fragment/ItemlistFragment.java38
-rw-r--r--src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java14
-rw-r--r--src/de/danoeh/antennapod/miroguide/model/MiroGuideItem.java4
-rw-r--r--src/de/danoeh/antennapod/preferences/UserPreferences.java77
-rw-r--r--src/de/danoeh/antennapod/service/PlaybackService.java386
-rw-r--r--src/de/danoeh/antennapod/service/download/DownloadService.java107
-rw-r--r--src/de/danoeh/antennapod/service/download/DownloadStatus.java7
-rw-r--r--src/de/danoeh/antennapod/service/download/HttpDownloader.java333
-rw-r--r--src/de/danoeh/antennapod/storage/DBReader.java8
-rw-r--r--src/de/danoeh/antennapod/storage/DBTasks.java21
-rw-r--r--src/de/danoeh/antennapod/storage/DBWriter.java13
-rw-r--r--src/de/danoeh/antennapod/storage/DownloadRequester.java8
-rw-r--r--src/de/danoeh/antennapod/storage/FeedItemStatistics.java4
-rw-r--r--src/de/danoeh/antennapod/storage/PodDBAdapter.java55
-rw-r--r--src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java2
-rw-r--r--src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java8
-rw-r--r--src/de/danoeh/antennapod/util/ChapterUtils.java8
-rw-r--r--src/de/danoeh/antennapod/util/DuckType.java115
-rw-r--r--src/de/danoeh/antennapod/util/LangUtils.java3
-rw-r--r--src/de/danoeh/antennapod/util/QueueAccess.java11
-rw-r--r--src/de/danoeh/antennapod/util/URLChecker.java11
-rw-r--r--src/de/danoeh/antennapod/util/comparator/DownloadStatusComparator.java3
-rw-r--r--src/de/danoeh/antennapod/util/comparator/FeedItemPubdateComparator.java2
-rw-r--r--src/de/danoeh/antennapod/util/comparator/PlaybackCompletionDateComparator.java4
-rw-r--r--src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java5
-rw-r--r--src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java4
-rw-r--r--src/de/danoeh/antennapod/util/playback/AudioPlayer.java30
-rw-r--r--src/de/danoeh/antennapod/util/playback/ExternalMedia.java8
-rw-r--r--src/de/danoeh/antennapod/util/playback/IPlayer.java64
-rw-r--r--src/de/danoeh/antennapod/util/playback/PlaybackController.java24
-rw-r--r--src/de/danoeh/antennapod/util/playback/VideoPlayer.java62
-rw-r--r--src/instrumentationTest/de/test/antennapod/service/download/HttpDownloaderTest.java62
-rw-r--r--src/instrumentationTest/de/test/antennapod/storage/DBTasksTest.java243
-rw-r--r--src/instrumentationTest/de/test/antennapod/storage/DBWriterTest.java351
-rw-r--r--src/instrumentationTest/de/test/antennapod/syndication/handler/FeedHandlerTest.java2
75 files changed, 2146 insertions, 702 deletions
diff --git a/.gitignore b/.gitignore
index cbf04e1bd..12a398578 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,3 +41,4 @@ proguard
libs
*.DS_Store
src/de/danoeh/antennapod/util/flattr/FlattrConfig.java
+gradle.properties
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index ed441e090..d99375a27 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -61,6 +61,7 @@
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
+ <data android:scheme="https"/>
<data android:host="*"/>
<data android:pathPattern=".*\\.xml"/>
<data android:pathPattern=".*\\.rss"/>
@@ -71,6 +72,7 @@
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
+ <data android:scheme="https"/>
<data android:host="feeds.feedburner.com"/>
<data android:host="feedproxy.google.com"/>
<data android:host="feeds2.feedburner.com"/>
@@ -82,6 +84,7 @@
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
+ <data android:scheme="https"/>
<data android:mimeType="text/xml"/>
<data android:mimeType="application/rss+xml"/>
<data android:mimeType="application/atom+xml"/>
diff --git a/build.gradle b/build.gradle
index e1fef631f..8f2c0f684 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,7 +3,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.5.5'
+ classpath 'com.android.tools.build:gradle:0.5.6'
}
}
apply plugin: 'android'
@@ -12,6 +12,17 @@ repositories {
mavenCentral()
}
dependencies {
+ def libsdir = new File('libs');
+ if (!libsdir.exists()) {
+ println "Creating libs directory"
+ libsdir.mkdir()
+ }
+ def prestoLib = new File('libs/presto_client-0.8.5.jar')
+ if (!prestoLib.exists()) {
+ println "Downloading presto library into libs folder"
+ new URL('http://www.aocate.com/presto/client/presto_client-0.8.5.jar').withInputStream{ i -> prestoLib.withOutputStream{ it << i }}
+ }
+
compile 'com.android.support:appcompat-v7:18.0.+'
compile 'org.apache.commons:commons-lang3:3.1'
compile ('org.shredzone.flattr4j:flattr4j-core:2.4') {
@@ -22,6 +33,7 @@ dependencies {
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 {
@@ -35,6 +47,31 @@ android {
testInstrumentationRunner "instrumentationTest.de.test.antennapod.AntennaPodTestRunner"
}
+ signingConfigs {
+ releaseConfig {
+ if (project.hasProperty('releaseStoreFile')) {
+ storeFile file(releaseStoreFile)
+ } else {
+ storeFile file('keystore')
+ }
+ if (project.hasProperty('releaseStorePassword')) {
+ storePassword releaseStorePassword
+ } else {
+ storePassword "password"
+ }
+ if (project.hasProperty('releaseKeyAlias')) {
+ keyAlias releaseKeyAlias
+ } else {
+ keyAlias "alias"
+ }
+ if (project.hasProperty('releaseKeyPassword')) {
+ keyPassword releaseKeyPassword
+ } else {
+ keyPassword "password"
+ }
+ }
+ }
+
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
@@ -51,5 +88,10 @@ android {
debug {
packageNameSuffix ".debug"
}
+ release {
+ runProguard true
+ proguardFile 'proguard.cfg'
+ signingConfig signingConfigs.releaseConfig
+ }
}
}
diff --git a/pom.xml b/pom.xml
index f945c9166..b72ff6b20 100644
--- a/pom.xml
+++ b/pom.xml
@@ -84,6 +84,14 @@
<artifactId>library</artifactId>
<version>2.4.0</version>
</dependency>
+ <dependency>
+ <groupId>com.aocate</groupId>
+ <artifactId>presto_client</artifactId>
+ <version>0.8.5</version>
+ <type>jar</type>
+ <scope>system</scope>
+ <systemPath>${project.basedir}/libs/presto_client-0.8.5.jar</systemPath>
+ </dependency>
</dependencies>
<build>
@@ -212,7 +220,7 @@
</manifest>
<proguard>
<skip>false</skip>
- <config>proguard.cfg</config>
+ <config>proguard-mvn.cfg</config>
</proguard>
</configuration>
<executions>
diff --git a/proguard-mvn.cfg b/proguard-mvn.cfg
new file mode 100644
index 000000000..70019bfcb
--- /dev/null
+++ b/proguard-mvn.cfg
@@ -0,0 +1,66 @@
+-printmapping out.map
+-renamesourcefileattribute SourceFile
+-keepattributes SourceFile,LineNumberTable
+
+-dontpreverify
+-repackageclasses ''
+-allowaccessmodification
+-optimizations !code/simplification/arithmetic
+-keepattributes *Annotation*
+
+-injars libs/presto_client-0.8.5.jar
+
+-keep public class * extends android.app.Activity
+-keep public class * extends android.app.Application
+-keep public class * extends android.app.Service
+-keep public class * extends android.content.BroadcastReceiver
+-keep public class * extends android.content.ContentProvider
+
+-keep public class * extends android.view.View {
+ public <init>(android.content.Context);
+ public <init>(android.content.Context, android.util.AttributeSet);
+ public <init>(android.content.Context, android.util.AttributeSet, int);
+ public void set*(...);
+}
+
+-keepclassmembers enum * {
+ public static **[] values();
+ public static ** valueOf(java.lang.String);
+}
+
+-keepclasseswithmembers class * {
+ public <init>(android.content.Context, android.util.AttributeSet);
+}
+
+-keepclasseswithmembers class * {
+ public <init>(android.content.Context, android.util.AttributeSet, int);
+}
+
+-keepclassmembers class * extends android.content.Context {
+ public void *(android.view.View);
+ public void *(android.view.MenuItem);
+}
+
+-keepclassmembers class * implements android.os.Parcelable {
+ static android.os.Parcelable$Creator CREATOR;
+}
+
+-keepclassmembers class **.R$* {
+ public static <fields>;
+}
+
+-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*
+
+-keep class org.shredzone.flattr4j.** { *; }
+-dontwarn org.shredzone.flattr4j.**
+
+-keep class org.apache.commons.** { *; }
+
+-dontskipnonpubliclibraryclassmembers
diff --git a/proguard.cfg b/proguard.cfg
index 3e5ad54e4..96111d41f 100644
--- a/proguard.cfg
+++ b/proguard.cfg
@@ -8,10 +8,7 @@
-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
+#-injars libs/presto_client-0.8.5.jar
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
@@ -52,10 +49,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/res/layout-land/audioplayer_activity.xml b/res/layout-land/audioplayer_activity.xml
index 521dbc68a..1e671c745 100644
--- a/res/layout-land/audioplayer_activity.xml
+++ b/res/layout-land/audioplayer_activity.xml
@@ -92,14 +92,12 @@
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
- android:layout_marginLeft="8dp"
- android:layout_marginRight="8dp"
android:background="?attr/borderless_button"
android:src="?attr/av_pause" />
<ImageButton
android:id="@+id/butRev"
- android:layout_width="80dp"
+ android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/butPlay"
android:background="?attr/borderless_button"
@@ -107,11 +105,22 @@
<ImageButton
android:id="@+id/butFF"
- android:layout_width="80dp"
+ android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_toRightOf="@id/butPlay"
android:background="?attr/borderless_button"
android:src="?attr/av_fast_forward" />
+
+ <Button
+ android:id="@+id/butPlaybackSpeed"
+ android:layout_width="60dp"
+ android:layout_height="match_parent"
+ android:layout_toRightOf="@id/butFF"
+ android:background="?attr/borderless_button"
+ android:src="?attr/av_fast_forward"
+ android:textColor="@color/gray"
+ android:textSize="@dimen/text_size_medium"
+ android:visibility="gone" />
</RelativeLayout>
<RelativeLayout
diff --git a/res/layout/audioplayer_activity.xml b/res/layout/audioplayer_activity.xml
index bf7540ed1..857d7140f 100644
--- a/res/layout/audioplayer_activity.xml
+++ b/res/layout/audioplayer_activity.xml
@@ -79,8 +79,6 @@
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
- android:layout_marginLeft="12dp"
- android:layout_marginRight="12dp"
android:background="?attr/borderless_button"
android:src="?attr/av_pause" />
@@ -99,6 +97,17 @@
android:layout_toRightOf="@id/butPlay"
android:background="?attr/borderless_button"
android:src="?attr/av_fast_forward" />
+
+ <Button
+ android:id="@+id/butPlaybackSpeed"
+ android:layout_width="80dp"
+ android:layout_height="match_parent"
+ android:layout_toRightOf="@id/butFF"
+ android:background="?attr/borderless_button"
+ android:src="?attr/av_fast_forward"
+ android:textColor="@color/gray"
+ android:textSize="@dimen/text_size_medium"
+ android:visibility="gone" />
</RelativeLayout>
<RelativeLayout
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 4036ff0f4..534b9df50 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -1,6 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
+ <string-array name="update_intervall_options">
+ <item>Manual</item>
+ <item>1 hour</item>
+ <item>2 hours</item>
+ <item>4 hours</item>
+ <item>8 hours</item>
+ <item>12 hours</item>
+ <item>24 hours</item>
+ </string-array>
+
<string-array name="update_intervall_values">
<item>0</item>
<item>1</item>
@@ -28,6 +38,50 @@
<item>80</item>
<item>100</item>
</string-array>
+ <string-array name="playback_speed_values">
+ <item>1.0</item>
+ <item>1.05</item>
+ <item>1.10</item>
+ <item>1.15</item>
+ <item>1.20</item>
+ <item>1.25</item>
+ <item>1.30</item>
+ <item>1.35</item>
+ <item>1.40</item>
+ <item>1.45</item>
+ <item>1.50</item>
+ <item>1.55</item>
+ <item>1.60</item>
+ <item>1.65</item>
+ <item>1.70</item>
+ <item>1.75</item>
+ <item>1.80</item>
+ <item>1.85</item>
+ <item>1.90</item>
+ <item>1.95</item>
+ <item>2.00</item>
+ <item>2.10</item>
+ <item>2.20</item>
+ <item>2.30</item>
+ <item>2.40</item>
+ <item>2.50</item>
+ <item>2.60</item>
+ <item>2.70</item>
+ <item>2.80</item>
+ <item>2.90</item>
+ <item>3.00</item>
+ <item>3.10</item>
+ <item>3.20</item>
+ <item>3.30</item>
+ <item>3.40</item>
+ <item>3.50</item>
+ <item>3.60</item>
+ <item>3.70</item>
+ <item>3.80</item>
+ <item>3.90</item>
+ <item>4.00</item>
+ </string-array>
+
<string-array name="autodl_select_networks_default_entries">
<item>N/A</item>
</string-array>
@@ -42,5 +96,6 @@
<item>0</item>
<item>1</item>
</string-array>
+
</resources> \ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cf33d0b4c..2be4f3025 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -47,6 +47,8 @@
<string name="processing_label">Processing</string>
<string name="loading_label">Loading...</string>
<string name="image_of_prefix">Image of:\u0020</string>
+ <string name="close_label">Close</string>
+
<!-- 'Add Feed' Activity labels -->
<string name="feedurl_label">Feed URL</string>
@@ -143,6 +145,12 @@
<string name="access_revoked_info">You have successfully revoked AntennaPod\'s access token to your account. In order to complete the process, you have to remove this app from the list of approved applications in your account settings on the flattr website.</string>
<string name="flattr_click_success">Successfully flattred this thing!</string>
<string name="flattring_label">Flattring</string>
+
+ <!-- Variable Speed -->
+ <string name="download_plugin_label">Download Plugin</string>
+ <string name="no_playback_plugin_title">Plugin Not Installed</string>
+ <string name="no_playback_plugin_msg">For variable speed playback to work, a third party library must be installed.\n\nTap \'Download Plugin\' to download a free plugin from the Play Store\n\nAny problems found using this plugin are not the responsibility of AntennaPod and should be reported to the plugin owner.</string>
+ <string name="set_playback_speed_label">Playback Speeds</string>
<!-- Empty list labels -->
<string name="no_items_label">There are no items in this list.</string>
@@ -185,9 +193,11 @@
<string name="pref_theme_title_light">Light</string>
<string name="pref_theme_title_dark">Dark</string>
<string name="pref_episode_cache_unlimited">Unlimited</string>
- <string name="pref_update_interval_hours_plural">hours</string>
- <string name="pref_update_interval_hours_singular">hour</string>
- <string name="pref_update_interval_hours_manual">Manual</string>
+ <string name="pref_update_interval_hours_plural">hours</string>
+ <string name="pref_update_interval_hours_singular">hour</string>
+ <string name="pref_update_interval_hours_manual">Manual</string>
+ <string name="pref_playback_speed_title">Playback Speeds</string>
+ <string name="pref_playback_speed_sum">Customize the speeds available for variable speed audio playback</string>
<!-- Search -->
@@ -253,4 +263,4 @@
<string name="pref_pausePlaybackForFocusLoss_sum">Pause playback instead of lowering volume when another app wants to play sounds</string>
<string name="pref_pausePlaybackForFocusLoss_title">Pause for interruptions</string>
-</resources> \ No newline at end of file
+</resources>
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
index 931aa5c2d..7a127dc6a 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -17,6 +17,11 @@
android:key="prefFollowQueue"
android:summary="@string/pref_followQueue_sum"
android:title="@string/pref_followQueue_title" />
+ <Preference
+ android:key="prefPlaybackSpeedLauncher"
+ android:summary="@string/pref_playback_speed_sum"
+ android:title="@string/pref_playback_speed_title" />
+
<CheckBoxPreference
android:defaultValue="false"
android:enabled="true"
diff --git a/src/de/danoeh/antennapod/activity/AudioplayerActivity.java b/src/de/danoeh/antennapod/activity/AudioplayerActivity.java
index 0a4c8ae14..db4373036 100644
--- a/src/de/danoeh/antennapod/activity/AudioplayerActivity.java
+++ b/src/de/danoeh/antennapod/activity/AudioplayerActivity.java
@@ -12,7 +12,9 @@ import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
+import android.view.View.OnLongClickListener;
import android.widget.ArrayAdapter;
+import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView.ScaleType;
import android.widget.ListView;
@@ -22,11 +24,14 @@ import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.ChapterListAdapter;
import de.danoeh.antennapod.asynctask.ImageLoader;
+import de.danoeh.antennapod.dialog.VariableSpeedDialog;
import de.danoeh.antennapod.feed.Chapter;
import de.danoeh.antennapod.feed.MediaType;
import de.danoeh.antennapod.feed.SimpleChapter;
import de.danoeh.antennapod.fragment.CoverFragment;
import de.danoeh.antennapod.fragment.ItemDescriptionFragment;
+import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.util.playback.ExternalMedia;
import de.danoeh.antennapod.util.playback.Playable;
@@ -56,6 +61,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
private TextView txtvTitle;
private TextView txtvFeed;
+ private Button butPlaybackSpeed;
private ImageButton butNavLeft;
private ImageButton butNavRight;
@@ -218,7 +224,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
if (savedPosition != -1) {
switchToFragment(savedPosition);
}
-
+
}
@Override
@@ -363,6 +369,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
txtvFeed = (TextView) findViewById(R.id.txtvFeed);
butNavLeft = (ImageButton) findViewById(R.id.butNavLeft);
butNavRight = (ImageButton) findViewById(R.id.butNavRight);
+ butPlaybackSpeed = (Button) findViewById(R.id.butPlaybackSpeed);
butNavLeft.setOnClickListener(new OnClickListener() {
@@ -390,6 +397,65 @@ public class AudioplayerActivity extends MediaplayerActivity {
}
}
});
+
+ butPlaybackSpeed.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (controller != null && controller.canSetPlaybackSpeed()) {
+ String[] availableSpeeds = UserPreferences
+ .getPlaybackSpeedArray();
+ String currentSpeed = UserPreferences.getPlaybackSpeed();
+
+ // Provide initial value in case the speed list has changed
+ // out from under us
+ // and our current speed isn't in the new list
+ String newSpeed;
+ if (availableSpeeds.length > 0) {
+ newSpeed = availableSpeeds[0];
+ } else {
+ newSpeed = "1.0";
+ }
+
+ for (int i = 0; i < availableSpeeds.length; i++) {
+ if (availableSpeeds[i].equals(currentSpeed)) {
+ if (i == availableSpeeds.length - 1) {
+ newSpeed = availableSpeeds[0];
+ } else {
+ newSpeed = availableSpeeds[i + 1];
+ }
+ break;
+ }
+ }
+ UserPreferences.setPlaybackSpeed(newSpeed);
+ controller.setPlaybackSpeed(Float.parseFloat(newSpeed));
+ }
+ }
+ });
+
+ butPlaybackSpeed.setOnLongClickListener(new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ VariableSpeedDialog.showDialog(AudioplayerActivity.this);
+ return true;
+ }
+ });
+ }
+
+ @Override
+ protected void onPlaybackSpeedChange() {
+ super.onPlaybackSpeedChange();
+ updateButPlaybackSpeed();
+ }
+
+ private void updateButPlaybackSpeed() {
+ if (controller == null
+ || (controller.getCurrentPlaybackSpeedMultiplier() == -1)) {
+ butPlaybackSpeed.setVisibility(View.GONE);
+ } else {
+ butPlaybackSpeed.setVisibility(View.VISIBLE);
+ butPlaybackSpeed.setText(UserPreferences.getPlaybackSpeed());
+ }
}
@Override
@@ -421,7 +487,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
((AudioplayerContentFragment) currentlyShownFragment)
.onDataSetChanged(media);
}
-
+ updateButPlaybackSpeed();
}
public void notifyMediaPositionChanged() {
diff --git a/src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java b/src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java
index 984491174..62273c960 100644
--- a/src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java
+++ b/src/de/danoeh/antennapod/activity/DirectoryChooserActivity.java
@@ -347,7 +347,9 @@ public class DirectoryChooserActivity extends ActionBarActivity {
* CREATE_DIRECTORY_NAME.
*/
private int createFolder() {
- if (selectedDir != null && selectedDir.canWrite()) {
+ if (selectedDir == null) {
+ return R.string.create_folder_error;
+ } else if (selectedDir.canWrite()) {
File newDir = new File(selectedDir, CREATE_DIRECTORY_NAME);
if (!newDir.exists()) {
boolean result = newDir.mkdir();
@@ -359,10 +361,8 @@ public class DirectoryChooserActivity extends ActionBarActivity {
} else {
return R.string.create_folder_error_already_exists;
}
- } else if (selectedDir.canWrite() == false) {
- return R.string.create_folder_error_no_write_access;
} else {
- return R.string.create_folder_error;
+ return R.string.create_folder_error_no_write_access;
}
}
diff --git a/src/de/danoeh/antennapod/activity/DownloadActivity.java b/src/de/danoeh/antennapod/activity/DownloadActivity.java
index 40c75d336..57c86f760 100644
--- a/src/de/danoeh/antennapod/activity/DownloadActivity.java
+++ b/src/de/danoeh/antennapod/activity/DownloadActivity.java
@@ -121,7 +121,7 @@ public class DownloadActivity extends ActionBarActivity implements
contentRefresher.cancel(true);
}
contentRefresher = new AsyncTask<Void, Void, Void>() {
- private final int WAITING_INTERVALL = 1000;
+ private static final int WAITING_INTERVAL = 1000;
@Override
protected void onProgressUpdate(Void... values) {
@@ -137,7 +137,7 @@ public class DownloadActivity extends ActionBarActivity implements
protected Void doInBackground(Void... params) {
while (!isCancelled()) {
try {
- Thread.sleep(WAITING_INTERVALL);
+ Thread.sleep(WAITING_INTERVAL);
publishProgress();
} catch (InterruptedException e) {
return null;
diff --git a/src/de/danoeh/antennapod/activity/DownloadLogActivity.java b/src/de/danoeh/antennapod/activity/DownloadLogActivity.java
index 949834596..5e48371b8 100644
--- a/src/de/danoeh/antennapod/activity/DownloadLogActivity.java
+++ b/src/de/danoeh/antennapod/activity/DownloadLogActivity.java
@@ -37,7 +37,7 @@ public class DownloadLogActivity extends ActionBarActivity {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- listview = (ListView) findViewById(R.layout.listview_activity);
+ listview = (ListView) findViewById(R.id.listview);
dla = new DownloadLogAdapter(this, itemAccess);
listview.setAdapter(dla);
diff --git a/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java b/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java
index c53a4cc7b..8fba44e5c 100644
--- a/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java
+++ b/src/de/danoeh/antennapod/activity/FeedItemlistActivity.java
@@ -7,6 +7,7 @@ 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;
@@ -57,6 +58,7 @@ public class FeedItemlistActivity extends ActionBarActivity {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.feeditemlist_activity);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
long feedId = getIntent().getLongExtra(
FeedlistFragment.EXTRA_SELECTED_FEED, -1);
@@ -122,6 +124,8 @@ public class FeedItemlistActivity extends ActionBarActivity {
SearchManager searchManager =
(SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.search_item));
+
+
searchView.setIconifiedByDefault(true);
searchView.setSearchableInfo(
diff --git a/src/de/danoeh/antennapod/activity/ItemviewActivity.java b/src/de/danoeh/antennapod/activity/ItemviewActivity.java
index 4c0fdfdba..c2833760d 100644
--- a/src/de/danoeh/antennapod/activity/ItemviewActivity.java
+++ b/src/de/danoeh/antennapod/activity/ItemviewActivity.java
@@ -1,5 +1,6 @@
package de.danoeh.antennapod.activity;
+import android.media.AudioManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
@@ -45,6 +46,7 @@ public class ItemviewActivity extends ActionBarActivity {
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
getSupportActionBar().setDisplayShowTitleEnabled(false);
EventDistributor.getInstance().register(contentUpdate);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
long itemId = getIntent().getLongExtra(
ItemlistFragment.EXTRA_SELECTED_FEEDITEM, -1);
diff --git a/src/de/danoeh/antennapod/activity/MainActivity.java b/src/de/danoeh/antennapod/activity/MainActivity.java
index 20c53553b..4f25a07f1 100644
--- a/src/de/danoeh/antennapod/activity/MainActivity.java
+++ b/src/de/danoeh/antennapod/activity/MainActivity.java
@@ -6,6 +6,7 @@ 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;
@@ -55,8 +56,9 @@ public class MainActivity extends ActionBarActivity {
StorageUtils.checkStorageAvailability(this);
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);
@@ -181,7 +183,12 @@ public class MainActivity extends ActionBarActivity {
SearchManager searchManager =
(SearchManager) getSystemService(Context.SEARCH_SERVICE);
- SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.search_item));
+ MenuItem searchItem = menu.findItem(R.id.search_item);
+ SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
+ if (searchView == null) {
+ MenuItemCompat.setActionView(searchItem, new SearchView(this));
+ searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
+ }
searchView.setIconifiedByDefault(true);
SearchableInfo info = searchManager.getSearchableInfo(getComponentName());
diff --git a/src/de/danoeh/antennapod/activity/MediaplayerActivity.java b/src/de/danoeh/antennapod/activity/MediaplayerActivity.java
index b5b694995..af244f2ed 100644
--- a/src/de/danoeh/antennapod/activity/MediaplayerActivity.java
+++ b/src/de/danoeh/antennapod/activity/MediaplayerActivity.java
@@ -4,6 +4,7 @@ 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;
@@ -128,10 +129,19 @@ public abstract class MediaplayerActivity extends ActionBarActivity
public void onPlaybackEnd() {
finish();
}
+
+ @Override
+ public void onPlaybackSpeedChange() {
+ MediaplayerActivity.this.onPlaybackSpeedChange();
+ }
};
}
+ protected void onPlaybackSpeedChange() {
+
+ }
+
protected void onServiceQueried() {
supportInvalidateOptionsMenu();
}
@@ -143,8 +153,9 @@ public abstract class MediaplayerActivity extends ActionBarActivity
if (AppConfig.DEBUG)
Log.d(TAG, "Creating Activity");
StorageUtils.checkStorageAvailability(this);
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
- orientation = getResources().getConfiguration().orientation;
+ 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 c039e96f8..e3d77a68a 100644
--- a/src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java
+++ b/src/de/danoeh/antennapod/activity/MiroGuideCategoryActivity.java
@@ -22,7 +22,7 @@ import de.danoeh.antennapod.preferences.UserPreferences;
public class MiroGuideCategoryActivity extends ActionBarActivity {
private static final String TAG = "MiroGuideCategoryActivity";
- public static String EXTRA_CATEGORY = "category";
+ public static final String EXTRA_CATEGORY = "category";
private ViewPager viewpager;
private CategoryPagerAdapter pagerAdapter;
diff --git a/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java b/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
index cb1c66cab..fbac7057d 100644
--- a/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
+++ b/src/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java
@@ -157,7 +157,7 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
@Override
public void run() {
- String reasonDetailed = new String();
+ String reasonDetailed = "";
boolean successful = false;
FeedHandler handler = new FeedHandler();
try {
diff --git a/src/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java b/src/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java
index dc698a851..58e3a96dd 100644
--- a/src/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java
+++ b/src/de/danoeh/antennapod/activity/OpmlImportFromIntentActivity.java
@@ -7,6 +7,7 @@ import java.net.URL;
import android.app.AlertDialog;
import android.os.Bundle;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.util.LangUtils;
/** Lets the user start the OPML-import process. */
public class OpmlImportFromIntentActivity extends OpmlImportBaseActivity {
@@ -20,7 +21,8 @@ public class OpmlImportFromIntentActivity extends OpmlImportBaseActivity {
try {
URL mOpmlURL = new URL(getIntent().getData().toString());
- BufferedReader in = new BufferedReader(new InputStreamReader(mOpmlURL.openStream()));
+ BufferedReader in = new BufferedReader(new InputStreamReader(mOpmlURL.openStream(),
+ LangUtils.UTF_8));
startImport(in);
} catch (Exception e) {
new AlertDialog.Builder(this).setMessage("Cannot open XML - Reason: " + e.getMessage()).show();
diff --git a/src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java b/src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java
index 259689abf..ece78006f 100644
--- a/src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java
+++ b/src/de/danoeh/antennapod/activity/OpmlImportFromPathActivity.java
@@ -1,8 +1,10 @@
package de.danoeh.antennapod.activity;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
-import java.io.FileReader;
+import java.io.InputStreamReader;
+import java.io.IOException;
import java.io.Reader;
import android.app.AlertDialog;
@@ -20,6 +22,7 @@ import android.widget.Toast;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.util.LangUtils;
import de.danoeh.antennapod.util.StorageUtils;
/**
@@ -125,8 +128,10 @@ public class OpmlImportFromPathActivity extends OpmlImportBaseActivity {
}
private void startImport(File file) {
+ Reader mReader = null;
try {
- Reader mReader = new FileReader(file);
+ mReader = new InputStreamReader(new FileInputStream(file),
+ LangUtils.UTF_8);
if (AppConfig.DEBUG) Log.d(TAG, "Parsing " + file.toString());
startImport(mReader);
} catch (FileNotFoundException e) {
diff --git a/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java b/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java
index 77dae303e..e376b08b8 100644
--- a/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java
+++ b/src/de/danoeh/antennapod/activity/OrganizeQueueActivity.java
@@ -51,6 +51,7 @@ public class OrganizeQueueActivity extends ActionBarActivity implements
listView = (DragSortListView) findViewById(android.R.id.list);
listView.setDropListener(dropListener);
listView.setRemoveListener(removeListener);
+ listView.setEmptyView(findViewById(android.R.id.empty));
loadData();
undoBarController = new UndoBarController(findViewById(R.id.undobar),
@@ -155,9 +156,11 @@ public class OrganizeQueueActivity extends ActionBarActivity implements
public void onUndo(Parcelable token) {
// Perform the undo
UndoToken undoToken = (UndoToken) token;
- long itemId = undoToken.getFeedItemId();
- int position = undoToken.getPosition();
- DBWriter.addQueueItemAt(OrganizeQueueActivity.this, itemId, 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 {
diff --git a/src/de/danoeh/antennapod/activity/PreferenceActivity.java b/src/de/danoeh/antennapod/activity/PreferenceActivity.java
index 880724c28..96471d06d 100644
--- a/src/de/danoeh/antennapod/activity/PreferenceActivity.java
+++ b/src/de/danoeh/antennapod/activity/PreferenceActivity.java
@@ -25,6 +25,7 @@ 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.dialog.VariableSpeedDialog;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.util.flattr.FlattrUtils;
@@ -41,7 +42,8 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
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_PLAYBACK_SPEED_LAUNCHER = "prefPlaybackSpeedLauncher";
+
private CheckBoxPreference[] selectedNetworks;
@SuppressWarnings("deprecation")
@@ -156,6 +158,14 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
return true;
}
});
+ findPreference(PREF_PLAYBACK_SPEED_LAUNCHER)
+ .setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ VariableSpeedDialog.showDialog(PreferenceActivity.this);
+ return true;
+ }
+ });
buildUpdateIntervalPreference();
buildAutodownloadSelectedNetworsPreference();
setSelectedNetworksEnabled(UserPreferences
diff --git a/src/de/danoeh/antennapod/activity/SearchActivity.java b/src/de/danoeh/antennapod/activity/SearchActivity.java
index 257ae86ae..86f7301cf 100644
--- a/src/de/danoeh/antennapod/activity/SearchActivity.java
+++ b/src/de/danoeh/antennapod/activity/SearchActivity.java
@@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.List;
import android.annotation.SuppressLint;
+import android.app.Activity;
import android.app.SearchManager;
import android.content.Intent;
import android.os.Bundle;
@@ -140,32 +141,34 @@ public class SearchActivity extends ActionBarActivity implements AdapterView.OnI
@Override
public void run() {
Log.d(TAG, "Starting background work");
+ final Activity activity = SearchActivity.this;
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);
- }
+ .performSearch(activity, query, feedID);
+ activity.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();
+ for (SearchResult s : result) {
+ searchAdapter.add(s);
}
- });
- }
+ searchAdapter.notifyDataSetChanged();
+ txtvStatus
+ .setText(R.string.search_status_no_results);
+ if (!searchAdapter.isEmpty()) {
+ txtvStatus.setVisibility(View.GONE);
+ } else {
+ txtvStatus.setVisibility(View.VISIBLE);
+ }
+ }
+ });
+
}
};
thread.start();
diff --git a/src/de/danoeh/antennapod/adapter/ChapterListAdapter.java b/src/de/danoeh/antennapod/adapter/ChapterListAdapter.java
index 3e9b586ce..5a8dfb2bf 100644
--- a/src/de/danoeh/antennapod/adapter/ChapterListAdapter.java
+++ b/src/de/danoeh/antennapod/adapter/ChapterListAdapter.java
@@ -144,53 +144,6 @@ public class ChapterListAdapter extends ArrayAdapter<Chapter> {
TextView link;
}
- private LinkMovementMethod linkMovementMethod = new LinkMovementMethod() {
-
- @Override
- public boolean onTouchEvent(TextView widget, Spannable buffer,
- MotionEvent event) {
- Object text = widget.getText();
- if (text instanceof Spanned) {
- int action = event.getAction();
-
- if (action == MotionEvent.ACTION_UP
- || action == MotionEvent.ACTION_DOWN) {
- int x = (int) event.getX();
- int y = (int) event.getY();
-
- x -= widget.getTotalPaddingLeft();
- y -= widget.getTotalPaddingTop();
-
- x += widget.getScrollX();
- y += widget.getScrollY();
-
- Layout layout = widget.getLayout();
- int line = layout.getLineForVertical(y);
- int off = layout.getOffsetForHorizontal(line, x);
-
- ClickableSpan[] link = buffer.getSpans(off, off,
- ClickableSpan.class);
-
- if (link.length != 0) {
- if (action == MotionEvent.ACTION_UP) {
- link[0].onClick(widget);
- } else if (action == MotionEvent.ACTION_DOWN) {
- Selection.setSelection(buffer,
- buffer.getSpanStart(link[0]),
- buffer.getSpanEnd(link[0]));
- }
- return true;
- }
- }
-
- }
-
- return false;
-
- }
-
- };
-
@Override
public int getCount() {
// ignore invalid chapters
diff --git a/src/de/danoeh/antennapod/asynctask/BitmapDecodeWorkerTask.java b/src/de/danoeh/antennapod/asynctask/BitmapDecodeWorkerTask.java
index 4380bc6ea..7ba68ae22 100644
--- a/src/de/danoeh/antennapod/asynctask/BitmapDecodeWorkerTask.java
+++ b/src/de/danoeh/antennapod/asynctask/BitmapDecodeWorkerTask.java
@@ -47,7 +47,7 @@ public class BitmapDecodeWorkerTask extends Thread {
*/
protected boolean tagsMatching(ImageView target) {
return target.getTag() == null
- || target.getTag() == imageResource.getImageLoaderCacheKey();
+ || target.getTag().equals(imageResource.getImageLoaderCacheKey());
}
protected void onPostExecute() {
diff --git a/src/de/danoeh/antennapod/asynctask/ImageLoader.java b/src/de/danoeh/antennapod/asynctask/ImageLoader.java
index fb807f469..45a99e704 100644
--- a/src/de/danoeh/antennapod/asynctask/ImageLoader.java
+++ b/src/de/danoeh/antennapod/asynctask/ImageLoader.java
@@ -77,7 +77,7 @@ public class ImageLoader {
});
}
- public static ImageLoader getInstance() {
+ public static synchronized ImageLoader getInstance() {
if (singleton == null) {
singleton = new ImageLoader();
}
diff --git a/src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java b/src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java
index e14e22917..745bc7079 100644
--- a/src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java
+++ b/src/de/danoeh/antennapod/asynctask/OpmlExportWorker.java
@@ -1,8 +1,9 @@
package de.danoeh.antennapod.asynctask;
import java.io.File;
-import java.io.FileWriter;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStreamWriter;
import java.util.Arrays;
import android.annotation.SuppressLint;
@@ -16,6 +17,7 @@ import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.opml.OpmlWriter;
import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.util.LangUtils;
import de.danoeh.antennapod.storage.DBReader;
/** Writes an OPML file into the export directory in the background. */
@@ -49,13 +51,21 @@ public class OpmlExportWorker extends AsyncTask<Void, Void, Void> {
output.delete();
}
}
+ OutputStreamWriter writer = null;
try {
- FileWriter writer = new FileWriter(output);
+ writer = new OutputStreamWriter(new FileOutputStream(output), LangUtils.UTF_8);
opmlWriter.writeDocument(DBReader.getFeedList(context), writer);
- writer.close();
} catch (IOException e) {
e.printStackTrace();
exception = e;
+ } finally {
+ if (writer != null) {
+ try {
+ writer.close();
+ } catch (IOException ioe) {
+ exception = ioe;
+ }
+ }
}
return null;
}
diff --git a/src/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java b/src/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java
index 4d9c9867e..64e678086 100644
--- a/src/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java
+++ b/src/de/danoeh/antennapod/asynctask/OpmlFeedQueuer.java
@@ -1,5 +1,6 @@
package de.danoeh.antennapod.asynctask;
+import java.util.Arrays;
import java.util.Date;
import android.annotation.SuppressLint;
@@ -22,7 +23,7 @@ public class OpmlFeedQueuer extends AsyncTask<Void, Void, Void> {
public OpmlFeedQueuer(Context context, int[] selection) {
super();
this.context = context;
- this.selection = selection;
+ this.selection = Arrays.copyOf(selection, selection.length);
}
@Override
diff --git a/src/de/danoeh/antennapod/asynctask/OpmlImportWorker.java b/src/de/danoeh/antennapod/asynctask/OpmlImportWorker.java
index 5af06895f..4816c25ab 100644
--- a/src/de/danoeh/antennapod/asynctask/OpmlImportWorker.java
+++ b/src/de/danoeh/antennapod/asynctask/OpmlImportWorker.java
@@ -64,6 +64,13 @@ public class OpmlImportWorker extends
@Override
protected void onPostExecute(ArrayList<OpmlElement> result) {
+ if (mReader != null) {
+ try {
+ mReader.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
progDialog.dismiss();
if (exception != null) {
if (AppConfig.DEBUG)
diff --git a/src/de/danoeh/antennapod/dialog/VariableSpeedDialog.java b/src/de/danoeh/antennapod/dialog/VariableSpeedDialog.java
new file mode 100644
index 000000000..e6cbe37d1
--- /dev/null
+++ b/src/de/danoeh/antennapod/dialog/VariableSpeedDialog.java
@@ -0,0 +1,100 @@
+package de.danoeh.antennapod.dialog;
+
+import java.util.Arrays;
+import java.util.List;
+
+import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.preferences.UserPreferences;
+
+public class VariableSpeedDialog {
+ private VariableSpeedDialog() {
+ }
+
+ public static void showDialog(final Context context) {
+ if (com.aocate.media.MediaPlayer.isPrestoLibraryInstalled(context)) {
+ showSpeedSelectorDialog(context);
+ } else {
+ showGetPluginDialog(context);
+ }
+ }
+
+ private static void showGetPluginDialog(final Context context) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.no_playback_plugin_title);
+ builder.setMessage(R.string.no_playback_plugin_msg);
+ builder.setNegativeButton(R.string.close_label, null);
+ builder.setPositiveButton(R.string.download_plugin_label,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ try {
+ Intent playStoreIntent = new Intent(
+ Intent.ACTION_VIEW,
+ Uri.parse("market://details?id=com.falconware.prestissimo"));
+ context.startActivity(playStoreIntent);
+ } catch (ActivityNotFoundException e) {
+ // this is usually thrown on an emulator if the Android market is not installed
+ e.printStackTrace();
+ }
+ }
+ });
+ builder.create().show();
+ }
+
+ private static void showSpeedSelectorDialog(final Context context) {
+ final String[] speedValues = context.getResources().getStringArray(
+ R.array.playback_speed_values);
+ // According to Java spec these get initialized to false on creation
+ final boolean[] speedChecked = new boolean[speedValues.length];
+
+ // Build the "isChecked" array so that multiChoice dialog is
+ // populated correctly
+ List<String> selectedSpeedList = Arrays.asList(UserPreferences
+ .getPlaybackSpeedArray());
+ for (int i = 0; i < speedValues.length; i++) {
+ speedChecked[i] = selectedSpeedList.contains(speedValues[i]);
+ }
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.set_playback_speed_label);
+ builder.setMultiChoiceItems(R.array.playback_speed_values,
+ speedChecked, new DialogInterface.OnMultiChoiceClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which,
+ boolean isChecked) {
+ speedChecked[which] = isChecked;
+ }
+
+ });
+ builder.setNegativeButton(android.R.string.cancel, null);
+ builder.setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ int choiceCount = 0;
+ for (int i = 0; i < speedChecked.length; i++) {
+ if (speedChecked[i]) {
+ choiceCount++;
+ }
+ }
+ String[] newSpeedValues = new String[choiceCount];
+ int newSpeedIndex = 0;
+ for (int i = 0; i < speedChecked.length; i++) {
+ if (speedChecked[i]) {
+ newSpeedValues[newSpeedIndex++] = speedValues[i];
+ }
+ }
+
+ UserPreferences.setPlaybackSpeedArray(newSpeedValues);
+
+ }
+ });
+ builder.create().show();
+ }
+}
diff --git a/src/de/danoeh/antennapod/feed/EventDistributor.java b/src/de/danoeh/antennapod/feed/EventDistributor.java
index c538808e2..56333da52 100644
--- a/src/de/danoeh/antennapod/feed/EventDistributor.java
+++ b/src/de/danoeh/antennapod/feed/EventDistributor.java
@@ -39,7 +39,7 @@ public class EventDistributor extends Observable {
events = new ConcurrentLinkedQueue<Integer>();
}
- public static EventDistributor getInstance() {
+ public static synchronized EventDistributor getInstance() {
if (instance == null) {
instance = new EventDistributor();
}
diff --git a/src/de/danoeh/antennapod/feed/Feed.java b/src/de/danoeh/antennapod/feed/Feed.java
index 9cee1a86a..032930f83 100644
--- a/src/de/danoeh/antennapod/feed/Feed.java
+++ b/src/de/danoeh/antennapod/feed/Feed.java
@@ -55,7 +55,11 @@ public class Feed extends FeedFile {
super(fileUrl, downloadUrl, downloaded);
this.id = id;
this.title = title;
- this.lastUpdate = lastUpdate;
+ if (lastUpdate != null) {
+ this.lastUpdate = (Date) lastUpdate.clone();
+ } else {
+ this.lastUpdate = null;
+ }
this.link = link;
this.description = description;
this.paymentLink = paymentLink;
@@ -83,7 +87,7 @@ public class Feed extends FeedFile {
*/
public Feed(String url, Date lastUpdate) {
super(null, url, false);
- this.lastUpdate = lastUpdate;
+ this.lastUpdate = (lastUpdate != null) ? (Date) lastUpdate.clone() : null;
}
/**
@@ -314,19 +318,12 @@ public class Feed extends FeedFile {
this.items = list;
}
- /**
- * 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;
+ return (lastUpdate != null) ? (Date) lastUpdate.clone() : null;
}
public void setLastUpdate(Date lastUpdate) {
- this.lastUpdate = lastUpdate;
+ this.lastUpdate = (lastUpdate != null) ? (Date) lastUpdate.clone() : null;
}
public String getFeedIdentifier() {
diff --git a/src/de/danoeh/antennapod/feed/FeedItem.java b/src/de/danoeh/antennapod/feed/FeedItem.java
index 54682397e..a80460ece 100644
--- a/src/de/danoeh/antennapod/feed/FeedItem.java
+++ b/src/de/danoeh/antennapod/feed/FeedItem.java
@@ -48,6 +48,19 @@ public class FeedItem extends FeedComponent implements
this.read = true;
}
+ /**
+ * This constructor should be used for creating test objects.
+ * */
+ public FeedItem(long id, String title, String itemIdentifier, String link, Date pubDate, boolean read, Feed feed) {
+ this.id = id;
+ this.title = title;
+ this.itemIdentifier = itemIdentifier;
+ this.link = link;
+ this.pubDate = (pubDate != null) ? (Date) pubDate.clone() : null;
+ this.read = read;
+ this.feed = feed;
+ }
+
public void updateFromOther(FeedItem other) {
super.updateFromOther(other);
if (other.title != null) {
@@ -123,19 +136,35 @@ public class FeedItem extends FeedComponent implements
}
public Date getPubDate() {
- return pubDate;
+ if (pubDate != null) {
+ return (Date) pubDate.clone();
+ } else {
+ return null;
+ }
}
public void setPubDate(Date pubDate) {
- this.pubDate = pubDate;
+ if (pubDate != null) {
+ this.pubDate = (Date) pubDate.clone();
+ } else {
+ this.pubDate = null;
+ }
}
public FeedMedia getMedia() {
return media;
}
+ /**
+ * Sets the media object of this FeedItem. If the given
+ * FeedMedia object is not null, it's 'item'-attribute value
+ * will also be set to this item.
+ * */
public void setMedia(FeedMedia media) {
this.media = media;
+ if (media != null && media.getItem() != this) {
+ media.setItem(this);
+ }
}
public Feed getFeed() {
diff --git a/src/de/danoeh/antennapod/feed/FeedMedia.java b/src/de/danoeh/antennapod/feed/FeedMedia.java
index 2e0eac7a4..492867983 100644
--- a/src/de/danoeh/antennapod/feed/FeedMedia.java
+++ b/src/de/danoeh/antennapod/feed/FeedMedia.java
@@ -53,7 +53,8 @@ public class FeedMedia extends FeedFile implements Playable {
this.position = position;
this.size = size;
this.mime_type = mime_type;
- this.playbackCompletionDate = playbackCompletionDate;
+ this.playbackCompletionDate = playbackCompletionDate == null
+ ? null : (Date) playbackCompletionDate.clone();
}
public FeedMedia(long id, FeedItem item) {
@@ -164,16 +165,25 @@ public class FeedMedia extends FeedFile implements Playable {
return item;
}
+ /**
+ * Sets the item object of this FeedMedia. If the given
+ * FeedItem object is not null, it's 'media'-attribute value
+ * will also be set to this media object.
+ * */
public void setItem(FeedItem item) {
this.item = item;
+ if (item != null && item.getMedia() != this) {
+ item.setMedia(this);
+ }
}
public Date getPlaybackCompletionDate() {
- return playbackCompletionDate;
- }
+ return playbackCompletionDate == null
+ ? null : (Date) playbackCompletionDate.clone(); }
public void setPlaybackCompletionDate(Date playbackCompletionDate) {
- this.playbackCompletionDate = playbackCompletionDate;
+ this.playbackCompletionDate = playbackCompletionDate == null
+ ? null : (Date) playbackCompletionDate.clone();
}
public boolean isInProgress() {
@@ -304,7 +314,7 @@ public class FeedMedia extends FeedFile implements Playable {
@Override
public void saveCurrentPosition(SharedPreferences pref, int newPosition) {
position = newPosition;
- DBWriter.setFeedMediaPosition(PodcastApp.getInstance(), this);
+ DBWriter.setFeedMediaPlaybackInformation(PodcastApp.getInstance(), this);
}
@Override
diff --git a/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java b/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java
index 10312b20b..933263d7d 100644
--- a/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java
+++ b/src/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java
@@ -173,6 +173,12 @@ public class ExternalPlayerFragment extends Fragment {
.newOnPlayButtonClickListener());
}
}
+
+ @Override
+ public void onPlaybackSpeedChange() {
+ // TODO Auto-generated method stub
+
+ }
};
}
diff --git a/src/de/danoeh/antennapod/fragment/FeedlistFragment.java b/src/de/danoeh/antennapod/fragment/FeedlistFragment.java
index 3e8679bca..0dc29c415 100644
--- a/src/de/danoeh/antennapod/fragment/FeedlistFragment.java
+++ b/src/de/danoeh/antennapod/fragment/FeedlistFragment.java
@@ -3,6 +3,7 @@ package de.danoeh.antennapod.fragment;
import java.util.List;
import android.annotation.SuppressLint;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
@@ -29,9 +30,9 @@ import de.danoeh.antennapod.storage.FeedItemStatistics;
import de.danoeh.antennapod.util.menuhandler.FeedMenuHandler;
public class FeedlistFragment extends Fragment implements
- ActionMode.Callback, AdapterView.OnItemClickListener,
- AdapterView.OnItemLongClickListener {
- private static final String TAG = "FeedlistFragment";
+ ActionMode.Callback, AdapterView.OnItemClickListener,
+ AdapterView.OnItemLongClickListener {
+ private static final String TAG = "FeedlistFragment";
private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED
| EventDistributor.DOWNLOAD_QUEUED
@@ -94,12 +95,16 @@ public class FeedlistFragment extends Fragment implements
AsyncTask<Void, Void, List[]> loadTask = new AsyncTask<Void, Void, List[]>() {
@Override
protected List[] doInBackground(Void... params) {
- return new List[]{DBReader.getFeedList(getActivity()),
- DBReader.getFeedStatisticsList(getActivity())};
+ 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);
@@ -160,9 +165,14 @@ public class FeedlistFragment extends Fragment implements
}
@Override
+ public void onDestroy() {
+ super.onDestroy();
+ EventDistributor.getInstance().unregister(contentUpdate);
+ }
+
+ @Override
public void onPause() {
super.onPause();
- EventDistributor.getInstance().unregister(contentUpdate);
if (mActionMode != null) {
mActionMode.finish();
}
@@ -255,9 +265,9 @@ public class FeedlistFragment extends Fragment implements
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 (AppConfig.DEBUG)
+ Log.d(TAG, "Selected Feed with title " + selection.getTitle());
if (mActionMode != null) {
mActionMode.finish();
}
diff --git a/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java b/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java
index 9183180c1..c996f497e 100644
--- a/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java
+++ b/src/de/danoeh/antennapod/fragment/ItemDescriptionFragment.java
@@ -1,5 +1,6 @@
package de.danoeh.antennapod.fragment;
+import android.content.*;
import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBarActivity;
import de.danoeh.antennapod.feed.FeedItem;
@@ -9,10 +10,6 @@ import org.apache.commons.lang3.StringEscapeUtils;
import android.annotation.SuppressLint;
import android.app.Activity;
-import android.content.ClipData;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.net.Uri;
import android.os.AsyncTask;
@@ -117,7 +114,12 @@ public class ItemDescriptionFragment extends Fragment {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
- startActivity(intent);
+ try {
+ startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ e.printStackTrace();
+ return false;
+ }
return true;
}
@@ -138,6 +140,7 @@ public class ItemDescriptionFragment extends Fragment {
}
});
+
registerForContextMenu(webvDescription);
return webvDescription;
}
@@ -371,11 +374,10 @@ public class ItemDescriptionFragment extends Fragment {
Callable<String> shownotesLoadTask = shownotesProvider.loadShownotes();
final String shownotes = shownotesLoadTask.call();
- data = "";
data = StringEscapeUtils.unescapeHtml4(shownotes);
Activity activity = getActivity();
if (activity != null) {
- TypedArray res = getActivity()
+ TypedArray res = activity
.getTheme()
.obtainStyledAttributes(
new int[]{android.R.attr.textColorPrimary});
diff --git a/src/de/danoeh/antennapod/fragment/ItemlistFragment.java b/src/de/danoeh/antennapod/fragment/ItemlistFragment.java
index 40637544d..282bb4d5c 100644
--- a/src/de/danoeh/antennapod/fragment/ItemlistFragment.java
+++ b/src/de/danoeh/antennapod/fragment/ItemlistFragment.java
@@ -50,7 +50,6 @@ public class ItemlistFragment extends ListFragment {
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 DownloadRequester requester = DownloadRequester.getInstance();
private Feed feed;
protected List<Long> queue;
@@ -61,6 +60,8 @@ public class ItemlistFragment extends ListFragment {
/** Argument for FeeditemlistAdapter */
protected boolean showFeedtitle;
+ private AsyncTask<Long, Void, Feed> currentLoadTask;
+
public ItemlistFragment(boolean showFeedtitle) {
super();
this.showFeedtitle = showFeedtitle;
@@ -116,11 +117,21 @@ public class ItemlistFragment extends ListFragment {
return inflater.inflate(R.layout.feeditemlist, container, false);
}
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- loadData();
- }
+ @Override
+ public void onStart() {
+ super.onStart();
+ EventDistributor.getInstance().register(contentUpdate);
+ loadData();
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ EventDistributor.getInstance().unregister(contentUpdate);
+ if (currentLoadTask != null) {
+ currentLoadTask.cancel(true);
+ }
+ }
protected void loadData() {
final long feedId;
@@ -156,8 +167,6 @@ public class ItemlistFragment extends ListFragment {
} else {
Log.e(TAG, "Could not load queue");
}
- if (result.getItems().isEmpty()) {
- }
setEmptyViewIfListIsEmpty();
if (fila != null) {
fila.notifyDataSetChanged();
@@ -171,6 +180,7 @@ public class ItemlistFragment extends ListFragment {
}
}
};
+ currentLoadTask = loadTask;
loadTask.execute(feedId);
}
@@ -188,17 +198,6 @@ public class ItemlistFragment extends ListFragment {
}
@Override
- public void onPause() {
- super.onPause();
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- EventDistributor.getInstance().unregister(contentUpdate);
- }
-
- @Override
public void onResume() {
super.onResume();
getActivity().runOnUiThread(new Runnable() {
@@ -209,7 +208,6 @@ public class ItemlistFragment extends ListFragment {
}
});
updateProgressBarVisibility();
- EventDistributor.getInstance().register(contentUpdate);
}
@Override
diff --git a/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java b/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java
index 99bef4bd8..ee1a3ea21 100644
--- a/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java
+++ b/src/de/danoeh/antennapod/miroguide/conn/MiroGuideConnector.java
@@ -16,6 +16,8 @@ import org.json.JSONObject;
import android.net.Uri;
+import de.danoeh.antennapod.util.LangUtils;
+
/** Executes HTTP requests and returns the results. */
public class MiroGuideConnector {
private HttpClient httpClient;
@@ -73,12 +75,14 @@ public class MiroGuideConnector {
if (response.getStatusLine().getStatusCode() == 200) {
HttpEntity entity = response.getEntity();
if (entity != null) {
- InputStream in = entity.getContent();
-
BufferedReader reader = new BufferedReader(
- new InputStreamReader(in));
- result = reader.readLine();
- in.close();
+ new InputStreamReader(entity.getContent(),
+ LangUtils.UTF_8));
+ try {
+ result = reader.readLine();
+ } finally {
+ reader.close();
+ }
}
} else {
throw new MiroGuideException(response.getStatusLine()
diff --git a/src/de/danoeh/antennapod/miroguide/model/MiroGuideItem.java b/src/de/danoeh/antennapod/miroguide/model/MiroGuideItem.java
index 89a2250df..cb5b15c56 100644
--- a/src/de/danoeh/antennapod/miroguide/model/MiroGuideItem.java
+++ b/src/de/danoeh/antennapod/miroguide/model/MiroGuideItem.java
@@ -12,7 +12,7 @@ public class MiroGuideItem {
super();
this.name = name;
this.description = description;
- this.date = date;
+ this.date = (Date) date.clone();
this.url = url;
}
@@ -30,7 +30,7 @@ public class MiroGuideItem {
}
public Date getDate() {
- return date;
+ return (Date) date.clone();
}
public String getUrl() {
diff --git a/src/de/danoeh/antennapod/preferences/UserPreferences.java b/src/de/danoeh/antennapod/preferences/UserPreferences.java
index a7df27ddc..0530ab466 100644
--- a/src/de/danoeh/antennapod/preferences/UserPreferences.java
+++ b/src/de/danoeh/antennapod/preferences/UserPreferences.java
@@ -2,9 +2,13 @@ package de.danoeh.antennapod.preferences;
import java.io.File;
import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
+import org.json.JSONArray;
+import org.json.JSONException;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -41,12 +45,14 @@ public class UserPreferences implements
public static final String PREF_ENABLE_AUTODL_WIFI_FILTER = "prefEnableAutoDownloadWifiFilter";
private static final String PREF_AUTODL_SELECTED_NETWORKS = "prefAutodownloadSelectedNetworks";
public static final String PREF_EPISODE_CACHE_SIZE = "prefEpisodeCacheSize";
+ private static final String PREF_PLAYBACK_SPEED = "prefPlaybackSpeed";
+ private static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray";
public static final String PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS = "prefPauseForFocusLoss";
private static int EPISODE_CACHE_SIZE_UNLIMITED = -1;
private static UserPreferences instance;
- private Context context;
+ private final Context context;
// Preferences
private boolean pauseOnHeadsetDisconnect;
@@ -61,6 +67,8 @@ public class UserPreferences implements
private boolean enableAutodownloadWifiFilter;
private String[] autodownloadSelectedNetworks;
private int episodeCacheSize;
+ private String playbackSpeed;
+ private String[] playbackSpeedArray;
private boolean pauseForFocusLoss;
private UserPreferences(Context context) {
@@ -85,6 +93,7 @@ public class UserPreferences implements
createNoMediaFile();
PreferenceManager.getDefaultSharedPreferences(context)
.registerOnSharedPreferenceChangeListener(instance);
+
}
private void loadPreferences() {
@@ -110,6 +119,9 @@ public class UserPreferences implements
episodeCacheSize = readEpisodeCacheSize(sp.getString(
PREF_EPISODE_CACHE_SIZE, "20"));
enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false);
+ playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0");
+ playbackSpeedArray = readPlaybackSpeedArray(sp.getString(
+ PREF_PLAYBACK_SPEED_ARRAY, null));
pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false);
}
@@ -138,6 +150,36 @@ public class UserPreferences implements
}
}
+ private String[] readPlaybackSpeedArray(String valueFromPrefs) {
+ String[] selectedSpeeds = null;
+ // If this preference hasn't been set yet, return the default options
+ if (valueFromPrefs == null) {
+ String[] allSpeeds = context.getResources().getStringArray(
+ R.array.playback_speed_values);
+ List<String> speedList = new LinkedList<String>();
+ for (String speedStr : allSpeeds) {
+ float speed = Float.parseFloat(speedStr);
+ if (speed < 2.0001 && speed * 10 % 1 == 0) {
+ speedList.add(speedStr);
+ }
+ }
+ selectedSpeeds = speedList.toArray(new String[speedList.size()]);
+ } else {
+ try {
+ JSONArray jsonArray = new JSONArray(valueFromPrefs);
+ selectedSpeeds = new String[jsonArray.length()];
+ for (int i = 0; i < jsonArray.length(); i++) {
+ selectedSpeeds[i] = jsonArray.getString(i);
+ }
+ } catch (JSONException e) {
+ Log.e(TAG,
+ "Got JSON error when trying to get speeds from JSONArray");
+ e.printStackTrace();
+ }
+ }
+ return selectedSpeeds;
+ }
+
private static void instanceAvailable() {
if (instance == null) {
throw new IllegalStateException(
@@ -172,7 +214,8 @@ public class UserPreferences implements
public static boolean isDisplayOnlyEpisodes() {
instanceAvailable();
- return instance.displayOnlyEpisodes;
+ //return instance.displayOnlyEpisodes;
+ return false;
}
public static boolean isAutoDelete() {
@@ -199,6 +242,16 @@ public class UserPreferences implements
return EPISODE_CACHE_SIZE_UNLIMITED;
}
+ public static String getPlaybackSpeed() {
+ instanceAvailable();
+ return instance.playbackSpeed;
+ }
+
+ public static String[] getPlaybackSpeedArray() {
+ instanceAvailable();
+ return instance.playbackSpeedArray;
+ }
+
/**
* Returns the capacity of the episode cache. This method will return the
* negative integer EPISODE_CACHE_SIZE_UNLIMITED if the cache size is set to
@@ -258,11 +311,31 @@ public class UserPreferences implements
PREF_EPISODE_CACHE_SIZE, "20"));
} else if (key.equals(PREF_ENABLE_AUTODL)) {
enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false);
+ } else if (key.equals(PREF_PLAYBACK_SPEED)) {
+ playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0");
+ } else if (key.equals(PREF_PLAYBACK_SPEED_ARRAY)) {
+ playbackSpeedArray = readPlaybackSpeedArray(sp.getString(
+ PREF_PLAYBACK_SPEED_ARRAY, null));
} else if (key.equals(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS)) {
pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false);
}
}
+ public static void setPlaybackSpeed(String speed) {
+ PreferenceManager.getDefaultSharedPreferences(instance.context).edit()
+ .putString(PREF_PLAYBACK_SPEED, speed).apply();
+ }
+
+ public static void setPlaybackSpeedArray(String[] speeds) {
+ JSONArray jsonArray = new JSONArray();
+ for (String speed : speeds) {
+ jsonArray.put(speed);
+ }
+ PreferenceManager.getDefaultSharedPreferences(instance.context).edit()
+ .putString(PREF_PLAYBACK_SPEED_ARRAY, jsonArray.toString())
+ .apply();
+ }
+
public static void setAutodownloadSelectedNetworks(Context context,
String[] value) {
SharedPreferences.Editor editor = PreferenceManager
diff --git a/src/de/danoeh/antennapod/service/PlaybackService.java b/src/de/danoeh/antennapod/service/PlaybackService.java
index d04046223..a5deb0a8a 100644
--- a/src/de/danoeh/antennapod/service/PlaybackService.java
+++ b/src/de/danoeh/antennapod/service/PlaybackService.java
@@ -45,9 +45,13 @@ 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;
+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;
/**
@@ -119,7 +123,12 @@ public class PlaybackService extends Service {
*/
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.
*/
@@ -132,13 +141,12 @@ public class PlaybackService extends Service {
private static final int NOTIFICATION_ID = 1;
+ private volatile IPlayer player;
+ private RemoteControlClient remoteControlClient;
private AudioManager audioManager;
private ComponentName mediaButtonReceiver;
- private MediaPlayer player;
- private RemoteControlClient remoteControlClient;
-
- private Playable media;
+ private volatile Playable media;
/**
* True if media should be streamed (Extracted from Intent Extra) .
@@ -252,7 +260,6 @@ public class PlaybackService extends Service {
}
);
dbLoaderExecutor = Executors.newSingleThreadExecutor();
- player = createMediaPlayer();
mediaButtonReceiver = new ComponentName(getPackageName(),
MediaButtonReceiver.class.getName());
@@ -273,22 +280,43 @@ public class PlaybackService extends Service {
loadQueue();
}
- private MediaPlayer createMediaPlayer() {
- return createMediaPlayer(new MediaPlayer());
- }
-
- private MediaPlayer createMediaPlayer(MediaPlayer mp) {
- if (mp != null) {
- mp.setOnPreparedListener(preparedListener);
- mp.setOnCompletionListener(completionListener);
- mp.setOnSeekCompleteListener(onSeekCompleteListener);
- mp.setOnErrorListener(onErrorListener);
- mp.setOnBufferingUpdateListener(onBufferingUpdateListener);
- mp.setOnInfoListener(onInfoListener);
+ private IPlayer createMediaPlayer() {
+ IPlayer player;
+ if (media == null || media.getMediaType() == MediaType.VIDEO) {
+ player = new VideoPlayer();
+ } else {
+ player = new AudioPlayer(this);
}
- return mp;
+ return createMediaPlayer(player);
}
+ private IPlayer createMediaPlayer(IPlayer mp) {
+ if (mp != null && media != null) {
+ if (media.getMediaType() == MediaType.AUDIO) {
+ ((AudioPlayer) mp).setOnPreparedListener(audioPreparedListener);
+ ((AudioPlayer) mp)
+ .setOnCompletionListener(audioCompletionListener);
+ ((AudioPlayer) mp)
+ .setOnSeekCompleteListener(audioSeekCompleteListener);
+ ((AudioPlayer) mp).setOnErrorListener(audioErrorListener);
+ ((AudioPlayer) mp)
+ .setOnBufferingUpdateListener(audioBufferingUpdateListener);
+ ((AudioPlayer) mp).setOnInfoListener(audioInfoListener);
+ } else {
+ ((VideoPlayer) mp).setOnPreparedListener(videoPreparedListener);
+ ((VideoPlayer) mp)
+ .setOnCompletionListener(videoCompletionListener);
+ ((VideoPlayer) mp)
+ .setOnSeekCompleteListener(videoSeekCompleteListener);
+ ((VideoPlayer) mp).setOnErrorListener(videoErrorListener);
+ ((VideoPlayer) mp)
+ .setOnBufferingUpdateListener(videoBufferingUpdateListener);
+ ((VideoPlayer) mp).setOnInfoListener(videoInfoListener);
+ }
+ }
+ return mp;
+ }
+
@SuppressLint("NewApi")
@Override
public void onDestroy() {
@@ -482,7 +510,7 @@ public class PlaybackService extends Service {
seekDelta(-PlaybackController.DEFAULT_SEEK_DELTA);
break;
}
- }
+ }
}
/**
@@ -575,6 +603,7 @@ public class PlaybackService extends Service {
Log.d(TAG, "Setting up media player");
try {
MediaType mediaType = media.getMediaType();
+ player = createMediaPlayer();
if (mediaType == MediaType.AUDIO) {
if (AppConfig.DEBUG)
Log.d(TAG, "Mime type is audio");
@@ -669,105 +698,169 @@ public class PlaybackService extends Service {
}
}
- private MediaPlayer.OnPreparedListener preparedListener = new MediaPlayer.OnPreparedListener() {
- @Override
- public void onPrepared(MediaPlayer mp) {
- 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();
+ 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);
+ }
+ };
- if (startWhenPrepared) {
- play();
- }
- }
- };
+ 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();
- private MediaPlayer.OnSeekCompleteListener onSeekCompleteListener = new MediaPlayer.OnSeekCompleteListener() {
+ if (startWhenPrepared) {
+ play();
+ }
+ }
- @Override
- public void onSeekComplete(MediaPlayer mp) {
- if (status == PlayerStatus.SEEKING) {
- setStatus(statusBeforeSeek);
- }
+ 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 MediaPlayer.OnInfoListener onInfoListener = new MediaPlayer.OnInfoListener() {
+ private final void genericSeekCompleteListener() {
+ if (status == PlayerStatus.SEEKING) {
+ setStatus(statusBeforeSeek);
+ }
+ }
- @Override
- public boolean onInfo(MediaPlayer mp, int what, int extra) {
- 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.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 MediaPlayer.OnErrorListener onErrorListener = new MediaPlayer.OnErrorListener() {
- private static final String TAG = "PlaybackService.onErrorListener";
+ 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);
+ }
+ };
- @Override
- public boolean onError(MediaPlayer mp, int what, int extra) {
- Log.w(TAG, "An error has occured: " + what);
- if (mp.isPlaying()) {
- pause(true, true);
- }
- sendNotificationBroadcast(NOTIFICATION_TYPE_ERROR, what);
- setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING);
- stopSelf();
- return true;
- }
- };
+ 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 MediaPlayer.OnCompletionListener completionListener = new MediaPlayer.OnCompletionListener() {
+ 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);
+ }
+ };
- @Override
- public void onCompletion(MediaPlayer mp) {
- endPlayback(true);
- }
- };
+ 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 MediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() {
+ 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;
+ }
- @Override
- public void onBufferingUpdate(MediaPlayer mp, int percent) {
- sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent);
+ 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)
@@ -790,7 +883,6 @@ public class PlaybackService extends Service {
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;
@@ -870,7 +962,7 @@ 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 reinit
@@ -946,6 +1038,7 @@ public class PlaybackService extends Service {
Log.d(TAG, "Resuming/Starting playback");
writePlaybackPreferences();
+ setSpeed(Float.parseFloat(UserPreferences.getPlaybackSpeed()));
player.start();
if (status != PlayerStatus.PAUSED) {
player.seekTo((int) media.getPosition());
@@ -1131,7 +1224,7 @@ public class PlaybackService extends Service {
/**
* Seek a specific position from the current position
- *
+ *
* @param delta
* offset from current position (positive or negative)
* */
@@ -1289,18 +1382,20 @@ public class PlaybackService extends Service {
isPlaying = true;
}
- Intent i = new Intent(AVRCP_ACTION_PLAYER_STATUS_CHANGED);
- i.putExtra("id", 1);
- i.putExtra("artist", "");
- i.putExtra("album", media.getFeedTitle());
- i.putExtra("track", media.getEpisodeTitle());
- i.putExtra("playing", isPlaying);
- if (queue != null) {
- i.putExtra("ListSize", queue.size());
+ if (media != null) {
+ Intent i = new Intent(AVRCP_ACTION_PLAYER_STATUS_CHANGED);
+ i.putExtra("id", 1);
+ i.putExtra("artist", "");
+ i.putExtra("album", media.getFeedTitle());
+ i.putExtra("track", media.getEpisodeTitle());
+ i.putExtra("playing", isPlaying);
+ if (queue != null) {
+ i.putExtra("ListSize", queue.size());
+ }
+ i.putExtra("duration", media.getDuration());
+ i.putExtra("position", media.getPosition());
+ sendBroadcast(i);
}
- i.putExtra("duration", media.getDuration());
- i.putExtra("position", media.getPosition());
- sendBroadcast(i);
}
/**
@@ -1377,7 +1472,7 @@ public class PlaybackService extends Service {
}
}
}
- };
+ };
/** Periodically saves the position of the media file */
class PositionSaver implements Runnable {
@@ -1479,7 +1574,7 @@ public class PlaybackService extends Service {
return media;
}
- public MediaPlayer getPlayer() {
+ public IPlayer getPlayer() {
return player;
}
@@ -1492,6 +1587,53 @@ public class PlaybackService extends Service {
postStatusUpdateIntent();
}
+ public boolean canSetSpeed() {
+ if (player != null && media != null && media.getMediaType() == MediaType.AUDIO) {
+ return ((AudioPlayer) player).canSetSpeed();
+ }
+ return false;
+ }
+
+ public boolean canSetPitch() {
+ if (player != null && media != null && media.getMediaType() == MediaType.AUDIO) {
+ return ((AudioPlayer) player).canSetPitch();
+ }
+ return false;
+ }
+
+ public void setSpeed(float 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(float pitch) {
+ if (media != null && media.getMediaType() == MediaType.AUDIO) {
+ AudioPlayer audioPlayer = (AudioPlayer) player;
+ if (audioPlayer.canSetPitch()) {
+ audioPlayer.setPlaybackPitch((float) pitch);
+ }
+ }
+ }
+
+ public float 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
diff --git a/src/de/danoeh/antennapod/service/download/DownloadService.java b/src/de/danoeh/antennapod/service/download/DownloadService.java
index 2056efab2..4040c85a8 100644
--- a/src/de/danoeh/antennapod/service/download/DownloadService.java
+++ b/src/de/danoeh/antennapod/service/download/DownloadService.java
@@ -184,7 +184,7 @@ public class DownloadService extends Service {
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent.getParcelableExtra(EXTRA_REQUEST) != null) {
onDownloadQueued(intent);
- } else if (numberOfDownloads.equals(0)) {
+ } else if (numberOfDownloads.get() == 0) {
stopSelf();
}
return Service.START_NOT_STICKY;
@@ -421,52 +421,24 @@ public class DownloadService extends Service {
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));
+ handler.post(new Runnable() {
+ @Override
+ public void run() {
+ 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));
+ }
+ });
}
/**
@@ -561,7 +533,7 @@ public class DownloadService extends Service {
Log.d(TAG, numberOfDownloads.get() + " downloads left");
}
- if (numberOfDownloads.get() <= 0) {
+ if (numberOfDownloads.get() <= 0 && DownloadRequester.getInstance().hasNoDownloads()) {
if (AppConfig.DEBUG)
Log.d(TAG, "Number of downloads is " + numberOfDownloads.get() + ", attempting shutdown");
stopSelf();
@@ -647,28 +619,21 @@ public class DownloadService extends Service {
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();
- DBWriter.addDownloadStatus(
- DownloadService.this,
- new DownloadStatus(
- savedFeedRef.getImage(),
- savedFeedRef
- .getImage()
- .getHumanReadableIdentifier(),
- DownloadError.ERROR_REQUEST_ERROR,
- false, e.getMessage()));
- }
- }
- });
-
+ 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) {
@@ -730,7 +695,7 @@ public class DownloadService extends Service {
}
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;
@@ -835,8 +800,9 @@ public class DownloadService extends Service {
media.setFile_url(request.getDestination());
// Get duration
- MediaPlayer mediaplayer = new MediaPlayer();
+ MediaPlayer mediaplayer = null;
try {
+ mediaplayer = new MediaPlayer();
mediaplayer.setDataSource(media.getFile_url());
mediaplayer.prepare();
media.setDuration(mediaplayer.getDuration());
@@ -845,8 +811,13 @@ public class DownloadService extends Service {
mediaplayer.reset();
} catch (IOException e) {
e.printStackTrace();
+ } catch (RuntimeException e) {
+ // Thrown by MediaPlayer initialization on some devices
+ e.printStackTrace();
} finally {
- mediaplayer.release();
+ if (mediaplayer != null) {
+ mediaplayer.release();
+ }
}
if (media.getItem().getChapters() == null) {
diff --git a/src/de/danoeh/antennapod/service/download/DownloadStatus.java b/src/de/danoeh/antennapod/service/download/DownloadStatus.java
index 62e54cbb4..487c3b3de 100644
--- a/src/de/danoeh/antennapod/service/download/DownloadStatus.java
+++ b/src/de/danoeh/antennapod/service/download/DownloadStatus.java
@@ -52,7 +52,7 @@ public class DownloadStatus {
this.feedfileId = feedfileId;
this.reason = reason;
this.successful = successful;
- this.completionDate = completionDate;
+ this.completionDate = (Date) completionDate.clone();
this.reasonDetailed = reasonDetailed;
this.feedfileType = feedfileType;
}
@@ -133,7 +133,7 @@ public class DownloadStatus {
}
public Date getCompletionDate() {
- return completionDate;
+ return (Date) completionDate.clone();
}
public long getFeedfileId() {
@@ -162,6 +162,7 @@ public class DownloadStatus {
this.successful = false;
this.reason = reason;
this.reasonDetailed = reasonDetailed;
+ this.done = true;
}
public void setCancelled() {
@@ -172,7 +173,7 @@ public class DownloadStatus {
}
public void setCompletionDate(Date completionDate) {
- this.completionDate = completionDate;
+ this.completionDate = (Date) completionDate.clone();
}
public void setId(long id) {
diff --git a/src/de/danoeh/antennapod/service/download/HttpDownloader.java b/src/de/danoeh/antennapod/service/download/HttpDownloader.java
index c9671ceb3..582fb9575 100644
--- a/src/de/danoeh/antennapod/service/download/HttpDownloader.java
+++ b/src/de/danoeh/antennapod/service/download/HttpDownloader.java
@@ -6,12 +6,12 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import org.apache.commons.io.IOUtils;
+import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
@@ -30,161 +30,186 @@ import de.danoeh.antennapod.util.DownloadError;
import de.danoeh.antennapod.util.StorageUtils;
public class HttpDownloader extends Downloader {
- private static final String TAG = "HttpDownloader";
-
- private static final int MAX_REDIRECTS = 5;
-
- private static final int BUFFER_SIZE = 8 * 1024;
- private static final int CONNECTION_TIMEOUT = 30000;
- private static final int SOCKET_TIMEOUT = 30000;
-
- public HttpDownloader(DownloadRequest request) {
- super(request);
- }
-
- private DefaultHttpClient createHttpClient() {
- DefaultHttpClient httpClient = new DefaultHttpClient();
- HttpParams params = httpClient.getParams();
- params.setIntParameter("http.protocol.max-redirects", MAX_REDIRECTS);
- params.setBooleanParameter("http.protocol.reject-relative-redirect",
- false);
- HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT);
- HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT);
- HttpClientParams.setRedirecting(params, true);
-
- // Workaround for broken URLs in redirection
- ((AbstractHttpClient) httpClient)
- .setRedirectHandler(new APRedirectHandler());
- return httpClient;
- }
-
- @Override
- protected void download() {
- DefaultHttpClient httpClient = null;
- OutputStream out = null;
- InputStream connection = null;
- try {
- HttpGet httpGet = new HttpGet(request.getSource());
- httpClient = createHttpClient();
- HttpResponse response = httpClient.execute(httpGet);
- HttpEntity httpEntity = response.getEntity();
- int responseCode = response.getStatusLine().getStatusCode();
- if (AppConfig.DEBUG)
- Log.d(TAG, "Response code is " + responseCode);
- if (responseCode == HttpURLConnection.HTTP_OK && httpEntity != null) {
- if (StorageUtils.storageAvailable(PodcastApp.getInstance())) {
- File destination = new File(request.getDestination());
- if (!destination.exists()) {
- connection = AndroidHttpClient
- .getUngzippedContent(httpEntity);
- InputStream in = new BufferedInputStream(connection);
- out = new BufferedOutputStream(new FileOutputStream(
- destination));
- byte[] buffer = new byte[BUFFER_SIZE];
- int count = 0;
- request.setStatusMsg(R.string.download_running);
- if (AppConfig.DEBUG)
- Log.d(TAG, "Getting size of download");
- request.setSize(httpEntity.getContentLength());
- if (AppConfig.DEBUG)
- 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 (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);
- request.setSoFar(request.getSoFar() + count);
- request.setProgressPercent((int) (((double) request
- .getSoFar() / (double) request
- .getSize()) * 100));
- }
- if (cancelled) {
- onCancelled();
- } else {
- onSuccess();
- }
- } else {
- onFail(DownloadError.ERROR_NOT_ENOUGH_SPACE, null);
- }
- } else {
- Log.w(TAG, "File already exists");
- onFail(DownloadError.ERROR_FILE_EXISTS, null);
- }
- } else {
- onFail(DownloadError.ERROR_DEVICE_NOT_FOUND, null);
- }
- } else {
- onFail(DownloadError.ERROR_HTTP_DATA_ERROR,
- String.valueOf(responseCode));
- }
- } catch (IllegalArgumentException e) {
- e.printStackTrace();
- onFail(DownloadError.ERROR_MALFORMED_URL, e.getMessage());
- } catch (SocketTimeoutException e) {
- e.printStackTrace();
- onFail(DownloadError.ERROR_CONNECTION_ERROR, e.getMessage());
- } catch (UnknownHostException e) {
- e.printStackTrace();
- onFail(DownloadError.ERROR_UNKNOWN_HOST, e.getMessage());
- } catch (IOException e) {
- e.printStackTrace();
- onFail(DownloadError.ERROR_IO_ERROR, e.getMessage());
- } catch (NullPointerException e) {
- // might be thrown by connection.getInputStream()
- e.printStackTrace();
- onFail(DownloadError.ERROR_CONNECTION_ERROR, request.getSource());
- } finally {
- IOUtils.closeQuietly(out);
- if (httpClient != null) {
- httpClient.getConnectionManager().shutdown();
- }
- }
- }
-
- private void onSuccess() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Download was successful");
- result.setSuccessful();
- }
-
- private void onFail(DownloadError reason, String reasonDetailed) {
- if (AppConfig.DEBUG) {
- Log.d(TAG, "Download failed");
- }
+ private static final String TAG = "HttpDownloader";
+
+ private static final int MAX_REDIRECTS = 5;
+
+ private static final int BUFFER_SIZE = 8 * 1024;
+ private static final int CONNECTION_TIMEOUT = 30000;
+ private static final int SOCKET_TIMEOUT = 30000;
+
+ public HttpDownloader(DownloadRequest request) {
+ super(request);
+ }
+
+ private DefaultHttpClient createHttpClient() {
+ DefaultHttpClient httpClient = new DefaultHttpClient();
+ HttpParams params = httpClient.getParams();
+ params.setIntParameter("http.protocol.max-redirects", MAX_REDIRECTS);
+ params.setBooleanParameter("http.protocol.reject-relative-redirect",
+ false);
+ HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT);
+ HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT);
+ HttpClientParams.setRedirecting(params, true);
+
+ // Workaround for broken URLs in redirection
+ ((AbstractHttpClient) httpClient)
+ .setRedirectHandler(new APRedirectHandler());
+ return httpClient;
+ }
+
+ @Override
+ protected void download() {
+ DefaultHttpClient httpClient = null;
+ BufferedOutputStream out = null;
+ InputStream connection = null;
+ try {
+ HttpGet httpGet = new HttpGet(request.getSource());
+ httpClient = createHttpClient();
+ HttpResponse response = httpClient.execute(httpGet);
+ HttpEntity httpEntity = response.getEntity();
+ int responseCode = response.getStatusLine().getStatusCode();
+ Header contentEncodingHeader = response.getFirstHeader("Content-Encoding");
+
+ final boolean isGzip = contentEncodingHeader != null &&
+ contentEncodingHeader.getValue().equalsIgnoreCase("gzip");
+
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Response code is " + responseCode);
+
+ if (responseCode != HttpURLConnection.HTTP_OK || httpEntity == null) {
+ onFail(DownloadError.ERROR_HTTP_DATA_ERROR,
+ String.valueOf(responseCode));
+ return;
+ }
+
+ if (!StorageUtils.storageAvailable(PodcastApp.getInstance())) {
+ onFail(DownloadError.ERROR_DEVICE_NOT_FOUND, null);
+ return;
+ }
+
+ File destination = new File(request.getDestination());
+ if (destination.exists()) {
+ Log.w(TAG, "File already exists");
+ onFail(DownloadError.ERROR_FILE_EXISTS, null);
+ return;
+ }
+
+ connection = new BufferedInputStream(AndroidHttpClient
+ .getUngzippedContent(httpEntity));
+ out = new BufferedOutputStream(new FileOutputStream(
+ destination));
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int count = 0;
+ request.setStatusMsg(R.string.download_running);
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Getting size of download");
+ request.setSize(httpEntity.getContentLength());
+ if (AppConfig.DEBUG)
+ 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 (request.getSize() != DownloadStatus.SIZE_UNKNOWN
+ && request.getSize() > freeSpace) {
+ onFail(DownloadError.ERROR_NOT_ENOUGH_SPACE, null);
+ return;
+ }
+
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Starting download");
+ while (!cancelled
+ && (count = connection.read(buffer)) != -1) {
+ out.write(buffer, 0, count);
+ request.setSoFar(request.getSoFar() + count);
+ request.setProgressPercent((int) (((double) request
+ .getSoFar() / (double) request
+ .getSize()) * 100));
+ }
+ if (cancelled) {
+ onCancelled();
+ } else {
+ out.flush();
+ // check if size specified in the response header is the same as the size of the
+ // written file. This check cannot be made if compression was used
+ if (!isGzip && request.getSize() != DownloadStatus.SIZE_UNKNOWN &&
+ request.getSoFar() != request.getSize()) {
+ onFail(DownloadError.ERROR_IO_ERROR,
+ "Download completed but size: " +
+ request.getSoFar() +
+ " does not equal expected size " +
+ request.getSize());
+ return;
+ }
+ onSuccess();
+ }
+
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ onFail(DownloadError.ERROR_MALFORMED_URL, e.getMessage());
+ } catch (SocketTimeoutException e) {
+ e.printStackTrace();
+ onFail(DownloadError.ERROR_CONNECTION_ERROR, e.getMessage());
+ } catch (UnknownHostException e) {
+ e.printStackTrace();
+ onFail(DownloadError.ERROR_UNKNOWN_HOST, e.getMessage());
+ } catch (IOException e) {
+ e.printStackTrace();
+ onFail(DownloadError.ERROR_IO_ERROR, e.getMessage());
+ } catch (NullPointerException e) {
+ // might be thrown by connection.getInputStream()
+ e.printStackTrace();
+ onFail(DownloadError.ERROR_CONNECTION_ERROR, request.getSource());
+ } finally {
+ IOUtils.closeQuietly(out);
+ if (httpClient != null) {
+ httpClient.getConnectionManager().shutdown();
+ }
+ }
+ }
+
+ private void onSuccess() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Download was successful");
+ result.setSuccessful();
+ }
+
+ private void onFail(DownloadError reason, String reasonDetailed) {
+ if (AppConfig.DEBUG) {
+ Log.d(TAG, "Download failed");
+ }
result.setFailed(reason, reasonDetailed);
- cleanup();
- }
+ cleanup();
+ }
- private void onCancelled() {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Download was cancelled");
+ private void onCancelled() {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Download was cancelled");
result.setCancelled();
- cleanup();
- }
-
- /** Deletes unfinished downloads. */
- private void cleanup() {
- if (request.getDestination() != null) {
- File dest = new File(request.getDestination());
- if (dest.exists()) {
- boolean rc = dest.delete();
- if (AppConfig.DEBUG)
- Log.d(TAG, "Deleted file " + dest.getName() + "; Result: "
- + rc);
- } else {
- if (AppConfig.DEBUG)
- Log.d(TAG, "cleanup() didn't delete file: does not exist.");
- }
- }
- }
+ cleanup();
+ }
+
+ /**
+ * Deletes unfinished downloads.
+ */
+ private void cleanup() {
+ if (request.getDestination() != null) {
+ File dest = new File(request.getDestination());
+ if (dest.exists()) {
+ boolean rc = dest.delete();
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Deleted file " + dest.getName() + "; Result: "
+ + rc);
+ } else {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "cleanup() didn't delete file: does not exist.");
+ }
+ }
+ }
}
diff --git a/src/de/danoeh/antennapod/storage/DBReader.java b/src/de/danoeh/antennapod/storage/DBReader.java
index c96051874..28ab3d939 100644
--- a/src/de/danoeh/antennapod/storage/DBReader.java
+++ b/src/de/danoeh/antennapod/storage/DBReader.java
@@ -229,9 +229,11 @@ public final class DBReader {
title, item, link);
break;
}
- chapter.setId(chapterCursor
- .getLong(PodDBAdapter.KEY_ID_INDEX));
- item.getChapters().add(chapter);
+ if (chapter != null) {
+ chapter.setId(chapterCursor
+ .getLong(PodDBAdapter.KEY_ID_INDEX));
+ item.getChapters().add(chapter);
+ }
} while (chapterCursor.moveToNext());
}
chapterCursor.close();
diff --git a/src/de/danoeh/antennapod/storage/DBTasks.java b/src/de/danoeh/antennapod/storage/DBTasks.java
index b1efda658..ba2e743a8 100644
--- a/src/de/danoeh/antennapod/storage/DBTasks.java
+++ b/src/de/danoeh/antennapod/storage/DBTasks.java
@@ -28,6 +28,7 @@ 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.comparator.FeedItemPubdateComparator;
import de.danoeh.antennapod.util.exception.MediaFileNotFoundException;
/**
@@ -406,12 +407,13 @@ public final class DBTasks {
private static int performAutoCleanup(final Context context,
final int episodeNumber) {
- List<FeedItem> candidates = DBReader.getDownloadedItems(context);
- List<FeedItem> queue = DBReader.getQueue(context);
+ List<FeedItem> candidates = new ArrayList<FeedItem>();
+ List<FeedItem> downloadedItems = DBReader.getDownloadedItems(context);
+ QueueAccess queue = QueueAccess.IDListAccess(DBReader.getQueueIDList(context));
List<FeedItem> delete;
- for (FeedItem item : candidates) {
+ for (FeedItem item : downloadedItems) {
if (item.hasMedia() && item.getMedia().isDownloaded()
- && !queue.contains(item) && item.isRead()) {
+ && !queue.contains(item.getId()) && item.isRead()) {
candidates.add(item);
}
@@ -440,7 +442,13 @@ public final class DBTasks {
}
for (FeedItem item : delete) {
- DBWriter.deleteFeedMediaOfItem(context, item.getId());
+ try {
+ DBWriter.deleteFeedMediaOfItem(context, item.getId()).get();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ }
}
int counter = delete.size();
@@ -561,6 +569,7 @@ public final class DBTasks {
Log.d(TAG, "Feed with title " + newFeed.getTitle()
+ " already exists. Syncing new with existing one.");
+ Collections.sort(newFeed.getItems(), new FeedItemPubdateComparator());
savedFeed.setItems(DBReader.getFeedItemList(context, savedFeed));
if (savedFeed.compareWithOther(newFeed)) {
if (AppConfig.DEBUG)
@@ -578,7 +587,7 @@ public final class DBTasks {
final int i = idx;
item.setFeed(savedFeed);
savedFeed.getItems().add(i, item);
- DBWriter.markItemRead(context, item.getId(), false);
+ item.setRead(false);
} else {
oldItem.updateFromOther(item);
}
diff --git a/src/de/danoeh/antennapod/storage/DBWriter.java b/src/de/danoeh/antennapod/storage/DBWriter.java
index 5cdd6aee4..d96299dbe 100644
--- a/src/de/danoeh/antennapod/storage/DBWriter.java
+++ b/src/de/danoeh/antennapod/storage/DBWriter.java
@@ -73,7 +73,10 @@ public class DBWriter {
}
media.setDownloaded(false);
media.setFile_url(null);
- setFeedMedia(context, media);
+ 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
@@ -212,7 +215,7 @@ public class DBWriter {
media.setPlaybackCompletionDate(new Date());
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
- adapter.setMedia(media);
+ adapter.setFeedMediaPlaybackCompletionDate(media);
adapter.close();
EventDistributor.getInstance().sendPlaybackHistoryUpdateBroadcast();
@@ -654,18 +657,18 @@ public class DBWriter {
}
/**
- * Saves only value of the 'position'-attribute of a FeedMedia object.
+ * 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<?> setFeedMediaPosition(final Context context, final FeedMedia media) {
+ 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.setFeedMediaPosition(media);
+ adapter.setFeedMediaPlaybackInformation(media);
adapter.close();
}
});
diff --git a/src/de/danoeh/antennapod/storage/DownloadRequester.java b/src/de/danoeh/antennapod/storage/DownloadRequester.java
index 246b8bdfd..013162f0c 100644
--- a/src/de/danoeh/antennapod/storage/DownloadRequester.java
+++ b/src/de/danoeh/antennapod/storage/DownloadRequester.java
@@ -26,9 +26,9 @@ 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/";
+ public static final String IMAGE_DOWNLOADPATH = "images/";
+ public static final String FEED_DOWNLOADPATH = "cache/";
+ public static final String MEDIA_DOWNLOADPATH = "media/";
private static DownloadRequester downloader;
@@ -38,7 +38,7 @@ public class DownloadRequester {
downloads = new ConcurrentHashMap<String, DownloadRequest>();
}
- public static DownloadRequester getInstance() {
+ public static synchronized DownloadRequester getInstance() {
if (downloader == null) {
downloader = new DownloadRequester();
}
diff --git a/src/de/danoeh/antennapod/storage/FeedItemStatistics.java b/src/de/danoeh/antennapod/storage/FeedItemStatistics.java
index 17e838761..6b79dd144 100644
--- a/src/de/danoeh/antennapod/storage/FeedItemStatistics.java
+++ b/src/de/danoeh/antennapod/storage/FeedItemStatistics.java
@@ -17,7 +17,7 @@ public class FeedItemStatistics {
this.numberOfItems = numberOfItems;
this.numberOfNewItems = numberOfNewItems;
this.numberOfInProgressItems = numberOfInProgressItems;
- this.lastUpdate = lastUpdate;
+ this.lastUpdate = (lastUpdate != null) ? (Date) lastUpdate.clone() : null;
}
public long getFeedID() {
@@ -37,6 +37,6 @@ public class FeedItemStatistics {
}
public Date getLastUpdate() {
- return lastUpdate;
+ return (lastUpdate != null) ? (Date) lastUpdate.clone() : null;
}
}
diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java
index edcbe1cf4..d36d6184c 100644
--- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java
+++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java
@@ -10,7 +10,6 @@ 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;
@@ -235,7 +234,7 @@ public class PodDBAdapter {
/**
* Select id, description and content-encoded column from feeditems.
*/
- public static final String[] SEL_FI_EXTRA = {KEY_ID, KEY_DESCRIPTION,
+ private static final String[] SEL_FI_EXTRA = {KEY_ID, KEY_DESCRIPTION,
KEY_CONTENT_ENCODED, KEY_FEED};
// column indices for SEL_FI_EXTRA
@@ -279,6 +278,13 @@ public class PodDBAdapter {
//db.close();
}
+ public static boolean deleteDatabase(Context context) {
+ Log.w(TAG, "Deleting database");
+ dbHelperSingleton.close();
+ dbHelperSingleton = null;
+ return context.deleteDatabase(DATABASE_NAME);
+ }
+
/**
* Inserts or updates a feed entry
*
@@ -361,6 +367,7 @@ public class PodDBAdapter {
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());
@@ -379,14 +386,26 @@ public class PodDBAdapter {
return media.getId();
}
- public void setFeedMediaPosition(FeedMedia media) {
+ 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");
+ }
+ }
+
+ public void setFeedMediaPlaybackCompletionDate(FeedMedia media) {
+ if (media.getId() != 0) {
+ ContentValues values = new ContentValues();
+ values.put(KEY_PLAYBACK_COMPLETION_DATE, media.getPlaybackCompletionDate().getTime());
db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?",
new String[]{String.valueOf(media.getId())});
} else {
- Log.e(TAG, "setFeedMediaPosition: ID of media was 0");
+ Log.e(TAG, "setFeedMediaPlaybackCompletionDate: ID of media was 0");
}
}
@@ -397,8 +416,10 @@ public class PodDBAdapter {
public void setCompleteFeed(Feed feed) {
db.beginTransaction();
setFeed(feed);
- for (FeedItem item : feed.getItemsArray()) {
- setFeedItem(item);
+ if (feed.getItems() != null) {
+ for (FeedItem item : feed.getItems()) {
+ setFeedItem(item, false);
+ }
}
db.setTransactionSuccessful();
db.endTransaction();
@@ -407,7 +428,7 @@ public class PodDBAdapter {
public void setFeedItemlist(List<FeedItem> items) {
db.beginTransaction();
for (FeedItem item : items) {
- setFeedItem(item);
+ setFeedItem(item, true);
}
db.setTransactionSuccessful();
db.endTransaction();
@@ -415,7 +436,7 @@ public class PodDBAdapter {
public long setSingleFeedItem(FeedItem item) {
db.beginTransaction();
- long result = setFeedItem(item);
+ long result = setFeedItem(item, true);
db.setTransactionSuccessful();
db.endTransaction();
return result;
@@ -423,10 +444,12 @@ public class PodDBAdapter {
/**
* 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) {
+ private long setFeedItem(FeedItem item, boolean saveFeed) {
ContentValues values = new ContentValues();
values.put(KEY_TITLE, item.getTitle());
values.put(KEY_LINK, item.getLink());
@@ -438,7 +461,7 @@ public class PodDBAdapter {
}
values.put(KEY_PUBDATE, item.getPubDate().getTime());
values.put(KEY_PAYMENT_LINK, item.getPaymentLink());
- if (item.getFeed() != null) {
+ if (saveFeed && item.getFeed() != null) {
setFeed(item.getFeed());
}
values.put(KEY_FEED, item.getFeed().getId());
@@ -603,8 +626,10 @@ public class PodDBAdapter {
if (feed.getImage() != null) {
removeFeedImage(feed.getImage());
}
- for (FeedItem item : feed.getItemsArray()) {
- removeFeedItem(item);
+ if (feed.getItems() != null) {
+ for (FeedItem item : feed.getItems()) {
+ removeFeedItem(item);
+ }
}
db.delete(TABLE_NAME_FEEDS, KEY_ID + "=?",
new String[]{String.valueOf(feed.getId())});
@@ -756,7 +781,7 @@ public class PodDBAdapter {
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_FEEDITEM + " WHERE "
+ TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + ">0";
Cursor c = db.rawQuery(query, null);
return c;
@@ -985,7 +1010,7 @@ public class PodDBAdapter {
" 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)" +
+ " FROM FeedItems LEFT 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() {
diff --git a/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java b/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java
index 1efaac359..bcb0422ce 100644
--- a/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java
+++ b/src/de/danoeh/antennapod/syndication/namespace/atom/NSAtom.java
@@ -121,7 +121,7 @@ public class NSAtom extends Namespace {
if (state.getContentBuf() != null) {
content = state.getContentBuf().toString();
} else {
- content = new String();
+ content = "";
}
SyndElement topElement = state.getTagstack().peek();
String top = topElement.getName();
diff --git a/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java b/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java
index 30835434f..a1ed01354 100644
--- a/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java
+++ b/src/de/danoeh/antennapod/syndication/util/SyndDateUtils.java
@@ -11,7 +11,7 @@ import android.util.Log;
public class SyndDateUtils {
private static final String TAG = "DateUtils";
- public static final String[] RFC822DATES = { "dd MMM yy HH:mm:ss Z", };
+ private static final String[] RFC822DATES = { "dd MMM yy HH:mm:ss Z", };
/** RFC 3339 date format for UTC dates. */
public static final String RFC3339UTC = "yyyy-MM-dd'T'HH:mm:ss'Z'";
@@ -123,12 +123,12 @@ public class SyndDateUtils {
int idx = 0;
if (parts.length == 3) {
// string has hours
- result += Integer.valueOf(parts[idx]) * 3600000;
+ result += Integer.valueOf(parts[idx]) * 3600000L;
idx++;
}
- result += Integer.valueOf(parts[idx]) * 60000;
+ result += Integer.valueOf(parts[idx]) * 60000L;
idx++;
- result += (Float.valueOf(parts[idx])) * 1000;
+ result += (Float.valueOf(parts[idx])) * 1000L;
return result;
}
}
diff --git a/src/de/danoeh/antennapod/util/ChapterUtils.java b/src/de/danoeh/antennapod/util/ChapterUtils.java
index ac8149119..521bfebea 100644
--- a/src/de/danoeh/antennapod/util/ChapterUtils.java
+++ b/src/de/danoeh/antennapod/util/ChapterUtils.java
@@ -35,9 +35,9 @@ public class ChapterUtils {
* chapters.
*/
public static void readID3ChaptersFromPlayableStreamUrl(Playable p) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle());
if (p != null && p.getStreamUrl() != null) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle());
InputStream in = null;
try {
URL url = new URL(p.getStreamUrl());
@@ -86,9 +86,9 @@ public class ChapterUtils {
* chapters.
*/
public static void readID3ChaptersFromPlayableFileUrl(Playable p) {
- if (AppConfig.DEBUG)
- Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle());
if (p != null && p.localFileAvailable() && p.getLocalMediaUrl() != null) {
+ if (AppConfig.DEBUG)
+ Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle());
File source = new File(p.getLocalMediaUrl());
if (source.exists()) {
ChapterReader reader = new ChapterReader();
diff --git a/src/de/danoeh/antennapod/util/DuckType.java b/src/de/danoeh/antennapod/util/DuckType.java
new file mode 100644
index 000000000..0dfc01508
--- /dev/null
+++ b/src/de/danoeh/antennapod/util/DuckType.java
@@ -0,0 +1,115 @@
+/* Adapted from: http://thinking-in-code.blogspot.com/2008/11/duck-typing-in-java-using-dynamic.html */
+
+package de.danoeh.antennapod.util;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * Allows "duck typing" or dynamic invocation based on method signature rather
+ * than type hierarchy. In other words, rather than checking whether something
+ * IS-a duck, check whether it WALKS-like-a duck or QUACKS-like a duck.
+ *
+ * To use first use the coerce static method to indicate the object you want to
+ * do Duck Typing for, then specify an interface to the to method which you want
+ * to coerce the type to, e.g:
+ *
+ * public interface Foo { void aMethod(); } class Bar { ... public void
+ * aMethod() { ... } ... } Bar bar = ...; Foo foo =
+ * DuckType.coerce(bar).to(Foo.class); foo.aMethod();
+ *
+ *
+ */
+public class DuckType {
+
+ private final Object objectToCoerce;
+
+ private DuckType(Object objectToCoerce) {
+ this.objectToCoerce = objectToCoerce;
+ }
+
+ private class CoercedProxy implements InvocationHandler {
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ Method delegateMethod = findMethodBySignature(method);
+ assert delegateMethod != null;
+ return delegateMethod.invoke(DuckType.this.objectToCoerce, args);
+ }
+ }
+
+ /**
+ * Specify the duck typed object to coerce.
+ *
+ * @param object
+ * the object to coerce
+ * @return
+ */
+ public static DuckType coerce(Object object) {
+ return new DuckType(object);
+ }
+
+ /**
+ * Coerce the Duck Typed object to the given interface providing it
+ * implements all the necessary methods.
+ *
+ * @param
+ * @param iface
+ * @return an instance of the given interface that wraps the duck typed
+ * class
+ * @throws ClassCastException
+ * if the object being coerced does not implement all the
+ * methods in the given interface.
+ */
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public <T> T to(Class iface) {
+ assert iface.isInterface() : "cannot coerce object to a class, must be an interface";
+ if (isA(iface)) {
+ return (T) iface.cast(objectToCoerce);
+ }
+ if (quacksLikeA(iface)) {
+ return generateProxy(iface);
+ }
+ throw new ClassCastException("Could not coerce object of type " + objectToCoerce.getClass() + " to " + iface);
+ }
+
+ @SuppressWarnings("rawtypes")
+ private boolean isA(Class iface) {
+ return objectToCoerce.getClass().isInstance(iface);
+ }
+
+ /**
+ * Determine whether the duck typed object can be used with the given
+ * interface.
+ *
+ * @param Type
+ * of the interface to check.
+ * @param iface
+ * Interface class to check
+ * @return true if the object will support all the methods in the interface,
+ * false otherwise.
+ */
+ @SuppressWarnings("rawtypes")
+ public boolean quacksLikeA(Class iface) {
+ for (Method method : iface.getMethods()) {
+ if (findMethodBySignature(method) == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private <T> T generateProxy(Class iface) {
+ return (T) Proxy.newProxyInstance(iface.getClassLoader(), new Class[] { iface }, new CoercedProxy());
+ }
+
+ private Method findMethodBySignature(Method method) {
+ try {
+ return objectToCoerce.getClass().getMethod(method.getName(), method.getParameterTypes());
+ } catch (NoSuchMethodException e) {
+ return null;
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/de/danoeh/antennapod/util/LangUtils.java b/src/de/danoeh/antennapod/util/LangUtils.java
index 53f8de773..e6e1d8399 100644
--- a/src/de/danoeh/antennapod/util/LangUtils.java
+++ b/src/de/danoeh/antennapod/util/LangUtils.java
@@ -1,8 +1,11 @@
package de.danoeh.antennapod.util;
+import java.nio.charset.Charset;
import java.util.HashMap;
public class LangUtils {
+ public static final Charset UTF_8 = Charset.forName("UTF-8");
+
private static HashMap<String, String> languages;
static {
languages = new HashMap<String, String>();
diff --git a/src/de/danoeh/antennapod/util/QueueAccess.java b/src/de/danoeh/antennapod/util/QueueAccess.java
index ce9d11429..7a1c7fef2 100644
--- a/src/de/danoeh/antennapod/util/QueueAccess.java
+++ b/src/de/danoeh/antennapod/util/QueueAccess.java
@@ -51,9 +51,8 @@ public abstract class QueueAccess {
if (items == null) {
return false;
}
- Iterator<FeedItem> it = items.iterator();
- for (FeedItem i = it.next(); it.hasNext(); i = it.next()) {
- if (i.getId() == id) {
+ for (FeedItem item : items) {
+ if (item.getId() == id) {
return true;
}
}
@@ -63,8 +62,10 @@ public abstract class QueueAccess {
@Override
public boolean remove(long id) {
Iterator<FeedItem> it = items.iterator();
- for (FeedItem i = it.next(); it.hasNext(); i = it.next()) {
- if (i.getId() == id) {
+ FeedItem item;
+ while (it.hasNext()) {
+ item = it.next();
+ if (item.getId() == id) {
it.remove();
return true;
}
diff --git a/src/de/danoeh/antennapod/util/URLChecker.java b/src/de/danoeh/antennapod/util/URLChecker.java
index 6d9b8ff03..13668d4a9 100644
--- a/src/de/danoeh/antennapod/util/URLChecker.java
+++ b/src/de/danoeh/antennapod/util/URLChecker.java
@@ -19,13 +19,12 @@ public final class URLChecker {
* */
public static String prepareURL(String url) {
StringBuilder builder = new StringBuilder();
- url = url.trim();
- if (!url.startsWith("http")) {
+ if (url.startsWith("feed://")) {
+ if (AppConfig.DEBUG) Log.d(TAG, "Replacing feed:// with http://");
+ url = url.replace("feed://", "http://");
+ } else if (!(url.startsWith("http://") || url.startsWith("https://"))) {
+ if (AppConfig.DEBUG) Log.d(TAG, "Adding http:// at the beginning of the URL");
builder.append("http://");
- if (AppConfig.DEBUG) Log.d(TAG, "Missing http; appending");
- } else if (url.startsWith("https")) {
- if (AppConfig.DEBUG) Log.d(TAG, "Replacing https with http");
- url = url.replaceFirst("https", "http");
}
builder.append(url);
diff --git a/src/de/danoeh/antennapod/util/comparator/DownloadStatusComparator.java b/src/de/danoeh/antennapod/util/comparator/DownloadStatusComparator.java
index 2cfe52364..d0561252f 100644
--- a/src/de/danoeh/antennapod/util/comparator/DownloadStatusComparator.java
+++ b/src/de/danoeh/antennapod/util/comparator/DownloadStatusComparator.java
@@ -9,8 +9,7 @@ public class DownloadStatusComparator implements Comparator<DownloadStatus> {
@Override
public int compare(DownloadStatus lhs, DownloadStatus rhs) {
- return -lhs.getCompletionDate().compareTo(rhs.getCompletionDate());
-
+ return rhs.getCompletionDate().compareTo(lhs.getCompletionDate());
}
}
diff --git a/src/de/danoeh/antennapod/util/comparator/FeedItemPubdateComparator.java b/src/de/danoeh/antennapod/util/comparator/FeedItemPubdateComparator.java
index b9ee6c07e..c95c0833c 100644
--- a/src/de/danoeh/antennapod/util/comparator/FeedItemPubdateComparator.java
+++ b/src/de/danoeh/antennapod/util/comparator/FeedItemPubdateComparator.java
@@ -13,7 +13,7 @@ public class FeedItemPubdateComparator implements Comparator<FeedItem> {
}*/
@Override
public int compare(FeedItem lhs, FeedItem rhs) {
- return -lhs.getPubDate().compareTo(rhs.getPubDate());
+ return rhs.getPubDate().compareTo(lhs.getPubDate());
}
}
diff --git a/src/de/danoeh/antennapod/util/comparator/PlaybackCompletionDateComparator.java b/src/de/danoeh/antennapod/util/comparator/PlaybackCompletionDateComparator.java
index 2d0ce75ca..434a5a956 100644
--- a/src/de/danoeh/antennapod/util/comparator/PlaybackCompletionDateComparator.java
+++ b/src/de/danoeh/antennapod/util/comparator/PlaybackCompletionDateComparator.java
@@ -11,8 +11,8 @@ public class PlaybackCompletionDateComparator implements Comparator<FeedItem> {
&& lhs.getMedia().getPlaybackCompletionDate() != null
&& rhs.getMedia() != null
&& rhs.getMedia().getPlaybackCompletionDate() != null) {
- return -lhs.getMedia().getPlaybackCompletionDate()
- .compareTo(rhs.getMedia().getPlaybackCompletionDate());
+ return rhs.getMedia().getPlaybackCompletionDate()
+ .compareTo(lhs.getMedia().getPlaybackCompletionDate());
}
return 0;
}
diff --git a/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java b/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java
index aa029f672..fdd2011ae 100644
--- a/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java
+++ b/src/de/danoeh/antennapod/util/menuhandler/FeedItemMenuHandler.java
@@ -51,10 +51,13 @@ public class FeedItemMenuHandler {
* 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
+ * @return Returns true if selectedItem is not null.
* */
public static boolean onPrepareMenu(MenuInterface mi,
FeedItem selectedItem, boolean showExtendedMenu, QueueAccess queueAccess) {
+ if (selectedItem == null) {
+ return false;
+ }
DownloadRequester requester = DownloadRequester.getInstance();
boolean hasMedia = selectedItem.getMedia() != null;
boolean downloaded = hasMedia && selectedItem.getMedia().isDownloaded();
diff --git a/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java b/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java
index 843607617..446e024d9 100644
--- a/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java
+++ b/src/de/danoeh/antennapod/util/menuhandler/FeedMenuHandler.java
@@ -30,6 +30,10 @@ public class FeedMenuHandler {
}
public static boolean onPrepareOptionsMenu(Menu menu, Feed selectedFeed) {
+ if (selectedFeed == null) {
+ return false;
+ }
+
if (AppConfig.DEBUG)
Log.d(TAG, "Preparing options menu");
menu.findItem(R.id.mark_all_read_item).setVisible(
diff --git a/src/de/danoeh/antennapod/util/playback/AudioPlayer.java b/src/de/danoeh/antennapod/util/playback/AudioPlayer.java
new file mode 100644
index 000000000..68d31324d
--- /dev/null
+++ b/src/de/danoeh/antennapod/util/playback/AudioPlayer.java
@@ -0,0 +1,30 @@
+package de.danoeh.antennapod.util.playback;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+import com.aocate.media.MediaPlayer;
+
+public class AudioPlayer extends MediaPlayer implements IPlayer {
+ private static final String TAG = "AudioPlayer";
+
+ public AudioPlayer(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void setScreenOnWhilePlaying(boolean screenOn) {
+ Log.e(TAG, "Setting screen on while playing not supported in Audio Player");
+ throw new UnsupportedOperationException("Setting screen on while playing not supported in Audio Player");
+
+ }
+
+ @Override
+ public void setDisplay(SurfaceHolder sh) {
+ if (sh != null) {
+ Log.e(TAG, "Setting display not supported in Audio Player");
+ throw new UnsupportedOperationException("Setting display not supported in Audio Player");
+ }
+ }
+}
diff --git a/src/de/danoeh/antennapod/util/playback/ExternalMedia.java b/src/de/danoeh/antennapod/util/playback/ExternalMedia.java
index 1ada0ec03..e937ee437 100644
--- a/src/de/danoeh/antennapod/util/playback/ExternalMedia.java
+++ b/src/de/danoeh/antennapod/util/playback/ExternalMedia.java
@@ -25,7 +25,6 @@ public class ExternalMedia implements Playable {
private String episodeTitle;
private String feedTitle;
- private String shownotes;
private MediaType mediaType = MediaType.AUDIO;
private List<Chapter> chapters;
private int duration;
@@ -80,8 +79,13 @@ public class ExternalMedia implements Playable {
.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
feedTitle = mmr
.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM);
- duration = Integer.parseInt(mmr
+ try {
+ duration = Integer.parseInt(mmr
.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ throw new PlayableException("NumberFormatException when reading duration of media file");
+ }
ChapterUtils.loadChaptersFromFileUrl(this);
}
diff --git a/src/de/danoeh/antennapod/util/playback/IPlayer.java b/src/de/danoeh/antennapod/util/playback/IPlayer.java
new file mode 100644
index 000000000..ca9b36358
--- /dev/null
+++ b/src/de/danoeh/antennapod/util/playback/IPlayer.java
@@ -0,0 +1,64 @@
+package de.danoeh.antennapod.util.playback;
+
+import java.io.IOException;
+
+import android.view.SurfaceHolder;
+
+public interface IPlayer {
+ boolean canSetPitch();
+
+ boolean canSetSpeed();
+
+ float getCurrentPitchStepsAdjustment();
+
+ int getCurrentPosition();
+
+ float getCurrentSpeedMultiplier();
+
+ int getDuration();
+
+ float getMaxSpeedMultiplier();
+
+ float getMinSpeedMultiplier();
+
+ boolean isLooping();
+
+ boolean isPlaying();
+
+ void pause();
+
+ void prepare() throws IllegalStateException, IOException;
+
+ void prepareAsync();
+
+ void release();
+
+ void reset();
+
+ void seekTo(int msec);
+
+ void setAudioStreamType(int streamtype);
+
+ void setScreenOnWhilePlaying(boolean screenOn);
+
+ void setDataSource(String path) throws IllegalStateException, IOException,
+ IllegalArgumentException, SecurityException;
+
+ void setDisplay(SurfaceHolder sh);
+
+ void setEnableSpeedAdjustment(boolean enableSpeedAdjustment);
+
+ void setLooping(boolean looping);
+
+ void setPitchStepsAdjustment(float pitchSteps);
+
+ void setPlaybackPitch(float f);
+
+ void setPlaybackSpeed(float f);
+
+ void setVolume(float left, float right);
+
+ void start();
+
+ void stop();
+}
diff --git a/src/de/danoeh/antennapod/util/playback/PlaybackController.java b/src/de/danoeh/antennapod/util/playback/PlaybackController.java
index 5a5b43a6e..f5d1847b3 100644
--- a/src/de/danoeh/antennapod/util/playback/PlaybackController.java
+++ b/src/de/danoeh/antennapod/util/playback/PlaybackController.java
@@ -342,6 +342,9 @@ public abstract class PlaybackController {
case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_END:
onPlaybackEnd();
break;
+ case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE:
+ onPlaybackSpeedChange();
+ break;
}
} else {
@@ -369,6 +372,8 @@ public abstract class PlaybackController {
}
};
+ public abstract void onPlaybackSpeedChange();
+
public abstract void onShutdownNotification();
/**
@@ -663,6 +668,24 @@ public abstract class PlaybackController {
return status;
}
+ public boolean canSetPlaybackSpeed() {
+ return playbackService != null && playbackService.canSetSpeed();
+ }
+
+ public void setPlaybackSpeed(float speed) {
+ if (playbackService != null) {
+ playbackService.setSpeed(speed);
+ }
+ }
+
+ public float getCurrentPlaybackSpeedMultiplier() {
+ if (canSetPlaybackSpeed()) {
+ return playbackService.getCurrentPlaybackSpeed();
+ } else {
+ return -1;
+ }
+ }
+
public boolean isPlayingVideo() {
if (playbackService != null) {
return PlaybackService.isPlayingVideo();
@@ -670,6 +693,7 @@ public abstract class PlaybackController {
return false;
}
+
/**
* Returns true if PlaybackController can communicate with the playback
* service.
diff --git a/src/de/danoeh/antennapod/util/playback/VideoPlayer.java b/src/de/danoeh/antennapod/util/playback/VideoPlayer.java
new file mode 100644
index 000000000..f0a50542c
--- /dev/null
+++ b/src/de/danoeh/antennapod/util/playback/VideoPlayer.java
@@ -0,0 +1,62 @@
+package de.danoeh.antennapod.util.playback;
+
+import android.media.MediaPlayer;
+import android.util.Log;
+
+public class VideoPlayer extends MediaPlayer implements IPlayer {
+ private static final String TAG = "VideoPlayer";
+
+ @Override
+ public boolean canSetPitch() {
+ return false;
+ }
+
+ @Override
+ public boolean canSetSpeed() {
+ return false;
+ }
+
+ @Override
+ public float getCurrentPitchStepsAdjustment() {
+ return 1;
+ }
+
+ @Override
+ public float getCurrentSpeedMultiplier() {
+ return 1;
+ }
+
+ @Override
+ public float getMaxSpeedMultiplier() {
+ return 1;
+ }
+
+ @Override
+ public float getMinSpeedMultiplier() {
+ return 1;
+ }
+
+ @Override
+ public void setEnableSpeedAdjustment(boolean enableSpeedAdjustment) throws UnsupportedOperationException {
+ Log.e(TAG, "Setting enable speed adjustment unsupported in video player");
+ throw new UnsupportedOperationException("Setting enable speed adjustment unsupported in video player");
+ }
+
+ @Override
+ public void setPitchStepsAdjustment(float pitchSteps) {
+ Log.e(TAG, "Setting pitch steps adjustment unsupported in video player");
+ throw new UnsupportedOperationException("Setting pitch steps adjustment unsupported in video player");
+ }
+
+ @Override
+ public void setPlaybackPitch(float f) {
+ Log.e(TAG, "Setting playback pitch unsupported in video player");
+ throw new UnsupportedOperationException("Setting playback pitch unsupported in video player");
+ }
+
+ @Override
+ public void setPlaybackSpeed(float f) {
+ Log.e(TAG, "Setting playback speed unsupported in video player");
+ throw new UnsupportedOperationException("Setting playback speed unsupported in video player");
+ }
+}
diff --git a/src/instrumentationTest/de/test/antennapod/service/download/HttpDownloaderTest.java b/src/instrumentationTest/de/test/antennapod/service/download/HttpDownloaderTest.java
index 2cd5734d5..8df35ce67 100644
--- a/src/instrumentationTest/de/test/antennapod/service/download/HttpDownloaderTest.java
+++ b/src/instrumentationTest/de/test/antennapod/service/download/HttpDownloaderTest.java
@@ -2,18 +2,21 @@ package instrumentationTest.de.test.antennapod.service.download;
import java.io.File;
+import android.test.InstrumentationTestCase;
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 {
+public class HttpDownloaderTest extends InstrumentationTestCase {
private static final String TAG = "HttpDownloaderTest";
private static final String DOWNLOAD_DIR = "testdownloads";
private static boolean successful = true;
+ private File destDir;
+
public HttpDownloaderTest() {
super();
}
@@ -21,17 +24,23 @@ public class HttpDownloaderTest extends AndroidTestCase {
@Override
protected void tearDown() throws Exception {
super.tearDown();
- File externalDir = getContext().getExternalFilesDir(DOWNLOAD_DIR);
- assertNotNull(externalDir);
- File[] contents = externalDir.listFiles();
+ File[] contents = destDir.listFiles();
for (File f : contents) {
assertTrue(f.delete());
}
}
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ destDir = getInstrumentation().getTargetContext().getExternalFilesDir(DOWNLOAD_DIR);
+ assertNotNull(destDir);
+ assertTrue(destDir.exists());
+ }
+
private FeedFileImpl setupFeedFile(String downloadUrl, String title) {
FeedFileImpl feedfile = new FeedFileImpl(downloadUrl);
- String fileUrl = new File(getContext().getExternalFilesDir(DOWNLOAD_DIR).getAbsolutePath(), title).getAbsolutePath();
+ String fileUrl = new File(destDir, title).getAbsolutePath();
File file = new File(fileUrl);
Log.d(TAG, "Deleting file: " + file.delete());
feedfile.setFile_url(fileUrl);
@@ -47,42 +56,16 @@ public class HttpDownloaderTest extends AndroidTestCase {
assertNotNull(status);
assertTrue(status.isSuccessful() == expectedResult);
assertTrue(status.isDone());
- assertTrue(new File(feedFile.getFile_url()).exists());
+ // the file should not exist if the download has failed
+ assertTrue(new File(feedFile.getFile_url()).exists() == expectedResult);
}
- 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 testPassingHttp() {
+ download("http://httpbin.org/status/200", "test200", true);
+ }
+
+ public void testPassingHttps() {
+ download("https://httpbin.org/status/200", "test200", true);
}
public void testRedirect() {
@@ -111,6 +94,7 @@ public class HttpDownloaderTest extends AndroidTestCase {
downloader.call();
}
};
+ t.start();
downloader.cancel();
try {
t.join();
diff --git a/src/instrumentationTest/de/test/antennapod/storage/DBTasksTest.java b/src/instrumentationTest/de/test/antennapod/storage/DBTasksTest.java
index ef48b8d5f..7631a5787 100644
--- a/src/instrumentationTest/de/test/antennapod/storage/DBTasksTest.java
+++ b/src/instrumentationTest/de/test/antennapod/storage/DBTasksTest.java
@@ -1,9 +1,252 @@
package instrumentationTest.de.test.antennapod.storage;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
import android.test.InstrumentationTestCase;
+import de.danoeh.antennapod.feed.Feed;
+import de.danoeh.antennapod.feed.FeedItem;
+import de.danoeh.antennapod.feed.FeedMedia;
+import de.danoeh.antennapod.preferences.UserPreferences;
+import de.danoeh.antennapod.storage.DBReader;
+import de.danoeh.antennapod.storage.DBTasks;
+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;
/**
* Test class for DBTasks
*/
public class DBTasksTest extends InstrumentationTestCase {
+ private static final String TEST_FOLDER = "testDBTasks";
+ private static final int EPISODE_CACHE_SIZE = 5;
+
+ private File destFolder;
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ final Context context = getInstrumentation().getTargetContext();
+ assertTrue(PodDBAdapter.deleteDatabase(context));
+
+ for (File f : destFolder.listFiles()) {
+ assertTrue(f.delete());
+ }
+ assertTrue(destFolder.delete());
+
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
+ assertNotNull(destFolder);
+ assertTrue(destFolder.exists());
+ assertTrue(destFolder.canWrite());
+
+ final Context context = getInstrumentation().getTargetContext();
+ context.deleteDatabase(PodDBAdapter.DATABASE_NAME);
+ // make sure database is created
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.close();
+
+ SharedPreferences.Editor prefEdit = PreferenceManager.getDefaultSharedPreferences(getInstrumentation().getTargetContext().getApplicationContext()).edit();
+ prefEdit.putString(UserPreferences.PREF_EPISODE_CACHE_SIZE, Integer.toString(EPISODE_CACHE_SIZE));
+ prefEdit.commit();
+ }
+
+ public void testPerformAutoCleanupShouldDelete() throws IOException {
+ final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2;
+
+ Feed feed = new Feed("url", new Date(), "title");
+ List<FeedItem> items = new ArrayList<FeedItem>();
+ feed.setItems(items);
+ List<File> files = new ArrayList<File>();
+ for (int i = 0; i < NUM_ITEMS; i++) {
+ FeedItem item = new FeedItem(0, "title", "id", "link", new Date(), true, feed);
+
+ File f = new File(destFolder, "file " + i);
+ assertTrue(f.createNewFile());
+ files.add(f);
+ item.setMedia(new FeedMedia(0, item, 1, 0, 1L, "m", f.getAbsolutePath(), "url", true, new Date(NUM_ITEMS - i)));
+ items.add(item);
+ }
+
+ PodDBAdapter adapter = new PodDBAdapter(getInstrumentation().getTargetContext());
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ assertTrue(feed.getId() != 0);
+ for (FeedItem item : items) {
+ assertTrue(item.getId() != 0);
+ assertTrue(item.getMedia().getId() != 0);
+ }
+ DBTasks.performAutoCleanup(getInstrumentation().getTargetContext());
+ for (int i = 0; i < files.size(); i++) {
+ if (i < EPISODE_CACHE_SIZE) {
+ assertTrue(files.get(i).exists());
+ } else {
+ assertFalse(files.get(i).exists());
+ }
+ }
+ }
+
+ public void testPerformAutoCleanupShouldNotDeleteBecauseUnread() throws IOException {
+ final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2;
+
+ Feed feed = new Feed("url", new Date(), "title");
+ List<FeedItem> items = new ArrayList<FeedItem>();
+ feed.setItems(items);
+ List<File> files = new ArrayList<File>();
+ for (int i = 0; i < NUM_ITEMS; i++) {
+ FeedItem item = new FeedItem(0, "title", "id", "link", new Date(), false, feed);
+
+ File f = new File(destFolder, "file " + i);
+ assertTrue(f.createNewFile());
+ assertTrue(f.exists());
+ files.add(f);
+ item.setMedia(new FeedMedia(0, item, 1, 0, 1L, "m", f.getAbsolutePath(), "url", true, new Date(NUM_ITEMS - i)));
+ items.add(item);
+ }
+
+ PodDBAdapter adapter = new PodDBAdapter(getInstrumentation().getTargetContext());
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ assertTrue(feed.getId() != 0);
+ for (FeedItem item : items) {
+ assertTrue(item.getId() != 0);
+ assertTrue(item.getMedia().getId() != 0);
+ }
+ DBTasks.performAutoCleanup(getInstrumentation().getTargetContext());
+ for (File file : files) {
+ assertTrue(file.exists());
+ }
+ }
+
+ public void testPerformAutoCleanupShouldNotDeleteBecauseInQueue() throws IOException {
+ final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2;
+
+ Feed feed = new Feed("url", new Date(), "title");
+ List<FeedItem> items = new ArrayList<FeedItem>();
+ feed.setItems(items);
+ List<File> files = new ArrayList<File>();
+ for (int i = 0; i < NUM_ITEMS; i++) {
+ FeedItem item = new FeedItem(0, "title", "id", "link", new Date(), true, feed);
+
+ File f = new File(destFolder, "file " + i);
+ assertTrue(f.createNewFile());
+ assertTrue(f.exists());
+ files.add(f);
+ item.setMedia(new FeedMedia(0, item, 1, 0, 1L, "m", f.getAbsolutePath(), "url", true, new Date(NUM_ITEMS - i)));
+ items.add(item);
+ }
+
+ PodDBAdapter adapter = new PodDBAdapter(getInstrumentation().getTargetContext());
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.setQueue(items);
+ adapter.close();
+
+ assertTrue(feed.getId() != 0);
+ for (FeedItem item : items) {
+ assertTrue(item.getId() != 0);
+ assertTrue(item.getMedia().getId() != 0);
+ }
+ DBTasks.performAutoCleanup(getInstrumentation().getTargetContext());
+ for (File file : files) {
+ assertTrue(file.exists());
+ }
+ }
+
+ public void testUpdateFeedNewFeed() {
+ final Context context = getInstrumentation().getTargetContext();
+ final int NUM_ITEMS = 10;
+
+ Feed feed = new Feed("url", new Date(), "title");
+ feed.setItems(new ArrayList<FeedItem>());
+ for (int i = 0; i < NUM_ITEMS; i++) {
+ feed.getItems().add(new FeedItem(0, "item " + i, "id " + i, "link " + i, new Date(), false, feed));
+ }
+ Feed newFeed = DBTasks.updateFeed(context, feed);
+
+ assertTrue(newFeed == feed);
+ assertTrue(feed.getId() != 0);
+ for (FeedItem item : feed.getItems()) {
+ assertFalse(item.isRead());
+ assertTrue(item.getId() != 0);
+ }
+ }
+
+ public void testUpdateFeedUpdatedFeed() {
+ final Context context = getInstrumentation().getTargetContext();
+ final int NUM_ITEMS_OLD = 10;
+ final int NUM_ITEMS_NEW = 10;
+
+ final Feed feed = new Feed("url", new Date(), "title");
+ feed.setItems(new ArrayList<FeedItem>());
+ for (int i = 0; i < NUM_ITEMS_OLD; i++) {
+ feed.getItems().add(new FeedItem(0, "item " + i, "id " + i, "link " + i, new Date(i), true, feed));
+ }
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ // ensure that objects have been saved in db, then reset
+ assertTrue(feed.getId() != 0);
+ final long feedID = feed.getId();
+ feed.setId(0);
+ List<Long> itemIDs = new ArrayList<Long>();
+ for (FeedItem item : feed.getItems()) {
+ assertTrue(item.getId() != 0);
+ itemIDs.add(item.getId());
+ item.setId(0);
+ }
+
+ for (int i = NUM_ITEMS_OLD; i < NUM_ITEMS_NEW + NUM_ITEMS_OLD; i++) {
+ feed.getItems().add(0, new FeedItem(0, "item " + i, "id " + i, "link " + i, new Date(i), true, feed));
+ }
+
+ final Feed newFeed = DBTasks.updateFeed(context, feed);
+ assertTrue(feed != newFeed);
+
+ updatedFeedTest(newFeed, feedID, itemIDs, NUM_ITEMS_OLD, NUM_ITEMS_NEW);
+
+ final Feed feedFromDB = DBReader.getFeed(context, newFeed.getId());
+ assertNotNull(feedFromDB);
+ assertTrue(feedFromDB.getId() == newFeed.getId());
+ updatedFeedTest(feedFromDB, feedID, itemIDs, NUM_ITEMS_OLD, NUM_ITEMS_NEW);
+ }
+
+ private void updatedFeedTest(final Feed newFeed, long feedID, List<Long> itemIDs, final int NUM_ITEMS_OLD, final int NUM_ITEMS_NEW) {
+ assertTrue(newFeed.getId() == feedID);
+ assertTrue(newFeed.getItems().size() == NUM_ITEMS_NEW + NUM_ITEMS_OLD);
+ Collections.reverse(newFeed.getItems());
+ Date lastDate = new Date(0);
+ for (int i = 0; i < NUM_ITEMS_OLD; i++) {
+ FeedItem item = newFeed.getItems().get(i);
+ assertTrue(item.getFeed() == newFeed);
+ assertTrue(item.getId() == itemIDs.get(i));
+ assertTrue(item.isRead());
+ assertTrue(item.getPubDate().getTime() >= lastDate.getTime());
+ lastDate = item.getPubDate();
+ }
+ for (int i = NUM_ITEMS_OLD; i < NUM_ITEMS_NEW + NUM_ITEMS_OLD; i++) {
+ FeedItem item = newFeed.getItems().get(i);
+ assertTrue(item.getFeed() == newFeed);
+ assertTrue(item.getId() != 0);
+ assertFalse(item.isRead());
+ assertTrue(item.getPubDate().getTime() >= lastDate.getTime());
+ lastDate = item.getPubDate();
+ }
+ }
}
diff --git a/src/instrumentationTest/de/test/antennapod/storage/DBWriterTest.java b/src/instrumentationTest/de/test/antennapod/storage/DBWriterTest.java
index dbec84370..84ac7a9cc 100644
--- a/src/instrumentationTest/de/test/antennapod/storage/DBWriterTest.java
+++ b/src/instrumentationTest/de/test/antennapod/storage/DBWriterTest.java
@@ -3,6 +3,7 @@ package instrumentationTest.de.test.antennapod.storage;
import android.content.Context;
import android.database.Cursor;
import android.test.InstrumentationTestCase;
+import android.util.Log;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedImage;
import de.danoeh.antennapod.feed.FeedItem;
@@ -14,10 +15,10 @@ 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.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -25,13 +26,15 @@ import java.util.concurrent.TimeoutException;
* Test class for DBWriter
*/
public class DBWriterTest extends InstrumentationTestCase {
+ private static final String TAG = "DBWriterTest";
private static final String TEST_FOLDER = "testDBWriter";
+ private static final long TIMEOUT = 5L;
@Override
protected void tearDown() throws Exception {
super.tearDown();
final Context context = getInstrumentation().getTargetContext();
- context.deleteDatabase(PodDBAdapter.DATABASE_NAME);
+ assertTrue(PodDBAdapter.deleteDatabase(getInstrumentation().getTargetContext()));
File testDir = context.getExternalFilesDir(TEST_FOLDER);
assertNotNull(testDir);
@@ -59,10 +62,7 @@ public class DBWriterTest extends InstrumentationTestCase {
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);
+ FeedItem item = new FeedItem(0, "Item", "Item", "url", new Date(), true, feed);
FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", dest.getAbsolutePath(), "download_url", true, null);
item.setMedia(media);
@@ -84,42 +84,11 @@ public class DBWriterTest extends InstrumentationTestCase {
assertNull(media.getFile_url());
}
- public void testDeleteFeedMediaOfItemFileDoesNotExists() throws IOException, ExecutionException, InterruptedException {
- File dest = new File(getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER), "testFile");
-
- 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", false, 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 feed = new Feed("url", new Date(), "title");
feed.setItems(new ArrayList<FeedItem>());
// create Feed image
@@ -132,10 +101,7 @@ public class DBWriterTest extends InstrumentationTestCase {
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);
+ FeedItem item = new FeedItem(0, "Item " + i, "Item" + i, "url", new Date(), true, feed);
feed.getItems().add(item);
File enc = new File(destFolder, "file " + i);
@@ -158,7 +124,7 @@ public class DBWriterTest extends InstrumentationTestCase {
assertTrue(item.getMedia().getId() != 0);
}
- DBWriter.deleteFeed(getInstrumentation().getTargetContext(), feed.getId()).get(5, TimeUnit.SECONDS);
+ DBWriter.deleteFeed(getInstrumentation().getTargetContext(), feed.getId()).get(TIMEOUT, TimeUnit.SECONDS);
// check if files still exist
assertFalse(imgFile.exists());
@@ -188,7 +154,7 @@ public class DBWriterTest extends InstrumentationTestCase {
File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
assertNotNull(destFolder);
- Feed feed = new Feed ("url", new Date(), "title");
+ Feed feed = new Feed("url", new Date(), "title");
feed.setItems(new ArrayList<FeedItem>());
feed.setImage(null);
@@ -196,16 +162,13 @@ public class DBWriterTest extends InstrumentationTestCase {
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);
+ FeedItem item = new FeedItem(0, "Item " + i, "Item" + i, "url", new Date(), true, feed);
feed.getItems().add(item);
File enc = new File(destFolder, "file " + i);
assertTrue(enc.createNewFile());
- itemFiles.add(enc);
+ itemFiles.add(enc);
FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", enc.getAbsolutePath(), "download_url", true, null);
item.setMedia(media);
}
@@ -221,7 +184,7 @@ public class DBWriterTest extends InstrumentationTestCase {
assertTrue(item.getMedia().getId() != 0);
}
- DBWriter.deleteFeed(getInstrumentation().getTargetContext(), feed.getId()).get(5, TimeUnit.SECONDS);
+ DBWriter.deleteFeed(getInstrumentation().getTargetContext(), feed.getId()).get(TIMEOUT, TimeUnit.SECONDS);
// check if files still exist
for (File f : itemFiles) {
@@ -247,7 +210,7 @@ public class DBWriterTest extends InstrumentationTestCase {
File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
assertNotNull(destFolder);
- Feed feed = new Feed ("url", new Date(), "title");
+ Feed feed = new Feed("url", new Date(), "title");
feed.setItems(null);
// create Feed image
@@ -265,7 +228,7 @@ public class DBWriterTest extends InstrumentationTestCase {
assertTrue(feed.getId() != 0);
assertTrue(feed.getImage().getId() != 0);
- DBWriter.deleteFeed(getInstrumentation().getTargetContext(), feed.getId()).get(5, TimeUnit.SECONDS);
+ DBWriter.deleteFeed(getInstrumentation().getTargetContext(), feed.getId()).get(TIMEOUT, TimeUnit.SECONDS);
// check if files still exist
assertFalse(imgFile.exists());
@@ -284,7 +247,7 @@ public class DBWriterTest extends InstrumentationTestCase {
File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
assertNotNull(destFolder);
- Feed feed = new Feed ("url", new Date(), "title");
+ Feed feed = new Feed("url", new Date(), "title");
feed.setItems(new ArrayList<FeedItem>());
// create Feed image
@@ -296,10 +259,7 @@ public class DBWriterTest extends InstrumentationTestCase {
// 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);
+ FeedItem item = new FeedItem(0, "Item " + i, "Item" + i, "url", new Date(), true, feed);
feed.getItems().add(item);
}
@@ -315,7 +275,7 @@ public class DBWriterTest extends InstrumentationTestCase {
assertTrue(item.getId() != 0);
}
- DBWriter.deleteFeed(getInstrumentation().getTargetContext(), feed.getId()).get(5, TimeUnit.SECONDS);
+ DBWriter.deleteFeed(getInstrumentation().getTargetContext(), feed.getId()).get(TIMEOUT, TimeUnit.SECONDS);
// check if files still exist
assertFalse(imgFile.exists());
@@ -339,7 +299,7 @@ public class DBWriterTest extends InstrumentationTestCase {
File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
assertNotNull(destFolder);
- Feed feed = new Feed ("url", new Date(), "title");
+ Feed feed = new Feed("url", new Date(), "title");
feed.setItems(new ArrayList<FeedItem>());
// create Feed image
@@ -351,10 +311,7 @@ public class DBWriterTest extends InstrumentationTestCase {
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);
+ FeedItem item = new FeedItem(0, "Item " + i, "Item" + i, "url", new Date(), true, feed);
feed.getItems().add(item);
File enc = new File(destFolder, "file " + i);
@@ -387,7 +344,7 @@ public class DBWriterTest extends InstrumentationTestCase {
queueCursor.close();
adapter.close();
- DBWriter.deleteFeed(getInstrumentation().getTargetContext(), feed.getId()).get(5, TimeUnit.SECONDS);
+ DBWriter.deleteFeed(getInstrumentation().getTargetContext(), feed.getId()).get(TIMEOUT, TimeUnit.SECONDS);
adapter.open();
Cursor c = adapter.getFeedCursor(feed.getId());
@@ -414,7 +371,7 @@ public class DBWriterTest extends InstrumentationTestCase {
File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
assertNotNull(destFolder);
- Feed feed = new Feed ("url", new Date(), "title");
+ Feed feed = new Feed("url", new Date(), "title");
feed.setItems(new ArrayList<FeedItem>());
// create Feed image
@@ -426,10 +383,7 @@ public class DBWriterTest extends InstrumentationTestCase {
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);
+ FeedItem item = new FeedItem(0, "Item " + i, "Item" + i, "url", new Date(), true, feed);
feed.getItems().add(item);
File enc = new File(destFolder, "file " + i);
@@ -451,7 +405,7 @@ public class DBWriterTest extends InstrumentationTestCase {
assertTrue(item.getMedia().getId() != 0);
}
- DBWriter.deleteFeed(getInstrumentation().getTargetContext(), feed.getId()).get(5, TimeUnit.SECONDS);
+ DBWriter.deleteFeed(getInstrumentation().getTargetContext(), feed.getId()).get(TIMEOUT, TimeUnit.SECONDS);
adapter = new PodDBAdapter(getInstrumentation().getContext());
adapter.open();
@@ -470,4 +424,261 @@ public class DBWriterTest extends InstrumentationTestCase {
c.close();
}
}
+
+ private FeedMedia playbackHistorySetup(Date playbackCompletionDate) {
+ final Context context = getInstrumentation().getTargetContext();
+ Feed feed = new Feed("url", new Date(), "title");
+ feed.setItems(new ArrayList<FeedItem>());
+ FeedItem item = new FeedItem(0, "title", "id", "link", new Date(), true, feed);
+ FeedMedia media = new FeedMedia(0, item, 10, 0, 1, "mime", null, "url", false, playbackCompletionDate);
+ feed.getItems().add(item);
+ item.setMedia(media);
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+ assertTrue(media.getId() != 0);
+ return media;
+ }
+
+ public void testAddItemToPlaybackHistoryNotPlayedYet() throws ExecutionException, InterruptedException {
+ final Context context = getInstrumentation().getTargetContext();
+
+ FeedMedia media = playbackHistorySetup(null);
+ DBWriter.addItemToPlaybackHistory(context, media).get();
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ media = DBReader.getFeedMedia(context, media.getId());
+ adapter.close();
+
+ assertNotNull(media);
+ assertNotNull(media.getPlaybackCompletionDate());
+ }
+
+ public void testAddItemToPlaybackHistoryAlreadyPlayed() throws ExecutionException, InterruptedException {
+ final long OLD_DATE = 0;
+ final Context context = getInstrumentation().getTargetContext();
+
+ FeedMedia media = playbackHistorySetup(new Date(OLD_DATE));
+ DBWriter.addItemToPlaybackHistory(getInstrumentation().getTargetContext(), media).get();
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ media = DBReader.getFeedMedia(context, media.getId());
+ adapter.close();
+
+ assertNotNull(media);
+ assertNotNull(media.getPlaybackCompletionDate());
+ assertFalse(OLD_DATE == media.getPlaybackCompletionDate().getTime());
+ }
+
+ private Feed queueTestSetupMultipleItems(final int NUM_ITEMS) throws InterruptedException, ExecutionException, TimeoutException {
+ final Context context = getInstrumentation().getTargetContext();
+ Feed feed = new Feed("url", new Date(), "title");
+ feed.setItems(new ArrayList<FeedItem>());
+ for (int i = 0; i < NUM_ITEMS; i++) {
+ FeedItem item = new FeedItem(0, "title " + i, "id " + i, "link " + i, new Date(), true, feed);
+ feed.getItems().add(item);
+ }
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ for (FeedItem item : feed.getItems()) {
+ assertTrue(item.getId() != 0);
+ }
+ List<Future<?>> futures = new ArrayList<Future<?>>();
+ for (FeedItem item : feed.getItems()) {
+ futures.add(DBWriter.addQueueItem(context, item.getId()));
+ }
+ for (Future<?> f : futures) {
+ f.get(TIMEOUT, TimeUnit.SECONDS);
+ }
+ return feed;
+ }
+
+ public void testAddQueueItemSingleItem() throws InterruptedException, ExecutionException, TimeoutException {
+ final Context context = getInstrumentation().getTargetContext();
+ Feed feed = new Feed("url", new Date(), "title");
+ feed.setItems(new ArrayList<FeedItem>());
+ FeedItem item = new FeedItem(0, "title", "id", "link", new Date(), true, feed);
+ feed.getItems().add(item);
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ assertTrue(item.getId() != 0);
+ DBWriter.addQueueItem(context, item.getId()).get(TIMEOUT, TimeUnit.SECONDS);
+
+ adapter = new PodDBAdapter(context);
+ adapter.open();
+ Cursor cursor = adapter.getQueueIDCursor();
+ assertTrue(cursor.moveToFirst());
+ assertTrue(cursor.getLong(0) == item.getId());
+ cursor.close();
+ adapter.close();
+ }
+
+ public void testAddQueueItemSingleItemAlreadyInQueue() throws InterruptedException, ExecutionException, TimeoutException {
+ final Context context = getInstrumentation().getTargetContext();
+ Feed feed = new Feed("url", new Date(), "title");
+ feed.setItems(new ArrayList<FeedItem>());
+ FeedItem item = new FeedItem(0, "title", "id", "link", new Date(), true, feed);
+ feed.getItems().add(item);
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ assertTrue(item.getId() != 0);
+ DBWriter.addQueueItem(context, item.getId()).get(TIMEOUT, TimeUnit.SECONDS);
+
+ adapter = new PodDBAdapter(context);
+ adapter.open();
+ Cursor cursor = adapter.getQueueIDCursor();
+ assertTrue(cursor.moveToFirst());
+ assertTrue(cursor.getLong(0) == item.getId());
+ cursor.close();
+ adapter.close();
+
+ DBWriter.addQueueItem(context, item.getId()).get(TIMEOUT, TimeUnit.SECONDS);
+ adapter = new PodDBAdapter(context);
+ adapter.open();
+ cursor = adapter.getQueueIDCursor();
+ assertTrue(cursor.moveToFirst());
+ assertTrue(cursor.getLong(0) == item.getId());
+ assertTrue(cursor.getCount() == 1);
+ cursor.close();
+ adapter.close();
+ }
+
+ public void testAddQueueItemMultipleItems() throws InterruptedException, ExecutionException, TimeoutException {
+ final Context context = getInstrumentation().getTargetContext();
+ final int NUM_ITEMS = 10;
+
+ Feed feed = queueTestSetupMultipleItems(NUM_ITEMS);
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ Cursor cursor = adapter.getQueueIDCursor();
+ assertTrue(cursor.moveToFirst());
+ assertTrue(cursor.getCount() == NUM_ITEMS);
+ for (int i = 0; i < NUM_ITEMS; i++) {
+ assertTrue(cursor.moveToPosition(i));
+ assertTrue(cursor.getLong(0) == feed.getItems().get(i).getId());
+ }
+ cursor.close();
+ adapter.close();
+ }
+
+ public void testClearQueue() throws InterruptedException, ExecutionException, TimeoutException {
+ final Context context = getInstrumentation().getTargetContext();
+ final int NUM_ITEMS = 10;
+
+ Feed feed = queueTestSetupMultipleItems(NUM_ITEMS);
+ DBWriter.clearQueue(context).get(TIMEOUT, TimeUnit.SECONDS);
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ Cursor cursor = adapter.getQueueIDCursor();
+ assertFalse(cursor.moveToFirst());
+ cursor.close();
+ adapter.close();
+ }
+
+ public void testRemoveQueueItem() throws InterruptedException, ExecutionException, TimeoutException {
+ final int NUM_ITEMS = 10;
+ final Context context = getInstrumentation().getTargetContext();
+ Feed feed = new Feed("url", new Date(), "title");
+ feed.setItems(new ArrayList<FeedItem>());
+ for (int i = 0; i < NUM_ITEMS; i++) {
+ FeedItem item = new FeedItem(0, "title " + i, "id " + i, "link " + i, new Date(), true, feed);
+ feed.getItems().add(item);
+ }
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ for (FeedItem item : feed.getItems()) {
+ assertTrue(item.getId() != 0);
+ }
+ for (int removeIndex = 0; removeIndex < NUM_ITEMS; removeIndex++) {
+ final long id = feed.getItems().get(removeIndex).getId();
+ adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setQueue(feed.getItems());
+ adapter.close();
+
+ DBWriter.removeQueueItem(context, id, false).get(TIMEOUT, TimeUnit.SECONDS);
+ adapter = new PodDBAdapter(context);
+ adapter.open();
+ Cursor queue = adapter.getQueueIDCursor();
+ assertTrue(queue.getCount() == NUM_ITEMS - 1);
+ for (int i = 0; i < queue.getCount(); i++) {
+ assertTrue(queue.moveToPosition(i));
+ final long queueID = queue.getLong(0);
+ assertTrue(queueID != id); // removed item is no longer in queue
+ boolean idFound = false;
+ for (FeedItem item : feed.getItems()) { // items that were not removed are still in the queue
+ idFound = idFound | (item.getId() == queueID);
+ }
+ assertTrue(idFound);
+ }
+
+ queue.close();
+ adapter.close();
+ }
+ }
+
+ public void testMoveQueueItem() throws InterruptedException, ExecutionException, TimeoutException {
+ final int NUM_ITEMS = 10;
+ final Context context = getInstrumentation().getTargetContext();
+ Feed feed = new Feed("url", new Date(), "title");
+ feed.setItems(new ArrayList<FeedItem>());
+ for (int i = 0; i < NUM_ITEMS; i++) {
+ FeedItem item = new FeedItem(0, "title " + i, "id " + i, "link " + i, new Date(), true, feed);
+ feed.getItems().add(item);
+ }
+
+ PodDBAdapter adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ for (FeedItem item : feed.getItems()) {
+ assertTrue(item.getId() != 0);
+ }
+ for (int from = 0; from < NUM_ITEMS; from++) {
+ for (int to = 0; to < NUM_ITEMS; to++) {
+ if (from == to) {
+ continue;
+ }
+ Log.d(TAG, String.format("testMoveQueueItem: From=%d, To=%d", from, to));
+ final long fromID = feed.getItems().get(from).getId();
+
+ adapter = new PodDBAdapter(context);
+ adapter.open();
+ adapter.setQueue(feed.getItems());
+ adapter.close();
+
+ DBWriter.moveQueueItem(context, from, to, false).get(TIMEOUT, TimeUnit.SECONDS);
+ adapter = new PodDBAdapter(context);
+ adapter.open();
+ Cursor queue = adapter.getQueueIDCursor();
+ assertTrue(queue.getCount() == NUM_ITEMS);
+ assertTrue(queue.moveToPosition(from));
+ assertFalse(queue.getLong(0) == fromID);
+ assertTrue(queue.moveToPosition(to));
+ assertTrue(queue.getLong(0) == fromID);
+
+ queue.close();
+ adapter.close();
+ }
+ }
+ }
+
}
diff --git a/src/instrumentationTest/de/test/antennapod/syndication/handler/FeedHandlerTest.java b/src/instrumentationTest/de/test/antennapod/syndication/handler/FeedHandlerTest.java
index 030df0fcb..95c3a3dba 100644
--- a/src/instrumentationTest/de/test/antennapod/syndication/handler/FeedHandlerTest.java
+++ b/src/instrumentationTest/de/test/antennapod/syndication/handler/FeedHandlerTest.java
@@ -124,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;