Merge branch 'develop' of https://github.com/danieloeh/AntennaPod into move-to-top

This commit is contained in:
Tom Hennen 2013-09-02 19:35:07 -04:00
commit d538e3899c
75 changed files with 2134 additions and 690 deletions

1
.gitignore vendored
View File

@ -41,3 +41,4 @@ proguard
libs libs
*.DS_Store *.DS_Store
src/de/danoeh/antennapod/util/flattr/FlattrConfig.java src/de/danoeh/antennapod/util/flattr/FlattrConfig.java
gradle.properties

View File

@ -61,6 +61,7 @@
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/> <category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/> <data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="*"/> <data android:host="*"/>
<data android:pathPattern=".*\\.xml"/> <data android:pathPattern=".*\\.xml"/>
<data android:pathPattern=".*\\.rss"/> <data android:pathPattern=".*\\.rss"/>
@ -71,6 +72,7 @@
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/> <category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/> <data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="feeds.feedburner.com"/> <data android:host="feeds.feedburner.com"/>
<data android:host="feedproxy.google.com"/> <data android:host="feedproxy.google.com"/>
<data android:host="feeds2.feedburner.com"/> <data android:host="feeds2.feedburner.com"/>
@ -82,6 +84,7 @@
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/> <category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/> <data android:scheme="http"/>
<data android:scheme="https"/>
<data android:mimeType="text/xml"/> <data android:mimeType="text/xml"/>
<data android:mimeType="application/rss+xml"/> <data android:mimeType="application/rss+xml"/>
<data android:mimeType="application/atom+xml"/> <data android:mimeType="application/atom+xml"/>

View File

@ -3,7 +3,7 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:0.5.5' classpath 'com.android.tools.build:gradle:0.5.6'
} }
} }
apply plugin: 'android' apply plugin: 'android'
@ -12,6 +12,17 @@ repositories {
mavenCentral() mavenCentral()
} }
dependencies { 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 'com.android.support:appcompat-v7:18.0.+'
compile 'org.apache.commons:commons-lang3:3.1' compile 'org.apache.commons:commons-lang3:3.1'
compile ('org.shredzone.flattr4j:flattr4j-core:2.4') { compile ('org.shredzone.flattr4j:flattr4j-core:2.4') {
@ -22,6 +33,7 @@ dependencies {
compile 'commons-io:commons-io:2.4' compile 'commons-io:commons-io:2.4'
compile 'com.nineoldandroids:library:2.4.0' compile 'com.nineoldandroids:library:2.4.0'
compile project(':submodules:dslv:library') compile project(':submodules:dslv:library')
compile files('libs/presto_client-0.8.5.jar')
} }
android { android {
@ -35,6 +47,31 @@ android {
testInstrumentationRunner "instrumentationTest.de.test.antennapod.AntennaPodTestRunner" 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 { sourceSets {
main { main {
manifest.srcFile 'AndroidManifest.xml' manifest.srcFile 'AndroidManifest.xml'
@ -51,5 +88,10 @@ android {
debug { debug {
packageNameSuffix ".debug" packageNameSuffix ".debug"
} }
release {
runProguard true
proguardFile 'proguard.cfg'
signingConfig signingConfigs.releaseConfig
}
} }
} }

10
pom.xml
View File

@ -84,6 +84,14 @@
<artifactId>library</artifactId> <artifactId>library</artifactId>
<version>2.4.0</version> <version>2.4.0</version>
</dependency> </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> </dependencies>
<build> <build>
@ -212,7 +220,7 @@
</manifest> </manifest>
<proguard> <proguard>
<skip>false</skip> <skip>false</skip>
<config>proguard.cfg</config> <config>proguard-mvn.cfg</config>
</proguard> </proguard>
</configuration> </configuration>
<executions> <executions>

66
proguard-mvn.cfg Normal file
View File

@ -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

View File

@ -8,10 +8,7 @@
-optimizations !code/simplification/arithmetic -optimizations !code/simplification/arithmetic
-keepattributes *Annotation* -keepattributes *Annotation*
#-libraryjars libs/android-support-v4.jar #-injars libs/presto_client-0.8.5.jar
#-libraryjars libs/commons-lang3-3.1.jar
#-libraryjars libs/flattr4j-core-2.4.jar
#-libraryjars libs/commons-io-2.4.jar
-keep public class * extends android.app.Activity -keep public class * extends android.app.Activity
-keep public class * extends android.app.Application -keep public class * extends android.app.Application
@ -52,10 +49,12 @@
public static <fields>; public static <fields>;
} }
-keep class android.support.v4.app.** { *; } -keep class android.support.v4.** { *; }
-keep interface android.support.v4.app.** { *; } -keep interface android.support.v4.** { *; }
-keep class com.actionbarsherlock.** { *; } -keep class android.support.v7.** { *; }
-keep interface com.actionbarsherlock.** { *; } -keep interface android.support.v7.** { *; }
-dontwarn android.support.v4.**
-dontwarn android.support.v7.**
-keepattributes *Annotation* -keepattributes *Annotation*

View File

@ -92,14 +92,12 @@
android:layout_width="80dp" android:layout_width="80dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="?attr/borderless_button" android:background="?attr/borderless_button"
android:src="?attr/av_pause" /> android:src="?attr/av_pause" />
<ImageButton <ImageButton
android:id="@+id/butRev" android:id="@+id/butRev"
android:layout_width="80dp" android:layout_width="60dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_toLeftOf="@id/butPlay" android:layout_toLeftOf="@id/butPlay"
android:background="?attr/borderless_button" android:background="?attr/borderless_button"
@ -107,11 +105,22 @@
<ImageButton <ImageButton
android:id="@+id/butFF" android:id="@+id/butFF"
android:layout_width="80dp" android:layout_width="60dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_toRightOf="@id/butPlay" android:layout_toRightOf="@id/butPlay"
android:background="?attr/borderless_button" android:background="?attr/borderless_button"
android:src="?attr/av_fast_forward" /> 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>
<RelativeLayout <RelativeLayout

View File

@ -79,8 +79,6 @@
android:layout_width="80dp" android:layout_width="80dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:background="?attr/borderless_button" android:background="?attr/borderless_button"
android:src="?attr/av_pause" /> android:src="?attr/av_pause" />
@ -99,6 +97,17 @@
android:layout_toRightOf="@id/butPlay" android:layout_toRightOf="@id/butPlay"
android:background="?attr/borderless_button" android:background="?attr/borderless_button"
android:src="?attr/av_fast_forward" /> 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>
<RelativeLayout <RelativeLayout

View File

@ -1,6 +1,16 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <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"> <string-array name="update_intervall_values">
<item>0</item> <item>0</item>
<item>1</item> <item>1</item>
@ -28,6 +38,50 @@
<item>80</item> <item>80</item>
<item>100</item> <item>100</item>
</string-array> </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"> <string-array name="autodl_select_networks_default_entries">
<item>N/A</item> <item>N/A</item>
</string-array> </string-array>
@ -42,5 +96,6 @@
<item>0</item> <item>0</item>
<item>1</item> <item>1</item>
</string-array> </string-array>
</resources> </resources>

View File

@ -47,6 +47,8 @@
<string name="processing_label">Processing</string> <string name="processing_label">Processing</string>
<string name="loading_label">Loading...</string> <string name="loading_label">Loading...</string>
<string name="image_of_prefix">Image of:\u0020</string> <string name="image_of_prefix">Image of:\u0020</string>
<string name="close_label">Close</string>
<!-- 'Add Feed' Activity labels --> <!-- 'Add Feed' Activity labels -->
<string name="feedurl_label">Feed URL</string> <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="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="flattr_click_success">Successfully flattred this thing!</string>
<string name="flattring_label">Flattring</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 --> <!-- Empty list labels -->
<string name="no_items_label">There are no items in this list.</string> <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_light">Light</string>
<string name="pref_theme_title_dark">Dark</string> <string name="pref_theme_title_dark">Dark</string>
<string name="pref_episode_cache_unlimited">Unlimited</string> <string name="pref_episode_cache_unlimited">Unlimited</string>
<string name="pref_update_interval_hours_plural">hours</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_singular">hour</string>
<string name="pref_update_interval_hours_manual">Manual</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 --> <!-- 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_sum">Pause playback instead of lowering volume when another app wants to play sounds</string>
<string name="pref_pausePlaybackForFocusLoss_title">Pause for interruptions</string> <string name="pref_pausePlaybackForFocusLoss_title">Pause for interruptions</string>
</resources> </resources>

View File

@ -17,6 +17,11 @@
android:key="prefFollowQueue" android:key="prefFollowQueue"
android:summary="@string/pref_followQueue_sum" android:summary="@string/pref_followQueue_sum"
android:title="@string/pref_followQueue_title" /> 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 <CheckBoxPreference
android:defaultValue="false" android:defaultValue="false"
android:enabled="true" android:enabled="true"

View File

@ -12,7 +12,9 @@ import android.util.Log;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener; import android.view.View.OnClickListener;
import android.view.Window; import android.view.Window;
import android.view.View.OnLongClickListener;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView.ScaleType; import android.widget.ImageView.ScaleType;
import android.widget.ListView; import android.widget.ListView;
@ -22,11 +24,14 @@ import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R; import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.ChapterListAdapter; import de.danoeh.antennapod.adapter.ChapterListAdapter;
import de.danoeh.antennapod.asynctask.ImageLoader; import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.dialog.VariableSpeedDialog;
import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.feed.Chapter;
import de.danoeh.antennapod.feed.MediaType; import de.danoeh.antennapod.feed.MediaType;
import de.danoeh.antennapod.feed.SimpleChapter; import de.danoeh.antennapod.feed.SimpleChapter;
import de.danoeh.antennapod.fragment.CoverFragment; import de.danoeh.antennapod.fragment.CoverFragment;
import de.danoeh.antennapod.fragment.ItemDescriptionFragment; 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.service.PlaybackService;
import de.danoeh.antennapod.util.playback.ExternalMedia; import de.danoeh.antennapod.util.playback.ExternalMedia;
import de.danoeh.antennapod.util.playback.Playable; import de.danoeh.antennapod.util.playback.Playable;
@ -56,6 +61,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
private TextView txtvTitle; private TextView txtvTitle;
private TextView txtvFeed; private TextView txtvFeed;
private Button butPlaybackSpeed;
private ImageButton butNavLeft; private ImageButton butNavLeft;
private ImageButton butNavRight; private ImageButton butNavRight;
@ -218,7 +224,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
if (savedPosition != -1) { if (savedPosition != -1) {
switchToFragment(savedPosition); switchToFragment(savedPosition);
} }
} }
@Override @Override
@ -363,6 +369,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
txtvFeed = (TextView) findViewById(R.id.txtvFeed); txtvFeed = (TextView) findViewById(R.id.txtvFeed);
butNavLeft = (ImageButton) findViewById(R.id.butNavLeft); butNavLeft = (ImageButton) findViewById(R.id.butNavLeft);
butNavRight = (ImageButton) findViewById(R.id.butNavRight); butNavRight = (ImageButton) findViewById(R.id.butNavRight);
butPlaybackSpeed = (Button) findViewById(R.id.butPlaybackSpeed);
butNavLeft.setOnClickListener(new OnClickListener() { 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 @Override
@ -421,7 +487,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
((AudioplayerContentFragment) currentlyShownFragment) ((AudioplayerContentFragment) currentlyShownFragment)
.onDataSetChanged(media); .onDataSetChanged(media);
} }
updateButPlaybackSpeed();
} }
public void notifyMediaPositionChanged() { public void notifyMediaPositionChanged() {

View File

@ -347,7 +347,9 @@ public class DirectoryChooserActivity extends ActionBarActivity {
* CREATE_DIRECTORY_NAME. * CREATE_DIRECTORY_NAME.
*/ */
private int createFolder() { 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); File newDir = new File(selectedDir, CREATE_DIRECTORY_NAME);
if (!newDir.exists()) { if (!newDir.exists()) {
boolean result = newDir.mkdir(); boolean result = newDir.mkdir();
@ -359,10 +361,8 @@ public class DirectoryChooserActivity extends ActionBarActivity {
} else { } else {
return R.string.create_folder_error_already_exists; return R.string.create_folder_error_already_exists;
} }
} else if (selectedDir.canWrite() == false) {
return R.string.create_folder_error_no_write_access;
} else { } else {
return R.string.create_folder_error; return R.string.create_folder_error_no_write_access;
} }
} }

View File

@ -121,7 +121,7 @@ public class DownloadActivity extends ActionBarActivity implements
contentRefresher.cancel(true); contentRefresher.cancel(true);
} }
contentRefresher = new AsyncTask<Void, Void, Void>() { contentRefresher = new AsyncTask<Void, Void, Void>() {
private final int WAITING_INTERVALL = 1000; private static final int WAITING_INTERVAL = 1000;
@Override @Override
protected void onProgressUpdate(Void... values) { protected void onProgressUpdate(Void... values) {
@ -137,7 +137,7 @@ public class DownloadActivity extends ActionBarActivity implements
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
while (!isCancelled()) { while (!isCancelled()) {
try { try {
Thread.sleep(WAITING_INTERVALL); Thread.sleep(WAITING_INTERVAL);
publishProgress(); publishProgress();
} catch (InterruptedException e) { } catch (InterruptedException e) {
return null; return null;

View File

@ -37,7 +37,7 @@ public class DownloadLogActivity extends ActionBarActivity {
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
listview = (ListView) findViewById(R.layout.listview_activity); listview = (ListView) findViewById(R.id.listview);
dla = new DownloadLogAdapter(this, itemAccess); dla = new DownloadLogAdapter(this, itemAccess);
listview.setAdapter(dla); listview.setAdapter(dla);

View File

@ -7,6 +7,7 @@ import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.media.AudioManager;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
@ -57,6 +58,7 @@ public class FeedItemlistActivity extends ActionBarActivity {
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.feeditemlist_activity); setContentView(R.layout.feeditemlist_activity);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
long feedId = getIntent().getLongExtra( long feedId = getIntent().getLongExtra(
FeedlistFragment.EXTRA_SELECTED_FEED, -1); FeedlistFragment.EXTRA_SELECTED_FEED, -1);
@ -122,6 +124,8 @@ public class FeedItemlistActivity extends ActionBarActivity {
SearchManager searchManager = SearchManager searchManager =
(SearchManager) getSystemService(Context.SEARCH_SERVICE); (SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.search_item)); SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.search_item));
searchView.setIconifiedByDefault(true); searchView.setIconifiedByDefault(true);
searchView.setSearchableInfo( searchView.setSearchableInfo(

View File

@ -1,5 +1,6 @@
package de.danoeh.antennapod.activity; package de.danoeh.antennapod.activity;
import android.media.AudioManager;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
@ -45,6 +46,7 @@ public class ItemviewActivity extends ActionBarActivity {
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
getSupportActionBar().setDisplayShowTitleEnabled(false); getSupportActionBar().setDisplayShowTitleEnabled(false);
EventDistributor.getInstance().register(contentUpdate); EventDistributor.getInstance().register(contentUpdate);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
long itemId = getIntent().getLongExtra( long itemId = getIntent().getLongExtra(
ItemlistFragment.EXTRA_SELECTED_FEEDITEM, -1); ItemlistFragment.EXTRA_SELECTED_FEEDITEM, -1);

View File

@ -6,6 +6,7 @@ import android.app.SearchManager;
import android.app.SearchableInfo; import android.app.SearchableInfo;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.media.AudioManager;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.app.FragmentPagerAdapter;
@ -55,8 +56,9 @@ public class MainActivity extends ActionBarActivity {
StorageUtils.checkStorageAvailability(this); StorageUtils.checkStorageAvailability(this);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.main); 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); viewpager = (ViewPager) findViewById(R.id.viewpager);
pagerAdapter = new TabsAdapter(this, viewpager); pagerAdapter = new TabsAdapter(this, viewpager);
@ -181,7 +183,12 @@ public class MainActivity extends ActionBarActivity {
SearchManager searchManager = SearchManager searchManager =
(SearchManager) getSystemService(Context.SEARCH_SERVICE); (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); searchView.setIconifiedByDefault(true);
SearchableInfo info = searchManager.getSearchableInfo(getComponentName()); SearchableInfo info = searchManager.getSearchableInfo(getComponentName());

View File

@ -4,6 +4,7 @@ import android.app.AlertDialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.graphics.PixelFormat; import android.graphics.PixelFormat;
import android.media.AudioManager;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBarActivity;
@ -128,10 +129,19 @@ public abstract class MediaplayerActivity extends ActionBarActivity
public void onPlaybackEnd() { public void onPlaybackEnd() {
finish(); finish();
} }
@Override
public void onPlaybackSpeedChange() {
MediaplayerActivity.this.onPlaybackSpeedChange();
}
}; };
} }
protected void onPlaybackSpeedChange() {
}
protected void onServiceQueried() { protected void onServiceQueried() {
supportInvalidateOptionsMenu(); supportInvalidateOptionsMenu();
} }
@ -143,8 +153,9 @@ public abstract class MediaplayerActivity extends ActionBarActivity
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "Creating Activity"); Log.d(TAG, "Creating Activity");
StorageUtils.checkStorageAvailability(this); StorageUtils.checkStorageAvailability(this);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
orientation = getResources().getConfiguration().orientation; orientation = getResources().getConfiguration().orientation;
getWindow().setFormat(PixelFormat.TRANSPARENT); getWindow().setFormat(PixelFormat.TRANSPARENT);
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
} }

View File

@ -22,7 +22,7 @@ import de.danoeh.antennapod.preferences.UserPreferences;
public class MiroGuideCategoryActivity extends ActionBarActivity { public class MiroGuideCategoryActivity extends ActionBarActivity {
private static final String TAG = "MiroGuideCategoryActivity"; private static final String TAG = "MiroGuideCategoryActivity";
public static String EXTRA_CATEGORY = "category"; public static final String EXTRA_CATEGORY = "category";
private ViewPager viewpager; private ViewPager viewpager;
private CategoryPagerAdapter pagerAdapter; private CategoryPagerAdapter pagerAdapter;

View File

@ -157,7 +157,7 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
@Override @Override
public void run() { public void run() {
String reasonDetailed = new String(); String reasonDetailed = "";
boolean successful = false; boolean successful = false;
FeedHandler handler = new FeedHandler(); FeedHandler handler = new FeedHandler();
try { try {

View File

@ -7,6 +7,7 @@ import java.net.URL;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.os.Bundle; import android.os.Bundle;
import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.util.LangUtils;
/** Lets the user start the OPML-import process. */ /** Lets the user start the OPML-import process. */
public class OpmlImportFromIntentActivity extends OpmlImportBaseActivity { public class OpmlImportFromIntentActivity extends OpmlImportBaseActivity {
@ -20,7 +21,8 @@ public class OpmlImportFromIntentActivity extends OpmlImportBaseActivity {
try { try {
URL mOpmlURL = new URL(getIntent().getData().toString()); 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); startImport(in);
} catch (Exception e) { } catch (Exception e) {
new AlertDialog.Builder(this).setMessage("Cannot open XML - Reason: " + e.getMessage()).show(); new AlertDialog.Builder(this).setMessage("Cannot open XML - Reason: " + e.getMessage()).show();

View File

@ -1,8 +1,10 @@
package de.danoeh.antennapod.activity; package de.danoeh.antennapod.activity;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileReader; import java.io.InputStreamReader;
import java.io.IOException;
import java.io.Reader; import java.io.Reader;
import android.app.AlertDialog; import android.app.AlertDialog;
@ -20,6 +22,7 @@ import android.widget.Toast;
import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R; import de.danoeh.antennapod.R;
import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.util.LangUtils;
import de.danoeh.antennapod.util.StorageUtils; import de.danoeh.antennapod.util.StorageUtils;
/** /**
@ -125,8 +128,10 @@ public class OpmlImportFromPathActivity extends OpmlImportBaseActivity {
} }
private void startImport(File file) { private void startImport(File file) {
Reader mReader = null;
try { try {
Reader mReader = new FileReader(file); mReader = new InputStreamReader(new FileInputStream(file),
LangUtils.UTF_8);
if (AppConfig.DEBUG) Log.d(TAG, "Parsing " + file.toString()); if (AppConfig.DEBUG) Log.d(TAG, "Parsing " + file.toString());
startImport(mReader); startImport(mReader);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {

View File

@ -51,6 +51,7 @@ public class OrganizeQueueActivity extends ActionBarActivity implements
listView = (DragSortListView) findViewById(android.R.id.list); listView = (DragSortListView) findViewById(android.R.id.list);
listView.setDropListener(dropListener); listView.setDropListener(dropListener);
listView.setRemoveListener(removeListener); listView.setRemoveListener(removeListener);
listView.setEmptyView(findViewById(android.R.id.empty));
loadData(); loadData();
undoBarController = new UndoBarController(findViewById(R.id.undobar), undoBarController = new UndoBarController(findViewById(R.id.undobar),
@ -155,9 +156,11 @@ public class OrganizeQueueActivity extends ActionBarActivity implements
public void onUndo(Parcelable token) { public void onUndo(Parcelable token) {
// Perform the undo // Perform the undo
UndoToken undoToken = (UndoToken) token; UndoToken undoToken = (UndoToken) token;
long itemId = undoToken.getFeedItemId(); if (token != null) {
int position = undoToken.getPosition(); long itemId = undoToken.getFeedItemId();
DBWriter.addQueueItemAt(OrganizeQueueActivity.this, itemId, position, false); int position = undoToken.getPosition();
DBWriter.addQueueItemAt(OrganizeQueueActivity.this, itemId, position, false);
}
} }
private static class OrganizeAdapter extends BaseAdapter { private static class OrganizeAdapter extends BaseAdapter {

View File

@ -25,6 +25,7 @@ import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R; import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.FlattrClickWorker; import de.danoeh.antennapod.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.asynctask.OpmlExportWorker; import de.danoeh.antennapod.asynctask.OpmlExportWorker;
import de.danoeh.antennapod.dialog.VariableSpeedDialog;
import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.util.flattr.FlattrUtils; 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_ABOUT = "prefAbout";
private static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir"; private static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir";
private static final String AUTO_DL_PREF_SCREEN = "prefAutoDownloadSettings"; private static final String AUTO_DL_PREF_SCREEN = "prefAutoDownloadSettings";
private static final String PREF_PLAYBACK_SPEED_LAUNCHER = "prefPlaybackSpeedLauncher";
private CheckBoxPreference[] selectedNetworks; private CheckBoxPreference[] selectedNetworks;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@ -156,6 +158,14 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
return true; return true;
} }
}); });
findPreference(PREF_PLAYBACK_SPEED_LAUNCHER)
.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
VariableSpeedDialog.showDialog(PreferenceActivity.this);
return true;
}
});
buildUpdateIntervalPreference(); buildUpdateIntervalPreference();
buildAutodownloadSelectedNetworsPreference(); buildAutodownloadSelectedNetworsPreference();
setSelectedNetworksEnabled(UserPreferences setSelectedNetworksEnabled(UserPreferences

View File

@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.SearchManager; import android.app.SearchManager;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
@ -140,32 +141,34 @@ public class SearchActivity extends ActionBarActivity implements AdapterView.OnI
@Override @Override
public void run() { public void run() {
Log.d(TAG, "Starting background work"); Log.d(TAG, "Starting background work");
final Activity activity = SearchActivity.this;
final List<SearchResult> result = FeedSearcher final List<SearchResult> result = FeedSearcher
.performSearch(SearchActivity.this, query, feedID); .performSearch(activity, query, feedID);
if (SearchActivity.this != null) { activity.runOnUiThread(new Runnable() {
SearchActivity.this.runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "Background work finished"); Log.d(TAG, "Background work finished");
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "Found " + result.size() Log.d(TAG, "Found " + result.size()
+ " results"); + " results");
searchAdapter.clear(); searchAdapter.clear();
searchAdapter.addAll(result); for (SearchResult s : result) {
searchAdapter.notifyDataSetChanged(); searchAdapter.add(s);
txtvStatus
.setText(R.string.search_status_no_results);
if (!searchAdapter.isEmpty()) {
txtvStatus.setVisibility(View.GONE);
} else {
txtvStatus.setVisibility(View.VISIBLE);
}
} }
}); searchAdapter.notifyDataSetChanged();
} txtvStatus
.setText(R.string.search_status_no_results);
if (!searchAdapter.isEmpty()) {
txtvStatus.setVisibility(View.GONE);
} else {
txtvStatus.setVisibility(View.VISIBLE);
}
}
});
} }
}; };
thread.start(); thread.start();

View File

@ -144,53 +144,6 @@ public class ChapterListAdapter extends ArrayAdapter<Chapter> {
TextView link; 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 @Override
public int getCount() { public int getCount() {
// ignore invalid chapters // ignore invalid chapters

View File

@ -47,7 +47,7 @@ public class BitmapDecodeWorkerTask extends Thread {
*/ */
protected boolean tagsMatching(ImageView target) { protected boolean tagsMatching(ImageView target) {
return target.getTag() == null return target.getTag() == null
|| target.getTag() == imageResource.getImageLoaderCacheKey(); || target.getTag().equals(imageResource.getImageLoaderCacheKey());
} }
protected void onPostExecute() { protected void onPostExecute() {

View File

@ -77,7 +77,7 @@ public class ImageLoader {
}); });
} }
public static ImageLoader getInstance() { public static synchronized ImageLoader getInstance() {
if (singleton == null) { if (singleton == null) {
singleton = new ImageLoader(); singleton = new ImageLoader();
} }

View File

@ -1,8 +1,9 @@
package de.danoeh.antennapod.asynctask; package de.danoeh.antennapod.asynctask;
import java.io.File; import java.io.File;
import java.io.FileWriter; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Arrays; import java.util.Arrays;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@ -16,6 +17,7 @@ import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.R; import de.danoeh.antennapod.R;
import de.danoeh.antennapod.opml.OpmlWriter; import de.danoeh.antennapod.opml.OpmlWriter;
import de.danoeh.antennapod.preferences.UserPreferences; import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.util.LangUtils;
import de.danoeh.antennapod.storage.DBReader; import de.danoeh.antennapod.storage.DBReader;
/** Writes an OPML file into the export directory in the background. */ /** 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(); output.delete();
} }
} }
OutputStreamWriter writer = null;
try { try {
FileWriter writer = new FileWriter(output); writer = new OutputStreamWriter(new FileOutputStream(output), LangUtils.UTF_8);
opmlWriter.writeDocument(DBReader.getFeedList(context), writer); opmlWriter.writeDocument(DBReader.getFeedList(context), writer);
writer.close();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
exception = e; exception = e;
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException ioe) {
exception = ioe;
}
}
} }
return null; return null;
} }

View File

@ -1,5 +1,6 @@
package de.danoeh.antennapod.asynctask; package de.danoeh.antennapod.asynctask;
import java.util.Arrays;
import java.util.Date; import java.util.Date;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@ -22,7 +23,7 @@ public class OpmlFeedQueuer extends AsyncTask<Void, Void, Void> {
public OpmlFeedQueuer(Context context, int[] selection) { public OpmlFeedQueuer(Context context, int[] selection) {
super(); super();
this.context = context; this.context = context;
this.selection = selection; this.selection = Arrays.copyOf(selection, selection.length);
} }
@Override @Override

View File

@ -64,6 +64,13 @@ public class OpmlImportWorker extends
@Override @Override
protected void onPostExecute(ArrayList<OpmlElement> result) { protected void onPostExecute(ArrayList<OpmlElement> result) {
if (mReader != null) {
try {
mReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
progDialog.dismiss(); progDialog.dismiss();
if (exception != null) { if (exception != null) {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)

View File

@ -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();
}
}

View File

@ -39,7 +39,7 @@ public class EventDistributor extends Observable {
events = new ConcurrentLinkedQueue<Integer>(); events = new ConcurrentLinkedQueue<Integer>();
} }
public static EventDistributor getInstance() { public static synchronized EventDistributor getInstance() {
if (instance == null) { if (instance == null) {
instance = new EventDistributor(); instance = new EventDistributor();
} }

View File

@ -55,7 +55,11 @@ public class Feed extends FeedFile {
super(fileUrl, downloadUrl, downloaded); super(fileUrl, downloadUrl, downloaded);
this.id = id; this.id = id;
this.title = title; this.title = title;
this.lastUpdate = lastUpdate; if (lastUpdate != null) {
this.lastUpdate = (Date) lastUpdate.clone();
} else {
this.lastUpdate = null;
}
this.link = link; this.link = link;
this.description = description; this.description = description;
this.paymentLink = paymentLink; this.paymentLink = paymentLink;
@ -83,7 +87,7 @@ public class Feed extends FeedFile {
*/ */
public Feed(String url, Date lastUpdate) { public Feed(String url, Date lastUpdate) {
super(null, url, false); 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; 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() { public Date getLastUpdate() {
return lastUpdate; return (lastUpdate != null) ? (Date) lastUpdate.clone() : null;
} }
public void setLastUpdate(Date lastUpdate) { public void setLastUpdate(Date lastUpdate) {
this.lastUpdate = lastUpdate; this.lastUpdate = (lastUpdate != null) ? (Date) lastUpdate.clone() : null;
} }
public String getFeedIdentifier() { public String getFeedIdentifier() {

View File

@ -48,6 +48,19 @@ public class FeedItem extends FeedComponent implements
this.read = true; 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) { public void updateFromOther(FeedItem other) {
super.updateFromOther(other); super.updateFromOther(other);
if (other.title != null) { if (other.title != null) {
@ -123,19 +136,35 @@ public class FeedItem extends FeedComponent implements
} }
public Date getPubDate() { public Date getPubDate() {
return pubDate; if (pubDate != null) {
return (Date) pubDate.clone();
} else {
return null;
}
} }
public void setPubDate(Date pubDate) { public void setPubDate(Date pubDate) {
this.pubDate = pubDate; if (pubDate != null) {
this.pubDate = (Date) pubDate.clone();
} else {
this.pubDate = null;
}
} }
public FeedMedia getMedia() { public FeedMedia getMedia() {
return media; 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) { public void setMedia(FeedMedia media) {
this.media = media; this.media = media;
if (media != null && media.getItem() != this) {
media.setItem(this);
}
} }
public Feed getFeed() { public Feed getFeed() {

View File

@ -53,7 +53,8 @@ public class FeedMedia extends FeedFile implements Playable {
this.position = position; this.position = position;
this.size = size; this.size = size;
this.mime_type = mime_type; this.mime_type = mime_type;
this.playbackCompletionDate = playbackCompletionDate; this.playbackCompletionDate = playbackCompletionDate == null
? null : (Date) playbackCompletionDate.clone();
} }
public FeedMedia(long id, FeedItem item) { public FeedMedia(long id, FeedItem item) {
@ -164,16 +165,25 @@ public class FeedMedia extends FeedFile implements Playable {
return item; 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) { public void setItem(FeedItem item) {
this.item = item; this.item = item;
if (item != null && item.getMedia() != this) {
item.setMedia(this);
}
} }
public Date getPlaybackCompletionDate() { public Date getPlaybackCompletionDate() {
return playbackCompletionDate; return playbackCompletionDate == null
} ? null : (Date) playbackCompletionDate.clone(); }
public void setPlaybackCompletionDate(Date playbackCompletionDate) { public void setPlaybackCompletionDate(Date playbackCompletionDate) {
this.playbackCompletionDate = playbackCompletionDate; this.playbackCompletionDate = playbackCompletionDate == null
? null : (Date) playbackCompletionDate.clone();
} }
public boolean isInProgress() { public boolean isInProgress() {
@ -304,7 +314,7 @@ public class FeedMedia extends FeedFile implements Playable {
@Override @Override
public void saveCurrentPosition(SharedPreferences pref, int newPosition) { public void saveCurrentPosition(SharedPreferences pref, int newPosition) {
position = newPosition; position = newPosition;
DBWriter.setFeedMediaPosition(PodcastApp.getInstance(), this); DBWriter.setFeedMediaPlaybackInformation(PodcastApp.getInstance(), this);
} }
@Override @Override

View File

@ -173,6 +173,12 @@ public class ExternalPlayerFragment extends Fragment {
.newOnPlayButtonClickListener()); .newOnPlayButtonClickListener());
} }
} }
@Override
public void onPlaybackSpeedChange() {
// TODO Auto-generated method stub
}
}; };
} }

View File

@ -3,6 +3,7 @@ package de.danoeh.antennapod.fragment;
import java.util.List; import java.util.List;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.os.AsyncTask; import android.os.AsyncTask;
@ -29,9 +30,9 @@ import de.danoeh.antennapod.storage.FeedItemStatistics;
import de.danoeh.antennapod.util.menuhandler.FeedMenuHandler; import de.danoeh.antennapod.util.menuhandler.FeedMenuHandler;
public class FeedlistFragment extends Fragment implements public class FeedlistFragment extends Fragment implements
ActionMode.Callback, AdapterView.OnItemClickListener, ActionMode.Callback, AdapterView.OnItemClickListener,
AdapterView.OnItemLongClickListener { AdapterView.OnItemLongClickListener {
private static final String TAG = "FeedlistFragment"; private static final String TAG = "FeedlistFragment";
private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED
| EventDistributor.DOWNLOAD_QUEUED | EventDistributor.DOWNLOAD_QUEUED
@ -94,12 +95,16 @@ public class FeedlistFragment extends Fragment implements
AsyncTask<Void, Void, List[]> loadTask = new AsyncTask<Void, Void, List[]>() { AsyncTask<Void, Void, List[]> loadTask = new AsyncTask<Void, Void, List[]>() {
@Override @Override
protected List[] doInBackground(Void... params) { protected List[] doInBackground(Void... params) {
return new List[]{DBReader.getFeedList(getActivity()), Context context = getActivity();
DBReader.getFeedStatisticsList(getActivity())}; if (context != null) {
return new List[]{DBReader.getFeedList(context),
DBReader.getFeedStatisticsList(context)};
} else {
return null;
}
} }
@Override @Override
protected void onPostExecute(List[] result) { protected void onPostExecute(List[] result) {
super.onPostExecute(result); super.onPostExecute(result);
@ -159,10 +164,15 @@ public class FeedlistFragment extends Fragment implements
EventDistributor.getInstance().register(contentUpdate); EventDistributor.getInstance().register(contentUpdate);
} }
@Override
public void onDestroy() {
super.onDestroy();
EventDistributor.getInstance().unregister(contentUpdate);
}
@Override @Override
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
EventDistributor.getInstance().unregister(contentUpdate);
if (mActionMode != null) { if (mActionMode != null) {
mActionMode.finish(); mActionMode.finish();
} }
@ -255,9 +265,9 @@ public class FeedlistFragment extends Fragment implements
public boolean onItemLongClick(AdapterView<?> parent, View view, public boolean onItemLongClick(AdapterView<?> parent, View view,
int position, long id) { int position, long id) {
Feed selection = fla.getItem(position); Feed selection = fla.getItem(position);
if (AppConfig.DEBUG)
Log.d(TAG, "Selected Feed with title " + selection.getTitle());
if (selection != null) { if (selection != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Selected Feed with title " + selection.getTitle());
if (mActionMode != null) { if (mActionMode != null) {
mActionMode.finish(); mActionMode.finish();
} }

View File

@ -1,5 +1,6 @@
package de.danoeh.antennapod.fragment; package de.danoeh.antennapod.fragment;
import android.content.*;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBarActivity;
import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedItem;
@ -9,10 +10,6 @@ import org.apache.commons.lang3.StringEscapeUtils;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; 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.content.res.TypedArray;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
@ -117,7 +114,12 @@ public class ItemDescriptionFragment extends Fragment {
@Override @Override
public boolean shouldOverrideUrlLoading(WebView view, String url) { public boolean shouldOverrideUrlLoading(WebView view, String url) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(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; return true;
} }
@ -138,6 +140,7 @@ public class ItemDescriptionFragment extends Fragment {
} }
}); });
registerForContextMenu(webvDescription); registerForContextMenu(webvDescription);
return webvDescription; return webvDescription;
} }
@ -371,11 +374,10 @@ public class ItemDescriptionFragment extends Fragment {
Callable<String> shownotesLoadTask = shownotesProvider.loadShownotes(); Callable<String> shownotesLoadTask = shownotesProvider.loadShownotes();
final String shownotes = shownotesLoadTask.call(); final String shownotes = shownotesLoadTask.call();
data = "";
data = StringEscapeUtils.unescapeHtml4(shownotes); data = StringEscapeUtils.unescapeHtml4(shownotes);
Activity activity = getActivity(); Activity activity = getActivity();
if (activity != null) { if (activity != null) {
TypedArray res = getActivity() TypedArray res = activity
.getTheme() .getTheme()
.obtainStyledAttributes( .obtainStyledAttributes(
new int[]{android.R.attr.textColorPrimary}); new int[]{android.R.attr.textColorPrimary});

View File

@ -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 EXTRA_SELECTED_FEEDITEM = "extra.de.danoeh.antennapod.activity.selected_feeditem";
public static final String ARGUMENT_FEED_ID = "argument.de.danoeh.antennapod.feed_id"; public static final String ARGUMENT_FEED_ID = "argument.de.danoeh.antennapod.feed_id";
protected InternalFeedItemlistAdapter fila; protected InternalFeedItemlistAdapter fila;
protected DownloadRequester requester = DownloadRequester.getInstance();
private Feed feed; private Feed feed;
protected List<Long> queue; protected List<Long> queue;
@ -61,6 +60,8 @@ public class ItemlistFragment extends ListFragment {
/** Argument for FeeditemlistAdapter */ /** Argument for FeeditemlistAdapter */
protected boolean showFeedtitle; protected boolean showFeedtitle;
private AsyncTask<Long, Void, Feed> currentLoadTask;
public ItemlistFragment(boolean showFeedtitle) { public ItemlistFragment(boolean showFeedtitle) {
super(); super();
this.showFeedtitle = showFeedtitle; this.showFeedtitle = showFeedtitle;
@ -116,11 +117,21 @@ public class ItemlistFragment extends ListFragment {
return inflater.inflate(R.layout.feeditemlist, container, false); return inflater.inflate(R.layout.feeditemlist, container, false);
} }
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onStart() {
super.onCreate(savedInstanceState); super.onStart();
loadData(); EventDistributor.getInstance().register(contentUpdate);
} loadData();
}
@Override
public void onDestroyView() {
super.onDestroyView();
EventDistributor.getInstance().unregister(contentUpdate);
if (currentLoadTask != null) {
currentLoadTask.cancel(true);
}
}
protected void loadData() { protected void loadData() {
final long feedId; final long feedId;
@ -156,8 +167,6 @@ public class ItemlistFragment extends ListFragment {
} else { } else {
Log.e(TAG, "Could not load queue"); Log.e(TAG, "Could not load queue");
} }
if (result.getItems().isEmpty()) {
}
setEmptyViewIfListIsEmpty(); setEmptyViewIfListIsEmpty();
if (fila != null) { if (fila != null) {
fila.notifyDataSetChanged(); fila.notifyDataSetChanged();
@ -171,6 +180,7 @@ public class ItemlistFragment extends ListFragment {
} }
} }
}; };
currentLoadTask = loadTask;
loadTask.execute(feedId); loadTask.execute(feedId);
} }
@ -187,17 +197,6 @@ public class ItemlistFragment extends ListFragment {
adapterCallback, showFeedtitle); adapterCallback, showFeedtitle);
} }
@Override
public void onPause() {
super.onPause();
}
@Override
public void onDestroy() {
super.onDestroy();
EventDistributor.getInstance().unregister(contentUpdate);
}
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
@ -209,7 +208,6 @@ public class ItemlistFragment extends ListFragment {
} }
}); });
updateProgressBarVisibility(); updateProgressBarVisibility();
EventDistributor.getInstance().register(contentUpdate);
} }
@Override @Override

View File

@ -16,6 +16,8 @@ import org.json.JSONObject;
import android.net.Uri; import android.net.Uri;
import de.danoeh.antennapod.util.LangUtils;
/** Executes HTTP requests and returns the results. */ /** Executes HTTP requests and returns the results. */
public class MiroGuideConnector { public class MiroGuideConnector {
private HttpClient httpClient; private HttpClient httpClient;
@ -73,12 +75,14 @@ public class MiroGuideConnector {
if (response.getStatusLine().getStatusCode() == 200) { if (response.getStatusLine().getStatusCode() == 200) {
HttpEntity entity = response.getEntity(); HttpEntity entity = response.getEntity();
if (entity != null) { if (entity != null) {
InputStream in = entity.getContent();
BufferedReader reader = new BufferedReader( BufferedReader reader = new BufferedReader(
new InputStreamReader(in)); new InputStreamReader(entity.getContent(),
result = reader.readLine(); LangUtils.UTF_8));
in.close(); try {
result = reader.readLine();
} finally {
reader.close();
}
} }
} else { } else {
throw new MiroGuideException(response.getStatusLine() throw new MiroGuideException(response.getStatusLine()

View File

@ -12,7 +12,7 @@ public class MiroGuideItem {
super(); super();
this.name = name; this.name = name;
this.description = description; this.description = description;
this.date = date; this.date = (Date) date.clone();
this.url = url; this.url = url;
} }
@ -30,7 +30,7 @@ public class MiroGuideItem {
} }
public Date getDate() { public Date getDate() {
return date; return (Date) date.clone();
} }
public String getUrl() { public String getUrl() {

View File

@ -2,9 +2,13 @@ package de.danoeh.antennapod.preferences;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.PendingIntent; import android.app.PendingIntent;
@ -41,12 +45,14 @@ public class UserPreferences implements
public static final String PREF_ENABLE_AUTODL_WIFI_FILTER = "prefEnableAutoDownloadWifiFilter"; public static final String PREF_ENABLE_AUTODL_WIFI_FILTER = "prefEnableAutoDownloadWifiFilter";
private static final String PREF_AUTODL_SELECTED_NETWORKS = "prefAutodownloadSelectedNetworks"; private static final String PREF_AUTODL_SELECTED_NETWORKS = "prefAutodownloadSelectedNetworks";
public static final String PREF_EPISODE_CACHE_SIZE = "prefEpisodeCacheSize"; 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"; public static final String PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS = "prefPauseForFocusLoss";
private static int EPISODE_CACHE_SIZE_UNLIMITED = -1; private static int EPISODE_CACHE_SIZE_UNLIMITED = -1;
private static UserPreferences instance; private static UserPreferences instance;
private Context context; private final Context context;
// Preferences // Preferences
private boolean pauseOnHeadsetDisconnect; private boolean pauseOnHeadsetDisconnect;
@ -61,6 +67,8 @@ public class UserPreferences implements
private boolean enableAutodownloadWifiFilter; private boolean enableAutodownloadWifiFilter;
private String[] autodownloadSelectedNetworks; private String[] autodownloadSelectedNetworks;
private int episodeCacheSize; private int episodeCacheSize;
private String playbackSpeed;
private String[] playbackSpeedArray;
private boolean pauseForFocusLoss; private boolean pauseForFocusLoss;
private UserPreferences(Context context) { private UserPreferences(Context context) {
@ -85,6 +93,7 @@ public class UserPreferences implements
createNoMediaFile(); createNoMediaFile();
PreferenceManager.getDefaultSharedPreferences(context) PreferenceManager.getDefaultSharedPreferences(context)
.registerOnSharedPreferenceChangeListener(instance); .registerOnSharedPreferenceChangeListener(instance);
} }
private void loadPreferences() { private void loadPreferences() {
@ -110,6 +119,9 @@ public class UserPreferences implements
episodeCacheSize = readEpisodeCacheSize(sp.getString( episodeCacheSize = readEpisodeCacheSize(sp.getString(
PREF_EPISODE_CACHE_SIZE, "20")); PREF_EPISODE_CACHE_SIZE, "20"));
enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false); 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); 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() { private static void instanceAvailable() {
if (instance == null) { if (instance == null) {
throw new IllegalStateException( throw new IllegalStateException(
@ -172,7 +214,8 @@ public class UserPreferences implements
public static boolean isDisplayOnlyEpisodes() { public static boolean isDisplayOnlyEpisodes() {
instanceAvailable(); instanceAvailable();
return instance.displayOnlyEpisodes; //return instance.displayOnlyEpisodes;
return false;
} }
public static boolean isAutoDelete() { public static boolean isAutoDelete() {
@ -199,6 +242,16 @@ public class UserPreferences implements
return EPISODE_CACHE_SIZE_UNLIMITED; 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 * 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 * 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")); PREF_EPISODE_CACHE_SIZE, "20"));
} else if (key.equals(PREF_ENABLE_AUTODL)) { } else if (key.equals(PREF_ENABLE_AUTODL)) {
enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false); 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)) { } else if (key.equals(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS)) {
pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false); 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, public static void setAutodownloadSelectedNetworks(Context context,
String[] value) { String[] value) {
SharedPreferences.Editor editor = PreferenceManager SharedPreferences.Editor editor = PreferenceManager

View File

@ -45,9 +45,13 @@ import de.danoeh.antennapod.storage.DBTasks;
import de.danoeh.antennapod.storage.DBWriter; import de.danoeh.antennapod.storage.DBWriter;
import de.danoeh.antennapod.util.BitmapDecoder; import de.danoeh.antennapod.util.BitmapDecoder;
import de.danoeh.antennapod.util.QueueAccess; import de.danoeh.antennapod.util.QueueAccess;
import de.danoeh.antennapod.util.DuckType;
import de.danoeh.antennapod.util.flattr.FlattrUtils; 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;
import de.danoeh.antennapod.util.playback.Playable.PlayableException; import de.danoeh.antennapod.util.playback.Playable.PlayableException;
import de.danoeh.antennapod.util.playback.VideoPlayer;
import de.danoeh.antennapod.util.playback.PlaybackController; 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; 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 * Returned by getPositionSafe() or getDurationSafe() if the playbackService
* is in an invalid state. * is in an invalid state.
*/ */
@ -132,13 +141,12 @@ public class PlaybackService extends Service {
private static final int NOTIFICATION_ID = 1; private static final int NOTIFICATION_ID = 1;
private volatile IPlayer player;
private RemoteControlClient remoteControlClient;
private AudioManager audioManager; private AudioManager audioManager;
private ComponentName mediaButtonReceiver; private ComponentName mediaButtonReceiver;
private MediaPlayer player; private volatile Playable media;
private RemoteControlClient remoteControlClient;
private Playable media;
/** /**
* True if media should be streamed (Extracted from Intent Extra) . * True if media should be streamed (Extracted from Intent Extra) .
@ -252,7 +260,6 @@ public class PlaybackService extends Service {
} }
); );
dbLoaderExecutor = Executors.newSingleThreadExecutor(); dbLoaderExecutor = Executors.newSingleThreadExecutor();
player = createMediaPlayer();
mediaButtonReceiver = new ComponentName(getPackageName(), mediaButtonReceiver = new ComponentName(getPackageName(),
MediaButtonReceiver.class.getName()); MediaButtonReceiver.class.getName());
@ -273,21 +280,42 @@ public class PlaybackService extends Service {
loadQueue(); loadQueue();
} }
private MediaPlayer createMediaPlayer() { private IPlayer createMediaPlayer() {
return createMediaPlayer(new MediaPlayer()); IPlayer player;
if (media == null || media.getMediaType() == MediaType.VIDEO) {
player = new VideoPlayer();
} else {
player = new AudioPlayer(this);
}
return createMediaPlayer(player);
} }
private MediaPlayer createMediaPlayer(MediaPlayer mp) { private IPlayer createMediaPlayer(IPlayer mp) {
if (mp != null) { if (mp != null && media != null) {
mp.setOnPreparedListener(preparedListener); if (media.getMediaType() == MediaType.AUDIO) {
mp.setOnCompletionListener(completionListener); ((AudioPlayer) mp).setOnPreparedListener(audioPreparedListener);
mp.setOnSeekCompleteListener(onSeekCompleteListener); ((AudioPlayer) mp)
mp.setOnErrorListener(onErrorListener); .setOnCompletionListener(audioCompletionListener);
mp.setOnBufferingUpdateListener(onBufferingUpdateListener); ((AudioPlayer) mp)
mp.setOnInfoListener(onInfoListener); .setOnSeekCompleteListener(audioSeekCompleteListener);
} ((AudioPlayer) mp).setOnErrorListener(audioErrorListener);
return mp; ((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") @SuppressLint("NewApi")
@Override @Override
@ -482,7 +510,7 @@ public class PlaybackService extends Service {
seekDelta(-PlaybackController.DEFAULT_SEEK_DELTA); seekDelta(-PlaybackController.DEFAULT_SEEK_DELTA);
break; break;
} }
} }
} }
/** /**
@ -575,6 +603,7 @@ public class PlaybackService extends Service {
Log.d(TAG, "Setting up media player"); Log.d(TAG, "Setting up media player");
try { try {
MediaType mediaType = media.getMediaType(); MediaType mediaType = media.getMediaType();
player = createMediaPlayer();
if (mediaType == MediaType.AUDIO) { if (mediaType == MediaType.AUDIO) {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "Mime type is audio"); Log.d(TAG, "Mime type is audio");
@ -669,105 +698,169 @@ public class PlaybackService extends Service {
} }
} }
private MediaPlayer.OnPreparedListener preparedListener = new MediaPlayer.OnPreparedListener() { private final com.aocate.media.MediaPlayer.OnPreparedListener audioPreparedListener = new com.aocate.media.MediaPlayer.OnPreparedListener() {
@Override @Override
public void onPrepared(MediaPlayer mp) { public void onPrepared(com.aocate.media.MediaPlayer mp) {
if (AppConfig.DEBUG) genericOnPrepared(mp);
Log.d(TAG, "Resource prepared"); }
mp.seekTo(media.getPosition()); };
if (media.getDuration() == 0) {
if (AppConfig.DEBUG)
Log.d(TAG, "Setting duration of media");
media.setDuration(mp.getDuration());
}
setStatus(PlayerStatus.PREPARED);
if (chapterLoader != null) {
chapterLoader.interrupt();
}
chapterLoader = new Thread() {
@Override
public void run() {
if (AppConfig.DEBUG)
Log.d(TAG, "Chapter loader started");
if (media != null && media.getChapters() == null) {
media.loadChapterMarks();
if (!isInterrupted() && media.getChapters() != null) {
sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD,
0);
}
}
if (AppConfig.DEBUG)
Log.d(TAG, "Chapter loader stopped");
}
};
chapterLoader.start();
if (startWhenPrepared) { private final android.media.MediaPlayer.OnPreparedListener videoPreparedListener = new android.media.MediaPlayer.OnPreparedListener() {
play(); @Override
} public void onPrepared(android.media.MediaPlayer mp) {
} genericOnPrepared(mp);
}; }
};
private MediaPlayer.OnSeekCompleteListener onSeekCompleteListener = new MediaPlayer.OnSeekCompleteListener() { 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();
@Override if (startWhenPrepared) {
public void onSeekComplete(MediaPlayer mp) { play();
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 MediaPlayer.OnInfoListener onInfoListener = new MediaPlayer.OnInfoListener() { private final android.media.MediaPlayer.OnSeekCompleteListener videoSeekCompleteListener = new android.media.MediaPlayer.OnSeekCompleteListener() {
@Override
public void onSeekComplete(android.media.MediaPlayer mp) {
genericSeekCompleteListener();
}
};
@Override private final void genericSeekCompleteListener() {
public boolean onInfo(MediaPlayer mp, int what, int extra) { if (status == PlayerStatus.SEEKING) {
switch (what) { setStatus(statusBeforeSeek);
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.OnErrorListener onErrorListener = new MediaPlayer.OnErrorListener() { private final com.aocate.media.MediaPlayer.OnInfoListener audioInfoListener = new com.aocate.media.MediaPlayer.OnInfoListener() {
private static final String TAG = "PlaybackService.onErrorListener"; @Override
public boolean onInfo(com.aocate.media.MediaPlayer mp, int what,
int extra) {
return genericInfoListener(what);
}
};
@Override private final android.media.MediaPlayer.OnInfoListener videoInfoListener = new android.media.MediaPlayer.OnInfoListener() {
public boolean onError(MediaPlayer mp, int what, int extra) { @Override
Log.w(TAG, "An error has occured: " + what); public boolean onInfo(android.media.MediaPlayer mp, int what, int extra) {
if (mp.isPlaying()) { return genericInfoListener(what);
pause(true, true); }
} };
sendNotificationBroadcast(NOTIFICATION_TYPE_ERROR, what);
setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING);
stopSelf();
return true;
}
};
private MediaPlayer.OnCompletionListener completionListener = new MediaPlayer.OnCompletionListener() { 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;
}
}
@Override private final com.aocate.media.MediaPlayer.OnErrorListener audioErrorListener = new com.aocate.media.MediaPlayer.OnErrorListener() {
public void onCompletion(MediaPlayer mp) { @Override
endPlayback(true); public boolean onError(com.aocate.media.MediaPlayer mp, int what,
} int extra) {
}; return genericOnError(mp, what, extra);
}
};
private MediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() { 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);
}
};
@Override private boolean genericOnError(Object inObj, int what, int extra) {
public void onBufferingUpdate(MediaPlayer mp, int percent) { final String TAG = "PlaybackService.onErrorListener";
sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent); Log.w(TAG, "An error has occured: " + what + " " + extra);
IPlayer mp = DuckType.coerce(inObj).to(IPlayer.class);
if (mp.isPlaying()) {
pause(true, true);
}
sendNotificationBroadcast(NOTIFICATION_TYPE_ERROR, what);
setCurrentlyPlayingMedia(PlaybackPreferences.NO_MEDIA_PLAYING);
stopSelf();
return true;
}
} private final com.aocate.media.MediaPlayer.OnCompletionListener audioCompletionListener = new com.aocate.media.MediaPlayer.OnCompletionListener() {
}; @Override
public void onCompletion(com.aocate.media.MediaPlayer mp) {
genericOnCompletion();
}
};
private final android.media.MediaPlayer.OnCompletionListener videoCompletionListener = new android.media.MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(android.media.MediaPlayer mp) {
genericOnCompletion();
}
};
private void genericOnCompletion() {
endPlayback(true);
}
private final com.aocate.media.MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener = new com.aocate.media.MediaPlayer.OnBufferingUpdateListener() {
@Override
public void onBufferingUpdate(com.aocate.media.MediaPlayer mp,
int percent) {
genericOnBufferingUpdate(percent);
}
};
private final android.media.MediaPlayer.OnBufferingUpdateListener videoBufferingUpdateListener = new android.media.MediaPlayer.OnBufferingUpdateListener() {
@Override
public void onBufferingUpdate(android.media.MediaPlayer mp, int percent) {
genericOnBufferingUpdate(percent);
}
};
private void genericOnBufferingUpdate(int percent) {
sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent);
}
private void endPlayback(boolean playNextEpisode) { private void endPlayback(boolean playNextEpisode) {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
@ -790,7 +883,6 @@ public class PlaybackService extends Service {
DBWriter.removeQueueItem(PlaybackService.this, item.getId(), true); DBWriter.removeQueueItem(PlaybackService.this, item.getId(), true);
} }
DBWriter.addItemToPlaybackHistory(PlaybackService.this, (FeedMedia) media); DBWriter.addItemToPlaybackHistory(PlaybackService.this, (FeedMedia) media);
DBWriter.setFeedMedia(PlaybackService.this, (FeedMedia) media);
long autoDeleteMediaId = ((FeedComponent) media).getId(); long autoDeleteMediaId = ((FeedComponent) media).getId();
if (shouldStream) { if (shouldStream) {
autoDeleteMediaId = -1; autoDeleteMediaId = -1;
@ -870,7 +962,7 @@ public class PlaybackService extends Service {
/** /**
* Saves the current position and pauses playback. Note that, if audiofocus * Saves the current position and pauses playback. Note that, if audiofocus
* is abandoned, the lockscreen controls will also disapear. * is abandoned, the lockscreen controls will also disapear.
* *
* @param abandonFocus * @param abandonFocus
* is true if the service should release audio focus * is true if the service should release audio focus
* @param reinit * @param reinit
@ -946,6 +1038,7 @@ public class PlaybackService extends Service {
Log.d(TAG, "Resuming/Starting playback"); Log.d(TAG, "Resuming/Starting playback");
writePlaybackPreferences(); writePlaybackPreferences();
setSpeed(Float.parseFloat(UserPreferences.getPlaybackSpeed()));
player.start(); player.start();
if (status != PlayerStatus.PAUSED) { if (status != PlayerStatus.PAUSED) {
player.seekTo((int) media.getPosition()); player.seekTo((int) media.getPosition());
@ -1131,7 +1224,7 @@ public class PlaybackService extends Service {
/** /**
* Seek a specific position from the current position * Seek a specific position from the current position
* *
* @param delta * @param delta
* offset from current position (positive or negative) * offset from current position (positive or negative)
* */ * */
@ -1289,18 +1382,20 @@ public class PlaybackService extends Service {
isPlaying = true; isPlaying = true;
} }
Intent i = new Intent(AVRCP_ACTION_PLAYER_STATUS_CHANGED); if (media != null) {
i.putExtra("id", 1); Intent i = new Intent(AVRCP_ACTION_PLAYER_STATUS_CHANGED);
i.putExtra("artist", ""); i.putExtra("id", 1);
i.putExtra("album", media.getFeedTitle()); i.putExtra("artist", "");
i.putExtra("track", media.getEpisodeTitle()); i.putExtra("album", media.getFeedTitle());
i.putExtra("playing", isPlaying); i.putExtra("track", media.getEpisodeTitle());
if (queue != null) { i.putExtra("playing", isPlaying);
i.putExtra("ListSize", queue.size()); 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 */ /** Periodically saves the position of the media file */
class PositionSaver implements Runnable { class PositionSaver implements Runnable {
@ -1479,7 +1574,7 @@ public class PlaybackService extends Service {
return media; return media;
} }
public MediaPlayer getPlayer() { public IPlayer getPlayer() {
return player; return player;
} }
@ -1492,6 +1587,53 @@ public class PlaybackService extends Service {
postStatusUpdateIntent(); 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 * call getDuration() on mediaplayer or return INVALID_TIME if player is in
* an invalid state. This method should be used instead of calling * an invalid state. This method should be used instead of calling

View File

@ -184,7 +184,7 @@ public class DownloadService extends Service {
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
if (intent.getParcelableExtra(EXTRA_REQUEST) != null) { if (intent.getParcelableExtra(EXTRA_REQUEST) != null) {
onDownloadQueued(intent); onDownloadQueued(intent);
} else if (numberOfDownloads.equals(0)) { } else if (numberOfDownloads.get() == 0) {
stopSelf(); stopSelf();
} }
return Service.START_NOT_STICKY; return Service.START_NOT_STICKY;
@ -421,52 +421,24 @@ public class DownloadService extends Service {
return null; 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 * Remove download from the DownloadRequester list and from the
* DownloadService list. * DownloadService list.
*/ */
private void removeDownload(final Downloader d) { private void removeDownload(final Downloader d) {
if (AppConfig.DEBUG) handler.post(new Runnable() {
Log.d(TAG, "Removing downloader: " @Override
+ d.getDownloadRequest().getSource()); public void run() {
boolean rc = downloads.remove(d); if (AppConfig.DEBUG)
if (AppConfig.DEBUG) Log.d(TAG, "Removing downloader: "
Log.d(TAG, "Result of downloads.remove: " + rc); + d.getDownloadRequest().getSource());
DownloadRequester.getInstance().removeDownload(d.getDownloadRequest()); boolean rc = downloads.remove(d);
sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED)); 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"); Log.d(TAG, numberOfDownloads.get() + " downloads left");
} }
if (numberOfDownloads.get() <= 0) { if (numberOfDownloads.get() <= 0 && DownloadRequester.getInstance().hasNoDownloads()) {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "Number of downloads is " + numberOfDownloads.get() + ", attempting shutdown"); Log.d(TAG, "Number of downloads is " + numberOfDownloads.get() + ", attempting shutdown");
stopSelf(); stopSelf();
@ -647,28 +619,21 @@ public class DownloadService extends Service {
Log.d(TAG, "Feed has image; Downloading...."); Log.d(TAG, "Feed has image; Downloading....");
savedFeed.getImage().setFeed(savedFeed); savedFeed.getImage().setFeed(savedFeed);
final Feed savedFeedRef = savedFeed; final Feed savedFeedRef = savedFeed;
handler.post(new Runnable() { try {
requester.downloadImage(DownloadService.this,
@Override savedFeedRef.getImage());
public void run() { } catch (DownloadRequestException e) {
try { e.printStackTrace();
requester.downloadImage(DownloadService.this, DBWriter.addDownloadStatus(
savedFeedRef.getImage()); DownloadService.this,
} catch (DownloadRequestException e) { new DownloadStatus(
e.printStackTrace(); savedFeedRef.getImage(),
DBWriter.addDownloadStatus( savedFeedRef
DownloadService.this, .getImage()
new DownloadStatus( .getHumanReadableIdentifier(),
savedFeedRef.getImage(), DownloadError.ERROR_REQUEST_ERROR,
savedFeedRef false, e.getMessage()));
.getImage() }
.getHumanReadableIdentifier(),
DownloadError.ERROR_REQUEST_ERROR,
false, e.getMessage()));
}
}
});
} }
} catch (SAXException e) { } catch (SAXException e) {
@ -730,7 +695,7 @@ public class DownloadService extends Service {
} }
private boolean hasValidFeedItems(Feed feed) { private boolean hasValidFeedItems(Feed feed) {
for (FeedItem item : feed.getItemsArray()) { for (FeedItem item : feed.getItems()) {
if (item.getTitle() == null) { if (item.getTitle() == null) {
Log.e(TAG, "Item has no title"); Log.e(TAG, "Item has no title");
return false; return false;
@ -835,8 +800,9 @@ public class DownloadService extends Service {
media.setFile_url(request.getDestination()); media.setFile_url(request.getDestination());
// Get duration // Get duration
MediaPlayer mediaplayer = new MediaPlayer(); MediaPlayer mediaplayer = null;
try { try {
mediaplayer = new MediaPlayer();
mediaplayer.setDataSource(media.getFile_url()); mediaplayer.setDataSource(media.getFile_url());
mediaplayer.prepare(); mediaplayer.prepare();
media.setDuration(mediaplayer.getDuration()); media.setDuration(mediaplayer.getDuration());
@ -845,8 +811,13 @@ public class DownloadService extends Service {
mediaplayer.reset(); mediaplayer.reset();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} catch (RuntimeException e) {
// Thrown by MediaPlayer initialization on some devices
e.printStackTrace();
} finally { } finally {
mediaplayer.release(); if (mediaplayer != null) {
mediaplayer.release();
}
} }
if (media.getItem().getChapters() == null) { if (media.getItem().getChapters() == null) {

View File

@ -52,7 +52,7 @@ public class DownloadStatus {
this.feedfileId = feedfileId; this.feedfileId = feedfileId;
this.reason = reason; this.reason = reason;
this.successful = successful; this.successful = successful;
this.completionDate = completionDate; this.completionDate = (Date) completionDate.clone();
this.reasonDetailed = reasonDetailed; this.reasonDetailed = reasonDetailed;
this.feedfileType = feedfileType; this.feedfileType = feedfileType;
} }
@ -133,7 +133,7 @@ public class DownloadStatus {
} }
public Date getCompletionDate() { public Date getCompletionDate() {
return completionDate; return (Date) completionDate.clone();
} }
public long getFeedfileId() { public long getFeedfileId() {
@ -162,6 +162,7 @@ public class DownloadStatus {
this.successful = false; this.successful = false;
this.reason = reason; this.reason = reason;
this.reasonDetailed = reasonDetailed; this.reasonDetailed = reasonDetailed;
this.done = true;
} }
public void setCancelled() { public void setCancelled() {
@ -172,7 +173,7 @@ public class DownloadStatus {
} }
public void setCompletionDate(Date completionDate) { public void setCompletionDate(Date completionDate) {
this.completionDate = completionDate; this.completionDate = (Date) completionDate.clone();
} }
public void setId(long id) { public void setId(long id) {

View File

@ -6,12 +6,12 @@ import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity; import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
@ -30,161 +30,186 @@ import de.danoeh.antennapod.util.DownloadError;
import de.danoeh.antennapod.util.StorageUtils; import de.danoeh.antennapod.util.StorageUtils;
public class HttpDownloader extends Downloader { public class HttpDownloader extends Downloader {
private static final String TAG = "HttpDownloader"; private static final String TAG = "HttpDownloader";
private static final int MAX_REDIRECTS = 5; private static final int MAX_REDIRECTS = 5;
private static final int BUFFER_SIZE = 8 * 1024; private static final int BUFFER_SIZE = 8 * 1024;
private static final int CONNECTION_TIMEOUT = 30000; private static final int CONNECTION_TIMEOUT = 30000;
private static final int SOCKET_TIMEOUT = 30000; private static final int SOCKET_TIMEOUT = 30000;
public HttpDownloader(DownloadRequest request) { public HttpDownloader(DownloadRequest request) {
super(request); super(request);
} }
private DefaultHttpClient createHttpClient() { private DefaultHttpClient createHttpClient() {
DefaultHttpClient httpClient = new DefaultHttpClient(); DefaultHttpClient httpClient = new DefaultHttpClient();
HttpParams params = httpClient.getParams(); HttpParams params = httpClient.getParams();
params.setIntParameter("http.protocol.max-redirects", MAX_REDIRECTS); params.setIntParameter("http.protocol.max-redirects", MAX_REDIRECTS);
params.setBooleanParameter("http.protocol.reject-relative-redirect", params.setBooleanParameter("http.protocol.reject-relative-redirect",
false); false);
HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT); HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT);
HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT); HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT);
HttpClientParams.setRedirecting(params, true); HttpClientParams.setRedirecting(params, true);
// Workaround for broken URLs in redirection // Workaround for broken URLs in redirection
((AbstractHttpClient) httpClient) ((AbstractHttpClient) httpClient)
.setRedirectHandler(new APRedirectHandler()); .setRedirectHandler(new APRedirectHandler());
return httpClient; return httpClient;
} }
@Override @Override
protected void download() { protected void download() {
DefaultHttpClient httpClient = null; DefaultHttpClient httpClient = null;
OutputStream out = null; BufferedOutputStream out = null;
InputStream connection = null; InputStream connection = null;
try { try {
HttpGet httpGet = new HttpGet(request.getSource()); HttpGet httpGet = new HttpGet(request.getSource());
httpClient = createHttpClient(); httpClient = createHttpClient();
HttpResponse response = httpClient.execute(httpGet); HttpResponse response = httpClient.execute(httpGet);
HttpEntity httpEntity = response.getEntity(); HttpEntity httpEntity = response.getEntity();
int responseCode = response.getStatusLine().getStatusCode(); int responseCode = response.getStatusLine().getStatusCode();
if (AppConfig.DEBUG) Header contentEncodingHeader = response.getFirstHeader("Content-Encoding");
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(); final boolean isGzip = contentEncodingHeader != null &&
if (AppConfig.DEBUG) contentEncodingHeader.getValue().equalsIgnoreCase("gzip");
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)
if (AppConfig.DEBUG) Log.d(TAG, "Response code is " + responseCode);
Log.d(TAG, "Download was successful");
result.setSuccessful();
}
private void onFail(DownloadError reason, String reasonDetailed) { if (responseCode != HttpURLConnection.HTTP_OK || httpEntity == null) {
if (AppConfig.DEBUG) { onFail(DownloadError.ERROR_HTTP_DATA_ERROR,
Log.d(TAG, "Download failed"); 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); result.setFailed(reason, reasonDetailed);
cleanup(); cleanup();
} }
private void onCancelled() { private void onCancelled() {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "Download was cancelled"); Log.d(TAG, "Download was cancelled");
result.setCancelled(); result.setCancelled();
cleanup(); cleanup();
} }
/** Deletes unfinished downloads. */ /**
private void cleanup() { * Deletes unfinished downloads.
if (request.getDestination() != null) { */
File dest = new File(request.getDestination()); private void cleanup() {
if (dest.exists()) { if (request.getDestination() != null) {
boolean rc = dest.delete(); File dest = new File(request.getDestination());
if (AppConfig.DEBUG) if (dest.exists()) {
Log.d(TAG, "Deleted file " + dest.getName() + "; Result: " boolean rc = dest.delete();
+ rc); if (AppConfig.DEBUG)
} else { Log.d(TAG, "Deleted file " + dest.getName() + "; Result: "
if (AppConfig.DEBUG) + rc);
Log.d(TAG, "cleanup() didn't delete file: does not exist."); } else {
} if (AppConfig.DEBUG)
} Log.d(TAG, "cleanup() didn't delete file: does not exist.");
} }
}
}
} }

View File

@ -229,9 +229,11 @@ public final class DBReader {
title, item, link); title, item, link);
break; break;
} }
chapter.setId(chapterCursor if (chapter != null) {
.getLong(PodDBAdapter.KEY_ID_INDEX)); chapter.setId(chapterCursor
item.getChapters().add(chapter); .getLong(PodDBAdapter.KEY_ID_INDEX));
item.getChapters().add(chapter);
}
} while (chapterCursor.moveToNext()); } while (chapterCursor.moveToNext());
} }
chapterCursor.close(); chapterCursor.close();

View File

@ -28,6 +28,7 @@ import de.danoeh.antennapod.service.download.DownloadStatus;
import de.danoeh.antennapod.util.DownloadError; import de.danoeh.antennapod.util.DownloadError;
import de.danoeh.antennapod.util.NetworkUtils; import de.danoeh.antennapod.util.NetworkUtils;
import de.danoeh.antennapod.util.QueueAccess; import de.danoeh.antennapod.util.QueueAccess;
import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator;
import de.danoeh.antennapod.util.exception.MediaFileNotFoundException; import de.danoeh.antennapod.util.exception.MediaFileNotFoundException;
/** /**
@ -406,12 +407,13 @@ public final class DBTasks {
private static int performAutoCleanup(final Context context, private static int performAutoCleanup(final Context context,
final int episodeNumber) { final int episodeNumber) {
List<FeedItem> candidates = DBReader.getDownloadedItems(context); List<FeedItem> candidates = new ArrayList<FeedItem>();
List<FeedItem> queue = DBReader.getQueue(context); List<FeedItem> downloadedItems = DBReader.getDownloadedItems(context);
QueueAccess queue = QueueAccess.IDListAccess(DBReader.getQueueIDList(context));
List<FeedItem> delete; List<FeedItem> delete;
for (FeedItem item : candidates) { for (FeedItem item : downloadedItems) {
if (item.hasMedia() && item.getMedia().isDownloaded() if (item.hasMedia() && item.getMedia().isDownloaded()
&& !queue.contains(item) && item.isRead()) { && !queue.contains(item.getId()) && item.isRead()) {
candidates.add(item); candidates.add(item);
} }
@ -440,7 +442,13 @@ public final class DBTasks {
} }
for (FeedItem item : delete) { 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(); int counter = delete.size();
@ -561,6 +569,7 @@ public final class DBTasks {
Log.d(TAG, "Feed with title " + newFeed.getTitle() Log.d(TAG, "Feed with title " + newFeed.getTitle()
+ " already exists. Syncing new with existing one."); + " already exists. Syncing new with existing one.");
Collections.sort(newFeed.getItems(), new FeedItemPubdateComparator());
savedFeed.setItems(DBReader.getFeedItemList(context, savedFeed)); savedFeed.setItems(DBReader.getFeedItemList(context, savedFeed));
if (savedFeed.compareWithOther(newFeed)) { if (savedFeed.compareWithOther(newFeed)) {
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
@ -578,7 +587,7 @@ public final class DBTasks {
final int i = idx; final int i = idx;
item.setFeed(savedFeed); item.setFeed(savedFeed);
savedFeed.getItems().add(i, item); savedFeed.getItems().add(i, item);
DBWriter.markItemRead(context, item.getId(), false); item.setRead(false);
} else { } else {
oldItem.updateFromOther(item); oldItem.updateFromOther(item);
} }

View File

@ -73,7 +73,10 @@ public class DBWriter {
} }
media.setDownloaded(false); media.setDownloaded(false);
media.setFile_url(null); 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 // If media is currently being played, change playback
// type to 'stream' and shutdown playback service // type to 'stream' and shutdown playback service
@ -212,7 +215,7 @@ public class DBWriter {
media.setPlaybackCompletionDate(new Date()); media.setPlaybackCompletionDate(new Date());
PodDBAdapter adapter = new PodDBAdapter(context); PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open(); adapter.open();
adapter.setMedia(media); adapter.setFeedMediaPlaybackCompletionDate(media);
adapter.close(); adapter.close();
EventDistributor.getInstance().sendPlaybackHistoryUpdateBroadcast(); 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 context A context that is used for opening a database connection.
* @param media The FeedMedia object. * @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() { return dbExec.submit(new Runnable() {
@Override @Override
public void run() { public void run() {
PodDBAdapter adapter = new PodDBAdapter(context); PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open(); adapter.open();
adapter.setFeedMediaPosition(media); adapter.setFeedMediaPlaybackInformation(media);
adapter.close(); adapter.close();
} }
}); });

View File

@ -26,9 +26,9 @@ import de.danoeh.antennapod.util.URLChecker;
public class DownloadRequester { public class DownloadRequester {
private static final String TAG = "DownloadRequester"; private static final String TAG = "DownloadRequester";
public static String IMAGE_DOWNLOADPATH = "images/"; public static final String IMAGE_DOWNLOADPATH = "images/";
public static String FEED_DOWNLOADPATH = "cache/"; public static final String FEED_DOWNLOADPATH = "cache/";
public static String MEDIA_DOWNLOADPATH = "media/"; public static final String MEDIA_DOWNLOADPATH = "media/";
private static DownloadRequester downloader; private static DownloadRequester downloader;
@ -38,7 +38,7 @@ public class DownloadRequester {
downloads = new ConcurrentHashMap<String, DownloadRequest>(); downloads = new ConcurrentHashMap<String, DownloadRequest>();
} }
public static DownloadRequester getInstance() { public static synchronized DownloadRequester getInstance() {
if (downloader == null) { if (downloader == null) {
downloader = new DownloadRequester(); downloader = new DownloadRequester();
} }

View File

@ -17,7 +17,7 @@ public class FeedItemStatistics {
this.numberOfItems = numberOfItems; this.numberOfItems = numberOfItems;
this.numberOfNewItems = numberOfNewItems; this.numberOfNewItems = numberOfNewItems;
this.numberOfInProgressItems = numberOfInProgressItems; this.numberOfInProgressItems = numberOfInProgressItems;
this.lastUpdate = lastUpdate; this.lastUpdate = (lastUpdate != null) ? (Date) lastUpdate.clone() : null;
} }
public long getFeedID() { public long getFeedID() {
@ -37,6 +37,6 @@ public class FeedItemStatistics {
} }
public Date getLastUpdate() { public Date getLastUpdate() {
return lastUpdate; return (lastUpdate != null) ? (Date) lastUpdate.clone() : null;
} }
} }

View File

@ -10,7 +10,6 @@ import android.database.DatabaseUtils;
import android.database.MergeCursor; import android.database.MergeCursor;
import android.database.SQLException; import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log; import android.util.Log;
@ -235,7 +234,7 @@ public class PodDBAdapter {
/** /**
* Select id, description and content-encoded column from feeditems. * 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}; KEY_CONTENT_ENCODED, KEY_FEED};
// column indices for SEL_FI_EXTRA // column indices for SEL_FI_EXTRA
@ -279,6 +278,13 @@ public class PodDBAdapter {
//db.close(); //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 * Inserts or updates a feed entry
* *
@ -361,6 +367,7 @@ public class PodDBAdapter {
values.put(KEY_DOWNLOAD_URL, media.getDownload_url()); values.put(KEY_DOWNLOAD_URL, media.getDownload_url());
values.put(KEY_DOWNLOADED, media.isDownloaded()); values.put(KEY_DOWNLOADED, media.isDownloaded());
values.put(KEY_FILE_URL, media.getFile_url()); values.put(KEY_FILE_URL, media.getFile_url());
if (media.getPlaybackCompletionDate() != null) { if (media.getPlaybackCompletionDate() != null) {
values.put(KEY_PLAYBACK_COMPLETION_DATE, media values.put(KEY_PLAYBACK_COMPLETION_DATE, media
.getPlaybackCompletionDate().getTime()); .getPlaybackCompletionDate().getTime());
@ -379,14 +386,26 @@ public class PodDBAdapter {
return media.getId(); return media.getId();
} }
public void setFeedMediaPosition(FeedMedia media) { public void setFeedMediaPlaybackInformation(FeedMedia media) {
if (media.getId() != 0) { if (media.getId() != 0) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(KEY_POSITION, media.getPosition()); values.put(KEY_POSITION, media.getPosition());
values.put(KEY_DURATION, media.getDuration());
db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?", db.update(TABLE_NAME_FEED_MEDIA, values, KEY_ID + "=?",
new String[]{String.valueOf(media.getId())}); new String[]{String.valueOf(media.getId())});
} else { } else {
Log.e(TAG, "setFeedMediaPosition: ID of media was 0"); 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, "setFeedMediaPlaybackCompletionDate: ID of media was 0");
} }
} }
@ -397,8 +416,10 @@ public class PodDBAdapter {
public void setCompleteFeed(Feed feed) { public void setCompleteFeed(Feed feed) {
db.beginTransaction(); db.beginTransaction();
setFeed(feed); setFeed(feed);
for (FeedItem item : feed.getItemsArray()) { if (feed.getItems() != null) {
setFeedItem(item); for (FeedItem item : feed.getItems()) {
setFeedItem(item, false);
}
} }
db.setTransactionSuccessful(); db.setTransactionSuccessful();
db.endTransaction(); db.endTransaction();
@ -407,7 +428,7 @@ public class PodDBAdapter {
public void setFeedItemlist(List<FeedItem> items) { public void setFeedItemlist(List<FeedItem> items) {
db.beginTransaction(); db.beginTransaction();
for (FeedItem item : items) { for (FeedItem item : items) {
setFeedItem(item); setFeedItem(item, true);
} }
db.setTransactionSuccessful(); db.setTransactionSuccessful();
db.endTransaction(); db.endTransaction();
@ -415,7 +436,7 @@ public class PodDBAdapter {
public long setSingleFeedItem(FeedItem item) { public long setSingleFeedItem(FeedItem item) {
db.beginTransaction(); db.beginTransaction();
long result = setFeedItem(item); long result = setFeedItem(item, true);
db.setTransactionSuccessful(); db.setTransactionSuccessful();
db.endTransaction(); db.endTransaction();
return result; return result;
@ -423,10 +444,12 @@ public class PodDBAdapter {
/** /**
* Inserts or updates a feeditem entry * 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 * @return the id of the entry
*/ */
private long setFeedItem(FeedItem item) { private long setFeedItem(FeedItem item, boolean saveFeed) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(KEY_TITLE, item.getTitle()); values.put(KEY_TITLE, item.getTitle());
values.put(KEY_LINK, item.getLink()); values.put(KEY_LINK, item.getLink());
@ -438,7 +461,7 @@ public class PodDBAdapter {
} }
values.put(KEY_PUBDATE, item.getPubDate().getTime()); values.put(KEY_PUBDATE, item.getPubDate().getTime());
values.put(KEY_PAYMENT_LINK, item.getPaymentLink()); values.put(KEY_PAYMENT_LINK, item.getPaymentLink());
if (item.getFeed() != null) { if (saveFeed && item.getFeed() != null) {
setFeed(item.getFeed()); setFeed(item.getFeed());
} }
values.put(KEY_FEED, item.getFeed().getId()); values.put(KEY_FEED, item.getFeed().getId());
@ -603,8 +626,10 @@ public class PodDBAdapter {
if (feed.getImage() != null) { if (feed.getImage() != null) {
removeFeedImage(feed.getImage()); removeFeedImage(feed.getImage());
} }
for (FeedItem item : feed.getItemsArray()) { if (feed.getItems() != null) {
removeFeedItem(item); for (FeedItem item : feed.getItems()) {
removeFeedItem(item);
}
} }
db.delete(TABLE_NAME_FEEDS, KEY_ID + "=?", db.delete(TABLE_NAME_FEEDS, KEY_ID + "=?",
new String[]{String.valueOf(feed.getId())}); 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 final String query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS
+ " INNER JOIN " + TABLE_NAME_FEED_MEDIA + " ON " + " INNER JOIN " + TABLE_NAME_FEED_MEDIA + " ON "
+ TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "=" + 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"; + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + ">0";
Cursor c = db.rawQuery(query, null); Cursor c = db.rawQuery(query, null);
return c; return c;
@ -985,7 +1010,7 @@ public class PodDBAdapter {
" MAX(pubDate) AS latest_episode," + " MAX(pubDate) AS latest_episode," +
" COUNT(CASE WHEN position>0 THEN 1 END) AS in_progress," + " COUNT(CASE WHEN position>0 THEN 1 END) AS in_progress," +
" COUNT(CASE WHEN downloaded=1 THEN 1 END) AS episodes_downloaded " + " 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;"; " INNER JOIN Feeds ON Feeds.id = feed ORDER BY Feeds.title;";
public Cursor getFeedStatisticsCursor() { public Cursor getFeedStatisticsCursor() {

View File

@ -121,7 +121,7 @@ public class NSAtom extends Namespace {
if (state.getContentBuf() != null) { if (state.getContentBuf() != null) {
content = state.getContentBuf().toString(); content = state.getContentBuf().toString();
} else { } else {
content = new String(); content = "";
} }
SyndElement topElement = state.getTagstack().peek(); SyndElement topElement = state.getTagstack().peek();
String top = topElement.getName(); String top = topElement.getName();

View File

@ -11,7 +11,7 @@ import android.util.Log;
public class SyndDateUtils { public class SyndDateUtils {
private static final String TAG = "DateUtils"; 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. */ /** RFC 3339 date format for UTC dates. */
public static final String RFC3339UTC = "yyyy-MM-dd'T'HH:mm:ss'Z'"; public static final String RFC3339UTC = "yyyy-MM-dd'T'HH:mm:ss'Z'";
@ -123,12 +123,12 @@ public class SyndDateUtils {
int idx = 0; int idx = 0;
if (parts.length == 3) { if (parts.length == 3) {
// string has hours // string has hours
result += Integer.valueOf(parts[idx]) * 3600000; result += Integer.valueOf(parts[idx]) * 3600000L;
idx++; idx++;
} }
result += Integer.valueOf(parts[idx]) * 60000; result += Integer.valueOf(parts[idx]) * 60000L;
idx++; idx++;
result += (Float.valueOf(parts[idx])) * 1000; result += (Float.valueOf(parts[idx])) * 1000L;
return result; return result;
} }
} }

View File

@ -35,9 +35,9 @@ public class ChapterUtils {
* chapters. * chapters.
*/ */
public static void readID3ChaptersFromPlayableStreamUrl(Playable p) { 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 (p != null && p.getStreamUrl() != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle());
InputStream in = null; InputStream in = null;
try { try {
URL url = new URL(p.getStreamUrl()); URL url = new URL(p.getStreamUrl());
@ -86,9 +86,9 @@ public class ChapterUtils {
* chapters. * chapters.
*/ */
public static void readID3ChaptersFromPlayableFileUrl(Playable p) { 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 (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()); File source = new File(p.getLocalMediaUrl());
if (source.exists()) { if (source.exists()) {
ChapterReader reader = new ChapterReader(); ChapterReader reader = new ChapterReader();

View File

@ -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;
}
}
}

View File

@ -1,8 +1,11 @@
package de.danoeh.antennapod.util; package de.danoeh.antennapod.util;
import java.nio.charset.Charset;
import java.util.HashMap; import java.util.HashMap;
public class LangUtils { public class LangUtils {
public static final Charset UTF_8 = Charset.forName("UTF-8");
private static HashMap<String, String> languages; private static HashMap<String, String> languages;
static { static {
languages = new HashMap<String, String>(); languages = new HashMap<String, String>();

View File

@ -51,9 +51,8 @@ public abstract class QueueAccess {
if (items == null) { if (items == null) {
return false; return false;
} }
Iterator<FeedItem> it = items.iterator(); for (FeedItem item : items) {
for (FeedItem i = it.next(); it.hasNext(); i = it.next()) { if (item.getId() == id) {
if (i.getId() == id) {
return true; return true;
} }
} }
@ -63,8 +62,10 @@ public abstract class QueueAccess {
@Override @Override
public boolean remove(long id) { public boolean remove(long id) {
Iterator<FeedItem> it = items.iterator(); Iterator<FeedItem> it = items.iterator();
for (FeedItem i = it.next(); it.hasNext(); i = it.next()) { FeedItem item;
if (i.getId() == id) { while (it.hasNext()) {
item = it.next();
if (item.getId() == id) {
it.remove(); it.remove();
return true; return true;
} }

View File

@ -19,13 +19,12 @@ public final class URLChecker {
* */ * */
public static String prepareURL(String url) { public static String prepareURL(String url) {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
url = url.trim(); if (url.startsWith("feed://")) {
if (!url.startsWith("http")) { 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://"); 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); builder.append(url);

View File

@ -9,8 +9,7 @@ public class DownloadStatusComparator implements Comparator<DownloadStatus> {
@Override @Override
public int compare(DownloadStatus lhs, DownloadStatus rhs) { public int compare(DownloadStatus lhs, DownloadStatus rhs) {
return -lhs.getCompletionDate().compareTo(rhs.getCompletionDate()); return rhs.getCompletionDate().compareTo(lhs.getCompletionDate());
} }
} }

View File

@ -13,7 +13,7 @@ public class FeedItemPubdateComparator implements Comparator<FeedItem> {
}*/ }*/
@Override @Override
public int compare(FeedItem lhs, FeedItem rhs) { public int compare(FeedItem lhs, FeedItem rhs) {
return -lhs.getPubDate().compareTo(rhs.getPubDate()); return rhs.getPubDate().compareTo(lhs.getPubDate());
} }
} }

View File

@ -11,8 +11,8 @@ public class PlaybackCompletionDateComparator implements Comparator<FeedItem> {
&& lhs.getMedia().getPlaybackCompletionDate() != null && lhs.getMedia().getPlaybackCompletionDate() != null
&& rhs.getMedia() != null && rhs.getMedia() != null
&& rhs.getMedia().getPlaybackCompletionDate() != null) { && rhs.getMedia().getPlaybackCompletionDate() != null) {
return -lhs.getMedia().getPlaybackCompletionDate() return rhs.getMedia().getPlaybackCompletionDate()
.compareTo(rhs.getMedia().getPlaybackCompletionDate()); .compareTo(lhs.getMedia().getPlaybackCompletionDate());
} }
return 0; return 0;
} }

View File

@ -51,10 +51,13 @@ public class FeedItemMenuHandler {
* parameter should be set to false if the menu space is limited. * parameter should be set to false if the menu space is limited.
* @param queueAccess * @param queueAccess
* Used for testing if the queue contains the selected item * 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, public static boolean onPrepareMenu(MenuInterface mi,
FeedItem selectedItem, boolean showExtendedMenu, QueueAccess queueAccess) { FeedItem selectedItem, boolean showExtendedMenu, QueueAccess queueAccess) {
if (selectedItem == null) {
return false;
}
DownloadRequester requester = DownloadRequester.getInstance(); DownloadRequester requester = DownloadRequester.getInstance();
boolean hasMedia = selectedItem.getMedia() != null; boolean hasMedia = selectedItem.getMedia() != null;
boolean downloaded = hasMedia && selectedItem.getMedia().isDownloaded(); boolean downloaded = hasMedia && selectedItem.getMedia().isDownloaded();

View File

@ -30,6 +30,10 @@ public class FeedMenuHandler {
} }
public static boolean onPrepareOptionsMenu(Menu menu, Feed selectedFeed) { public static boolean onPrepareOptionsMenu(Menu menu, Feed selectedFeed) {
if (selectedFeed == null) {
return false;
}
if (AppConfig.DEBUG) if (AppConfig.DEBUG)
Log.d(TAG, "Preparing options menu"); Log.d(TAG, "Preparing options menu");
menu.findItem(R.id.mark_all_read_item).setVisible( menu.findItem(R.id.mark_all_read_item).setVisible(

View File

@ -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");
}
}
}

View File

@ -25,7 +25,6 @@ public class ExternalMedia implements Playable {
private String episodeTitle; private String episodeTitle;
private String feedTitle; private String feedTitle;
private String shownotes;
private MediaType mediaType = MediaType.AUDIO; private MediaType mediaType = MediaType.AUDIO;
private List<Chapter> chapters; private List<Chapter> chapters;
private int duration; private int duration;
@ -80,8 +79,13 @@ public class ExternalMedia implements Playable {
.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE); .extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
feedTitle = mmr feedTitle = mmr
.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM); .extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM);
duration = Integer.parseInt(mmr try {
duration = Integer.parseInt(mmr
.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)); .extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
} catch (NumberFormatException e) {
e.printStackTrace();
throw new PlayableException("NumberFormatException when reading duration of media file");
}
ChapterUtils.loadChaptersFromFileUrl(this); ChapterUtils.loadChaptersFromFileUrl(this);
} }

View File

@ -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();
}

View File

@ -342,6 +342,9 @@ public abstract class PlaybackController {
case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_END: case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_END:
onPlaybackEnd(); onPlaybackEnd();
break; break;
case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE:
onPlaybackSpeedChange();
break;
} }
} else { } else {
@ -369,6 +372,8 @@ public abstract class PlaybackController {
} }
}; };
public abstract void onPlaybackSpeedChange();
public abstract void onShutdownNotification(); public abstract void onShutdownNotification();
/** /**
@ -663,6 +668,24 @@ public abstract class PlaybackController {
return status; 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() { public boolean isPlayingVideo() {
if (playbackService != null) { if (playbackService != null) {
return PlaybackService.isPlayingVideo(); return PlaybackService.isPlayingVideo();
@ -670,6 +693,7 @@ public abstract class PlaybackController {
return false; return false;
} }
/** /**
* Returns true if PlaybackController can communicate with the playback * Returns true if PlaybackController can communicate with the playback
* service. * service.

View File

@ -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");
}
}

View File

@ -2,18 +2,21 @@ package instrumentationTest.de.test.antennapod.service.download;
import java.io.File; import java.io.File;
import android.test.InstrumentationTestCase;
import de.danoeh.antennapod.feed.FeedFile; import de.danoeh.antennapod.feed.FeedFile;
import de.danoeh.antennapod.service.download.*; import de.danoeh.antennapod.service.download.*;
import android.test.AndroidTestCase; import android.test.AndroidTestCase;
import android.util.Log; import android.util.Log;
public class HttpDownloaderTest extends AndroidTestCase { public class HttpDownloaderTest extends InstrumentationTestCase {
private static final String TAG = "HttpDownloaderTest"; private static final String TAG = "HttpDownloaderTest";
private static final String DOWNLOAD_DIR = "testdownloads"; private static final String DOWNLOAD_DIR = "testdownloads";
private static boolean successful = true; private static boolean successful = true;
private File destDir;
public HttpDownloaderTest() { public HttpDownloaderTest() {
super(); super();
} }
@ -21,17 +24,23 @@ public class HttpDownloaderTest extends AndroidTestCase {
@Override @Override
protected void tearDown() throws Exception { protected void tearDown() throws Exception {
super.tearDown(); super.tearDown();
File externalDir = getContext().getExternalFilesDir(DOWNLOAD_DIR); File[] contents = destDir.listFiles();
assertNotNull(externalDir);
File[] contents = externalDir.listFiles();
for (File f : contents) { for (File f : contents) {
assertTrue(f.delete()); 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) { private FeedFileImpl setupFeedFile(String downloadUrl, String title) {
FeedFileImpl feedfile = new FeedFileImpl(downloadUrl); 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); File file = new File(fileUrl);
Log.d(TAG, "Deleting file: " + file.delete()); Log.d(TAG, "Deleting file: " + file.delete());
feedfile.setFile_url(fileUrl); feedfile.setFile_url(fileUrl);
@ -47,42 +56,16 @@ public class HttpDownloaderTest extends AndroidTestCase {
assertNotNull(status); assertNotNull(status);
assertTrue(status.isSuccessful() == expectedResult); assertTrue(status.isSuccessful() == expectedResult);
assertTrue(status.isDone()); 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() { public void testPassingHttp() {
final String[] urls = { download("http://httpbin.org/status/200", "test200", true);
"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", public void testPassingHttps() {
"http://rss.sciam.com/sciam/60-second-mind", download("https://httpbin.org/status/200", "test200", true);
"http://rss.sciam.com/sciam/60-second-space",
"http://rss.sciam.com/sciam/60-second-health",
"http://rss.sciam.com/sciam/60-second-tech",
"http://risky.biz/feeds/risky-business",
"http://risky.biz/feeds/rb2",
"http://podcast.hr-online.de/lateline/podcast.xml",
"http://bitlove.org/nsemak/mikrodilettanten/feed",
"http://bitlove.org/moepmoeporg/riotburnz/feed",
"http://bitlove.org/moepmoeporg/schachcast/feed",
"http://bitlove.org/moepmoeporg/sundaymoaning/feed",
"http://bitlove.org/motofunk/anekdotkast/feed",
"http://bitlove.org/motofunk/motofunk/feed",
"http://bitlove.org/nerdinand/zch/feed",
"http://podcast.homerj.de/podcasts.xml",
"http://www.dradio.de/rss/podcast/sendungen/wissenschaftundbildung/",
"http://www.dradio.de/rss/podcast/sendungen/wirtschaftundverbraucher/",
"http://www.dradio.de/rss/podcast/sendungen/literatur/",
"http://www.dradio.de/rss/podcast/sendungen/sport/",
"http://www.dradio.de/rss/podcast/sendungen/wirtschaftundgesellschaft/",
"http://www.dradio.de/rss/podcast/sendungen/filmederwoche/",
"http://www.blacksweetstories.com/feed/podcast/",
"http://feeds.5by5.tv/buildanalyze",
"http://bitlove.org/ranzzeit/ranz/feed"
};
for (int i = 0; i < urls.length; i++) {
download(urls[i], Integer.toString(i), true);
}
} }
public void testRedirect() { public void testRedirect() {
@ -111,6 +94,7 @@ public class HttpDownloaderTest extends AndroidTestCase {
downloader.call(); downloader.call();
} }
}; };
t.start();
downloader.cancel(); downloader.cancel();
try { try {
t.join(); t.join();

View File

@ -1,9 +1,252 @@
package instrumentationTest.de.test.antennapod.storage; package instrumentationTest.de.test.antennapod.storage;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.test.InstrumentationTestCase; 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 * Test class for DBTasks
*/ */
public class DBTasksTest extends InstrumentationTestCase { 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();
}
}
} }

View File

@ -3,6 +3,7 @@ package instrumentationTest.de.test.antennapod.storage;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.test.InstrumentationTestCase; import android.test.InstrumentationTestCase;
import android.util.Log;
import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.feed.FeedImage; import de.danoeh.antennapod.feed.FeedImage;
import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedItem;
@ -14,10 +15,10 @@ import de.danoeh.antennapod.storage.PodDBAdapter;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
@ -25,13 +26,15 @@ import java.util.concurrent.TimeoutException;
* Test class for DBWriter * Test class for DBWriter
*/ */
public class DBWriterTest extends InstrumentationTestCase { public class DBWriterTest extends InstrumentationTestCase {
private static final String TAG = "DBWriterTest";
private static final String TEST_FOLDER = "testDBWriter"; private static final String TEST_FOLDER = "testDBWriter";
private static final long TIMEOUT = 5L;
@Override @Override
protected void tearDown() throws Exception { protected void tearDown() throws Exception {
super.tearDown(); super.tearDown();
final Context context = getInstrumentation().getTargetContext(); final Context context = getInstrumentation().getTargetContext();
context.deleteDatabase(PodDBAdapter.DATABASE_NAME); assertTrue(PodDBAdapter.deleteDatabase(getInstrumentation().getTargetContext()));
File testDir = context.getExternalFilesDir(TEST_FOLDER); File testDir = context.getExternalFilesDir(TEST_FOLDER);
assertNotNull(testDir); assertNotNull(testDir);
@ -59,10 +62,7 @@ public class DBWriterTest extends InstrumentationTestCase {
Feed feed = new Feed("url", new Date(), "title"); Feed feed = new Feed("url", new Date(), "title");
List<FeedItem> items = new ArrayList<FeedItem>(); List<FeedItem> items = new ArrayList<FeedItem>();
feed.setItems(items); feed.setItems(items);
FeedItem item = new FeedItem(); FeedItem item = new FeedItem(0, "Item", "Item", "url", new Date(), true, feed);
item.setTitle("title");
item.setPubDate(new Date());
item.setFeed(feed);
FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", dest.getAbsolutePath(), "download_url", true, null); FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", dest.getAbsolutePath(), "download_url", true, null);
item.setMedia(media); item.setMedia(media);
@ -84,42 +84,11 @@ public class DBWriterTest extends InstrumentationTestCase {
assertNull(media.getFile_url()); 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 { public void testDeleteFeed() throws IOException, ExecutionException, InterruptedException, TimeoutException {
File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER); File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
assertNotNull(destFolder); assertNotNull(destFolder);
Feed feed = new Feed ("url", new Date(), "title"); Feed feed = new Feed("url", new Date(), "title");
feed.setItems(new ArrayList<FeedItem>()); feed.setItems(new ArrayList<FeedItem>());
// create Feed image // create Feed image
@ -132,10 +101,7 @@ public class DBWriterTest extends InstrumentationTestCase {
List<File> itemFiles = new ArrayList<File>(); List<File> itemFiles = new ArrayList<File>();
// create items with downloaded media files // create items with downloaded media files
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
FeedItem item = new FeedItem(); FeedItem item = new FeedItem(0, "Item " + i, "Item" + i, "url", new Date(), true, feed);
item.setTitle("Item " + i);
item.setPubDate(new Date(System.currentTimeMillis()));
item.setFeed(feed);
feed.getItems().add(item); feed.getItems().add(item);
File enc = new File(destFolder, "file " + i); File enc = new File(destFolder, "file " + i);
@ -158,7 +124,7 @@ public class DBWriterTest extends InstrumentationTestCase {
assertTrue(item.getMedia().getId() != 0); 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 // check if files still exist
assertFalse(imgFile.exists()); assertFalse(imgFile.exists());
@ -188,7 +154,7 @@ public class DBWriterTest extends InstrumentationTestCase {
File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER); File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
assertNotNull(destFolder); assertNotNull(destFolder);
Feed feed = new Feed ("url", new Date(), "title"); Feed feed = new Feed("url", new Date(), "title");
feed.setItems(new ArrayList<FeedItem>()); feed.setItems(new ArrayList<FeedItem>());
feed.setImage(null); feed.setImage(null);
@ -196,16 +162,13 @@ public class DBWriterTest extends InstrumentationTestCase {
List<File> itemFiles = new ArrayList<File>(); List<File> itemFiles = new ArrayList<File>();
// create items with downloaded media files // create items with downloaded media files
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
FeedItem item = new FeedItem(); FeedItem item = new FeedItem(0, "Item " + i, "Item" + i, "url", new Date(), true, feed);
item.setTitle("Item " + i);
item.setPubDate(new Date(System.currentTimeMillis()));
item.setFeed(feed);
feed.getItems().add(item); feed.getItems().add(item);
File enc = new File(destFolder, "file " + i); File enc = new File(destFolder, "file " + i);
assertTrue(enc.createNewFile()); 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); FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", enc.getAbsolutePath(), "download_url", true, null);
item.setMedia(media); item.setMedia(media);
} }
@ -221,7 +184,7 @@ public class DBWriterTest extends InstrumentationTestCase {
assertTrue(item.getMedia().getId() != 0); 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 // check if files still exist
for (File f : itemFiles) { for (File f : itemFiles) {
@ -247,7 +210,7 @@ public class DBWriterTest extends InstrumentationTestCase {
File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER); File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
assertNotNull(destFolder); assertNotNull(destFolder);
Feed feed = new Feed ("url", new Date(), "title"); Feed feed = new Feed("url", new Date(), "title");
feed.setItems(null); feed.setItems(null);
// create Feed image // create Feed image
@ -265,7 +228,7 @@ public class DBWriterTest extends InstrumentationTestCase {
assertTrue(feed.getId() != 0); assertTrue(feed.getId() != 0);
assertTrue(feed.getImage().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 // check if files still exist
assertFalse(imgFile.exists()); assertFalse(imgFile.exists());
@ -284,7 +247,7 @@ public class DBWriterTest extends InstrumentationTestCase {
File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER); File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
assertNotNull(destFolder); assertNotNull(destFolder);
Feed feed = new Feed ("url", new Date(), "title"); Feed feed = new Feed("url", new Date(), "title");
feed.setItems(new ArrayList<FeedItem>()); feed.setItems(new ArrayList<FeedItem>());
// create Feed image // create Feed image
@ -296,10 +259,7 @@ public class DBWriterTest extends InstrumentationTestCase {
// create items // create items
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
FeedItem item = new FeedItem(); FeedItem item = new FeedItem(0, "Item " + i, "Item" + i, "url", new Date(), true, feed);
item.setTitle("Item " + i);
item.setPubDate(new Date(System.currentTimeMillis()));
item.setFeed(feed);
feed.getItems().add(item); feed.getItems().add(item);
} }
@ -315,7 +275,7 @@ public class DBWriterTest extends InstrumentationTestCase {
assertTrue(item.getId() != 0); 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 // check if files still exist
assertFalse(imgFile.exists()); assertFalse(imgFile.exists());
@ -339,7 +299,7 @@ public class DBWriterTest extends InstrumentationTestCase {
File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER); File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
assertNotNull(destFolder); assertNotNull(destFolder);
Feed feed = new Feed ("url", new Date(), "title"); Feed feed = new Feed("url", new Date(), "title");
feed.setItems(new ArrayList<FeedItem>()); feed.setItems(new ArrayList<FeedItem>());
// create Feed image // create Feed image
@ -351,10 +311,7 @@ public class DBWriterTest extends InstrumentationTestCase {
List<File> itemFiles = new ArrayList<File>(); List<File> itemFiles = new ArrayList<File>();
// create items with downloaded media files // create items with downloaded media files
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
FeedItem item = new FeedItem(); FeedItem item = new FeedItem(0, "Item " + i, "Item" + i, "url", new Date(), true, feed);
item.setTitle("Item " + i);
item.setPubDate(new Date(System.currentTimeMillis()));
item.setFeed(feed);
feed.getItems().add(item); feed.getItems().add(item);
File enc = new File(destFolder, "file " + i); File enc = new File(destFolder, "file " + i);
@ -387,7 +344,7 @@ public class DBWriterTest extends InstrumentationTestCase {
queueCursor.close(); queueCursor.close();
adapter.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(); adapter.open();
Cursor c = adapter.getFeedCursor(feed.getId()); Cursor c = adapter.getFeedCursor(feed.getId());
@ -414,7 +371,7 @@ public class DBWriterTest extends InstrumentationTestCase {
File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER); File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
assertNotNull(destFolder); assertNotNull(destFolder);
Feed feed = new Feed ("url", new Date(), "title"); Feed feed = new Feed("url", new Date(), "title");
feed.setItems(new ArrayList<FeedItem>()); feed.setItems(new ArrayList<FeedItem>());
// create Feed image // create Feed image
@ -426,10 +383,7 @@ public class DBWriterTest extends InstrumentationTestCase {
List<File> itemFiles = new ArrayList<File>(); List<File> itemFiles = new ArrayList<File>();
// create items with downloaded media files // create items with downloaded media files
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
FeedItem item = new FeedItem(); FeedItem item = new FeedItem(0, "Item " + i, "Item" + i, "url", new Date(), true, feed);
item.setTitle("Item " + i);
item.setPubDate(new Date(System.currentTimeMillis()));
item.setFeed(feed);
feed.getItems().add(item); feed.getItems().add(item);
File enc = new File(destFolder, "file " + i); File enc = new File(destFolder, "file " + i);
@ -451,7 +405,7 @@ public class DBWriterTest extends InstrumentationTestCase {
assertTrue(item.getMedia().getId() != 0); 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 = new PodDBAdapter(getInstrumentation().getContext());
adapter.open(); adapter.open();
@ -470,4 +424,261 @@ public class DBWriterTest extends InstrumentationTestCase {
c.close(); 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();
}
}
}
} }

View File

@ -124,7 +124,7 @@ public class FeedHandlerTest extends AndroidTestCase {
} }
private boolean hasValidFeedItems(Feed feed) { private boolean hasValidFeedItems(Feed feed) {
for (FeedItem item : feed.getItemsArray()) { for (FeedItem item : feed.getItems()) {
if (item.getTitle() == null) { if (item.getTitle() == null) {
Log.e(TAG, "Item has no title"); Log.e(TAG, "Item has no title");
return false; return false;