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

This commit is contained in:
Tom Hennen 2013-09-09 18:21:00 -04:00
commit 34a5e62339
123 changed files with 6917 additions and 1058 deletions

1
.gitignore vendored
View File

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

View File

@ -21,6 +21,7 @@ trans.ru-RU = res/values-ru/strings.xml
trans.ru_RU = res/values-ru/strings.xml
trans.uk_UA = res/values-uk-rUA/strings.xml
trans.zh_CN = res/values-zh-rCN/strings.xml
trans.sv_SE = res/values-sv-rSE/strings.xml
[antennapod.description]
file_filter = description/<lang>.txt

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.danoeh.antennapod"
android:versionCode="31"
android:versionName="0.9.7.4" >
android:versionCode="32"
android:versionName="0.9.7.5" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@ -39,300 +39,379 @@
<activity
android:name=".activity.MainActivity"
android:configChanges="keyboardHidden|orientation"
android:label="@string/app_name" >
android:label="@string/app_name">
<meta-data
android:name="android.app.default_searchable"
android:value="de.danoeh.antennapod.activity.SearchActivity" />
android:value="de.danoeh.antennapod.activity.SearchActivity"/>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
android:resource="@xml/searchable"/>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name="de.danoeh.antennapod.activity.AddFeedActivity"
android:configChanges="keyboardHidden|orientation"
android:label="@string/add_new_feed_label"
android:windowSoftInputMode="adjustResize" >
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
<data android:host="*"/>
<data android:pathPattern=".*\\.xml"/>
<data android:pathPattern=".*\\.rss"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
<data android:host="feeds.feedburner.com"/>
<data android:host="feedproxy.google.com"/>
<data android:host="feeds2.feedburner.com"/>
<data android:host="feedsproxy.google.com"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
<data android:mimeType="text/xml"/>
<data android:mimeType="application/rss+xml"/>
<data android:mimeType="application/atom+xml"/>
<data android:mimeType="application/xml"/>
</intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="*"/>
<data android:pathPattern=".*\\.xml"/>
<data android:pathPattern=".*\\.rss"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:mimeType="text/plain" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="feeds.feedburner.com"/>
<data android:host="feedproxy.google.com"/>
<data android:host="feeds2.feedburner.com"/>
<data android:host="feedsproxy.google.com"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:mimeType="text/xml"/>
<data android:mimeType="application/rss+xml"/>
<data android:mimeType="application/atom+xml"/>
<data android:mimeType="application/xml"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
<activity
android:name="de.danoeh.antennapod.activity.FeedItemlistActivity"
android:configChanges="orientation|screenSize" >
android:configChanges="orientation|screenSize">
<meta-data
android:name="android.app.default_searchable"
android:value="de.danoeh.antennapod.activity.SearchActivity" />
android:value="de.danoeh.antennapod.activity.SearchActivity"/>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
android:resource="@xml/searchable"/>
</activity>
<activity
android:name="de.danoeh.antennapod.activity.ItemviewActivity"
android:configChanges="keyboard|orientation" />
android:configChanges="keyboard|orientation"/>
<activity
android:name="de.danoeh.antennapod.activity.DownloadActivity"
android:label="@string/downloads_label" />
android:label="@string/downloads_label"/>
<activity
android:name=".activity.AudioplayerActivity"
android:launchMode="singleTop" >
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="file" />
<data android:mimeType="audio/*" />
<data android:scheme="file"/>
<data android:mimeType="audio/*"/>
</intent-filter>
</activity>
<service
android:name=".service.download.DownloadService"
android:enabled="true" />
android:enabled="true"/>
<service
android:name="de.danoeh.antennapod.service.PlaybackService"
android:enabled="true" >
android:enabled="true">
</service>
<service
android:name=".service.GpodnetSyncService"
android:enabled="true">
</service>
<activity
android:name=".activity.PreferenceActivity"
android:configChanges="keyboardHidden|orientation"
android:label="@string/settings_label" >
android:label="@string/settings_label">
</activity>
<activity
android:name=".activity.DownloadLogActivity"
android:label="@string/download_log_label" >
android:label="@string/download_log_label">
</activity>
<receiver
android:name=".receiver.MediaButtonReceiver"
android:exported="true" >
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
<action android:name="android.intent.action.MEDIA_BUTTON"/>
</intent-filter>
<intent-filter>
<action android:name="de.danoeh.antennapod.NOTIFY_BUTTON_RECEIVER" />
<action android:name="de.danoeh.antennapod.NOTIFY_BUTTON_RECEIVER"/>
</intent-filter>
</receiver>
<activity android:name=".activity.FeedInfoActivity" >
<activity android:name=".activity.FeedInfoActivity">
</activity>
<service
android:name=".service.PlayerWidgetService"
android:enabled="true"
android:exported="false" >
android:exported="false">
</service>
<receiver android:name=".receiver.PlayerWidget" >
<receiver android:name=".receiver.PlayerWidget">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<intent-filter>
<action android:name="de.danoeh.antennapod.FORCE_WIDGET_UPDATE" />
<action android:name="de.danoeh.antennapod.FORCE_WIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/player_widget_info" />
android:resource="@xml/player_widget_info"/>
<intent-filter>
<action android:name="de.danoeh.antennapod.STOP_WIDGET_UPDATE" />
<action android:name="de.danoeh.antennapod.STOP_WIDGET_UPDATE"/>
</intent-filter>
</receiver>
<receiver android:name=".receiver.FeedUpdateReceiver" >
<receiver android:name=".receiver.FeedUpdateReceiver">
<intent-filter>
<action android:name="de.danoeh.antennapod.feedupdatereceiver.refreshFeeds" />
<action android:name="de.danoeh.antennapod.feedupdatereceiver.refreshFeeds"/>
</intent-filter>
</receiver>
<activity android:name=".activity.StorageErrorActivity" >
<activity android:name=".activity.StorageErrorActivity">
</activity>
<activity
android:name=".activity.FlattrAuthActivity"
android:label="@string/flattr_auth_label" >
android:label="@string/flattr_auth_label">
<intent-filter>
<action android:name=".activities.FlattrAuthActivity" />
<action android:name=".activities.FlattrAuthActivity"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="de.danoeh.antennapod"
android:scheme="flattr4j" />
android:scheme="flattr4j"/>
</intent-filter>
</activity>
<activity
android:name=".activity.AboutActivity"
android:label="@string/about_pref" >
android:label="@string/about_pref">
</activity>
<activity
android:name=".activity.OpmlImportFromPathActivity"
android:configChanges="keyboardHidden|orientation"
android:label="@string/opml_import_label" >
android:label="@string/opml_import_label">
</activity>
<activity
android:name=".activity.OpmlImportFromIntentActivity"
android:configChanges="keyboardHidden|orientation"
android:label="@string/opml_import_label" >
android:label="@string/opml_import_label">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="*"
android:mimeType="*/*"
android:pathPattern=".*\\.opml"
android:scheme="file" />
android:scheme="file"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="*"
android:pathPattern=".*\\.opml"
android:scheme="file"
android:mimeType="text/x-opml" />
android:mimeType="text/x-opml"/>
</intent-filter>
</activity>
<activity
android:name=".activity.OpmlFeedChooserActivity"
android:label="@string/opml_import_label" >
android:label="@string/opml_import_label">
</activity>
<activity
android:name=".activity.SearchActivity"
android:configChanges="keyboardHidden|orientation"
android:label="@string/search_results_label"
android:launchMode="singleTop" >
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<action android:name="android.intent.action.SEARCH"/>
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
android:resource="@xml/searchable"/>
</activity>
<activity
android:name=".activity.MiroGuideMainActivity"
android:label="@string/miro_guide_label" >
android:label="@string/miro_guide_label">
<meta-data
android:name="android.app.default_searchable"
android:value="de.danoeh.antennapod.activity.MiroGuideSearchActivity" />
android:value="de.danoeh.antennapod.activity.MiroGuideSearchActivity"/>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/miroguide_searchable" />
android:resource="@xml/miroguide_searchable"/>
</activity>
<activity
android:name=".activity.MiroGuideSearchActivity"
android:configChanges="keyboardHidden|orientation"
android:launchMode="singleTop" >
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<action android:name="android.intent.action.SEARCH"/>
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/miroguide_searchable" />
android:resource="@xml/miroguide_searchable"/>
</activity>
<activity
android:name=".activity.MiroGuideCategoryActivity"
android:configChanges="keyboardHidden|orientation" >
android:configChanges="keyboardHidden|orientation">
</activity>
<activity
android:name=".activity.MiroGuideChannelViewActivity"
android:configChanges="keyboard|orientation"
android:label="@string/miro_guide_label" >
android:label="@string/miro_guide_label">
</activity>
<activity
android:name=".activity.VideoplayerActivity"
android:configChanges="keyboardHidden|orientation"
android:screenOrientation="landscape" >
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="file" />
<data android:mimeType="video/*" />
<data android:scheme="file"/>
<data android:mimeType="video/*"/>
</intent-filter>
</activity>
<activity
android:name=".activity.PlaybackHistoryActivity"
android:label="@string/playback_history_label" />
android:label="@string/playback_history_label"/>
<activity
android:name=".activity.DirectoryChooserActivity"
android:label="@string/choose_data_directory" />
android:label="@string/choose_data_directory"/>
<activity
android:name=".activity.OrganizeQueueActivity"
android:configChanges="orientation"
android:label="@string/organize_queue_label" >
android:label="@string/organize_queue_label">
</activity>
<activity
android:name=".activity.gpoddernet.GpodnetMainActivity"
android:configChanges="orientation"
android:label="@string/gpodnet_main_label">
<meta-data
android:name="android.app.default_searchable"
android:value="de.danoeh.antennapod.activity.gpoddernet.GpodnetSearchActivity"/>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/gpodnet_searchable"/>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="de.danoeh.antennapod.activity.AddFeedActivity" />
</activity>
<activity
android:name=".activity.gpoddernet.GpodnetTagActivity"
android:configChanges="orientation">
<meta-data
android:name="android.app.default_searchable"
android:value="de.danoeh.antennapod.activity.gpoddernet.GpodnetSearchActivity"/>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/gpodnet_searchable"/>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="de.danoeh.antennapod.activity.gpoddernet.GpodnetMainActivity" />
</activity>
<receiver android:name=".receiver.ConnectivityActionReceiver" >
<activity
android:name=".activity.gpoddernet.GpodnetSearchActivity"
android:configChanges="orientation"
android:label="@string/search_label"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
<action android:name="android.intent.action.SEARCH"/>
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/gpodnet_searchable"/>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="de.danoeh.antennapod.activity.gpoddernet.GpodnetMainActivity" />
</activity>
<activity
android:name=".activity.DefaultOnlineFeedViewActivity"
android:configChanges="orientation"/>
<activity
android:name=".activity.gpoddernet.GpodnetAuthenticationActivity"
android:configChanges="orientation"
android:label="@string/gpodnet_auth_label"
android:screenOrientation="portrait">
<intent-filter>
<action android:name=".activity.gpoddernet.GpodnetAuthenticationActivity"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="de.danoeh.antennapod.activity.PreferenceActivity" />
</activity>
<receiver android:name=".receiver.ConnectivityActionReceiver">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
</receiver>
<receiver android:name=".receiver.AlarmUpdateReceiver" >
<receiver android:name=".receiver.AlarmUpdateReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PACKAGE_REPLACED" />
<action android:name="android.intent.action.PACKAGE_REPLACED"/>
<data
android:path="de.danoeh.antennapod"
android:scheme="package" />
android:scheme="package"/>
</intent-filter>
</receiver>
</application>

View File

@ -1,6 +1,14 @@
Change Log
==========
Version 0.9.7.5
---------------
* Reduced application startup time
* Reduced memory usage
* Added option to change the playback speed
* Added Swedish translation
* Several bugfixes and improvements
Version 0.9.7.4
---------------
* Episode cache size can now be set to unlimited

View File

@ -40,7 +40,7 @@
<body>
<div id="header" align="center">
<img src="logo.png" alt="Logo" width="100px" height="100px"/>
<p>AntennaPod, Version 0.9.7.4</p>
<p>AntennaPod, Version 0.9.7.5</p>
<p>Copyright © 2012 Daniel Oeh</p>
<p>Licensed under the MIT License <a href="LICENSE.html">(View)</a></p>
</div>
@ -57,5 +57,7 @@
licensed under the Apache 2.0 license
<h2>drag-sort-listview <a href="https://github.com/bauerca/drag-sort-listview">(Link)</a></h2>
licensed under the Apache 2.0 license
<h2>Presto Client <a href="http://www.aocate.com/presto/">(Link)</a></h2>
licensed under the Apache 2.0 license
</body>
</html>

View File

@ -12,9 +12,20 @@ repositories {
mavenCentral()
}
dependencies {
def libsdir = new File('libs');
if (!libsdir.exists()) {
println "Creating libs directory"
libsdir.mkdir()
}
def prestoLib = new File('libs/presto_client-0.8.5.jar')
if (!prestoLib.exists()) {
println "Downloading presto library into libs folder"
new URL('http://www.aocate.com/presto/client/presto_client-0.8.5.jar').withInputStream{ i -> prestoLib.withOutputStream{ it << i }}
}
compile 'com.android.support:appcompat-v7:18.0.+'
compile 'org.apache.commons:commons-lang3:3.1'
compile ('org.shredzone.flattr4j:flattr4j-core:2.4') {
compile ('org.shredzone.flattr4j:flattr4j-core:2.7') {
exclude group: 'org.apache.httpcomponents', module: 'httpcore'
exclude group: 'org.apache.httpcomponents', module: 'httpclient'
exclude group: 'org.json', module: 'json'
@ -22,6 +33,7 @@ dependencies {
compile 'commons-io:commons-io:2.4'
compile 'com.nineoldandroids:library:2.4.0'
compile project(':submodules:dslv:library')
compile files('libs/presto_client-0.8.5.jar')
}
android {
@ -35,6 +47,31 @@ android {
testInstrumentationRunner "instrumentationTest.de.test.antennapod.AntennaPodTestRunner"
}
signingConfigs {
releaseConfig {
if (project.hasProperty('releaseStoreFile')) {
storeFile file(releaseStoreFile)
} else {
storeFile file('keystore')
}
if (project.hasProperty('releaseStorePassword')) {
storePassword releaseStorePassword
} else {
storePassword "password"
}
if (project.hasProperty('releaseKeyAlias')) {
keyAlias releaseKeyAlias
} else {
keyAlias "alias"
}
if (project.hasProperty('releaseKeyPassword')) {
keyPassword releaseKeyPassword
} else {
keyPassword "password"
}
}
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
@ -51,5 +88,10 @@ android {
debug {
packageNameSuffix ".debug"
}
release {
runProguard true
proguardFile 'proguard.cfg'
signingConfig signingConfigs.releaseConfig
}
}
}

12
pom.xml
View File

@ -5,7 +5,7 @@
<groupId>de.danoeh</groupId>
<artifactId>antennapod</artifactId>
<packaging>apk</packaging>
<version>0.9.7.4</version>
<version>0.9.7.5</version>
<name>AntennaPod</name>
@ -84,6 +84,14 @@
<artifactId>library</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>com.aocate</groupId>
<artifactId>presto_client</artifactId>
<version>0.8.5</version>
<type>jar</type>
<scope>system</scope>
<systemPath>${project.basedir}/libs/presto_client-0.8.5.jar</systemPath>
</dependency>
</dependencies>
<build>
@ -212,7 +220,7 @@
</manifest>
<proguard>
<skip>false</skip>
<config>proguard.cfg</config>
<config>proguard-mvn.cfg</config>
</proguard>
</configuration>
<executions>

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
-keepattributes *Annotation*
#-libraryjars libs/android-support-v4.jar
#-libraryjars libs/commons-lang3-3.1.jar
#-libraryjars libs/flattr4j-core-2.4.jar
#-libraryjars libs/commons-io-2.4.jar
#-injars libs/presto_client-0.8.5.jar
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application

View File

@ -92,14 +92,12 @@
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:background="?attr/borderless_button"
android:src="?attr/av_pause" />
<ImageButton
android:id="@+id/butRev"
android:layout_width="80dp"
android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_toLeftOf="@id/butPlay"
android:background="?attr/borderless_button"
@ -107,11 +105,22 @@
<ImageButton
android:id="@+id/butFF"
android:layout_width="80dp"
android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_toRightOf="@id/butPlay"
android:background="?attr/borderless_button"
android:src="?attr/av_fast_forward" />
<Button
android:id="@+id/butPlaybackSpeed"
android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_toRightOf="@id/butFF"
android:background="?attr/borderless_button"
android:src="?attr/av_fast_forward"
android:textColor="@color/gray"
android:textSize="@dimen/text_size_medium"
android:visibility="gone" />
</RelativeLayout>
<RelativeLayout

View File

@ -1,20 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/footer"
android:layout_width="fill_parent"
android:layout_height="48dp"
android:layout_alignParentBottom="true" >
android:focusableInTouchMode="true"
android:layout_alignParentBottom="true">
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_alignParentTop="true"
android:background="?android:attr/dividerVertical" />
android:background="?android:attr/dividerVertical"/>
<View
android:id="@+id/horizontal_divider"
@ -24,7 +25,7 @@
android:layout_centerHorizontal="true"
android:layout_marginBottom="4dp"
android:layout_marginTop="4dp"
android:background="?android:attr/dividerVertical" />
android:background="?android:attr/dividerVertical"/>
<Button
android:id="@+id/butCancel"
@ -35,7 +36,7 @@
android:layout_alignParentTop="true"
android:layout_toLeftOf="@id/horizontal_divider"
android:background="?android:attr/selectableItemBackground"
android:text="@string/cancel_label" />
android:text="@string/cancel_label"/>
<Button
android:id="@+id/butConfirm"
@ -46,7 +47,7 @@
android:layout_alignParentTop="true"
android:layout_toRightOf="@id/horizontal_divider"
android:background="?android:attr/selectableItemBackground"
android:text="@string/confirm_label" />
android:text="@string/confirm_label"/>
</RelativeLayout>
<ScrollView
@ -54,21 +55,22 @@
android:layout_height="0dp"
android:layout_above="@id/footer"
android:layout_alignParentTop="true"
android:scrollbars="vertical" >
android:scrollbars="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
android:layout_height="wrap_content">
<TextView
android:id="@+id/txtvFeedurl"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_margin="8dp"
android:focusable="true"
android:focusableInTouchMode="true"
android:text="@string/txtvfeedurl_label" />
android:layout_margin="16dp"
android:textSize="@dimen/text_size_large"
android:textColor="@color/bright_blue"
android:textStyle="italic"
android:text="@string/txtvfeedurl_label"/>
<EditText
android:id="@+id/etxtFeedurl"
@ -77,23 +79,35 @@
android:layout_below="@id/txtvFeedurl"
android:layout_margin="8dp"
android:hint="@string/feedurl_label"
android:inputType="textUri" />
android:inputType="textUri"/>
<TextView
android:id="@+id/txtvBrowseMiroguide"
android:id="@+id/txtvPodcastDirectories"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/etxtFeedurl"
android:layout_margin="8dp"
android:text="@string/txtv_browse_miroguide_label" />
android:textSize="@dimen/text_size_large"
android:textColor="@color/bright_blue"
android:textStyle="italic"
android:text="@string/podcastdirectories_label"/>
<Button
android:id="@+id/butBrowseGpoddernet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/txtvPodcastDirectories"
android:layout_margin="8dp"
android:text="@string/gpodnet_main_label"/>
<Button
android:id="@+id/butBrowseMiroguide"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/txtvBrowseMiroguide"
android:layout_below="@id/butBrowseGpoddernet"
android:layout_margin="8dp"
android:text="@string/browse_miroguide_label" />
android:text="@string/miro_guide_label"/>
<TextView
android:id="@+id/txtvOpmlImport"
@ -101,17 +115,28 @@
android:layout_height="wrap_content"
android:layout_below="@id/butBrowseMiroguide"
android:layout_margin="8dp"
android:text="@string/opml_import_txtv_button_lable" />
android:textSize="@dimen/text_size_large"
android:textColor="@color/bright_blue"
android:textStyle="italic"
android:text="@string/opml_import_label"/>
<TextView
android:id="@+id/txtvOpmlImportExpl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/txtvOpmlImport"
android:layout_margin="8dp"
android:text="@string/opml_import_txtv_button_lable"/>
<Button
android:id="@+id/butOpmlImport"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/txtvOpmlImport"
android:layout_below="@id/txtvOpmlImportExpl"
android:layout_marginBottom="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:text="@string/opml_import_label" />
android:text="@string/opml_import_label"/>
</RelativeLayout>
</ScrollView>

View File

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<EditText
android:id="@+id/etxtUsername"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_margin="16dp"
android:hint="@string/username_label"/>
<EditText
android:id="@+id/etxtPassword"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_margin="16dp"
android:inputType="textPassword"
android:hint="@string/password_label"/>
<CheckBox
android:id="@+id/chkSaveUsernamePassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/save_username_password_label"/>
</LinearLayout>
<RelativeLayout
android:id="@+id/footer"
android:layout_width="fill_parent"
android:layout_height="48dp" >
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:layout_alignParentTop="true"
android:background="?android:attr/dividerVertical" />
<View
android:id="@+id/horizontal_divider"
android:layout_width="1dip"
android:layout_height="fill_parent"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="4dp"
android:layout_marginTop="4dp"
android:background="?android:attr/dividerVertical" />
<Button
android:id="@+id/butCancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_toLeftOf="@id/horizontal_divider"
android:background="?android:attr/selectableItemBackground"
android:text="@string/cancel_label" />
<Button
android:id="@+id/butConfirm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_toRightOf="@id/horizontal_divider"
android:background="?android:attr/selectableItemBackground"
android:text="@string/confirm_label" />
</RelativeLayout>
</LinearLayout>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/footer"
@ -10,21 +10,21 @@
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal" >
android:orientation="horizontal">
<Button
android:id="@+id/butConfirm"
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/confirm_label" />
android:text="@string/confirm_label"/>
<Button
android:id="@+id/butCancel"
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/cancel_label" />
android:text="@string/cancel_label"/>
</LinearLayout>
<ScrollView
@ -32,21 +32,22 @@
android:layout_height="0dp"
android:layout_above="@id/footer"
android:layout_alignParentTop="true"
android:scrollbars="vertical" >
android:scrollbars="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
android:layout_height="wrap_content">
<TextView
android:id="@+id/txtvFeedurl"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_margin="8dp"
android:focusable="true"
android:focusableInTouchMode="true"
android:text="@string/txtvfeedurl_label" />
android:layout_margin="16dp"
android:textSize="@dimen/text_size_large"
android:textColor="@color/bright_blue"
android:textStyle="italic"
android:text="@string/txtvfeedurl_label"/>
<EditText
android:id="@+id/etxtFeedurl"
@ -55,23 +56,35 @@
android:layout_below="@id/txtvFeedurl"
android:layout_margin="8dp"
android:hint="@string/feedurl_label"
android:inputType="textUri" />
android:inputType="textUri"/>
<TextView
android:id="@+id/txtvBrowseMiroguide"
android:id="@+id/txtvPodcastDirectories"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/etxtFeedurl"
android:layout_margin="8dp"
android:text="@string/txtv_browse_miroguide_label" />
android:textSize="@dimen/text_size_large"
android:textColor="@color/bright_blue"
android:textStyle="italic"
android:text="@string/podcastdirectories_label"/>
<Button
android:id="@+id/butBrowseGpoddernet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/txtvPodcastDirectories"
android:layout_margin="8dp"
android:text="@string/gpodnet_main_label"/>
<Button
android:id="@+id/butBrowseMiroguide"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/txtvBrowseMiroguide"
android:layout_below="@id/butBrowseGpoddernet"
android:layout_margin="8dp"
android:text="@string/browse_miroguide_label" />
android:text="@string/miro_guide_label"/>
<TextView
android:id="@+id/txtvOpmlImport"
@ -79,17 +92,28 @@
android:layout_height="wrap_content"
android:layout_below="@id/butBrowseMiroguide"
android:layout_margin="8dp"
android:text="@string/opml_import_txtv_button_lable" />
android:textSize="@dimen/text_size_large"
android:textColor="@color/bright_blue"
android:textStyle="italic"
android:text="@string/opml_import_label"/>
<TextView
android:id="@+id/txtvOpmlImportExpl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/txtvOpmlImport"
android:layout_margin="8dp"
android:text="@string/opml_import_txtv_button_lable"/>
<Button
android:id="@+id/butOpmlImport"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/txtvOpmlImport"
android:layout_below="@id/txtvOpmlImportExpl"
android:layout_marginBottom="8dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:text="@string/opml_import_label" />
android:text="@string/opml_import_label"/>
</RelativeLayout>
</ScrollView>

View File

@ -79,8 +79,6 @@
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:background="?attr/borderless_button"
android:src="?attr/av_pause" />
@ -99,6 +97,17 @@
android:layout_toRightOf="@id/butPlay"
android:background="?attr/borderless_button"
android:src="?attr/av_fast_forward" />
<Button
android:id="@+id/butPlaybackSpeed"
android:layout_width="80dp"
android:layout_height="match_parent"
android:layout_toRightOf="@id/butFF"
android:background="?attr/borderless_button"
android:src="?attr/av_fast_forward"
android:textColor="@color/gray"
android:textSize="@dimen/text_size_medium"
android:visibility="gone" />
</RelativeLayout>
<RelativeLayout

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<EditText
android:id="@+id/etxtUsername"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_margin="16dp"
android:hint="@string/username_label"/>
<EditText
android:id="@+id/etxtPassword"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_margin="16dp"
android:inputType="textPassword"
android:hint="@string/password_label"/>
<CheckBox
android:id="@+id/chkSaveUsernamePassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/save_username_password_label"/>
</LinearLayout>
<LinearLayout
style="@android:style/ButtonBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/butConfirm"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:text="@string/confirm_label"
android:layout_weight="1"/>
<Button
android:id="@+id/butCancel"
android:text="@string/cancel_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1">
<android.support.v4.view.PagerTabStrip
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top" />
</android.support.v4.view.ViewPager>
</LinearLayout>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<GridView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/gridView"
android:stretchMode="columnWidth"
android:numColumns="auto_fit"
android:verticalSpacing="4dp"
android:horizontalSpacing="4dp"
android:gravity="center"
android:columnWidth="200dp"
tools:listitem="@layout/gpodnet_podcast_listitem"/>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/progressBar"
android:layout_gravity="center"
android:indeterminateOnly="true"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/txtvError"
android:layout_gravity="center"
android:visibility="gone"
android:textSize="@dimen/text_size_small"/>
</FrameLayout>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imgvCover"
android:layout_width="@dimen/thumbnail_length_itemlist"
android:layout_height="@dimen/thumbnail_length_itemlist"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginRight="4dip"
android:adjustViewBounds="true"
android:cropToPadding="true"
android:scaleType="fitXY" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="@dimen/thumbnail_length_itemlist"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/imgvCover"
android:layout_marginRight="8dp"
android:orientation="vertical" >
<TextView
android:id="@+id/txtvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_small" />
<TextView
android:id="@+id/txtvDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="2"
android:ellipsize="end"
android:textColor="?android:attr/textColorTertiary"
android:textSize="@dimen/text_size_micro" />
</LinearLayout>
</RelativeLayout>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<FrameLayout
android:id="@+id/searchListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<FrameLayout
android:id="@+id/taglistFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ViewFlipper
android:id="@+id/viewflipper"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</ScrollView>

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@id/txtvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/gpodnetauth_login_title"
android:layout_alignParentTop="true"
android:textSize="@dimen/text_size_large"
android:layout_margin="16dp"
android:textColor="@color/bright_blue"
android:textStyle="italic"/>
<TextView
android:id="@id/txtvDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/gpodnetauth_login_descr"
android:layout_below="@id/txtvTitle"
android:textSize="@dimen/text_size_medium"
android:textColor="?android:attr/textColorSecondary"
android:layout_margin="16dp"/>
<EditText
android:id="@+id/etxtUsername"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/username_label"
android:layout_below="@id/txtvDescription"
android:layout_margin="8dp"/>
<EditText
android:id="@+id/etxtPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/password_label"
android:layout_below="@id/etxtUsername"
android:inputType="textPassword"
android:layout_margin="8dp"/>
<Button
android:id="@+id/butLogin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/etxtPassword"
android:layout_alignParentRight="true"
android:text="@string/gpodnetauth_login_butLabel"
android:layout_margin="8dp"/>
<TextView
android:id="@+id/txtvError"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_below="@id/etxtPassword"
android:layout_alignParentLeft="true"
android:layout_toLeftOf="@id/butLogin"
android:textColor="@color/download_failed_red"
android:textSize="@dimen/text_size_small"
android:maxLines="2"
android:ellipsize="end"
android:gravity="center"
android:layout_margin="16dp"/>
<ProgressBar
android:id="@+id/progBarLogin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_alignTop="@+id/butLogin"
android:layout_toLeftOf="@+id/butLogin"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/text_size_medium"
android:textColor="?android:attr/textColorSecondary"
android:layout_margin="16dp"
android:text="@string/gpodnetauth_login_register"
android:autoLink="web"
android:layout_below="@id/butLogin"/>
</RelativeLayout>

View File

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/txtvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/gpodnetauth_device_title"
android:layout_alignParentTop="true"
android:textSize="@dimen/text_size_large"
android:layout_margin="16dp"
android:textColor="@color/bright_blue"
android:textStyle="italic"/>
<TextView
android:id="@+id/txtvDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/gpodnetauth_device_descr"
android:layout_below="@id/txtvTitle"
android:textSize="@dimen/text_size_medium"
android:textColor="?android:attr/textColorSecondary"
android:layout_margin="16dp"/>
<EditText
android:id="@+id/etxtCaption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/gpodnetauth_device_caption"
android:layout_below="@id/txtvDescription"
android:layout_margin="8dp"/>
<EditText
android:id="@+id/etxtDeviceID"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/gpodnetauth_device_deviceID"
android:layout_below="@id/etxtCaption"
android:layout_margin="8dp"/>
<Button
android:id="@+id/butCreateNewDevice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_alignParentRight="true"
android:layout_below="@id/etxtDeviceID"
android:text="@string/gpodnetauth_device_butCreateNewDevice"/>
<TextView
android:id="@+id/txtvError"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@id/etxtCaption"
android:layout_alignBottom="@id/butCreateNewDevice"
android:textColor="@color/download_failed_red"
android:layout_margin="16dp"
android:textSize="@dimen/text_size_medium"
/>
<ProgressBar
android:id="@+id/progbarCreateDevice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@id/butCreateNewDevice"
android:layout_toLeftOf="@id/butCreateNewDevice"
android:textColor="@color/download_failed_red"
android:textSize="@dimen/text_size_medium"
android:visibility="gone"
/>
<TextView
android:id="@+id/txtvChooseExistingDevice"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/gpodnetauth_device_chooseExistingDevice"
android:layout_below="@id/butCreateNewDevice"
android:textSize="@dimen/text_size_medium"
android:textColor="?android:attr/textColorSecondary"
android:layout_margin="16dp"/>
<Button
android:id="@+id/butChooseExistingDevice"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/gpodnetauth_device_butChoose"
android:layout_below="@+id/spinnerChooseDevice"
android:layout_alignLeft="@+id/butCreateNewDevice"
android:layout_alignRight="@+id/butCreateNewDevice"/>
<Spinner
android:id="@+id/spinnerChooseDevice"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_below="@id/txtvChooseExistingDevice"
android:layout_alignParentLeft="true"
android:layout_margin="8dp"
android:layout_alignParentRight="true"/>
</RelativeLayout>

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/txtvTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/gpodnetauth_finish_title"
android:layout_alignParentTop="true"
android:textSize="@dimen/text_size_large"
android:layout_margin="16dp"
android:textColor="@color/bright_blue"
android:textStyle="italic"/>
<TextView
android:id="@+id/txtvDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/gpodnetauth_finish_descr"
android:layout_below="@id/txtvTitle"
android:textSize="@dimen/text_size_medium"
android:textColor="?android:attr/textColorSecondary"
android:layout_margin="16dp"/>
<Button
android:id="@+id/butSyncNow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/txtvDescription"
android:layout_margin="16dp"
android:text="@string/gpodnetauth_finish_butsyncnow"/>
<Button
android:id="@+id/butGoMainscreen"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/butSyncNow"
android:layout_margin="16dp"
android:text="@string/gpodnetauth_finish_butgomainscreen"/>
</RelativeLayout>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/txtvTitle"
android:layout_margin="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lines="1"
android:ellipsize="end"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_small"/>
<TextView
android:id="@+id/txtvDescription"
android:layout_margin="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lines="3"
android:ellipsize="end"
android:textColor="?android:attr/textColorTertiary"
android:textSize="@dimen/text_size_micro"/>
</LinearLayout>

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imgvCover"
android:layout_width="@dimen/thumbnail_length_onlinefeedview"
android:layout_height="@dimen/thumbnail_length_onlinefeedview"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_margin="4dp"/>
<TextView
android:id="@+id/txtvTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center_vertical"
android:layout_alignTop="@id/imgvCover"
android:layout_toRightOf="@id/imgvCover"
android:layout_alignParentRight="true"
android:lines="1"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_medium"
android:layout_margin="4dp"/>
<TextView
android:id="@+id/txtvAuthor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:layout_below="@id/txtvTitle"
android:layout_toRightOf="@id/imgvCover"
android:lines="1"
android:ellipsize="end"
android:textColor="?android:attr/textColorSecondary"
android:textSize="@dimen/text_size_small"/>
<Button
android:id="@+id/butSubscribe"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:text="@string/subscribe_label"
android:layout_below="@id/txtvAuthor"
android:layout_alignParentRight="true"
/>
<TextView
android:id="@+id/txtvDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/butSubscribe"
android:maxLines="3"
android:ellipsize="end"
android:textColor="?android:attr/textColorTertiary"
android:textSize="@dimen/text_size_micro"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:layout_margin="4dp"/>
</RelativeLayout>

View File

@ -1,6 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="update_intervall_options">
<item>Manual</item>
<item>1 hour</item>
<item>2 hours</item>
<item>4 hours</item>
<item>8 hours</item>
<item>12 hours</item>
<item>24 hours</item>
</string-array>
<string-array name="update_intervall_values">
<item>0</item>
<item>1</item>
@ -28,6 +38,50 @@
<item>80</item>
<item>100</item>
</string-array>
<string-array name="playback_speed_values">
<item>1.0</item>
<item>1.05</item>
<item>1.10</item>
<item>1.15</item>
<item>1.20</item>
<item>1.25</item>
<item>1.30</item>
<item>1.35</item>
<item>1.40</item>
<item>1.45</item>
<item>1.50</item>
<item>1.55</item>
<item>1.60</item>
<item>1.65</item>
<item>1.70</item>
<item>1.75</item>
<item>1.80</item>
<item>1.85</item>
<item>1.90</item>
<item>1.95</item>
<item>2.00</item>
<item>2.10</item>
<item>2.20</item>
<item>2.30</item>
<item>2.40</item>
<item>2.50</item>
<item>2.60</item>
<item>2.70</item>
<item>2.80</item>
<item>2.90</item>
<item>3.00</item>
<item>3.10</item>
<item>3.20</item>
<item>3.30</item>
<item>3.40</item>
<item>3.50</item>
<item>3.60</item>
<item>3.70</item>
<item>3.80</item>
<item>3.90</item>
<item>4.00</item>
</string-array>
<string-array name="autodl_select_networks_default_entries">
<item>N/A</item>
</string-array>
@ -42,5 +96,4 @@
<item>0</item>
<item>1</item>
</string-array>
</resources>

View File

@ -28,7 +28,7 @@
<color name="status_playing">#E0EE5F52</color>
<color name="overlay_dark">#262C31</color>
<color name="overlay_light">#DDDDDD</color>
<!-- Use Gingerbread-orange -->
<color name="selection_background_color_dark">#FEBB20</color>
<color name="selection_background_color_light">#FEBB20</color>

View File

@ -12,4 +12,5 @@
<dimen name="text_size_large">22sp</dimen>
<dimen name="status_indicator_width">36dp</dimen>
<dimen name="thumbnail_length_itemlist">80dp</dimen>
<dimen name="thumbnail_length_onlinefeedview">110dp</dimen>
</resources>

View File

@ -15,5 +15,9 @@
<item name="organize_queue_item" type="id"/>
<item name="drag_handle" type="id"/>
<item name="skip_episode_item" type="id"/>
<item name="image_disk_cache_key" type="id"/>
<item name="imageloader_key" type="id"/>
<item name="notification_gpodnet_sync_error" type="id"/>
<item name="notification_gpodnet_sync_autherror" type="id"/>
</resources>

View File

@ -17,6 +17,8 @@
<string name="cancel_download_label">Cancel Download</string>
<string name="download_log_label">Download log</string>
<string name="playback_history_label">Playback history</string>
<string name="gpodnet_main_label">gpodder.net</string>
<string name="gpodnet_auth_label">gpodder.net login</string>
<!-- Webview actions -->
<string name="open_in_browser_label">Open in browser</string>
@ -47,10 +49,14 @@
<string name="processing_label">Processing</string>
<string name="loading_label">Loading...</string>
<string name="image_of_prefix">Image of:\u0020</string>
<string name="save_username_password_label">Save username and password</string>
<string name="close_label">Close</string>
<!-- 'Add Feed' Activity labels -->
<string name="feedurl_label">Feed URL</string>
<string name="txtvfeedurl_label">Type in the URL of the Feed here:</string>
<string name="txtvfeedurl_label">Add Podcast by URL</string>
<string name="podcastdirectories_label">Podcast directories</string>
<!-- Actions on feeds -->
<string name="mark_all_read_label">Mark all as read</string>
@ -145,6 +151,12 @@
<string name="access_revoked_info">You have successfully revoked AntennaPod\'s access token to your account. In order to complete the process, you have to remove this app from the list of approved applications in your account settings on the flattr website.</string>
<string name="flattr_click_success">Successfully flattred this thing!</string>
<string name="flattring_label">Flattring</string>
<!-- Variable Speed -->
<string name="download_plugin_label">Download Plugin</string>
<string name="no_playback_plugin_title">Plugin Not Installed</string>
<string name="no_playback_plugin_msg">For variable speed playback to work, a third party library must be installed.\n\nTap \'Download Plugin\' to download a free plugin from the Play Store\n\nAny problems found using this plugin are not the responsibility of AntennaPod and should be reported to the plugin owner.</string>
<string name="set_playback_speed_label">Playback Speeds</string>
<!-- Empty list labels -->
<string name="no_items_label">There are no items in this list.</string>
@ -154,6 +166,8 @@
<string name="other_pref">Other</string>
<string name="about_pref">About</string>
<string name="queue_label">Queue</string>
<string name="services_label">Services</string>
<string name="flattr_label">Flattr</string>
<string name="pref_pauseOnHeadsetDisconnect_sum">Pause playback when the headphones are disconnected</string>
<string name="pref_followQueue_sum">Jump to next queue item when playback completes</string>
<string name="playback_pref">Playback</string>
@ -190,6 +204,14 @@
<string name="pref_update_interval_hours_plural">hours</string>
<string name="pref_update_interval_hours_singular">hour</string>
<string name="pref_update_interval_hours_manual">Manual</string>
<string name="pref_gpodnet_authenticate_title">Login</string>
<string name="pref_gpodnet_authenticate_sum">Login with your gpodder.net account in order to sync your subscriptions.</string>
<string name="pref_gpodnet_logout_title">Logout</string>
<string name="pref_gpodnet_logout_toast">Logout was successful</string>
<string name="pref_gpodnet_setlogin_information_title">Change login information</string>
<string name="pref_gpodnet_setlogin_information_sum">Change the login information for your gpodder.net account.</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 -->
@ -240,6 +262,37 @@
<string name="add_feed_label">Add feed</string>
<string name="miro_feed_added">Feed is being added</string>
<!-- gpodder.net -->
<string name="gpodnet_taglist_header">CATEGORIES</string>
<string name="gpodnet_toplist_header">TOP PODCASTS</string>
<string name="gpodnet_suggestions_header">SUGGESTIONS</string>
<string name="gpodnet_search_hint">Search gpodder.net</string>
<string name="gpodnetauth_login_title">Login</string>
<string name="gpodnetauth_login_descr">Welcome to the gpodder.net login process. First, type in your login information:</string>
<string name="gpodnetauth_login_butLabel">Login</string>
<string name="gpodnetauth_login_register">If you do not have an account yet, you can create one here:\nhttps://gpodder.net/register/</string>
<string name="username_label">Username</string>
<string name="password_label">Password</string>
<string name="gpodnetauth_device_title">Device Selection</string>
<string name="gpodnetauth_device_descr">Create a new device to use for your gpodder.net account or choose an existing one:</string>
<string name="gpodnetauth_device_deviceID">Device ID</string>
<string name="gpodnetauth_device_caption">Caption</string>
<string name="gpodnetauth_device_butCreateNewDevice">Create new device</string>
<string name="gpodnetauth_device_chooseExistingDevice">Choose existring device:</string>
<string name="gpodnetauth_device_errorEmpty">Device ID must not be empty</string>
<string name="gpodnetauth_device_errorAlreadyUsed">Device ID already in use</string>
<string name="gpodnetauth_device_butChoose">Choose</string>
<string name="gpodnetauth_finish_title">Login successful!</string>
<string name="gpodnetauth_finish_descr">Congratulations! Your gpodder.net account is now linked with your device. AntennaPod will from now on automagically sync subscriptions on your device with your gpodder.net account.</string>
<string name="gpodnetauth_finish_butsyncnow">Start sync now</string>
<string name="gpodnetauth_finish_butgomainscreen">Go to main screen</string>
<string name="gpodnetsync_auth_error_title">gpodder.net authentication error</string>
<string name="gpodnetsync_auth_error_descr">Wrong username or password</string>
<string name="gpodnetsync_error_title">gpodder.net sync error</string>
<string name="gpodnetsync_error_descr">An error occurred during syncing:\u0020</string>
<!-- Directory chooser -->
<string name="selected_folder_label">Selected folder:</string>
<string name="create_folder_label">Create folder</string>
@ -253,4 +306,8 @@
<string name="folder_not_empty_dialog_msg">The folder you have selected is not empty. Media downloads and other files will be placed directly in this folder. Continue anyway?</string>
<string name="set_to_default_folder">Choose default folder</string>
<!-- Online feed view -->
<string name="subscribe_label">Subscribe</string>
<string name="subscribed_label">Subscribed</string>
<string name="downloading_label">Downloading...</string>
</resources>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android" android:hint="@string/gpodnet_search_hint" android:label="@string/app_name" android:icon="@drawable/ic_launcher">
</searchable>

View File

@ -1,69 +1,127 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="@string/user_interface_label">
<CheckBoxPreference android:title="@string/pref_display_only_episodes_title" android:summary="@string/pref_display_only_episodes_sum" android:key="prefDisplayOnlyEpisodes"/>
<ListPreference android:entryValues="@array/theme_values" android:entries="@array/theme_options" android:title="@string/pref_set_theme_title" android:key="prefTheme" android:summary="@string/pref_set_theme_sum" android:defaultValue="0"/>
</PreferenceCategory><PreferenceCategory android:title="@string/playback_pref" >
<CheckBoxPreference
android:title="@string/pref_display_only_episodes_title"
android:summary="@string/pref_display_only_episodes_sum"
android:key="prefDisplayOnlyEpisodes"/>
<ListPreference
android:entryValues="@array/theme_values"
android:entries="@array/theme_options"
android:title="@string/pref_set_theme_title"
android:key="prefTheme"
android:summary="@string/pref_set_theme_sum"
android:defaultValue="0"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/playback_pref">
<CheckBoxPreference
android:defaultValue="true"
android:enabled="true"
android:key="prefPauseOnHeadsetDisconnect"
android:summary="@string/pref_pauseOnHeadsetDisconnect_sum"
android:title="@string/pref_pauseOnHeadsetDisconnect_title" />
android:title="@string/pref_pauseOnHeadsetDisconnect_title"/>
<CheckBoxPreference
android:defaultValue="false"
android:enabled="true"
android:key="prefFollowQueue"
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" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/network_pref" >
<PreferenceCategory android:title="@string/network_pref">
<ListPreference
android:defaultValue="0"
android:entries="@array/update_intervall_values"
android:entryValues="@array/update_intervall_values"
android:key="prefAutoUpdateIntervall"
android:summary="@string/pref_autoUpdateIntervall_sum"
android:title="@string/pref_autoUpdateIntervall_title" />
android:title="@string/pref_autoUpdateIntervall_title"/>
<CheckBoxPreference
android:defaultValue="false"
android:enabled="true"
android:key="prefMobileUpdate"
android:summary="@string/pref_mobileUpdate_sum"
android:title="@string/pref_mobileUpdate_title" />
<ListPreference android:defaultValue="20" android:entries="@array/episode_cache_size_entries" android:key="prefEpisodeCacheSize" android:title="@string/pref_episode_cache_title" android:entryValues="@array/episode_cache_size_values"/><PreferenceScreen android:summary="@string/pref_automatic_download_sum" android:key="prefAutoDownloadSettings" android:title="@string/pref_automatic_download_title">
<CheckBoxPreference android:key="prefEnableAutoDl" android:title="@string/pref_automatic_download_title" android:defaultValue="false"/><CheckBoxPreference android:key="prefEnableAutoDownloadWifiFilter" android:title="@string/pref_autodl_wifi_filter_title" android:summary="@string/pref_autodl_wifi_filter_sum"/>
</PreferenceScreen>
</PreferenceCategory>
<PreferenceCategory android:title="@string/flattr_settings_label" >
android:title="@string/pref_mobileUpdate_title"/>
<ListPreference
android:defaultValue="20"
android:entries="@array/episode_cache_size_entries"
android:key="prefEpisodeCacheSize"
android:title="@string/pref_episode_cache_title"
android:entryValues="@array/episode_cache_size_values"/>
<PreferenceScreen
android:key="pref_flattr_authenticate"
android:summary="@string/pref_flattr_auth_sum"
android:title="@string/pref_flattr_auth_title" >
<intent android:action=".activities.FlattrAuthActivity" />
android:summary="@string/pref_automatic_download_sum"
android:key="prefAutoDownloadSettings"
android:title="@string/pref_automatic_download_title">
<CheckBoxPreference
android:key="prefEnableAutoDl"
android:title="@string/pref_automatic_download_title"
android:defaultValue="false"/>
<CheckBoxPreference
android:key="prefEnableAutoDownloadWifiFilter"
android:title="@string/pref_autodl_wifi_filter_title"
android:summary="@string/pref_autodl_wifi_filter_sum"/>
</PreferenceScreen>
<Preference
android:key="prefRevokeAccess"
android:summary="@string/pref_revokeAccess_sum"
android:title="@string/pref_revokeAccess_title" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/other_pref" >
<Preference android:title="@string/choose_data_directory" android:key="prefChooseDataDir"/><Preference
<PreferenceCategory android:title="@string/services_label">
<PreferenceScreen
android:key="prefFlattrSettings"
android:title="@string/flattr_label">
<PreferenceScreen
android:key="pref_flattr_authenticate"
android:summary="@string/pref_flattr_auth_sum"
android:title="@string/pref_flattr_auth_title">
<intent android:action=".activities.FlattrAuthActivity"/>
</PreferenceScreen>
<Preference
android:key="prefRevokeAccess"
android:summary="@string/pref_revokeAccess_sum"
android:title="@string/pref_revokeAccess_title"/>
</PreferenceScreen>
<PreferenceScreen
android:key="prefFlattrSettings"
android:title="@string/gpodnet_main_label">
<PreferenceScreen
android:key="pref_gpodnet_authenticate"
android:title="@string/pref_gpodnet_authenticate_title"
android:summary="@string/pref_gpodnet_authenticate_sum">
<intent android:action=".activity.gpoddernet.GpodnetAuthenticationActivity"/>
</PreferenceScreen>
<Preference
android:key="pref_gpodnet_setlogin_information"
android:title="@string/pref_gpodnet_setlogin_information_title"
android:summary="@string/pref_gpodnet_setlogin_information_sum"/>
<Preference
android:key="pref_gpodnet_logout"
android:title="@string/pref_gpodnet_logout_title"/>
</PreferenceScreen>
</PreferenceCategory>
<PreferenceCategory android:title="@string/other_pref">
<Preference
android:title="@string/choose_data_directory"
android:key="prefChooseDataDir"/>
<Preference
android:key="prefFlattrThisApp"
android:summary="@string/pref_flattr_this_app_sum"
android:title="@string/pref_flattr_this_app_title" >
android:title="@string/pref_flattr_this_app_title">
</Preference>
<Preference android:key="prefOpmlExport" android:title="@string/opml_export_label"/><Preference
<Preference
android:key="prefOpmlExport"
android:title="@string/opml_export_label"/>
<Preference
android:key="prefAbout"
android:title="@string/about_pref" />
android:title="@string/about_pref"/>
</PreferenceCategory>
</PreferenceScreen>

View File

@ -3,4 +3,6 @@ package de.danoeh.antennapod;
public final class AppConfig {
/** Should be used for debug logging. */
public final static boolean DEBUG = true;
/** Should be used when setting User-Agent header for HTTP-requests. */
public final static String USER_AGENT = "AntennaPod/0.9.8.0";
}

View File

@ -5,6 +5,7 @@ import java.util.Date;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import de.danoeh.antennapod.activity.gpoddernet.GpodnetMainActivity;
import org.apache.commons.lang3.StringUtils;
import android.app.AlertDialog;
@ -37,6 +38,7 @@ public class AddFeedActivity extends ActionBarActivity {
private EditText etxtFeedurl;
private Button butBrowseMiroGuide;
private Button butBrowserGpoddernet;
private Button butOpmlImport;
private Button butConfirm;
private Button butCancel;
@ -63,6 +65,7 @@ public class AddFeedActivity extends ActionBarActivity {
}
butBrowseMiroGuide = (Button) findViewById(R.id.butBrowseMiroguide);
butBrowserGpoddernet = (Button) findViewById(R.id.butBrowseGpoddernet);
butOpmlImport = (Button) findViewById(R.id.butOpmlImport);
butConfirm = (Button) findViewById(R.id.butConfirm);
butCancel = (Button) findViewById(R.id.butCancel);
@ -75,6 +78,13 @@ public class AddFeedActivity extends ActionBarActivity {
MiroGuideMainActivity.class));
}
});
butBrowserGpoddernet.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(AddFeedActivity.this,
GpodnetMainActivity.class));
}
});
butOpmlImport.setOnClickListener(new OnClickListener() {

View File

@ -12,7 +12,9 @@ import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.view.View.OnLongClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView.ScaleType;
import android.widget.ListView;
@ -22,11 +24,14 @@ import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.ChapterListAdapter;
import de.danoeh.antennapod.asynctask.ImageLoader;
import de.danoeh.antennapod.dialog.VariableSpeedDialog;
import de.danoeh.antennapod.feed.Chapter;
import de.danoeh.antennapod.feed.MediaType;
import de.danoeh.antennapod.feed.SimpleChapter;
import de.danoeh.antennapod.fragment.CoverFragment;
import de.danoeh.antennapod.fragment.ItemDescriptionFragment;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.util.playback.ExternalMedia;
import de.danoeh.antennapod.util.playback.Playable;
@ -56,6 +61,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
private TextView txtvTitle;
private TextView txtvFeed;
private Button butPlaybackSpeed;
private ImageButton butNavLeft;
private ImageButton butNavRight;
@ -218,7 +224,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
if (savedPosition != -1) {
switchToFragment(savedPosition);
}
}
@Override
@ -363,6 +369,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
txtvFeed = (TextView) findViewById(R.id.txtvFeed);
butNavLeft = (ImageButton) findViewById(R.id.butNavLeft);
butNavRight = (ImageButton) findViewById(R.id.butNavRight);
butPlaybackSpeed = (Button) findViewById(R.id.butPlaybackSpeed);
butNavLeft.setOnClickListener(new OnClickListener() {
@ -390,6 +397,65 @@ public class AudioplayerActivity extends MediaplayerActivity {
}
}
});
butPlaybackSpeed.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (controller != null && controller.canSetPlaybackSpeed()) {
String[] availableSpeeds = UserPreferences
.getPlaybackSpeedArray();
String currentSpeed = UserPreferences.getPlaybackSpeed();
// Provide initial value in case the speed list has changed
// out from under us
// and our current speed isn't in the new list
String newSpeed;
if (availableSpeeds.length > 0) {
newSpeed = availableSpeeds[0];
} else {
newSpeed = "1.0";
}
for (int i = 0; i < availableSpeeds.length; i++) {
if (availableSpeeds[i].equals(currentSpeed)) {
if (i == availableSpeeds.length - 1) {
newSpeed = availableSpeeds[0];
} else {
newSpeed = availableSpeeds[i + 1];
}
break;
}
}
UserPreferences.setPlaybackSpeed(newSpeed);
controller.setPlaybackSpeed(Float.parseFloat(newSpeed));
}
}
});
butPlaybackSpeed.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
VariableSpeedDialog.showDialog(AudioplayerActivity.this);
return true;
}
});
}
@Override
protected void onPlaybackSpeedChange() {
super.onPlaybackSpeedChange();
updateButPlaybackSpeed();
}
private void updateButPlaybackSpeed() {
if (controller == null
|| (controller.getCurrentPlaybackSpeedMultiplier() == -1)) {
butPlaybackSpeed.setVisibility(View.GONE);
} else {
butPlaybackSpeed.setVisibility(View.VISIBLE);
butPlaybackSpeed.setText(UserPreferences.getPlaybackSpeed());
}
}
@Override
@ -421,7 +487,7 @@ public class AudioplayerActivity extends MediaplayerActivity {
((AudioplayerContentFragment) currentlyShownFragment)
.onDataSetChanged(media);
}
updateButPlaybackSpeed();
}
public void notifyMediaPositionChanged() {

View File

@ -0,0 +1,164 @@
package de.danoeh.antennapod.activity;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.FeedItemlistDescriptionAdapter;
import de.danoeh.antennapod.asynctask.ImageDiskCache;
import de.danoeh.antennapod.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.storage.DownloadRequester;
import java.util.Date;
import java.util.List;
/**
* Created by daniel on 24.08.13.
*/
public class DefaultOnlineFeedViewActivity extends OnlineFeedViewActivity {
private static final int EVENTS = EventDistributor.DOWNLOAD_HANDLED | EventDistributor.DOWNLOAD_QUEUED | EventDistributor.FEED_LIST_UPDATE;
private volatile List<Feed> feeds;
private Feed feed;
private Button subscribeButton;
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void loadData() {
super.loadData();
feeds = DBReader.getFeedList(this);
}
@Override
protected void showFeedInformation(final Feed feed) {
super.showFeedInformation(feed);
setContentView(R.layout.listview_activity);
this.feed = feed;
EventDistributor.getInstance().register(listener);
ListView listView = (ListView) findViewById(R.id.listview);
LayoutInflater inflater = (LayoutInflater)
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View header = inflater.inflate(R.layout.onlinefeedview_header, null);
listView.addHeaderView(header);
listView.setAdapter(new FeedItemlistDescriptionAdapter(this, 0, feed.getItems()));
ImageView cover = (ImageView) header.findViewById(R.id.imgvCover);
TextView title = (TextView) header.findViewById(R.id.txtvTitle);
TextView author = (TextView) header.findViewById(R.id.txtvAuthor);
TextView description = (TextView) header.findViewById(R.id.txtvDescription);
subscribeButton = (Button) header.findViewById(R.id.butSubscribe);
if (feed.getImage() != null) {
ImageDiskCache.getDefaultInstance().loadThumbnailBitmap(feed.getImage().getDownload_url(), cover, (int) getResources().getDimension(
R.dimen.thumbnail_length));
}
title.setText(feed.getTitle());
author.setText(feed.getAuthor());
description.setText(feed.getDescription());
subscribeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
DownloadRequester.getInstance().downloadFeed(
DefaultOnlineFeedViewActivity.this,
new Feed(feed.getDownload_url(), new Date(), feed
.getTitle()));
} catch (DownloadRequestException e) {
e.printStackTrace();
DownloadRequestErrorDialogCreator.newRequestErrorDialog(DefaultOnlineFeedViewActivity.this,
e.getMessage());
}
setSubscribeButtonState(feed);
}
});
setSubscribeButtonState(feed);
}
private boolean feedInFeedlist(Feed feed) {
if (feeds == null || feed == null)
return false;
for (Feed f : feeds) {
if (f.getIdentifyingValue().equals(feed.getIdentifyingValue())) {
return true;
}
}
return false;
}
private void setSubscribeButtonState(Feed feed) {
if (subscribeButton != null && feed != null) {
if (DownloadRequester.getInstance().isDownloadingFile(feed.getDownload_url())) {
subscribeButton.setEnabled(false);
subscribeButton.setText(R.string.downloading_label);
} else if (feedInFeedlist(feed)) {
subscribeButton.setEnabled(false);
subscribeButton.setText(R.string.subscribed_label);
} else {
subscribeButton.setEnabled(true);
subscribeButton.setText(R.string.subscribe_label);
}
}
}
EventDistributor.EventListener listener = new EventDistributor.EventListener() {
@Override
public void update(EventDistributor eventDistributor, Integer arg) {
if ((arg & EventDistributor.FEED_LIST_UPDATE) != 0) {
new AsyncTask<Void, Void, List<Feed>>() {
@Override
protected List<Feed> doInBackground(Void... params) {
return DBReader.getFeedList(DefaultOnlineFeedViewActivity.this);
}
@Override
protected void onPostExecute(List<Feed> feeds) {
super.onPostExecute(feeds);
DefaultOnlineFeedViewActivity.this.feeds = feeds;
setSubscribeButtonState(feed);
}
}.execute();
} else if ((arg & EVENTS) != 0) {
setSubscribeButtonState(feed);
}
}
};
@Override
protected void onStop() {
super.onStop();
EventDistributor.getInstance().unregister(listener);
}
}

View File

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

View File

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

View File

@ -124,6 +124,8 @@ public class FeedItemlistActivity extends ActionBarActivity {
SearchManager searchManager =
(SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.search_item));
searchView.setIconifiedByDefault(true);
searchView.setSearchableInfo(

View File

@ -183,7 +183,12 @@ public class MainActivity extends ActionBarActivity {
SearchManager searchManager =
(SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.search_item));
MenuItem searchItem = menu.findItem(R.id.search_item);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
if (searchView == null) {
MenuItemCompat.setActionView(searchItem, new SearchView(this));
searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
}
searchView.setIconifiedByDefault(true);
SearchableInfo info = searchManager.getSearchableInfo(getComponentName());

View File

@ -129,10 +129,19 @@ public abstract class MediaplayerActivity extends ActionBarActivity
public void onPlaybackEnd() {
finish();
}
@Override
public void onPlaybackSpeedChange() {
MediaplayerActivity.this.onPlaybackSpeedChange();
}
};
}
protected void onPlaybackSpeedChange() {
}
protected void onServiceQueried() {
supportInvalidateOptionsMenu();
}

View File

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

View File

@ -1,23 +1,15 @@
package de.danoeh.antennapod.activity;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import javax.xml.parsers.ParserConfigurationException;
import android.support.v7.app.ActionBarActivity;
import org.xml.sax.SAXException;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.Gravity;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.feed.Feed;
@ -25,7 +17,6 @@ import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.download.DownloadRequest;
import de.danoeh.antennapod.service.download.DownloadStatus;
import de.danoeh.antennapod.service.download.Downloader;
import de.danoeh.antennapod.service.download.DownloaderCallback;
import de.danoeh.antennapod.service.download.HttpDownloader;
import de.danoeh.antennapod.syndication.handler.FeedHandler;
import de.danoeh.antennapod.syndication.handler.UnsupportedFeedtypeException;
@ -33,207 +24,238 @@ import de.danoeh.antennapod.util.DownloadError;
import de.danoeh.antennapod.util.FileNameGenerator;
import de.danoeh.antennapod.util.StorageUtils;
import de.danoeh.antennapod.util.URLChecker;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
import java.util.Date;
/**
* Downloads a feed from a feed URL and parses it. Subclasses can display the
* feed object that was parsed. This activity MUST be started with a given URL
* or an Exception will be thrown.
*
* <p/>
* If the feed cannot be downloaded or parsed, an error dialog will be displayed
* and the activity will finish as soon as the error dialog is closed.
*/
public abstract class OnlineFeedViewActivity extends ActionBarActivity {
private static final String TAG = "OnlineFeedViewActivity";
private static final String ARG_FEEDURL = "arg.feedurl";
private static final String TAG = "OnlineFeedViewActivity";
public static final String ARG_FEEDURL = "arg.feedurl";
public static final int RESULT_ERROR = 2;
/** Optional argument: specify a title for the actionbar. */
public static final String ARG_TITLE = "title";
private Feed feed;
private Downloader downloader;
public static final int RESULT_ERROR = 2;
@Override
protected void onCreate(Bundle arg0) {
setTheme(UserPreferences.getTheme());
super.onCreate(arg0);
StorageUtils.checkStorageAvailability(this);
final String feedUrl = getIntent().getStringExtra(ARG_FEEDURL);
if (feedUrl == null) {
throw new IllegalArgumentException(
"Activity must be started with feedurl argument!");
}
if (AppConfig.DEBUG)
Log.d(TAG, "Activity was started with url " + feedUrl);
setLoadingLayout();
startFeedDownload(feedUrl);
}
private Feed feed;
private Downloader downloader;
@Override
protected void onStop() {
super.onStop();
if (downloader != null && !downloader.isFinished()) {
downloader.cancel();
}
}
@Override
protected void onCreate(Bundle arg0) {
setTheme(UserPreferences.getTheme());
super.onCreate(arg0);
private DownloaderCallback downloaderCallback = new DownloaderCallback() {
@Override
public void onDownloadCompleted(final Downloader downloader) {
runOnUiThread(new Runnable() {
if (getIntent() != null && getIntent().hasExtra(ARG_TITLE)) {
getSupportActionBar().setTitle(getIntent().getStringExtra(ARG_TITLE));
}
@Override
public void run() {
DownloadStatus status = downloader.getResult();
if (status != null) {
if (!status.isCancelled()) {
if (status.isSuccessful()) {
parseFeed();
} else {
String errorMsg = status.getReason().getErrorString(
OnlineFeedViewActivity.this);
if (errorMsg != null
&& status.getReasonDetailed() != null) {
errorMsg += " ("
+ status.getReasonDetailed() + ")";
}
showErrorDialog(errorMsg);
}
}
} else {
Log.wtf(TAG,
"DownloadStatus returned by Downloader was null");
finish();
}
}
});
StorageUtils.checkStorageAvailability(this);
final String feedUrl = getIntent().getStringExtra(ARG_FEEDURL);
if (feedUrl == null) {
throw new IllegalArgumentException(
"Activity must be started with feedurl argument!");
}
if (AppConfig.DEBUG)
Log.d(TAG, "Activity was started with url " + feedUrl);
setLoadingLayout();
startFeedDownload(feedUrl);
}
}
};
@Override
protected void onStop() {
super.onStop();
if (downloader != null && !downloader.isFinished()) {
downloader.cancel();
}
}
private void startFeedDownload(String url) {
if (AppConfig.DEBUG)
Log.d(TAG, "Starting feed download");
url = URLChecker.prepareURL(url);
feed = new Feed(url, new Date());
String fileUrl = new File(getExternalCacheDir(),
FileNameGenerator.generateFileName(feed.getDownload_url()))
.toString();
feed.setFile_url(fileUrl);
DownloadRequest request = new DownloadRequest(feed.getFile_url(),
feed.getDownload_url(), "OnlineFeed", 0, Feed.FEEDFILETYPE_FEED);
/* TODO update
HttpDownloader httpDownloader = new HttpDownloader(downloaderCallback,
request);
httpDownloader.start();
*/
}
/** Displays a progress indicator. */
private void setLoadingLayout() {
LinearLayout ll = new LinearLayout(this);
LinearLayout.LayoutParams llLayoutParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT);
private void onDownloadCompleted(final Downloader downloader) {
runOnUiThread(new Runnable() {
ProgressBar pb = new ProgressBar(this);
pb.setIndeterminate(true);
LinearLayout.LayoutParams pbLayoutParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
pbLayoutParams.gravity = Gravity.CENTER;
ll.addView(pb, pbLayoutParams);
addContentView(ll, llLayoutParams);
}
@Override
public void run() {
if (AppConfig.DEBUG) Log.d(TAG, "Download was completed");
DownloadStatus status = downloader.getResult();
if (status != null) {
if (!status.isCancelled()) {
if (status.isSuccessful()) {
parseFeed();
} else {
String errorMsg = status.getReason().getErrorString(
OnlineFeedViewActivity.this);
if (errorMsg != null
&& status.getReasonDetailed() != null) {
errorMsg += " ("
+ status.getReasonDetailed() + ")";
}
showErrorDialog(errorMsg);
}
}
} else {
Log.wtf(TAG,
"DownloadStatus returned by Downloader was null");
finish();
}
}
});
private void parseFeed() {
if (feed == null || feed.getFile_url() == null) {
throw new IllegalStateException(
"feed must be non-null and downloaded when parseFeed is called");
}
}
if (AppConfig.DEBUG)
Log.d(TAG, "Parsing feed");
private void startFeedDownload(String url) {
if (AppConfig.DEBUG)
Log.d(TAG, "Starting feed download");
url = URLChecker.prepareURL(url);
feed = new Feed(url, new Date());
String fileUrl = new File(getExternalCacheDir(),
FileNameGenerator.generateFileName(feed.getDownload_url()))
.toString();
feed.setFile_url(fileUrl);
final DownloadRequest request = new DownloadRequest(feed.getFile_url(),
feed.getDownload_url(), "OnlineFeed", 0, Feed.FEEDFILETYPE_FEED);
downloader = new HttpDownloader(
request);
new Thread() {
@Override
public void run() {
loadData();
downloader.call();
onDownloadCompleted(downloader);
}
}.start();
Thread thread = new Thread() {
@Override
public void run() {
String reasonDetailed = new String();
boolean successful = false;
FeedHandler handler = new FeedHandler();
try {
handler.parseFeed(feed);
successful = true;
} catch (SAXException e) {
e.printStackTrace();
reasonDetailed = e.getMessage();
} catch (IOException e) {
e.printStackTrace();
reasonDetailed = e.getMessage();
} catch (ParserConfigurationException e) {
e.printStackTrace();
reasonDetailed = e.getMessage();
} catch (UnsupportedFeedtypeException e) {
e.printStackTrace();
reasonDetailed = e.getMessage();
} finally {
boolean rc = new File(feed.getFile_url()).delete();
if (AppConfig.DEBUG)
Log.d(TAG, "Deleted feed source file. Result: " + rc);
}
}
if (successful) {
runOnUiThread(new Runnable() {
@Override
public void run() {
showFeedInformation();
}
});
} else {
final String errorMsg =
DownloadError.ERROR_PARSER_EXCEPTION.getErrorString(
OnlineFeedViewActivity.this)
+ " (" + reasonDetailed + ")";
runOnUiThread(new Runnable() {
/**
* Displays a progress indicator.
*/
private void setLoadingLayout() {
RelativeLayout rl = new RelativeLayout(this);
RelativeLayout.LayoutParams rlLayoutParams = new RelativeLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT);
@Override
public void run() {
showErrorDialog(errorMsg);
}
});
}
}
};
thread.start();
}
ProgressBar pb = new ProgressBar(this);
pb.setIndeterminate(true);
RelativeLayout.LayoutParams pbLayoutParams = new RelativeLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
pbLayoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
rl.addView(pb, pbLayoutParams);
addContentView(rl, rlLayoutParams);
}
/** Called when feed parsed successfully */
protected void showFeedInformation() {
private void parseFeed() {
if (feed == null || feed.getFile_url() == null) {
throw new IllegalStateException(
"feed must be non-null and downloaded when parseFeed is called");
}
}
if (AppConfig.DEBUG)
Log.d(TAG, "Parsing feed");
private void showErrorDialog(String errorMsg) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.error_label);
if (errorMsg != null) {
builder.setMessage(getString(R.string.error_msg_prefix) + errorMsg);
} else {
builder.setMessage(R.string.error_msg_prefix);
}
builder.setNeutralButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
Thread thread = new Thread() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
builder.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
setResult(RESULT_ERROR);
finish();
}
});
}
@Override
public void run() {
String reasonDetailed = "";
boolean successful = false;
FeedHandler handler = new FeedHandler();
try {
handler.parseFeed(feed);
successful = true;
} catch (SAXException e) {
e.printStackTrace();
reasonDetailed = e.getMessage();
} catch (IOException e) {
e.printStackTrace();
reasonDetailed = e.getMessage();
} catch (ParserConfigurationException e) {
e.printStackTrace();
reasonDetailed = e.getMessage();
} catch (UnsupportedFeedtypeException e) {
e.printStackTrace();
reasonDetailed = e.getMessage();
} finally {
boolean rc = new File(feed.getFile_url()).delete();
if (AppConfig.DEBUG)
Log.d(TAG, "Deleted feed source file. Result: " + rc);
}
if (successful) {
runOnUiThread(new Runnable() {
@Override
public void run() {
showFeedInformation(feed);
}
});
} else {
final String errorMsg =
DownloadError.ERROR_PARSER_EXCEPTION.getErrorString(
OnlineFeedViewActivity.this)
+ " (" + reasonDetailed + ")";
runOnUiThread(new Runnable() {
@Override
public void run() {
showErrorDialog(errorMsg);
}
});
}
}
};
thread.start();
}
/**
* Can be used to load data asynchronously.
* */
protected void loadData() {
}
/**
* Called when feed parsed successfully
*/
protected void showFeedInformation(Feed feed) {
}
private void showErrorDialog(String errorMsg) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.error_label);
if (errorMsg != null) {
builder.setMessage(getString(R.string.error_msg_prefix) + errorMsg);
} else {
builder.setMessage(R.string.error_msg_prefix);
}
builder.setNeutralButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
builder.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
setResult(RESULT_ERROR);
finish();
}
});
}
}

View File

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

View File

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

View File

@ -1,10 +1,5 @@
package de.danoeh.antennapod.activity;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources.Theme;
@ -18,16 +13,24 @@ import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceScreen;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.FlattrClickWorker;
import de.danoeh.antennapod.asynctask.OpmlExportWorker;
import de.danoeh.antennapod.dialog.AuthenticationDialog;
import de.danoeh.antennapod.dialog.VariableSpeedDialog;
import de.danoeh.antennapod.preferences.GpodnetPreferences;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.util.flattr.FlattrUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* The main preference activity
*/
@ -41,6 +44,11 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
private static final String PREF_ABOUT = "prefAbout";
private static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir";
private static final String AUTO_DL_PREF_SCREEN = "prefAutoDownloadSettings";
private static final String PREF_PLAYBACK_SPEED_LAUNCHER = "prefPlaybackSpeedLauncher";
private static final String PREF_GPODNET_LOGIN = "pref_gpodnet_authenticate";
private static final String PREF_GPODNET_SETLOGIN_INFORMATION = "pref_gpodnet_setlogin_information";
private static final String PREF_GPODNET_LOGOUT = "pref_gpodnet_logout";
private CheckBoxPreference[] selectedNetworks;
@ -54,9 +62,9 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
getActionBar().setDisplayHomeAsUpEnabled(true);
}
addPreferencesFromResource(R.xml.preferences);
findPreference(PREF_FLATTR_THIS_APP).setOnPreferenceClickListener(
new OnPreferenceClickListener() {
addPreferencesFromResource(R.xml.preferences);
findPreference(PREF_FLATTR_THIS_APP).setOnPreferenceClickListener(
new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
@ -156,11 +164,53 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
return true;
}
});
findPreference(PREF_PLAYBACK_SPEED_LAUNCHER)
.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
VariableSpeedDialog.showDialog(PreferenceActivity.this);
return true;
}
});
findPreference(PREF_GPODNET_SETLOGIN_INFORMATION).setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
AuthenticationDialog dialog = new AuthenticationDialog(PreferenceActivity.this,
R.string.pref_gpodnet_setlogin_information_title, false, false, GpodnetPreferences.getUsername(),
null) {
@Override
protected void onConfirmed(String username, String password, boolean saveUsernamePassword) {
GpodnetPreferences.setPassword(password);
}
};
dialog.show();
return true;
}
});
findPreference(PREF_GPODNET_LOGOUT).setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
GpodnetPreferences.logout();
Toast toast = Toast.makeText(PreferenceActivity.this, R.string.pref_gpodnet_logout_toast, Toast.LENGTH_SHORT);
toast.show();
updateGpodnetPreferenceScreen();
return true;
}
});
buildUpdateIntervalPreference();
buildAutodownloadSelectedNetworsPreference();
setSelectedNetworksEnabled(UserPreferences
.isEnableAutodownloadWifiFilter());
}
private void updateGpodnetPreferenceScreen() {
final boolean loggedIn = GpodnetPreferences.loggedIn();
findPreference(PREF_GPODNET_LOGIN).setEnabled(!loggedIn);
findPreference(PREF_GPODNET_SETLOGIN_INFORMATION).setEnabled(loggedIn);
findPreference(PREF_GPODNET_LOGOUT).setEnabled(loggedIn);
}
private void buildUpdateIntervalPreference() {
@ -204,6 +254,7 @@ public class PreferenceActivity extends android.preference.PreferenceActivity {
checkItemVisibility();
setEpisodeCacheSizeText(UserPreferences.getEpisodeCacheSize());
setDataFolderText();
updateGpodnetPreferenceScreen();
}
@SuppressWarnings("deprecation")

View File

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

View File

@ -0,0 +1,44 @@
package de.danoeh.antennapod.activity.gpoddernet;
import android.app.SearchManager;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.SearchView;
import android.view.Menu;
import android.view.MenuItem;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.preferences.UserPreferences;
/**
* Created by daniel on 23.08.13.
*/
public class GpodnetActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuItemCompat.setShowAsAction(menu.add(Menu.NONE, R.id.search_item, Menu.NONE, R.string.search_label)
.setIcon(
obtainStyledAttributes(
new int[]{R.attr.action_search})
.getDrawable(0)),
MenuItem.SHOW_AS_ACTION_IF_ROOM);
MenuItemCompat.setActionView(menu.findItem(R.id.search_item), new SearchView(this));
SearchManager searchManager =
(SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.search_item));
searchView.setIconifiedByDefault(true);
searchView.setSearchableInfo(
searchManager.getSearchableInfo(getComponentName()));
return true;
}
}

View File

@ -0,0 +1,370 @@
package de.danoeh.antennapod.activity.gpoddernet;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.*;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.gpoddernet.GpodnetService;
import de.danoeh.antennapod.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.gpoddernet.model.GpodnetDevice;
import de.danoeh.antennapod.preferences.GpodnetPreferences;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.GpodnetSyncService;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/**
* Guides the user through the authentication process
* Step 1: Request username and password from user
* Step 2: Choose device from a list of available devices or create a new one
* Step 3: Choose from a list of actions
*/
public class GpodnetAuthenticationActivity extends ActionBarActivity {
private static final String TAG = "GpodnetAuthenticationActivity";
private static final String CURRENT_STEP = "current_step";
private ViewFlipper viewFlipper;
private static final int STEP_DEFAULT = -1;
private static final int STEP_LOGIN = 0;
private static final int STEP_DEVICE = 1;
private static final int STEP_FINISH = 2;
private int currentStep = -1;
private GpodnetService service;
private volatile String username;
private volatile String password;
private volatile GpodnetDevice selectedDevice;
View[] views;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setTheme(UserPreferences.getTheme());
setContentView(R.layout.gpodnetauth_activity);
service = new GpodnetService();
viewFlipper = (ViewFlipper) findViewById(R.id.viewflipper);
LayoutInflater inflater = (LayoutInflater)
getSystemService(Context.LAYOUT_INFLATER_SERVICE);
views = new View[]{
inflater.inflate(R.layout.gpodnetauth_credentials, null),
inflater.inflate(R.layout.gpodnetauth_device, null),
inflater.inflate(R.layout.gpodnetauth_finish, null)
};
for (View view : views) {
viewFlipper.addView(view);
}
advance();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (service != null) {
service.shutdown();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
}
private void setupLoginView(View view) {
final EditText username = (EditText) view.findViewById(R.id.etxtUsername);
final EditText password = (EditText) view.findViewById(R.id.etxtPassword);
final Button login = (Button) view.findViewById(R.id.butLogin);
final TextView txtvError = (TextView) view.findViewById(R.id.txtvError);
final ProgressBar progressBar = (ProgressBar) view.findViewById(R.id.progBarLogin);
login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final String usernameStr = username.getText().toString();
final String passwordStr = password.getText().toString();
if (AppConfig.DEBUG) Log.d(TAG, "Checking login credentials");
new AsyncTask<GpodnetService, Void, Void>() {
volatile Exception exception;
@Override
protected void onPreExecute() {
super.onPreExecute();
login.setEnabled(false);
progressBar.setVisibility(View.VISIBLE);
txtvError.setVisibility(View.GONE);
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
login.setEnabled(true);
progressBar.setVisibility(View.GONE);
if (exception == null) {
advance();
} else {
txtvError.setText(exception.getMessage());
txtvError.setVisibility(View.VISIBLE);
}
}
@Override
protected Void doInBackground(GpodnetService... params) {
try {
params[0].authenticate(usernameStr, passwordStr);
GpodnetAuthenticationActivity.this.username = usernameStr;
GpodnetAuthenticationActivity.this.password = passwordStr;
} catch (GpodnetServiceException e) {
e.printStackTrace();
exception = e;
}
return null;
}
}.execute(service);
}
});
}
private void setupDeviceView(View view) {
final EditText deviceID = (EditText) view.findViewById(R.id.etxtDeviceID);
final EditText caption = (EditText) view.findViewById(R.id.etxtCaption);
final Button createNewDevice = (Button) view.findViewById(R.id.butCreateNewDevice);
final Button chooseDevice = (Button) view.findViewById(R.id.butChooseExistingDevice);
final TextView txtvError = (TextView) view.findViewById(R.id.txtvError);
final ProgressBar progBarCreateDevice = (ProgressBar) view.findViewById(R.id.progbarCreateDevice);
final Spinner spinnerDevices = (Spinner) view.findViewById(R.id.spinnerChooseDevice);
// load device list
final AtomicReference<List<GpodnetDevice>> devices = new AtomicReference<List<GpodnetDevice>>();
new AsyncTask<GpodnetService, Void, List<GpodnetDevice>>() {
private volatile Exception exception;
@Override
protected void onPreExecute() {
super.onPreExecute();
chooseDevice.setEnabled(false);
spinnerDevices.setEnabled(false);
createNewDevice.setEnabled(false);
}
@Override
protected void onPostExecute(List<GpodnetDevice> gpodnetDevices) {
super.onPostExecute(gpodnetDevices);
if (gpodnetDevices != null) {
List<String> deviceNames = new ArrayList<String>();
for (GpodnetDevice device : gpodnetDevices) {
deviceNames.add(device.getCaption());
}
spinnerDevices.setAdapter(new ArrayAdapter<String>(GpodnetAuthenticationActivity.this,
android.R.layout.simple_spinner_dropdown_item, deviceNames));
spinnerDevices.setEnabled(true);
if (!deviceNames.isEmpty()) {
chooseDevice.setEnabled(true);
}
devices.set(gpodnetDevices);
createNewDevice.setEnabled(true);
}
}
@Override
protected List<GpodnetDevice> doInBackground(GpodnetService... params) {
try {
return params[0].getDevices(username);
} catch (GpodnetServiceException e) {
e.printStackTrace();
exception = e;
return null;
}
}
}.execute(service);
createNewDevice.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (checkDeviceIDText(deviceID, txtvError, devices.get())) {
final String deviceStr = deviceID.getText().toString();
final String captionStr = caption.getText().toString();
new AsyncTask<GpodnetService, Void, GpodnetDevice>() {
private volatile Exception exception;
@Override
protected void onPreExecute() {
super.onPreExecute();
createNewDevice.setEnabled(false);
chooseDevice.setEnabled(false);
progBarCreateDevice.setVisibility(View.VISIBLE);
txtvError.setVisibility(View.GONE);
}
@Override
protected void onPostExecute(GpodnetDevice result) {
super.onPostExecute(result);
createNewDevice.setEnabled(true);
chooseDevice.setEnabled(true);
progBarCreateDevice.setVisibility(View.GONE);
if (exception == null) {
selectedDevice = result;
advance();
} else {
txtvError.setText(exception.getMessage());
txtvError.setVisibility(View.VISIBLE);
}
}
@Override
protected GpodnetDevice doInBackground(GpodnetService... params) {
try {
params[0].configureDevice(username, deviceStr, captionStr, GpodnetDevice.DeviceType.MOBILE);
return new GpodnetDevice(deviceStr, captionStr, GpodnetDevice.DeviceType.MOBILE.toString(), 0);
} catch (GpodnetServiceException e) {
e.printStackTrace();
exception = e;
}
return null;
}
}.execute(service);
}
}
});
deviceID.setText(generateDeviceID());
chooseDevice.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final int position = spinnerDevices.getSelectedItemPosition();
selectedDevice = devices.get().get(position);
advance();
}
});
}
private String generateDeviceID() {
final int DEVICE_ID_LENGTH = 10;
StringBuilder buffer = new StringBuilder(DEVICE_ID_LENGTH);
SecureRandom random = new SecureRandom();
for (int i = 0; i < DEVICE_ID_LENGTH; i++) {
buffer.append(random.nextInt(10));
}
return buffer.toString();
}
private boolean checkDeviceIDText(EditText deviceID, TextView txtvError, List<GpodnetDevice> devices) {
String text = deviceID.getText().toString();
if (text.length() == 0) {
txtvError.setText(R.string.gpodnetauth_device_errorEmpty);
txtvError.setVisibility(View.VISIBLE);
return false;
} else {
if (devices != null) {
for (GpodnetDevice device : devices) {
if (device.getId().equals(text)) {
txtvError.setText(R.string.gpodnetauth_device_errorAlreadyUsed);
txtvError.setVisibility(View.VISIBLE);
return false;
}
}
txtvError.setVisibility(View.GONE);
return true;
}
return true;
}
}
private void setupFinishView(View view) {
final Button sync = (Button) view.findViewById(R.id.butSyncNow);
final Button back = (Button) view.findViewById(R.id.butGoMainscreen);
sync.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
GpodnetSyncService.sendSyncIntent(GpodnetAuthenticationActivity.this);
finish();
}
});
back.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(GpodnetAuthenticationActivity.this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
}
});
}
private void writeLoginCredentials() {
if (AppConfig.DEBUG) Log.d(TAG, "Writing login credentials");
GpodnetPreferences.setUsername(username);
GpodnetPreferences.setPassword(password);
GpodnetPreferences.setDeviceID(selectedDevice.getId());
}
private void advance() {
if (currentStep < STEP_FINISH) {
View view = views[currentStep + 1];
if (currentStep == STEP_DEFAULT) {
setupLoginView(view);
} else if (currentStep == STEP_LOGIN) {
if (username == null || password == null) {
throw new IllegalStateException("Username and password must not be null here");
} else {
setupDeviceView(view);
}
} else if (currentStep == STEP_DEVICE) {
if (selectedDevice == null) {
throw new IllegalStateException("Device must not be null here");
} else {
writeLoginCredentials();
setupFinishView(view);
}
}
if (currentStep != STEP_DEFAULT) {
viewFlipper.showNext();
}
currentStep++;
} else {
finish();
}
}
}

View File

@ -0,0 +1,89 @@
package de.danoeh.antennapod.activity.gpoddernet;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.app.NavUtils;
import android.support.v4.view.ViewPager;
import android.view.MenuItem;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.fragment.gpodnet.PodcastTopListFragment;
import de.danoeh.antennapod.fragment.gpodnet.SuggestionListFragment;
import de.danoeh.antennapod.fragment.gpodnet.TagListFragment;
import de.danoeh.antennapod.preferences.GpodnetPreferences;
/**
* Created by daniel on 22.08.13.
*/
public class GpodnetMainActivity extends GpodnetActivity {
private static final String TAG = "GPodnetMainActivity";
private static final int POS_TAGS = 0;
private static final int POS_TOPLIST = 1;
private static final int POS_SUGGESTIONS = 2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.gpodnet_main);
ViewPager viewpager = (ViewPager) findViewById(R.id.viewpager);
viewpager.setAdapter(new PagerAdapter(getSupportFragmentManager()));
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
private class PagerAdapter extends FragmentStatePagerAdapter {
private static final int NUM_PAGES_LOGGED_OUT = 2;
private static final int NUM_PAGES_LOGGED_IN = 3;
private final int NUM_PAGES;
public PagerAdapter(FragmentManager fm) {
super(fm);
NUM_PAGES = NUM_PAGES_LOGGED_OUT;
}
@Override
public Fragment getItem(int i) {
switch (i) {
case POS_TAGS:
return new TagListFragment();
case POS_TOPLIST:
return new PodcastTopListFragment();
case POS_SUGGESTIONS:
return new SuggestionListFragment();
default:
return null;
}
}
@Override
public CharSequence getPageTitle(int position) {
switch (position) {
case POS_TAGS:
return getString(R.string.gpodnet_taglist_header);
case POS_TOPLIST:
return getString(R.string.gpodnet_toplist_header);
case POS_SUGGESTIONS:
return getString(R.string.gpodnet_suggestions_header);
default:
return super.getPageTitle(position);
}
}
@Override
public int getCount() {
return NUM_PAGES;
}
}
}

View File

@ -0,0 +1,63 @@
package de.danoeh.antennapod.activity.gpoddernet;
import android.app.SearchManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.NavUtils;
import android.view.MenuItem;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.fragment.gpodnet.SearchListFragment;
import org.apache.commons.lang3.StringUtils;
/**
* Created by daniel on 23.08.13.
*/
public class GpodnetSearchActivity extends GpodnetActivity {
private SearchListFragment searchFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.gpodnet_search);
}
@Override
protected void onResume() {
super.onResume();
Intent intent = getIntent();
if (StringUtils.equals(intent.getAction(), Intent.ACTION_SEARCH)) {
handleSearchRequest(intent.getStringExtra(SearchManager.QUERY));
}
}
@Override
protected void onNewIntent(Intent intent) {
setIntent(intent);
}
private void handleSearchRequest(String query) {
getSupportActionBar().setSubtitle(getString(R.string.search_term_label) + query);
if (searchFragment == null) {
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
searchFragment = SearchListFragment.newInstance(query);
transaction.replace(R.id.searchListFragment, searchFragment);
transaction.commit();
} else {
searchFragment.changeQuery(query);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@ -0,0 +1,64 @@
package de.danoeh.antennapod.activity.gpoddernet;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.NavUtils;
import android.view.MenuItem;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.fragment.gpodnet.PodcastListFragment;
import de.danoeh.antennapod.fragment.gpodnet.SearchListFragment;
import de.danoeh.antennapod.gpoddernet.GpodnetService;
import de.danoeh.antennapod.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.gpoddernet.model.GpodnetPodcast;
import de.danoeh.antennapod.gpoddernet.model.GpodnetTag;
import java.util.List;
/**
* Created by daniel on 23.08.13.
*/
public class GpodnetTagActivity extends GpodnetActivity{
private static final int PODCAST_COUNT = 50;
public static final String ARG_TAGNAME = "tagname";
private GpodnetTag tag;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.gpodnet_tag_activity);
if (!getIntent().hasExtra(ARG_TAGNAME)) {
throw new IllegalArgumentException("No tagname argument");
}
tag = new GpodnetTag(getIntent().getStringExtra(ARG_TAGNAME));
getSupportActionBar().setTitle(tag.getName());
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
Fragment taglistFragment = new TaglistFragment();
transaction.replace(R.id.taglistFragment, taglistFragment);
transaction.commit();
}
private class TaglistFragment extends PodcastListFragment {
@Override
protected List<GpodnetPodcast> loadPodcastData(GpodnetService service) throws GpodnetServiceException {
return service.getPodcastsForTag(tag, PODCAST_COUNT);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@ -144,53 +144,6 @@ public class ChapterListAdapter extends ArrayAdapter<Chapter> {
TextView link;
}
private LinkMovementMethod linkMovementMethod = new LinkMovementMethod() {
@Override
public boolean onTouchEvent(TextView widget, Spannable buffer,
MotionEvent event) {
Object text = widget.getText();
if (text instanceof Spanned) {
int action = event.getAction();
if (action == MotionEvent.ACTION_UP
|| action == MotionEvent.ACTION_DOWN) {
int x = (int) event.getX();
int y = (int) event.getY();
x -= widget.getTotalPaddingLeft();
y -= widget.getTotalPaddingTop();
x += widget.getScrollX();
y += widget.getScrollY();
Layout layout = widget.getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
ClickableSpan[] link = buffer.getSpans(off, off,
ClickableSpan.class);
if (link.length != 0) {
if (action == MotionEvent.ACTION_UP) {
link[0].onClick(widget);
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
}
return true;
}
}
}
return false;
}
};
@Override
public int getCount() {
// ignore invalid chapters

View File

@ -0,0 +1,55 @@
package de.danoeh.antennapod.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.feed.FeedItem;
import java.util.List;
/**
* Created by daniel on 24.08.13.
*/
public class FeedItemlistDescriptionAdapter extends ArrayAdapter<FeedItem> {
public FeedItemlistDescriptionAdapter(Context context, int resource, List<FeedItem> objects) {
super(context, resource, objects);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Holder holder;
FeedItem item = getItem(position);
// Inflate layout
if (convertView == null) {
holder = new Holder();
LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.itemdescription_listitem, null);
holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
holder.description = (TextView) convertView.findViewById(R.id.txtvDescription);
convertView.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
holder.title.setText(item.getTitle());
if (item.getDescription() != null) {
holder.description.setText(item.getDescription());
}
return convertView;
}
static class Holder {
TextView title;
TextView description;
}
}

View File

@ -0,0 +1,63 @@
package de.danoeh.antennapod.adapter.gpodnet;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.ImageDiskCache;
import de.danoeh.antennapod.gpoddernet.model.GpodnetPodcast;
import java.util.List;
/**
* Adapter for displaying a list of GPodnetPodcast-Objects.
*/
public class PodcastListAdapter extends ArrayAdapter<GpodnetPodcast> {
private final ImageDiskCache diskCache;
private final int thumbnailLength;
public PodcastListAdapter(Context context, int resource, List<GpodnetPodcast> objects) {
super(context, resource, objects);
diskCache = ImageDiskCache.getDefaultInstance();
thumbnailLength = (int) context.getResources().getDimension(R.dimen.thumbnail_length);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Holder holder;
GpodnetPodcast podcast = getItem(position);
// Inflate Layout
if (convertView == null) {
holder = new Holder();
LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.gpodnet_podcast_listitem, null);
holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
holder.description = (TextView) convertView.findViewById(R.id.txtvDescription);
holder.image = (ImageView) convertView.findViewById(R.id.imgvCover);
convertView.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
holder.title.setText(podcast.getTitle());
holder.description.setText(podcast.getDescription());
diskCache.loadThumbnailBitmap(podcast.getLogoUrl(), holder.image, thumbnailLength);
return convertView;
}
static class Holder {
TextView title;
TextView description;
ImageView image;
}
}

View File

@ -2,105 +2,115 @@ package de.danoeh.antennapod.asynctask;
import android.content.res.TypedArray;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.os.Handler;
import android.util.Log;
import android.widget.ImageView;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.asynctask.ImageLoader.ImageWorkerTaskResource;
import de.danoeh.antennapod.util.BitmapDecoder;
public class BitmapDecodeWorkerTask extends Thread {
protected int PREFERRED_LENGTH;
protected int PREFERRED_LENGTH;
public static final int FADE_DURATION = 500;
/** Can be thumbnail or cover */
protected int imageType;
/**
* Can be thumbnail or cover
*/
protected int imageType;
private static final String TAG = "BitmapDecodeWorkerTask";
private ImageView target;
protected CachedBitmap cBitmap;
private static final String TAG = "BitmapDecodeWorkerTask";
private ImageView target;
protected CachedBitmap cBitmap;
protected ImageLoader.ImageWorkerTaskResource imageResource;
protected ImageLoader.ImageWorkerTaskResource imageResource;
private Handler handler;
private Handler handler;
private final int defaultCoverResource;
private final int defaultCoverResource;
public BitmapDecodeWorkerTask(Handler handler, ImageView target,
ImageWorkerTaskResource imageResource, int length, int imageType) {
super();
this.handler = handler;
this.target = target;
this.imageResource = imageResource;
this.PREFERRED_LENGTH = length;
this.imageType = imageType;
TypedArray res = target.getContext().obtainStyledAttributes(
new int[] { R.attr.default_cover });
this.defaultCoverResource = res.getResourceId(0, 0);
res.recycle();
}
public BitmapDecodeWorkerTask(Handler handler, ImageView target,
ImageWorkerTaskResource imageResource, int length, int imageType) {
super();
this.handler = handler;
this.target = target;
this.imageResource = imageResource;
this.PREFERRED_LENGTH = length;
this.imageType = imageType;
this.defaultCoverResource = android.R.color.transparent;
}
/**
* Should return true if tag of the imageview is still the same it was
* before the bitmap was decoded
*/
protected boolean tagsMatching(ImageView target) {
return target.getTag() == null
|| target.getTag() == imageResource.getImageLoaderCacheKey();
}
/**
* Should return true if tag of the imageview is still the same it was
* before the bitmap was decoded
*/
protected boolean tagsMatching(ImageView target) {
return target.getTag(R.id.imageloader_key) == null
|| target.getTag(R.id.imageloader_key).equals(imageResource.getImageLoaderCacheKey());
}
protected void onPostExecute() {
// check if imageview is still supposed to display this image
if (tagsMatching(target) && cBitmap.getBitmap() != null) {
target.setImageBitmap(cBitmap.getBitmap());
} else {
if (AppConfig.DEBUG)
Log.d(TAG, "Not displaying image");
}
}
protected void onPostExecute() {
// check if imageview is still supposed to display this image
if (tagsMatching(target) && cBitmap.getBitmap() != null) {
Drawable[] drawables = new Drawable[]{
PodcastApp.getInstance().getResources().getDrawable(android.R.color.transparent),
new BitmapDrawable(PodcastApp.getInstance().getResources(), cBitmap.getBitmap())
};
TransitionDrawable transitionDrawable = new TransitionDrawable(drawables);
target.setImageDrawable(transitionDrawable);
transitionDrawable.startTransition(FADE_DURATION);
} else {
if (AppConfig.DEBUG)
Log.d(TAG, "Not displaying image");
}
}
@Override
public void run() {
cBitmap = new CachedBitmap(BitmapDecoder.decodeBitmapFromWorkerTaskResource(
PREFERRED_LENGTH, imageResource), PREFERRED_LENGTH);
if (cBitmap.getBitmap() != null) {
storeBitmapInCache(cBitmap);
} else {
Log.w(TAG, "Could not load bitmap. Using default image.");
cBitmap = new CachedBitmap(BitmapFactory.decodeResource(
target.getResources(), defaultCoverResource),
PREFERRED_LENGTH);
}
if (AppConfig.DEBUG)
Log.d(TAG, "Finished loading bitmaps");
@Override
public void run() {
cBitmap = new CachedBitmap(BitmapDecoder.decodeBitmapFromWorkerTaskResource(
PREFERRED_LENGTH, imageResource), PREFERRED_LENGTH);
if (cBitmap.getBitmap() != null) {
storeBitmapInCache(cBitmap);
} else {
Log.w(TAG, "Could not load bitmap. Using default image.");
cBitmap = new CachedBitmap(BitmapFactory.decodeResource(
target.getResources(), defaultCoverResource),
PREFERRED_LENGTH);
}
if (AppConfig.DEBUG)
Log.d(TAG, "Finished loading bitmaps");
endBackgroundTask();
}
endBackgroundTask();
}
protected final void endBackgroundTask() {
handler.post(new Runnable() {
protected final void endBackgroundTask() {
handler.post(new Runnable() {
@Override
public void run() {
onPostExecute();
}
@Override
public void run() {
onPostExecute();
}
});
}
});
}
protected void onInvalidStream() {
cBitmap = new CachedBitmap(BitmapFactory.decodeResource(
target.getResources(), defaultCoverResource), PREFERRED_LENGTH);
}
protected void onInvalidStream() {
cBitmap = new CachedBitmap(BitmapFactory.decodeResource(
target.getResources(), defaultCoverResource), PREFERRED_LENGTH);
}
protected void storeBitmapInCache(CachedBitmap cb) {
ImageLoader loader = ImageLoader.getInstance();
if (imageType == ImageLoader.IMAGE_TYPE_COVER) {
loader.addBitmapToCoverCache(imageResource.getImageLoaderCacheKey(), cb);
} else if (imageType == ImageLoader.IMAGE_TYPE_THUMBNAIL) {
loader.addBitmapToThumbnailCache(imageResource.getImageLoaderCacheKey(), cb);
}
}
protected void storeBitmapInCache(CachedBitmap cb) {
ImageLoader loader = ImageLoader.getInstance();
if (imageType == ImageLoader.IMAGE_TYPE_COVER) {
loader.addBitmapToCoverCache(imageResource.getImageLoaderCacheKey(), cb);
} else if (imageType == ImageLoader.IMAGE_TYPE_THUMBNAIL) {
loader.addBitmapToThumbnailCache(imageResource.getImageLoaderCacheKey(), cb);
}
}
}

View File

@ -0,0 +1,391 @@
package de.danoeh.antennapod.asynctask;
import android.os.Handler;
import android.util.Log;
import android.util.Pair;
import android.widget.ImageView;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.service.download.DownloadRequest;
import de.danoeh.antennapod.service.download.HttpDownloader;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Provides local cache for storing downloaded image. An image disk cache downloads images and stores them as long
* as the cache is not full. Once the cache is full, the image disk cache will delete older images.
*/
public class ImageDiskCache {
private static final String TAG = "ImageDiskCache";
private static HashMap<String, ImageDiskCache> cacheSingletons = new HashMap<String, ImageDiskCache>();
/**
* Return a default instance of an ImageDiskCache. This cache will store data in the external cache folder.
*/
public static synchronized ImageDiskCache getDefaultInstance() {
final String DEFAULT_PATH = "imagecache";
final long DEFAULT_MAX_CACHE_SIZE = 10 * 1024 * 1024;
File cacheDir = PodcastApp.getInstance().getExternalCacheDir();
if (cacheDir == null) {
return null;
}
return getInstance(new File(cacheDir, DEFAULT_PATH).getAbsolutePath(), DEFAULT_MAX_CACHE_SIZE);
}
/**
* Return an instance of an ImageDiskCache that stores images in the specified folder.
*/
public static synchronized ImageDiskCache getInstance(String path, long maxCacheSize) {
if (path == null) {
throw new NullPointerException();
}
if (cacheSingletons.containsKey(path)) {
return cacheSingletons.get(path);
}
ImageDiskCache cache = cacheSingletons.get(path);
if (cache == null) {
cache = new ImageDiskCache(path, maxCacheSize);
cacheSingletons.put(new File(path).getAbsolutePath(), cache);
}
cacheSingletons.put(path, cache);
return cache;
}
/**
* Filename - cache object mapping
*/
private static final String CACHE_FILE_NAME = "cachefile";
private ExecutorService executor;
private ConcurrentHashMap<String, DiskCacheObject> diskCache;
private final long maxCacheSize;
private int cacheSize;
private final File cacheFolder;
private Handler handler;
private ImageDiskCache(String path, long maxCacheSize) {
this.maxCacheSize = maxCacheSize;
this.cacheFolder = new File(path);
if (!cacheFolder.exists() && !cacheFolder.mkdir()) {
throw new IllegalArgumentException("Image disk cache could not create cache folder in: " + path);
}
executor = Executors.newFixedThreadPool(Runtime.getRuntime()
.availableProcessors());
handler = new Handler();
}
private synchronized void initCacheFolder() {
if (diskCache == null) {
if (AppConfig.DEBUG) Log.d(TAG, "Initializing cache folder");
File cacheFile = new File(cacheFolder, CACHE_FILE_NAME);
if (cacheFile.exists()) {
try {
InputStream in = new FileInputStream(cacheFile);
BufferedInputStream buffer = new BufferedInputStream(in);
ObjectInputStream objectInput = new ObjectInputStream(buffer);
diskCache = (ConcurrentHashMap<String, DiskCacheObject>) objectInput.readObject();
// calculate cache size
for (DiskCacheObject dco : diskCache.values()) {
cacheSize += dco.size;
}
deleteInvalidFiles();
} catch (IOException e) {
e.printStackTrace();
diskCache = new ConcurrentHashMap<String, DiskCacheObject>();
} catch (ClassCastException e) {
e.printStackTrace();
diskCache = new ConcurrentHashMap<String, DiskCacheObject>();
} catch (ClassNotFoundException e) {
e.printStackTrace();
diskCache = new ConcurrentHashMap<String, DiskCacheObject>();
}
} else {
diskCache = new ConcurrentHashMap<String, DiskCacheObject>();
}
}
}
private List<File> getCacheFileList() {
Collection<DiskCacheObject> values = diskCache.values();
List<File> files = new ArrayList<File>();
for (DiskCacheObject dco : values) {
files.add(dco.getFile());
}
files.add(new File(cacheFolder, CACHE_FILE_NAME));
return files;
}
private Pair<String, DiskCacheObject> getOldestCacheObject() {
Collection<String> keys = diskCache.keySet();
DiskCacheObject oldest = null;
String oldestKey = null;
for (String key : keys) {
if (oldestKey == null) {
oldestKey = key;
oldest = diskCache.get(key);
} else {
DiskCacheObject dco = diskCache.get(key);
if (oldest.timestamp > dco.timestamp) {
oldestKey = key;
oldest = diskCache.get(key);
}
}
}
return new Pair<String, DiskCacheObject>(oldestKey, oldest);
}
private synchronized void deleteCacheObject(String key, DiskCacheObject value) {
Log.i(TAG, "Deleting cached object: " + key);
diskCache.remove(key);
boolean result = value.getFile().delete();
if (!result) {
Log.w(TAG, "Could not delete file " + value.fileUrl);
}
cacheSize -= value.size;
}
private synchronized void deleteInvalidFiles() {
// delete files that are not stored inside the cache
File[] files = cacheFolder.listFiles();
List<File> cacheFiles = getCacheFileList();
for (File file : files) {
if (!cacheFiles.contains(file)) {
Log.i(TAG, "Deleting unused file: " + file.getAbsolutePath());
boolean result = file.delete();
if (!result) {
Log.w(TAG, "Could not delete file: " + file.getAbsolutePath());
}
}
}
}
private synchronized void cleanup() {
if (cacheSize > maxCacheSize) {
while (cacheSize > maxCacheSize) {
Pair<String, DiskCacheObject> oldest = getOldestCacheObject();
deleteCacheObject(oldest.first, oldest.second);
}
}
}
/**
* Loads a new image from the disk cache. If the image that the url points to has already been downloaded, the image will
* be loaded from the disk. Otherwise, the image will be downloaded first.
* The image will be stored in the thumbnail cache.
*/
public void loadThumbnailBitmap(final String url, final ImageView target, final int length) {
final ImageLoader il = ImageLoader.getInstance();
target.setTag(R.id.image_disk_cache_key, url);
if (diskCache != null) {
DiskCacheObject dco = getFromCacheIfAvailable(url);
if (dco != null) {
il.loadThumbnailBitmap(dco.loadImage(), target, length);
return;
}
}
target.setImageResource(android.R.color.transparent);
executor.submit(new ImageDownloader(url) {
@Override
protected void onImageLoaded(DiskCacheObject diskCacheObject) {
final Object tag = target.getTag(R.id.image_disk_cache_key);
if (tag != null || StringUtils.equals((String) tag, url)) {
il.loadThumbnailBitmap(diskCacheObject.loadImage(), target, length);
}
}
});
}
/**
* Loads a new image from the disk cache. If the image that the url points to has already been downloaded, the image will
* be loaded from the disk. Otherwise, the image will be downloaded first.
* The image will be stored in the cover cache.
*/
public void loadCoverBitmap(final String url, final ImageView target, final int length) {
final ImageLoader il = ImageLoader.getInstance();
target.setTag(R.id.image_disk_cache_key, url);
if (diskCache != null) {
DiskCacheObject dco = getFromCacheIfAvailable(url);
if (dco != null) {
il.loadThumbnailBitmap(dco.loadImage(), target, length);
return;
}
}
target.setImageResource(android.R.color.transparent);
executor.submit(new ImageDownloader(url) {
@Override
protected void onImageLoaded(DiskCacheObject diskCacheObject) {
final Object tag = target.getTag(R.id.image_disk_cache_key);
if (tag != null || StringUtils.equals((String) tag, url)) {
il.loadCoverBitmap(diskCacheObject.loadImage(), target, length);
}
}
});
}
private synchronized void addToDiskCache(String url, DiskCacheObject obj) {
if (diskCache == null) {
initCacheFolder();
}
if (AppConfig.DEBUG) Log.d(TAG, "Adding new image to disk cache: " + url);
diskCache.put(url, obj);
cacheSize += obj.size;
if (cacheSize > maxCacheSize) {
cleanup();
}
saveCacheInfoFile();
}
private synchronized void saveCacheInfoFile() {
OutputStream out = null;
try {
out = new BufferedOutputStream(new FileOutputStream(new File(cacheFolder, CACHE_FILE_NAME)));
ObjectOutputStream objOut = new ObjectOutputStream(out);
objOut.writeObject(diskCache);
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(out);
}
}
private synchronized DiskCacheObject getFromCacheIfAvailable(String key) {
if (diskCache == null) {
initCacheFolder();
}
DiskCacheObject dco = diskCache.get(key);
if (dco != null) {
dco.timestamp = System.currentTimeMillis();
}
return dco;
}
ConcurrentHashMap<String, File> runningDownloads = new ConcurrentHashMap<String, File>();
private abstract class ImageDownloader implements Runnable {
private String downloadUrl;
public ImageDownloader(String downloadUrl) {
this.downloadUrl = downloadUrl;
}
protected abstract void onImageLoaded(DiskCacheObject diskCacheObject);
public void run() {
DiskCacheObject tmp = getFromCacheIfAvailable(downloadUrl);
if (tmp != null) {
onImageLoaded(tmp);
return;
}
DiskCacheObject dco = null;
File newFile = new File(cacheFolder, Integer.toString(downloadUrl.hashCode()));
synchronized (ImageDiskCache.this) {
if (runningDownloads.containsKey(newFile.getAbsolutePath())) {
Log.d(TAG, "Download is already running: " + newFile.getAbsolutePath());
return;
} else {
runningDownloads.put(newFile.getAbsolutePath(), newFile);
}
}
if (newFile.exists()) {
newFile.delete();
}
HttpDownloader result = downloadFile(newFile.getAbsolutePath(), downloadUrl);
if (result.getResult().isSuccessful()) {
long size = result.getDownloadRequest().getSoFar();
dco = new DiskCacheObject(newFile.getAbsolutePath(), size);
addToDiskCache(downloadUrl, dco);
if (AppConfig.DEBUG) Log.d(TAG, "Image was downloaded");
} else {
Log.w(TAG, "Download of url " + downloadUrl + " failed. Reason: " + result.getResult().getReasonDetailed() + "(" + result.getResult().getReason() + ")");
}
if (dco != null) {
final DiskCacheObject dcoRef = dco;
handler.post(new Runnable() {
@Override
public void run() {
onImageLoaded(dcoRef);
}
});
}
runningDownloads.remove(newFile.getAbsolutePath());
}
private HttpDownloader downloadFile(String destination, String source) {
DownloadRequest request = new DownloadRequest(destination, source, "", 0, 0);
HttpDownloader downloader = new HttpDownloader(request);
downloader.call();
return downloader;
}
}
private static class DiskCacheObject implements Serializable {
private final String fileUrl;
/**
* Last usage of this image cache object.
*/
private long timestamp;
private final long size;
public DiskCacheObject(String fileUrl, long size) {
if (fileUrl == null) {
throw new NullPointerException();
}
this.fileUrl = fileUrl;
this.timestamp = System.currentTimeMillis();
this.size = size;
}
public File getFile() {
return new File(fileUrl);
}
public ImageLoader.ImageWorkerTaskResource loadImage() {
return new ImageLoader.ImageWorkerTaskResource() {
@Override
public InputStream openImageInputStream() {
try {
return new FileInputStream(getFile());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return null;
}
@Override
public InputStream reopenImageInputStream(InputStream input) {
IOUtils.closeQuietly(input);
return openImageInputStream();
}
@Override
public String getImageLoaderCacheKey() {
return fileUrl;
}
};
}
}
}

View File

@ -66,7 +66,7 @@ public class ImageLoader {
private ExecutorService createExecutor() {
return Executors.newFixedThreadPool(Runtime.getRuntime()
.availableProcessors() + 1, new ThreadFactory() {
.availableProcessors(), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
@ -77,7 +77,7 @@ public class ImageLoader {
});
}
public static ImageLoader getInstance() {
public static synchronized ImageLoader getInstance() {
if (singleton == null) {
singleton = new ImageLoader();
}
@ -106,7 +106,8 @@ public class ImageLoader {
.getContext());
if (source != null && source.getImageLoaderCacheKey() != null) {
CachedBitmap cBitmap = getBitmapFromCoverCache(source.getImageLoaderCacheKey());
target.setTag(R.id.imageloader_key, source.getImageLoaderCacheKey());
CachedBitmap cBitmap = getBitmapFromCoverCache(source.getImageLoaderCacheKey());
if (cBitmap != null && cBitmap.getLength() >= length) {
target.setImageBitmap(cBitmap.getBitmap());
} else {
@ -143,7 +144,8 @@ public class ImageLoader {
.getContext());
if (source != null && source.getImageLoaderCacheKey() != null) {
CachedBitmap cBitmap = getBitmapFromThumbnailCache(source.getImageLoaderCacheKey());
target.setTag(R.id.imageloader_key, source.getImageLoaderCacheKey());
CachedBitmap cBitmap = getBitmapFromThumbnailCache(source.getImageLoaderCacheKey());
if (cBitmap != null && cBitmap.getLength() >= length) {
target.setImageBitmap(cBitmap.getBitmap());
} else {
@ -195,11 +197,7 @@ public class ImageLoader {
}
private int getDefaultCoverResource(Context context) {
TypedArray res = context
.obtainStyledAttributes(new int[] { R.attr.default_cover });
final int defaultCoverResource = res.getResourceId(0, 0);
res.recycle();
return defaultCoverResource;
return android.R.color.transparent;
}
/**

View File

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

View File

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

View File

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

View File

@ -0,0 +1,89 @@
package de.danoeh.antennapod.dialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import de.danoeh.antennapod.R;
/**
* Displays a dialog with a username and password text field and an optional checkbox to save username and preferences.
*/
public abstract class AuthenticationDialog extends Dialog {
private final int titleRes;
private final boolean enableUsernameField;
private final boolean showSaveCredentialsCheckbox;
private final String usernameInitialValue;
private final String passwordInitialValue;
public AuthenticationDialog(Context context, int titleRes, boolean enableUsernameField, boolean showSaveCredentialsCheckbox, String usernameInitialValue, String passwordInitialValue) {
super(context);
this.titleRes = titleRes;
this.enableUsernameField = enableUsernameField;
this.showSaveCredentialsCheckbox = showSaveCredentialsCheckbox;
this.usernameInitialValue = usernameInitialValue;
this.passwordInitialValue = passwordInitialValue;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.authentication_dialog);
final EditText etxtUsername = (EditText) findViewById(R.id.etxtUsername);
final EditText etxtPassword = (EditText) findViewById(R.id.etxtPassword);
final CheckBox saveUsernamePassword = (CheckBox) findViewById(R.id.chkSaveUsernamePassword);
final Button butConfirm = (Button) findViewById(R.id.butConfirm);
final Button butCancel = (Button) findViewById(R.id.butCancel);
if (titleRes != 0) {
setTitle(titleRes);
} else {
requestWindowFeature(Window.FEATURE_NO_TITLE);
}
etxtUsername.setEnabled(enableUsernameField);
if (showSaveCredentialsCheckbox) {
saveUsernamePassword.setVisibility(View.VISIBLE);
} else {
saveUsernamePassword.setVisibility(View.GONE);
}
if (usernameInitialValue != null) {
etxtUsername.setText(usernameInitialValue);
}
if (passwordInitialValue != null) {
etxtPassword.setText(passwordInitialValue);
}
setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
onCancelled();
}
});
butCancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
cancel();
}
});
butConfirm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onConfirmed(etxtUsername.getText().toString(),
etxtPassword.getText().toString(),
showSaveCredentialsCheckbox && saveUsernamePassword.isChecked());
dismiss();
}
});
}
protected void onCancelled() {
}
protected abstract void onConfirmed(String username, String password, boolean saveUsernamePassword);
}

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>();
}
public static EventDistributor getInstance() {
public static synchronized EventDistributor getInstance() {
if (instance == null) {
instance = new EventDistributor();
}

View File

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

View File

@ -48,6 +48,19 @@ public class FeedItem extends FeedComponent implements
this.read = true;
}
/**
* This constructor should be used for creating test objects.
* */
public FeedItem(long id, String title, String itemIdentifier, String link, Date pubDate, boolean read, Feed feed) {
this.id = id;
this.title = title;
this.itemIdentifier = itemIdentifier;
this.link = link;
this.pubDate = (pubDate != null) ? (Date) pubDate.clone() : null;
this.read = read;
this.feed = feed;
}
public void updateFromOther(FeedItem other) {
super.updateFromOther(other);
if (other.title != null) {
@ -123,19 +136,35 @@ public class FeedItem extends FeedComponent implements
}
public Date getPubDate() {
return pubDate;
if (pubDate != null) {
return (Date) pubDate.clone();
} else {
return null;
}
}
public void setPubDate(Date pubDate) {
this.pubDate = pubDate;
if (pubDate != null) {
this.pubDate = (Date) pubDate.clone();
} else {
this.pubDate = null;
}
}
public FeedMedia getMedia() {
return media;
}
/**
* Sets the media object of this FeedItem. If the given
* FeedMedia object is not null, it's 'item'-attribute value
* will also be set to this item.
* */
public void setMedia(FeedMedia media) {
this.media = media;
if (media != null && media.getItem() != this) {
media.setItem(this);
}
}
public Feed getFeed() {

View File

@ -53,7 +53,8 @@ public class FeedMedia extends FeedFile implements Playable {
this.position = position;
this.size = size;
this.mime_type = mime_type;
this.playbackCompletionDate = playbackCompletionDate;
this.playbackCompletionDate = playbackCompletionDate == null
? null : (Date) playbackCompletionDate.clone();
}
public FeedMedia(long id, FeedItem item) {
@ -164,16 +165,25 @@ public class FeedMedia extends FeedFile implements Playable {
return item;
}
/**
* Sets the item object of this FeedMedia. If the given
* FeedItem object is not null, it's 'media'-attribute value
* will also be set to this media object.
* */
public void setItem(FeedItem item) {
this.item = item;
if (item != null && item.getMedia() != this) {
item.setMedia(this);
}
}
public Date getPlaybackCompletionDate() {
return playbackCompletionDate;
}
return playbackCompletionDate == null
? null : (Date) playbackCompletionDate.clone(); }
public void setPlaybackCompletionDate(Date playbackCompletionDate) {
this.playbackCompletionDate = playbackCompletionDate;
this.playbackCompletionDate = playbackCompletionDate == null
? null : (Date) playbackCompletionDate.clone();
}
public boolean isInProgress() {

View File

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

View File

@ -1,7 +1,5 @@
package de.danoeh.antennapod.fragment;
import java.util.List;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
@ -14,7 +12,6 @@ import android.support.v7.view.ActionMode;
import android.util.Log;
import android.view.*;
import android.widget.*;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.FeedItemlistActivity;
@ -29,6 +26,8 @@ import de.danoeh.antennapod.storage.DownloadRequestException;
import de.danoeh.antennapod.storage.FeedItemStatistics;
import de.danoeh.antennapod.util.menuhandler.FeedMenuHandler;
import java.util.List;
public class FeedlistFragment extends Fragment implements
ActionMode.Callback, AdapterView.OnItemClickListener,
AdapterView.OnItemLongClickListener {
@ -244,11 +243,18 @@ public class FeedlistFragment extends Fragment implements
return true;
}
private boolean actionModeDestroyWorkaround = false; // TODO remove this workaround
@Override
public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
selectedFeed = null;
fla.setSelectedItemIndex(FeedlistAdapter.SELECTION_NONE);
if (actionModeDestroyWorkaround) {
mActionMode = null;
selectedFeed = null;
fla.setSelectedItemIndex(FeedlistAdapter.SELECTION_NONE);
actionModeDestroyWorkaround = false;
} else {
actionModeDestroyWorkaround = true;
}
}
@Override
@ -265,9 +271,9 @@ public class FeedlistFragment extends Fragment implements
public boolean onItemLongClick(AdapterView<?> parent, View view,
int position, long id) {
Feed selection = fla.getItem(position);
if (AppConfig.DEBUG)
Log.d(TAG, "Selected Feed with title " + selection.getTitle());
if (selection != null) {
if (AppConfig.DEBUG)
Log.d(TAG, "Selected Feed with title " + selection.getTitle());
if (mActionMode != null) {
mActionMode.finish();
}

View File

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

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

View File

@ -0,0 +1,120 @@
package de.danoeh.antennapod.fragment.gpodnet;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.ProgressBar;
import android.widget.TextView;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.DefaultOnlineFeedViewActivity;
import de.danoeh.antennapod.activity.OnlineFeedViewActivity;
import de.danoeh.antennapod.adapter.gpodnet.PodcastListAdapter;
import de.danoeh.antennapod.gpoddernet.GpodnetService;
import de.danoeh.antennapod.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.gpoddernet.model.GpodnetPodcast;
import java.util.List;
/**
* Displays a list of GPodnetPodcast-Objects in a GridView
*/
public abstract class PodcastListFragment extends Fragment {
private static final String TAG = "PodcastListFragment";
private GridView gridView;
private ProgressBar progressBar;
private TextView txtvError;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setRetainInstance(true);
View root = inflater.inflate(R.layout.gpodnet_podcast_list, container, false);
gridView = (GridView) root.findViewById(R.id.gridView);
progressBar = (ProgressBar) root.findViewById(R.id.progressBar);
txtvError = (TextView) root.findViewById(R.id.txtvError);
gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
onPodcastSelected((GpodnetPodcast) gridView.getAdapter().getItem(position));
}
});
loadData();
return root;
}
protected void onPodcastSelected(GpodnetPodcast selection) {
if (AppConfig.DEBUG) Log.d(TAG, "Selected podcast: " + selection.toString());
Intent intent = new Intent(getActivity(), DefaultOnlineFeedViewActivity.class);
intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, selection.getUrl());
intent.putExtra(DefaultOnlineFeedViewActivity.ARG_TITLE, getString(R.string.gpodnet_main_label));
startActivity(intent);
}
protected abstract List<GpodnetPodcast> loadPodcastData(GpodnetService service) throws GpodnetServiceException;
protected final void loadData() {
AsyncTask<Void, Void, List<GpodnetPodcast>> loaderTask = new AsyncTask<Void, Void, List<GpodnetPodcast>>() {
volatile Exception exception = null;
@Override
protected List<GpodnetPodcast> doInBackground(Void... params) {
GpodnetService service = null;
try {
service = new GpodnetService();
return loadPodcastData(service);
} catch (GpodnetServiceException e) {
exception = e;
e.printStackTrace();
return null;
} finally {
if (service != null) {
service.shutdown();
}
}
}
@Override
protected void onPostExecute(List<GpodnetPodcast> gpodnetPodcasts) {
super.onPostExecute(gpodnetPodcasts);
final Context context = getActivity();
if (context != null && gpodnetPodcasts != null) {
PodcastListAdapter listAdapter = new PodcastListAdapter(context, 0, gpodnetPodcasts);
gridView.setAdapter(listAdapter);
listAdapter.notifyDataSetChanged();
progressBar.setVisibility(View.GONE);
gridView.setVisibility(View.VISIBLE);
} else if (context != null) {
gridView.setVisibility(View.GONE);
progressBar.setVisibility(View.GONE);
txtvError.setText(getString(R.string.error_msg_prefix) + exception.getMessage());
}
}
@Override
protected void onPreExecute() {
super.onPreExecute();
gridView.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
}
};
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
loaderTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
loaderTask.execute();
}
}
}

View File

@ -0,0 +1,22 @@
package de.danoeh.antennapod.fragment.gpodnet;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.gpoddernet.GpodnetService;
import de.danoeh.antennapod.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.gpoddernet.model.GpodnetPodcast;
import java.util.List;
/**
*
*/
public class PodcastTopListFragment extends PodcastListFragment {
private static final String TAG = "PodcastTopListFragment";
private static final int PODCAST_COUNT = 50;
@Override
protected List<GpodnetPodcast> loadPodcastData(GpodnetService service) throws GpodnetServiceException {
return service.getPodcastToplist(PODCAST_COUNT);
}
}

View File

@ -0,0 +1,48 @@
package de.danoeh.antennapod.fragment.gpodnet;
import android.os.Bundle;
import de.danoeh.antennapod.gpoddernet.GpodnetService;
import de.danoeh.antennapod.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.gpoddernet.model.GpodnetPodcast;
import java.util.List;
/**
* Created by daniel on 23.08.13.
*/
public class SearchListFragment extends PodcastListFragment {
private static final String ARG_QUERY = "query";
private String query;
public static SearchListFragment newInstance(String query) {
SearchListFragment fragment = new SearchListFragment();
Bundle args = new Bundle();
args.putString(ARG_QUERY, query);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null && getArguments().containsKey(ARG_QUERY)) {
this.query = getArguments().getString(ARG_QUERY);
} else {
this.query = "";
}
}
@Override
protected List<GpodnetPodcast> loadPodcastData(GpodnetService service) throws GpodnetServiceException {
return service.searchPodcasts(query, 0);
}
public void changeQuery(String query) {
if (query == null) {
throw new NullPointerException();
}
this.query = query;
loadData();
}
}

View File

@ -0,0 +1,26 @@
package de.danoeh.antennapod.fragment.gpodnet;
import de.danoeh.antennapod.gpoddernet.GpodnetService;
import de.danoeh.antennapod.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.gpoddernet.model.GpodnetPodcast;
import de.danoeh.antennapod.preferences.GpodnetPreferences;
import java.util.ArrayList;
import java.util.List;
/**
* Displays suggestions from gpodder.net
*/
public class SuggestionListFragment extends PodcastListFragment {
private static final int SUGGESTIONS_COUNT = 50;
@Override
protected List<GpodnetPodcast> loadPodcastData(GpodnetService service) throws GpodnetServiceException {
if (GpodnetPreferences.loggedIn()) {
service.authenticate(GpodnetPreferences.getUsername(), GpodnetPreferences.getPassword());
return service.getSuggestions(SUGGESTIONS_COUNT);
} else {
return new ArrayList<GpodnetPodcast>();
}
}
}

View File

@ -0,0 +1,96 @@
package de.danoeh.antennapod.fragment.gpodnet;
import android.R;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import de.danoeh.antennapod.activity.gpoddernet.GpodnetTagActivity;
import de.danoeh.antennapod.gpoddernet.GpodnetService;
import de.danoeh.antennapod.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.gpoddernet.model.GpodnetTag;
import java.util.ArrayList;
import java.util.List;
public class TagListFragment extends ListFragment {
private static final String TAG = "TagListFragment";
private static final int COUNT = 50;
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setRetainInstance(true);
getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String selectedTag = (String) getListAdapter().getItem(position);
Intent intent = new Intent(getActivity(), GpodnetTagActivity.class);
intent.putExtra(GpodnetTagActivity.ARG_TAGNAME, selectedTag);
startActivity(intent);
}
});
loadData();
}
private void loadData() {
AsyncTask<Void, Void, List<GpodnetTag>> task = new AsyncTask<Void, Void, List<GpodnetTag>>() {
private Exception exception;
@Override
protected List<GpodnetTag> doInBackground(Void... params) {
GpodnetService service = new GpodnetService();
try {
return service.getTopTags(COUNT);
} catch (GpodnetServiceException e) {
e.printStackTrace();
exception = e;
return null;
} finally {
service.shutdown();
}
}
@Override
protected void onPreExecute() {
super.onPreExecute();
setListShown(false);
}
@Override
protected void onPostExecute(List<GpodnetTag> gpodnetTags) {
super.onPostExecute(gpodnetTags);
final Context context = getActivity();
if (context != null) {
if (gpodnetTags != null) {
List<String> tagNames = new ArrayList<String>();
for (GpodnetTag tag : gpodnetTags) {
tagNames.add(tag.getName());
}
setListAdapter(new ArrayAdapter<String>(context, R.layout.simple_list_item_1, tagNames));
setListShown(true);
} else if (exception != null) {
TextView txtvError = new TextView(getActivity());
txtvError.setText(exception.getMessage());
getListView().setEmptyView(txtvError);
} else {
setListShown(true);
}
}
}
};
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
task.execute();
}
}
}

View File

@ -0,0 +1,35 @@
package de.danoeh.antennapod.gpoddernet;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
/**
* HTTP client for the gpodder.net service.
*/
public class GpodnetClient extends DefaultHttpClient {
private static SchemeRegistry prepareSchemeRegistry() {
SchemeRegistry sr = new SchemeRegistry();
Scheme http = new Scheme("http",
PlainSocketFactory.getSocketFactory(), 80);
sr.register(http);
Scheme https = new Scheme("https",
SSLSocketFactory.getSocketFactory(), 443);
sr.register(https);
return sr;
}
@Override
protected ClientConnectionManager createClientConnectionManager() {
return new ThreadSafeClientConnManager(new BasicHttpParams(), prepareSchemeRegistry());
}
}

View File

@ -0,0 +1,725 @@
package de.danoeh.antennapod.gpoddernet;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.gpoddernet.model.*;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.auth.AuthenticationException;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.params.CoreProtocolPNames;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
/**
* Communicates with the gpodder.net service.
*/
public class GpodnetService {
private static final String BASE_SCHEME = "https";
private static final String BASE_HOST = "gpodder.net";
private GpodnetClient httpClient;
public GpodnetService() {
httpClient = new GpodnetClient();
httpClient.getParams().setParameter(CoreProtocolPNames.USER_AGENT, AppConfig.USER_AGENT);
}
/**
* Returns the [count] most used tags.
*/
public List<GpodnetTag> getTopTags(int count)
throws GpodnetServiceException {
URI uri;
try {
uri = new URI(BASE_SCHEME, BASE_HOST, String.format(
"/api/2/tags/%d.json", count), null);
} catch (URISyntaxException e1) {
e1.printStackTrace();
throw new IllegalStateException(e1);
}
HttpGet request = new HttpGet(uri);
String response = executeRequest(request);
try {
JSONArray jsonTagList = new JSONArray(response);
List<GpodnetTag> tagList = new ArrayList<GpodnetTag>(
jsonTagList.length());
for (int i = 0; i < jsonTagList.length(); i++) {
JSONObject jObj = jsonTagList.getJSONObject(i);
String name = jObj.getString("tag");
int usage = jObj.getInt("usage");
tagList.add(new GpodnetTag(name, usage));
}
return tagList;
} catch (JSONException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
}
}
/**
* Returns the [count] most subscribed podcasts for the given tag.
*
* @throws IllegalArgumentException if tag is null
*/
public List<GpodnetPodcast> getPodcastsForTag(GpodnetTag tag, int count)
throws GpodnetServiceException {
if (tag == null) {
throw new IllegalArgumentException(
"Tag and title of tag must not be null");
}
try {
URI uri = new URI(BASE_SCHEME, BASE_HOST, String.format(
"/api/2/tag/%s/%d.json", tag.getName(), count), null);
HttpGet request = new HttpGet(uri);
String response = executeRequest(request);
JSONArray jsonArray = new JSONArray(response);
return readPodcastListFromJSONArray(jsonArray);
} catch (JSONException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
} catch (URISyntaxException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
}
}
/**
* Returns the toplist of podcast.
*
* @param count of elements that should be returned. Must be in range 1..100.
* @throws IllegalArgumentException if count is out of range.
*/
public List<GpodnetPodcast> getPodcastToplist(int count)
throws GpodnetServiceException {
if (count < 1 || count > 100) {
throw new IllegalArgumentException("Count must be in range 1..100");
}
try {
URI uri = new URI(BASE_SCHEME, BASE_HOST, String.format(
"/toplist/%d.json", count), null);
HttpGet request = new HttpGet(uri);
String response = executeRequest(request);
JSONArray jsonArray = new JSONArray(response);
return readPodcastListFromJSONArray(jsonArray);
} catch (JSONException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
} catch (URISyntaxException e) {
e.printStackTrace();
throw new IllegalStateException(e);
}
}
/**
* Returns a list of suggested podcasts for the user that is currently
* logged in.
* <p/>
* This method requires authentication.
*
* @param count The
* number of elements that should be returned. Must be in range
* 1..100.
* @throws IllegalArgumentException if count is out of range.
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
*/
public List<GpodnetPodcast> getSuggestions(int count) throws GpodnetServiceException {
if (count < 1 || count > 100) {
throw new IllegalArgumentException("Count must be in range 1..100");
}
try {
URI uri = new URI(BASE_SCHEME, BASE_HOST, String.format(
"/suggestions/%d.json", count), null);
HttpGet request = new HttpGet(uri);
String response = executeRequest(request);
JSONArray jsonArray = new JSONArray(response);
return readPodcastListFromJSONArray(jsonArray);
} catch (URISyntaxException e) {
e.printStackTrace();
throw new IllegalStateException(e);
} catch (JSONException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
}
}
/**
* Searches the podcast directory for a given string.
*
* @param query The search query
* @param scaledLogoSize The size of the logos that are returned by the search query.
* Must be in range 1..256. If the value is out of range, the
* default value defined by the gpodder.net API will be used.
*/
public List<GpodnetPodcast> searchPodcasts(String query, int scaledLogoSize)
throws GpodnetServiceException {
String parameters = (scaledLogoSize > 0 && scaledLogoSize <= 256) ? String
.format("q=%s&scale_logo=%d", query, scaledLogoSize) : String
.format("q=%s", query);
try {
URI uri = new URI(BASE_SCHEME, null, BASE_HOST, -1, "/search.json",
parameters, null);
System.out.println(uri.toASCIIString());
HttpGet request = new HttpGet(uri);
String response = executeRequest(request);
JSONArray jsonArray = new JSONArray(response);
return readPodcastListFromJSONArray(jsonArray);
} catch (JSONException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
} catch (URISyntaxException e) {
e.printStackTrace();
throw new IllegalStateException(e);
}
}
/**
* Returns all devices of a given user.
* <p/>
* This method requires authentication.
*
* @param username The username. Must be the same user as the one which is
* currently logged in.
* @throws IllegalArgumentException If username is null.
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
*/
public List<GpodnetDevice> getDevices(String username)
throws GpodnetServiceException {
if (username == null) {
throw new IllegalArgumentException("Username must not be null");
}
try {
URI uri = new URI(BASE_SCHEME, BASE_HOST, String.format(
"/api/2/devices/%s.json", username), null);
HttpGet request = new HttpGet(uri);
String response = executeRequest(request);
JSONArray devicesArray = new JSONArray(response);
List<GpodnetDevice> result = readDeviceListFromJSONArray(devicesArray);
return result;
} catch (URISyntaxException e) {
e.printStackTrace();
throw new IllegalStateException(e);
} catch (JSONException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
}
}
/**
* Configures the device of a given user.
* <p/>
* This method requires authentication.
*
* @param username The username. Must be the same user as the one which is
* currently logged in.
* @param deviceId The ID of the device that should be configured.
* @throws IllegalArgumentException If username or deviceId is null.
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
*/
public void configureDevice(String username, String deviceId,
String caption, GpodnetDevice.DeviceType type)
throws GpodnetServiceException {
if (username == null || deviceId == null) {
throw new IllegalArgumentException(
"Username and device ID must not be null");
}
try {
URI uri = new URI(BASE_SCHEME, BASE_HOST, String.format(
"/api/2/devices/%s/%s.json", username, deviceId), null);
HttpPost request = new HttpPost(uri);
if (caption != null || type != null) {
JSONObject jsonContent = new JSONObject();
if (caption != null) {
jsonContent.put("caption", caption);
}
if (type != null) {
jsonContent.put("type", type.toString());
}
StringEntity strEntity = new StringEntity(
jsonContent.toString(), "UTF-8");
strEntity.setContentType("application/json");
request.setEntity(strEntity);
}
executeRequest(request);
} catch (URISyntaxException e) {
e.printStackTrace();
throw new IllegalArgumentException(e);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
throw new IllegalStateException(e);
} catch (JSONException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
}
}
/**
* Returns the subscriptions of a specific device.
* <p/>
* This method requires authentication.
*
* @param username The username. Must be the same user as the one which is
* currently logged in.
* @param deviceId The ID of the device whose subscriptions should be returned.
* @return A list of subscriptions in OPML format.
* @throws IllegalArgumentException If username or deviceId is null.
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
*/
public String getSubscriptionsOfDevice(String username, String deviceId)
throws GpodnetServiceException {
if (username == null || deviceId == null) {
throw new IllegalArgumentException(
"Username and device ID must not be null");
}
try {
URI uri = new URI(BASE_SCHEME, BASE_HOST, String.format(
"/subscriptions/%s/%s.opml", username, deviceId), null);
HttpGet request = new HttpGet(uri);
String response = executeRequest(request);
return response;
} catch (URISyntaxException e) {
e.printStackTrace();
throw new IllegalArgumentException(e);
}
}
/**
* Returns all subscriptions of a specific user.
* <p/>
* This method requires authentication.
*
* @param username The username. Must be the same user as the one which is
* currently logged in.
* @return A list of subscriptions in OPML format.
* @throws IllegalArgumentException If username is null.
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
*/
public String getSubscriptionsOfUser(String username)
throws GpodnetServiceException {
if (username == null) {
throw new IllegalArgumentException("Username must not be null");
}
try {
URI uri = new URI(BASE_SCHEME, BASE_HOST, String.format(
"/subscriptions/%s.opml", username), null);
HttpGet request = new HttpGet(uri);
String response = executeRequest(request);
return response;
} catch (URISyntaxException e) {
e.printStackTrace();
throw new IllegalArgumentException(e);
}
}
/**
* Uploads the subscriptions of a specific device.
* <p/>
* This method requires authentication.
*
* @param username The username. Must be the same user as the one which is
* currently logged in.
* @param deviceId The ID of the device whose subscriptions should be updated.
* @param subscriptions A list of feed URLs containing all subscriptions of the
* device.
* @throws IllegalArgumentException If username, deviceId or subscriptions is null.
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
*/
public void uploadSubscriptions(String username, String deviceId,
List<String> subscriptions) throws GpodnetServiceException {
if (username == null || deviceId == null || subscriptions == null) {
throw new IllegalArgumentException(
"Username, device ID and subscriptions must not be null");
}
try {
URI uri = new URI(BASE_SCHEME, BASE_HOST, String.format(
"/subscriptions/%s/%s.txt", username, deviceId), null);
HttpPut request = new HttpPut(uri);
StringBuilder builder = new StringBuilder();
for (String s : subscriptions) {
builder.append(s);
builder.append("\n");
}
StringEntity entity = new StringEntity(builder.toString(), "UTF-8");
request.setEntity(entity);
executeRequest(request);
} catch (URISyntaxException e) {
e.printStackTrace();
throw new IllegalStateException(e);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
throw new IllegalStateException(e);
}
}
/**
* Updates the subscription list of a specific device.
* <p/>
* This method requires authentication.
*
* @param username The username. Must be the same user as the one which is
* currently logged in.
* @param deviceId The ID of the device whose subscriptions should be updated.
* @param added Collection of feed URLs of added feeds. This Collection MUST NOT contain any duplicates
* @param removed Collection of feed URLs of removed feeds. This Collection MUST NOT contain any duplicates
* @return a GpodnetUploadChangesResponse. See {@link de.danoeh.antennapod.gpoddernet.model.GpodnetUploadChangesResponse}
* for details.
* @throws java.lang.IllegalArgumentException if username, deviceId, added or removed is null.
* @throws de.danoeh.antennapod.gpoddernet.GpodnetServiceException if added or removed contain duplicates or if there
* is an authentication error.
*/
public GpodnetUploadChangesResponse uploadChanges(String username, String deviceId, Collection<String> added,
Collection<String> removed) throws GpodnetServiceException {
if (username == null || deviceId == null || added == null || removed == null) {
throw new IllegalArgumentException(
"Username, device ID, added and removed must not be null");
}
try {
URI uri = new URI(BASE_SCHEME, BASE_HOST, String.format(
"/api/2/subscriptions/%s/%s.json", username, deviceId), null);
final JSONObject requestObject = new JSONObject();
requestObject.put("add", new JSONArray(added));
requestObject.put("remove", new JSONArray(removed));
HttpPost request = new HttpPost(uri);
StringEntity entity = new StringEntity(requestObject.toString(), "UTF-8");
request.setEntity(entity);
final String response = executeRequest(request);
return GpodnetUploadChangesResponse.fromJSONObject(response);
} catch (URISyntaxException e) {
e.printStackTrace();
throw new IllegalStateException(e);
} catch (JSONException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
throw new IllegalStateException(e);
}
}
/**
* Returns all subscription changes of a specific device.
* <p/>
* This method requires authentication.
*
* @param username The username. Must be the same user as the one which is
* currently logged in.
* @param deviceId The ID of the device whose subscription changes should be
* downloaded.
* @param timestamp A timestamp that can be used to receive all changes since a
* specific point in time.
* @throws IllegalArgumentException If username or deviceId is null.
* @throws GpodnetServiceAuthenticationException If there is an authentication error.
*/
public GpodnetSubscriptionChange getSubscriptionChanges(String username,
String deviceId, long timestamp) throws GpodnetServiceException {
if (username == null || deviceId == null) {
throw new IllegalArgumentException(
"Username and device ID must not be null");
}
String params = String.format("since=%d", timestamp);
String path = String.format("/api/2/subscriptions/%s/%s.json",
username, deviceId);
try {
URI uri = new URI(BASE_SCHEME, null, BASE_HOST, -1, path, params,
null);
HttpGet request = new HttpGet(uri);
String response = executeRequest(request);
JSONObject changes = new JSONObject(response);
return readSubscriptionChangesFromJSONObject(changes);
} catch (URISyntaxException e) {
e.printStackTrace();
throw new IllegalStateException(e);
} catch (JSONException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
}
}
/**
* Logs in a specific user. This method must be called if any of the methods
* that require authentication is used.
*
* @throws IllegalArgumentException If username or password is null.
*/
public void authenticate(String username, String password)
throws GpodnetServiceException {
if (username == null || password == null) {
throw new IllegalArgumentException(
"Username and password must not be null");
}
URI uri;
try {
uri = new URI(BASE_SCHEME, BASE_HOST, String.format(
"/api/2/auth/%s/login.json", username), null);
} catch (URISyntaxException e) {
e.printStackTrace();
throw new GpodnetServiceException();
}
HttpPost request = new HttpPost(uri);
executeRequestWithAuthentication(request, username, password);
}
/**
* Shuts down the GpodnetService's HTTP client. The service will be shut down in a separate thread to avoid
* NetworkOnMainThreadExceptions.
*/
public void shutdown() {
new Thread() {
@Override
public void run() {
httpClient.getConnectionManager().shutdown();
}
}.start();
}
private String executeRequest(HttpRequestBase request)
throws GpodnetServiceException {
if (request == null) {
throw new IllegalArgumentException("request must not be null");
}
String responseString = null;
HttpResponse response = null;
try {
response = httpClient.execute(request);
checkStatusCode(response);
responseString = getStringFromEntity(response.getEntity());
} catch (ClientProtocolException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
} catch (IOException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
} finally {
if (response != null) {
try {
response.getEntity().consumeContent();
} catch (IOException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
}
}
}
return responseString;
}
private String executeRequestWithAuthentication(HttpRequestBase request,
String username, String password) throws GpodnetServiceException {
if (request == null || username == null || password == null) {
throw new IllegalArgumentException(
"request and credentials must not be null");
}
String result = null;
HttpResponse response = null;
try {
Header auth = new BasicScheme().authenticate(
new UsernamePasswordCredentials(username, password),
request);
request.addHeader(auth);
response = httpClient.execute(request);
checkStatusCode(response);
result = getStringFromEntity(response.getEntity());
} catch (ClientProtocolException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
} catch (IOException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
} catch (AuthenticationException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
} finally {
if (response != null) {
try {
response.getEntity().consumeContent();
} catch (IOException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
}
}
}
return result;
}
private String getStringFromEntity(HttpEntity entity)
throws GpodnetServiceException {
if (entity == null) {
throw new IllegalArgumentException("entity must not be null");
}
ByteArrayOutputStream outputStream;
int contentLength = (int) entity.getContentLength();
if (contentLength > 0) {
outputStream = new ByteArrayOutputStream(contentLength);
} else {
outputStream = new ByteArrayOutputStream();
}
try {
byte[] buffer = new byte[8 * 1024];
InputStream in = entity.getContent();
int count;
while ((count = in.read(buffer)) > 0) {
outputStream.write(buffer, 0, count);
}
} catch (IOException e) {
e.printStackTrace();
throw new GpodnetServiceException(e);
}
// System.out.println(outputStream.toString());
return outputStream.toString();
}
private void checkStatusCode(HttpResponse response)
throws GpodnetServiceException {
if (response == null) {
throw new IllegalArgumentException("response must not be null");
}
int responseCode = response.getStatusLine().getStatusCode();
if (responseCode != HttpStatus.SC_OK) {
if (responseCode == HttpStatus.SC_UNAUTHORIZED) {
throw new GpodnetServiceAuthenticationException("Wrong username or password");
} else {
throw new GpodnetServiceBadStatusCodeException(
"Bad response code: " + responseCode, responseCode);
}
}
}
private List<GpodnetPodcast> readPodcastListFromJSONArray(JSONArray array)
throws JSONException {
if (array == null) {
throw new IllegalArgumentException("array must not be null");
}
List<GpodnetPodcast> result = new ArrayList<GpodnetPodcast>(
array.length());
for (int i = 0; i < array.length(); i++) {
result.add(readPodcastFromJSONObject(array.getJSONObject(i)));
}
return result;
}
private GpodnetPodcast readPodcastFromJSONObject(JSONObject object)
throws JSONException {
String url = object.getString("url");
String title;
Object titleObj = object.opt("title");
if (titleObj != null && titleObj instanceof String) {
title = (String) titleObj;
} else {
title = url;
}
String description;
Object descriptionObj = object.opt("description");
if (descriptionObj != null && descriptionObj instanceof String) {
description = (String) descriptionObj;
} else {
description = "";
}
int subscribers = object.getInt("subscribers");
Object logoUrlObj = object.opt("logo_url");
String logoUrl = (logoUrlObj instanceof String) ? (String) logoUrlObj
: null;
if (logoUrl == null) {
Object scaledLogoUrl = object.opt("scaled_logo_url");
if (scaledLogoUrl != null && scaledLogoUrl instanceof String) {
logoUrl = (String) scaledLogoUrl;
}
}
String website = null;
Object websiteObj = object.opt("website");
if (websiteObj != null && websiteObj instanceof String) {
website = (String) websiteObj;
}
String mygpoLink = object.getString("mygpo_link");
return new GpodnetPodcast(url, title, description, subscribers,
logoUrl, website, mygpoLink);
}
private List<GpodnetDevice> readDeviceListFromJSONArray(JSONArray array)
throws JSONException {
if (array == null) {
throw new IllegalArgumentException("array must not be null");
}
List<GpodnetDevice> result = new ArrayList<GpodnetDevice>(
array.length());
for (int i = 0; i < array.length(); i++) {
result.add(readDeviceFromJSONObject(array.getJSONObject(i)));
}
return result;
}
private GpodnetDevice readDeviceFromJSONObject(JSONObject object)
throws JSONException {
String id = object.getString("id");
String caption = object.getString("caption");
String type = object.getString("type");
int subscriptions = object.getInt("subscriptions");
return new GpodnetDevice(id, caption, type, subscriptions);
}
private GpodnetSubscriptionChange readSubscriptionChangesFromJSONObject(
JSONObject object) throws JSONException {
if (object == null) {
throw new IllegalArgumentException("object must not be null");
}
List<String> added = new LinkedList<String>();
JSONArray jsonAdded = object.getJSONArray("add");
for (int i = 0; i < jsonAdded.length(); i++) {
added.add(jsonAdded.getString(i));
}
List<String> removed = new LinkedList<String>();
JSONArray jsonRemoved = object.getJSONArray("remove");
for (int i = 0; i < jsonRemoved.length(); i++) {
removed.add(jsonRemoved.getString(i));
}
long timestamp = object.getLong("timestamp");
return new GpodnetSubscriptionChange(added, removed, timestamp);
}
}

View File

@ -0,0 +1,21 @@
package de.danoeh.antennapod.gpoddernet;
public class GpodnetServiceAuthenticationException extends GpodnetServiceException {
public GpodnetServiceAuthenticationException() {
super();
}
public GpodnetServiceAuthenticationException(String message, Throwable cause) {
super(message, cause);
}
public GpodnetServiceAuthenticationException(String message) {
super(message);
}
public GpodnetServiceAuthenticationException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,12 @@
package de.danoeh.antennapod.gpoddernet;
public class GpodnetServiceBadStatusCodeException extends GpodnetServiceException {
int statusCode;
public GpodnetServiceBadStatusCodeException(String message, int statusCode) {
super(message);
this.statusCode = statusCode;
}
}

View File

@ -0,0 +1,19 @@
package de.danoeh.antennapod.gpoddernet;
public class GpodnetServiceException extends Exception {
public GpodnetServiceException() {
}
public GpodnetServiceException(String message) {
super(message);
}
public GpodnetServiceException(Throwable cause) {
super(cause);
}
public GpodnetServiceException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,72 @@
package de.danoeh.antennapod.gpoddernet.model;
public class GpodnetDevice {
private String id;
private String caption;
private DeviceType type;
private int subscriptions;
public GpodnetDevice(String id, String caption, String type,
int subscriptions) {
if (id == null) {
throw new IllegalArgumentException("ID must not be null");
}
this.id = id;
this.caption = caption;
this.type = DeviceType.fromString(type);
this.subscriptions = subscriptions;
}
@Override
public String toString() {
return "GpodnetDevice [id=" + id + ", caption=" + caption + ", type="
+ type + ", subscriptions=" + subscriptions + "]";
}
public static enum DeviceType {
DESKTOP, LAPTOP, MOBILE, SERVER, OTHER;
static DeviceType fromString(String s) {
if (s == null) {
return OTHER;
}
if (s.equals("desktop")) {
return DESKTOP;
} else if (s.equals("laptop")) {
return LAPTOP;
} else if (s.equals("mobile")) {
return MOBILE;
} else if (s.equals("server")) {
return SERVER;
} else {
return OTHER;
}
}
@Override
public String toString() {
return super.toString().toLowerCase();
}
}
public String getId() {
return id;
}
public String getCaption() {
return caption;
}
public DeviceType getType() {
return type;
}
public int getSubscriptions() {
return subscriptions;
}
}

View File

@ -0,0 +1,64 @@
package de.danoeh.antennapod.gpoddernet.model;
public class GpodnetPodcast {
private String url;
private String title;
private String description;
private int subscribers;
private String logoUrl;
private String website;
private String mygpoLink;
public GpodnetPodcast(String url, String title, String description,
int subscribers, String logoUrl, String website, String mygpoLink) {
if (url == null || title == null || description == null) {
throw new IllegalArgumentException(
"URL, title and description must not be null");
}
this.url = url;
this.title = title;
this.description = description;
this.subscribers = subscribers;
this.logoUrl = logoUrl;
this.website = website;
this.mygpoLink = mygpoLink;
}
@Override
public String toString() {
return "GpodnetPodcast [url=" + url + ", title=" + title
+ ", description=" + description + ", subscribers="
+ subscribers + ", logoUrl=" + logoUrl + ", website=" + website
+ ", mygpoLink=" + mygpoLink + "]";
}
public String getUrl() {
return url;
}
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
public int getSubscribers() {
return subscribers;
}
public String getLogoUrl() {
return logoUrl;
}
public String getWebsite() {
return website;
}
public String getMygpoLink() {
return mygpoLink;
}
}

View File

@ -0,0 +1,40 @@
package de.danoeh.antennapod.gpoddernet.model;
import java.util.List;
public class GpodnetSubscriptionChange {
private List<String> added;
private List<String> removed;
private long timestamp;
public GpodnetSubscriptionChange(List<String> added, List<String> removed,
long timestamp) {
if (added == null || removed == null) {
throw new IllegalArgumentException(
"added and remove must not be null");
}
this.added = added;
this.removed = removed;
this.timestamp = timestamp;
}
@Override
public String toString() {
return "GpodnetSubscriptionChange [added=" + added.toString()
+ ", removed=" + removed.toString() + ", timestamp="
+ timestamp + "]";
}
public List<String> getAdded() {
return added;
}
public List<String> getRemoved() {
return removed;
}
public long getTimestamp() {
return timestamp;
}
}

View File

@ -0,0 +1,46 @@
package de.danoeh.antennapod.gpoddernet.model;
import java.util.Comparator;
public class GpodnetTag {
private String name;
private int usage;
public GpodnetTag(String name, int usage) {
if (name == null) {
throw new IllegalArgumentException("Name must not be null");
}
this.name = name;
this.usage = usage;
}
public GpodnetTag(String name) {
super();
this.name = name;
}
@Override
public String toString() {
return "GpodnetTag [name=" + name + ", usage=" + usage + "]";
}
public String getName() {
return name;
}
public int getUsage() {
return usage;
}
public static class UsageComparator implements Comparator<GpodnetTag> {
@Override
public int compare(GpodnetTag o1, GpodnetTag o2) {
return o1.usage - o2.usage;
}
}
}

View File

@ -0,0 +1,56 @@
package de.danoeh.antennapod.gpoddernet.model;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Map;
/**
* Object returned by {@link de.danoeh.antennapod.gpoddernet.GpodnetService} in uploadChanges method.
*/
public class GpodnetUploadChangesResponse {
/**
* timestamp/ID that can be used for requesting changes since this upload.
*/
public final long timestamp;
/**
* URLs that should be updated. The key of the map is the original URL, the value of the map
* is the sanitized URL.
*/
public final Map<String, String> updatedUrls;
public GpodnetUploadChangesResponse(long timestamp, Map<String, String> updatedUrls) {
this.timestamp = timestamp;
this.updatedUrls = updatedUrls;
}
/**
* Creates a new GpodnetUploadChangesResponse-object from a JSON object that was
* returned by an uploadChanges call.
*
* @throws org.json.JSONException If the method could not parse the JSONObject.
*/
public static GpodnetUploadChangesResponse fromJSONObject(String objectString) throws JSONException {
final JSONObject object = new JSONObject(objectString);
final long timestamp = object.getLong("timestamp");
Map<String, String> updatedUrls = new HashMap<String, String>();
JSONArray urls = object.getJSONArray("update_urls");
for (int i = 0; i < urls.length(); i++) {
JSONArray urlPair = urls.getJSONArray(i);
updatedUrls.put(urlPair.getString(0), urlPair.getString(1));
}
return new GpodnetUploadChangesResponse(timestamp, updatedUrls);
}
@Override
public String toString() {
return "GpodnetUploadChangesResponse{" +
"timestamp=" + timestamp +
", updatedUrls=" + updatedUrls +
'}';
}
}

View File

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

View File

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

View File

@ -0,0 +1,217 @@
package de.danoeh.antennapod.preferences;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.PodcastApp;
import de.danoeh.antennapod.service.GpodnetSyncService;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
/**
* Manages preferences for accessing gpodder.net service
*/
public class GpodnetPreferences {
private static final String TAG = "GpodnetPreferences";
private static final String PREF_NAME = "gpodder.net";
public static final String PREF_GPODNET_USERNAME = "de.danoeh.antennapod.preferences.gpoddernet.username";
public static final String PREF_GPODNET_PASSWORD = "de.danoeh.antennapod.preferences.gpoddernet.password";
public static final String PREF_GPODNET_DEVICEID = "de.danoeh.antennapod.preferences.gpoddernet.deviceID";
public static final String PREF_LAST_SYNC_TIMESTAMP = "de.danoeh.antennapod.preferences.gpoddernet.last_sync_timestamp";
public static final String PREF_SYNC_ADDED = "de.danoeh.antennapod.preferences.gpoddernet.sync_added";
public static final String PREF_SYNC_REMOVED = "de.danoeh.antennapod.preferences.gpoddernet.sync_removed";
private static String username;
private static String password;
private static String deviceID;
private static ReentrantLock feedListLock = new ReentrantLock();
private static Set<String> addedFeeds;
private static Set<String> removedFeeds;
/**
* Last value returned by getSubscriptionChanges call. Will be used for all subsequent calls of getSubscriptionChanges.
*/
private static long lastSyncTimestamp;
private static boolean preferencesLoaded = false;
private static SharedPreferences getPreferences() {
return PodcastApp.getInstance().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
}
private static synchronized void ensurePreferencesLoaded() {
if (!preferencesLoaded) {
SharedPreferences prefs = getPreferences();
username = prefs.getString(PREF_GPODNET_USERNAME, null);
password = prefs.getString(PREF_GPODNET_PASSWORD, null);
deviceID = prefs.getString(PREF_GPODNET_DEVICEID, null);
lastSyncTimestamp = prefs.getLong(PREF_LAST_SYNC_TIMESTAMP, 0);
addedFeeds = readListFromString(prefs.getString(PREF_SYNC_ADDED, ""));
removedFeeds = readListFromString(prefs.getString(PREF_SYNC_REMOVED, ""));
preferencesLoaded = true;
}
}
private static void writePreference(String key, String value) {
SharedPreferences.Editor editor = getPreferences().edit();
editor.putString(key, value);
editor.commit();
}
private static void writePreference(String key, long value) {
SharedPreferences.Editor editor = getPreferences().edit();
editor.putLong(key, value);
editor.commit();
}
private static void writePreference(String key, Collection<String> value) {
SharedPreferences.Editor editor = getPreferences().edit();
editor.putString(key, writeListToString(value));
editor.commit();
}
public static String getUsername() {
ensurePreferencesLoaded();
return username;
}
public static void setUsername(String username) {
GpodnetPreferences.username = username;
writePreference(PREF_GPODNET_USERNAME, username);
}
public static String getPassword() {
ensurePreferencesLoaded();
return password;
}
public static void setPassword(String password) {
GpodnetPreferences.password = password;
writePreference(PREF_GPODNET_PASSWORD, password);
}
public static String getDeviceID() {
ensurePreferencesLoaded();
return deviceID;
}
public static void setDeviceID(String deviceID) {
GpodnetPreferences.deviceID = deviceID;
writePreference(PREF_GPODNET_DEVICEID, deviceID);
}
public static long getLastSyncTimestamp() {
ensurePreferencesLoaded();
return lastSyncTimestamp;
}
public static void setLastSyncTimestamp(long lastSyncTimestamp) {
GpodnetPreferences.lastSyncTimestamp = lastSyncTimestamp;
writePreference(PREF_LAST_SYNC_TIMESTAMP, lastSyncTimestamp);
}
public static void addAddedFeed(String feed) {
ensurePreferencesLoaded();
feedListLock.lock();
if (addedFeeds.add(feed)) {
writePreference(PREF_SYNC_ADDED, addedFeeds);
}
if (removedFeeds.remove(feed)) {
writePreference(PREF_SYNC_REMOVED, removedFeeds);
}
feedListLock.unlock();
GpodnetSyncService.sendSyncIntent(PodcastApp.getInstance());
}
public static void addRemovedFeed(String feed) {
ensurePreferencesLoaded();
feedListLock.lock();
if (removedFeeds.add(feed)) {
writePreference(PREF_SYNC_REMOVED, removedFeeds);
}
if (addedFeeds.remove(feed)) {
writePreference(PREF_SYNC_ADDED, addedFeeds);
}
feedListLock.unlock();
GpodnetSyncService.sendSyncIntent(PodcastApp.getInstance());
}
public static Set<String> getAddedFeedsCopy() {
ensurePreferencesLoaded();
Set<String> copy = new HashSet<String>();
feedListLock.lock();
copy.addAll(addedFeeds);
feedListLock.unlock();
return copy;
}
public static void removeAddedFeeds(Collection<String> removed) {
ensurePreferencesLoaded();
feedListLock.lock();
addedFeeds.removeAll(removed);
writePreference(PREF_SYNC_ADDED, addedFeeds);
feedListLock.unlock();
}
public static Set<String> getRemovedFeedsCopy() {
ensurePreferencesLoaded();
Set<String> copy = new HashSet<String>();
feedListLock.lock();
copy.addAll(removedFeeds);
feedListLock.unlock();
return copy;
}
public static void removeRemovedFeeds(Collection<String> removed) {
ensurePreferencesLoaded();
removedFeeds.removeAll(removed);
writePreference(PREF_SYNC_REMOVED, removedFeeds);
}
/**
* Returns true if device ID, username and password have a non-null value
*/
public static boolean loggedIn() {
ensurePreferencesLoaded();
return deviceID != null && username != null && password != null;
}
public static synchronized void logout() {
if (AppConfig.DEBUG) Log.d(TAG, "Logout: Clearing preferences");
setUsername(null);
setPassword(null);
setDeviceID(null);
addedFeeds.clear();
writePreference(PREF_SYNC_ADDED, addedFeeds);
removedFeeds.clear();
writePreference(PREF_SYNC_REMOVED, removedFeeds);
setLastSyncTimestamp(0);
}
private static Set<String> readListFromString(String s) {
Set<String> result = new HashSet<String>();
for (String item : s.split(" ")) {
result.add(item);
}
return result;
}
private static String writeListToString(Collection<String> c) {
StringBuilder result = new StringBuilder();
for (String item : c) {
result.append(item);
result.append(" ");
}
return result.toString().trim();
}
}

View File

@ -2,9 +2,13 @@ package de.danoeh.antennapod.preferences;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import android.app.AlarmManager;
import android.app.PendingIntent;
@ -41,11 +45,13 @@ public class UserPreferences implements
public static final String PREF_ENABLE_AUTODL_WIFI_FILTER = "prefEnableAutoDownloadWifiFilter";
private static final String PREF_AUTODL_SELECTED_NETWORKS = "prefAutodownloadSelectedNetworks";
public static final String PREF_EPISODE_CACHE_SIZE = "prefEpisodeCacheSize";
private static final String PREF_PLAYBACK_SPEED = "prefPlaybackSpeed";
private static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray";
private static int EPISODE_CACHE_SIZE_UNLIMITED = -1;
private static UserPreferences instance;
private Context context;
private final Context context;
// Preferences
private boolean pauseOnHeadsetDisconnect;
@ -60,6 +66,8 @@ public class UserPreferences implements
private boolean enableAutodownloadWifiFilter;
private String[] autodownloadSelectedNetworks;
private int episodeCacheSize;
private String playbackSpeed;
private String[] playbackSpeedArray;
private UserPreferences(Context context) {
this.context = context;
@ -83,6 +91,7 @@ public class UserPreferences implements
createNoMediaFile();
PreferenceManager.getDefaultSharedPreferences(context)
.registerOnSharedPreferenceChangeListener(instance);
}
private void loadPreferences() {
@ -108,6 +117,9 @@ public class UserPreferences implements
episodeCacheSize = readEpisodeCacheSize(sp.getString(
PREF_EPISODE_CACHE_SIZE, "20"));
enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false);
playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0");
playbackSpeedArray = readPlaybackSpeedArray(sp.getString(
PREF_PLAYBACK_SPEED_ARRAY, null));
}
private int readThemeValue(String valueFromPrefs) {
@ -135,6 +147,36 @@ public class UserPreferences implements
}
}
private String[] readPlaybackSpeedArray(String valueFromPrefs) {
String[] selectedSpeeds = null;
// If this preference hasn't been set yet, return the default options
if (valueFromPrefs == null) {
String[] allSpeeds = context.getResources().getStringArray(
R.array.playback_speed_values);
List<String> speedList = new LinkedList<String>();
for (String speedStr : allSpeeds) {
float speed = Float.parseFloat(speedStr);
if (speed < 2.0001 && speed * 10 % 1 == 0) {
speedList.add(speedStr);
}
}
selectedSpeeds = speedList.toArray(new String[speedList.size()]);
} else {
try {
JSONArray jsonArray = new JSONArray(valueFromPrefs);
selectedSpeeds = new String[jsonArray.length()];
for (int i = 0; i < jsonArray.length(); i++) {
selectedSpeeds[i] = jsonArray.getString(i);
}
} catch (JSONException e) {
Log.e(TAG,
"Got JSON error when trying to get speeds from JSONArray");
e.printStackTrace();
}
}
return selectedSpeeds;
}
private static void instanceAvailable() {
if (instance == null) {
throw new IllegalStateException(
@ -169,7 +211,8 @@ public class UserPreferences implements
public static boolean isDisplayOnlyEpisodes() {
instanceAvailable();
return instance.displayOnlyEpisodes;
//return instance.displayOnlyEpisodes;
return false;
}
public static boolean isAutoDelete() {
@ -196,6 +239,16 @@ public class UserPreferences implements
return EPISODE_CACHE_SIZE_UNLIMITED;
}
public static String getPlaybackSpeed() {
instanceAvailable();
return instance.playbackSpeed;
}
public static String[] getPlaybackSpeedArray() {
instanceAvailable();
return instance.playbackSpeedArray;
}
/**
* Returns the capacity of the episode cache. This method will return the
* negative integer EPISODE_CACHE_SIZE_UNLIMITED if the cache size is set to
@ -250,9 +303,29 @@ public class UserPreferences implements
PREF_EPISODE_CACHE_SIZE, "20"));
} else if (key.equals(PREF_ENABLE_AUTODL)) {
enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false);
} else if (key.equals(PREF_PLAYBACK_SPEED)) {
playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0");
} else if (key.equals(PREF_PLAYBACK_SPEED_ARRAY)) {
playbackSpeedArray = readPlaybackSpeedArray(sp.getString(
PREF_PLAYBACK_SPEED_ARRAY, null));
}
}
public static void setPlaybackSpeed(String speed) {
PreferenceManager.getDefaultSharedPreferences(instance.context).edit()
.putString(PREF_PLAYBACK_SPEED, speed).apply();
}
public static void setPlaybackSpeedArray(String[] speeds) {
JSONArray jsonArray = new JSONArray();
for (String speed : speeds) {
jsonArray.put(speed);
}
PreferenceManager.getDefaultSharedPreferences(instance.context).edit()
.putString(PREF_PLAYBACK_SPEED_ARRAY, jsonArray.toString())
.apply();
}
public static void setAutodownloadSelectedNetworks(Context context,
String[] value) {
SharedPreferences.Editor editor = PreferenceManager

View File

@ -0,0 +1,243 @@
package de.danoeh.antennapod.service;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.feed.Feed;
import de.danoeh.antennapod.gpoddernet.GpodnetService;
import de.danoeh.antennapod.gpoddernet.GpodnetServiceAuthenticationException;
import de.danoeh.antennapod.gpoddernet.GpodnetServiceException;
import de.danoeh.antennapod.gpoddernet.model.GpodnetSubscriptionChange;
import de.danoeh.antennapod.gpoddernet.model.GpodnetUploadChangesResponse;
import de.danoeh.antennapod.preferences.GpodnetPreferences;
import de.danoeh.antennapod.storage.*;
import de.danoeh.antennapod.util.NetworkUtils;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
/**
* Synchronizes local subscriptions with gpodder.net service. The service should be started with ACTION_SYNC as an action argument.
* This class also provides static methods for starting the GpodnetSyncService.
*/
public class GpodnetSyncService extends Service {
private static final String TAG = "GpodnetSyncService";
private static final long WAIT_INTERVAL = 5000L;
public static final String ARG_ACTION = "action";
public static final String ACTION_SYNC = "de.danoeh.antennapod.intent.action.sync";
private GpodnetService service;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
final String action = (intent != null) ? intent.getStringExtra(ARG_ACTION) : null;
if (action != null && action.equals(ACTION_SYNC)) {
Log.d(TAG, String.format("Waiting %d milliseconds before uploading changes", WAIT_INTERVAL));
syncWaiterThread.restart();
} else {
Log.e(TAG, "Received invalid intent: action argument is null or invalid");
}
return START_FLAG_REDELIVERY;
}
@Override
public void onDestroy() {
super.onDestroy();
if (AppConfig.DEBUG) Log.d(TAG, "onDestroy");
syncWaiterThread.interrupt();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
private synchronized GpodnetService tryLogin() throws GpodnetServiceException {
if (service == null) {
service = new GpodnetService();
service.authenticate(GpodnetPreferences.getUsername(), GpodnetPreferences.getPassword());
}
return service;
}
private synchronized void syncChanges() {
if (GpodnetPreferences.loggedIn() && NetworkUtils.networkAvailable(this)) {
final long timestamp = GpodnetPreferences.getLastSyncTimestamp();
try {
final List<String> localSubscriptions = DBReader.getFeedListDownloadUrls(this);
GpodnetService service = tryLogin();
if (timestamp == 0) {
// first sync: download all subscriptions...
GpodnetSubscriptionChange changes =
service.getSubscriptionChanges(GpodnetPreferences.getUsername(), GpodnetPreferences.getDeviceID(), 0);
if (AppConfig.DEBUG) Log.d(TAG, "Downloaded subscription changes: " + changes);
processSubscriptionChanges(localSubscriptions, changes);
// ... then upload all local subscriptions
if (AppConfig.DEBUG) Log.d(TAG, "Uploading subscription list: " + localSubscriptions);
GpodnetUploadChangesResponse uploadChangesResponse =
service.uploadChanges(GpodnetPreferences.getUsername(), GpodnetPreferences.getDeviceID(), localSubscriptions, new LinkedList<String>());
if (AppConfig.DEBUG) Log.d(TAG, "Uploading changes response: " + uploadChangesResponse);
DBWriter.updateFeedDownloadURLs(GpodnetSyncService.this, uploadChangesResponse.updatedUrls).get();
GpodnetPreferences.removeAddedFeeds(localSubscriptions);
GpodnetPreferences.removeRemovedFeeds(GpodnetPreferences.getRemovedFeedsCopy());
GpodnetPreferences.setLastSyncTimestamp(uploadChangesResponse.timestamp);
} else {
Set<String> added = GpodnetPreferences.getAddedFeedsCopy();
Set<String> removed = GpodnetPreferences.getRemovedFeedsCopy();
// download remote changes first...
GpodnetSubscriptionChange subscriptionChanges = service.getSubscriptionChanges(GpodnetPreferences.getUsername(), GpodnetPreferences.getDeviceID(), timestamp);
if (AppConfig.DEBUG) Log.d(TAG, "Downloaded subscription changes: " + subscriptionChanges);
processSubscriptionChanges(localSubscriptions, subscriptionChanges);
// ... then upload changes local changes
if (AppConfig.DEBUG) Log.d(TAG, String.format("Uploading subscriptions, Added: %s\nRemoved: %s",
added.toString(), removed));
GpodnetUploadChangesResponse uploadChangesResponse = service.uploadChanges(GpodnetPreferences.getUsername(), GpodnetPreferences.getDeviceID(), added, removed);
if (AppConfig.DEBUG) Log.d(TAG, "Upload subscriptions response: " + uploadChangesResponse);
GpodnetPreferences.removeAddedFeeds(added);
GpodnetPreferences.removeRemovedFeeds(removed);
DBWriter.updateFeedDownloadURLs(GpodnetSyncService.this, uploadChangesResponse.updatedUrls).get();
GpodnetPreferences.setLastSyncTimestamp(uploadChangesResponse.timestamp);
}
clearErrorNotifications();
} catch (GpodnetServiceException e) {
e.printStackTrace();
updateErrorNotification(e);
} catch (DownloadRequestException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
stopSelf();
}
private synchronized void processSubscriptionChanges(List<String> localSubscriptions, GpodnetSubscriptionChange changes) throws DownloadRequestException {
for (String downloadUrl : changes.getAdded()) {
if (!localSubscriptions.contains(downloadUrl)) {
Feed feed = new Feed(downloadUrl, new Date());
DownloadRequester.getInstance().downloadFeed(this, feed);
}
}
for (String downloadUrl : changes.getRemoved()) {
DBTasks.removeFeedWithDownloadUrl(GpodnetSyncService.this, downloadUrl);
}
}
private void clearErrorNotifications() {
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
nm.cancel(R.id.notification_gpodnet_sync_error);
nm.cancel(R.id.notification_gpodnet_sync_autherror);
}
private void updateErrorNotification(GpodnetServiceException exception) {
if (AppConfig.DEBUG) Log.d(TAG, "Posting error notification");
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
final String title;
final String description;
final int id;
if (exception instanceof GpodnetServiceAuthenticationException) {
title = getString(R.string.gpodnetsync_auth_error_title);
description = getString(R.string.gpodnetsync_auth_error_descr);
id = R.id.notification_gpodnet_sync_autherror;
} else {
title = getString(R.string.gpodnetsync_error_title);
description = getString(R.string.gpodnetsync_error_descr) + exception.getMessage();
id = R.id.notification_gpodnet_sync_error;
}
Notification notification = builder.setContentTitle(title)
.setContentText(description)
.setSmallIcon(R.drawable.stat_notify_sync_error)
.setAutoCancel(true)
.build();
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(id, notification);
}
private WaiterThread syncWaiterThread = new WaiterThread(WAIT_INTERVAL) {
@Override
public void onWaitCompleted() {
syncChanges();
}
};
private abstract class WaiterThread {
private long waitInterval;
private Thread thread;
private WaiterThread(long waitInterval) {
this.waitInterval = waitInterval;
reinit();
}
public abstract void onWaitCompleted();
public void exec() {
if (!thread.isAlive()) {
thread.start();
}
}
private void reinit() {
if (thread != null && thread.isAlive()) {
Log.d(TAG, "Interrupting waiter thread");
thread.interrupt();
}
thread = new Thread() {
@Override
public void run() {
try {
Thread.sleep(waitInterval);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!isInterrupted()) {
synchronized (this) {
onWaitCompleted();
}
}
}
};
}
public void restart() {
reinit();
exec();
}
public void interrupt() {
if (thread != null && thread.isAlive()) {
thread.interrupt();
}
}
}
public static void sendSyncIntent(Context context) {
if (GpodnetPreferences.loggedIn()) {
Intent intent = new Intent(context, GpodnetSyncService.class);
intent.putExtra(ARG_ACTION, ACTION_SYNC);
context.startService(intent);
}
}
}

View File

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

View File

@ -184,7 +184,7 @@ public class DownloadService extends Service {
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent.getParcelableExtra(EXTRA_REQUEST) != null) {
onDownloadQueued(intent);
} else if (numberOfDownloads.equals(0)) {
} else if (numberOfDownloads.get() == 0) {
stopSelf();
}
return Service.START_NOT_STICKY;
@ -421,52 +421,24 @@ public class DownloadService extends Service {
return null;
}
@SuppressLint("NewApi")
public void onDownloadCompleted(final Downloader downloader) {
final AsyncTask<Void, Void, Void> handlerTask = new AsyncTask<Void, Void, Void>() {
boolean successful;
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
if (!successful) {
queryDownloads();
}
}
@Override
protected void onPreExecute() {
super.onPreExecute();
removeDownload(downloader);
}
@Override
protected Void doInBackground(Void... params) {
return null;
}
};
if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.GINGERBREAD_MR1) {
handlerTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} else {
handlerTask.execute();
}
}
/**
* Remove download from the DownloadRequester list and from the
* DownloadService list.
*/
private void removeDownload(final Downloader d) {
if (AppConfig.DEBUG)
Log.d(TAG, "Removing downloader: "
+ d.getDownloadRequest().getSource());
boolean rc = downloads.remove(d);
if (AppConfig.DEBUG)
Log.d(TAG, "Result of downloads.remove: " + rc);
DownloadRequester.getInstance().removeDownload(d.getDownloadRequest());
sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
handler.post(new Runnable() {
@Override
public void run() {
if (AppConfig.DEBUG)
Log.d(TAG, "Removing downloader: "
+ d.getDownloadRequest().getSource());
boolean rc = downloads.remove(d);
if (AppConfig.DEBUG)
Log.d(TAG, "Result of downloads.remove: " + rc);
DownloadRequester.getInstance().removeDownload(d.getDownloadRequest());
sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
}
});
}
/**
@ -828,8 +800,9 @@ public class DownloadService extends Service {
media.setFile_url(request.getDestination());
// Get duration
MediaPlayer mediaplayer = new MediaPlayer();
MediaPlayer mediaplayer = null;
try {
mediaplayer = new MediaPlayer();
mediaplayer.setDataSource(media.getFile_url());
mediaplayer.prepare();
media.setDuration(mediaplayer.getDuration());
@ -838,8 +811,13 @@ public class DownloadService extends Service {
mediaplayer.reset();
} catch (IOException e) {
e.printStackTrace();
} catch (RuntimeException e) {
// Thrown by MediaPlayer initialization on some devices
e.printStackTrace();
} finally {
mediaplayer.release();
if (mediaplayer != null) {
mediaplayer.release();
}
}
if (media.getItem().getChapters() == null) {

View File

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

View File

@ -6,12 +6,12 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
@ -30,161 +30,186 @@ import de.danoeh.antennapod.util.DownloadError;
import de.danoeh.antennapod.util.StorageUtils;
public class HttpDownloader extends Downloader {
private static final String TAG = "HttpDownloader";
private static final 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 CONNECTION_TIMEOUT = 30000;
private static final int SOCKET_TIMEOUT = 30000;
private static final int BUFFER_SIZE = 8 * 1024;
private static final int CONNECTION_TIMEOUT = 30000;
private static final int SOCKET_TIMEOUT = 30000;
public HttpDownloader(DownloadRequest request) {
super(request);
}
public HttpDownloader(DownloadRequest request) {
super(request);
}
private DefaultHttpClient createHttpClient() {
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpParams params = httpClient.getParams();
params.setIntParameter("http.protocol.max-redirects", MAX_REDIRECTS);
params.setBooleanParameter("http.protocol.reject-relative-redirect",
false);
HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT);
HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT);
HttpClientParams.setRedirecting(params, true);
private DefaultHttpClient createHttpClient() {
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpParams params = httpClient.getParams();
params.setIntParameter("http.protocol.max-redirects", MAX_REDIRECTS);
params.setBooleanParameter("http.protocol.reject-relative-redirect",
false);
HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT);
HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT);
HttpClientParams.setRedirecting(params, true);
// Workaround for broken URLs in redirection
((AbstractHttpClient) httpClient)
.setRedirectHandler(new APRedirectHandler());
return httpClient;
}
// Workaround for broken URLs in redirection
((AbstractHttpClient) httpClient)
.setRedirectHandler(new APRedirectHandler());
return httpClient;
}
@Override
protected void download() {
DefaultHttpClient httpClient = null;
OutputStream out = null;
InputStream connection = null;
try {
HttpGet httpGet = new HttpGet(request.getSource());
httpClient = createHttpClient();
HttpResponse response = httpClient.execute(httpGet);
HttpEntity httpEntity = response.getEntity();
int responseCode = response.getStatusLine().getStatusCode();
if (AppConfig.DEBUG)
Log.d(TAG, "Response code is " + responseCode);
if (responseCode == HttpURLConnection.HTTP_OK && httpEntity != null) {
if (StorageUtils.storageAvailable(PodcastApp.getInstance())) {
File destination = new File(request.getDestination());
if (!destination.exists()) {
connection = AndroidHttpClient
.getUngzippedContent(httpEntity);
InputStream in = new BufferedInputStream(connection);
out = new BufferedOutputStream(new FileOutputStream(
destination));
byte[] buffer = new byte[BUFFER_SIZE];
int count = 0;
request.setStatusMsg(R.string.download_running);
if (AppConfig.DEBUG)
Log.d(TAG, "Getting size of download");
request.setSize(httpEntity.getContentLength());
if (AppConfig.DEBUG)
Log.d(TAG, "Size is " + request.getSize());
if (request.getSize() < 0) {
request.setSize(DownloadStatus.SIZE_UNKNOWN);
}
@Override
protected void download() {
DefaultHttpClient httpClient = null;
BufferedOutputStream out = null;
InputStream connection = null;
try {
HttpGet httpGet = new HttpGet(request.getSource());
httpClient = createHttpClient();
HttpResponse response = httpClient.execute(httpGet);
HttpEntity httpEntity = response.getEntity();
int responseCode = response.getStatusLine().getStatusCode();
Header contentEncodingHeader = response.getFirstHeader("Content-Encoding");
long freeSpace = StorageUtils.getFreeSpaceAvailable();
if (AppConfig.DEBUG)
Log.d(TAG, "Free space is " + freeSpace);
if (request.getSize() == DownloadStatus.SIZE_UNKNOWN
|| request.getSize() <= freeSpace) {
if (AppConfig.DEBUG)
Log.d(TAG, "Starting download");
while (!cancelled
&& (count = in.read(buffer)) != -1) {
out.write(buffer, 0, count);
request.setSoFar(request.getSoFar() + count);
request.setProgressPercent((int) (((double) request
.getSoFar() / (double) request
.getSize()) * 100));
}
if (cancelled) {
onCancelled();
} else {
onSuccess();
}
} else {
onFail(DownloadError.ERROR_NOT_ENOUGH_SPACE, null);
}
} else {
Log.w(TAG, "File already exists");
onFail(DownloadError.ERROR_FILE_EXISTS, null);
}
} else {
onFail(DownloadError.ERROR_DEVICE_NOT_FOUND, null);
}
} else {
onFail(DownloadError.ERROR_HTTP_DATA_ERROR,
String.valueOf(responseCode));
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
onFail(DownloadError.ERROR_MALFORMED_URL, e.getMessage());
} catch (SocketTimeoutException e) {
e.printStackTrace();
onFail(DownloadError.ERROR_CONNECTION_ERROR, e.getMessage());
} catch (UnknownHostException e) {
e.printStackTrace();
onFail(DownloadError.ERROR_UNKNOWN_HOST, e.getMessage());
} catch (IOException e) {
e.printStackTrace();
onFail(DownloadError.ERROR_IO_ERROR, e.getMessage());
} catch (NullPointerException e) {
// might be thrown by connection.getInputStream()
e.printStackTrace();
onFail(DownloadError.ERROR_CONNECTION_ERROR, request.getSource());
} finally {
IOUtils.closeQuietly(out);
if (httpClient != null) {
httpClient.getConnectionManager().shutdown();
}
}
}
final boolean isGzip = contentEncodingHeader != null &&
contentEncodingHeader.getValue().equalsIgnoreCase("gzip");
private void onSuccess() {
if (AppConfig.DEBUG)
Log.d(TAG, "Download was successful");
result.setSuccessful();
}
if (AppConfig.DEBUG)
Log.d(TAG, "Response code is " + responseCode);
private void onFail(DownloadError reason, String reasonDetailed) {
if (AppConfig.DEBUG) {
Log.d(TAG, "Download failed");
}
if (responseCode != HttpURLConnection.HTTP_OK || httpEntity == null) {
onFail(DownloadError.ERROR_HTTP_DATA_ERROR,
String.valueOf(responseCode));
return;
}
if (!StorageUtils.storageAvailable(PodcastApp.getInstance())) {
onFail(DownloadError.ERROR_DEVICE_NOT_FOUND, null);
return;
}
File destination = new File(request.getDestination());
if (destination.exists()) {
Log.w(TAG, "File already exists");
onFail(DownloadError.ERROR_FILE_EXISTS, null);
return;
}
connection = new BufferedInputStream(AndroidHttpClient
.getUngzippedContent(httpEntity));
out = new BufferedOutputStream(new FileOutputStream(
destination));
byte[] buffer = new byte[BUFFER_SIZE];
int count = 0;
request.setStatusMsg(R.string.download_running);
if (AppConfig.DEBUG)
Log.d(TAG, "Getting size of download");
request.setSize(httpEntity.getContentLength());
if (AppConfig.DEBUG)
Log.d(TAG, "Size is " + request.getSize());
if (request.getSize() < 0) {
request.setSize(DownloadStatus.SIZE_UNKNOWN);
}
long freeSpace = StorageUtils.getFreeSpaceAvailable();
if (AppConfig.DEBUG)
Log.d(TAG, "Free space is " + freeSpace);
if (request.getSize() != DownloadStatus.SIZE_UNKNOWN
&& request.getSize() > freeSpace) {
onFail(DownloadError.ERROR_NOT_ENOUGH_SPACE, null);
return;
}
if (AppConfig.DEBUG)
Log.d(TAG, "Starting download");
while (!cancelled
&& (count = connection.read(buffer)) != -1) {
out.write(buffer, 0, count);
request.setSoFar(request.getSoFar() + count);
request.setProgressPercent((int) (((double) request
.getSoFar() / (double) request
.getSize()) * 100));
}
if (cancelled) {
onCancelled();
} else {
out.flush();
// check if size specified in the response header is the same as the size of the
// written file. This check cannot be made if compression was used
if (!isGzip && request.getSize() != DownloadStatus.SIZE_UNKNOWN &&
request.getSoFar() != request.getSize()) {
onFail(DownloadError.ERROR_IO_ERROR,
"Download completed but size: " +
request.getSoFar() +
" does not equal expected size " +
request.getSize());
return;
}
onSuccess();
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
onFail(DownloadError.ERROR_MALFORMED_URL, e.getMessage());
} catch (SocketTimeoutException e) {
e.printStackTrace();
onFail(DownloadError.ERROR_CONNECTION_ERROR, e.getMessage());
} catch (UnknownHostException e) {
e.printStackTrace();
onFail(DownloadError.ERROR_UNKNOWN_HOST, e.getMessage());
} catch (IOException e) {
e.printStackTrace();
onFail(DownloadError.ERROR_IO_ERROR, e.getMessage());
} catch (NullPointerException e) {
// might be thrown by connection.getInputStream()
e.printStackTrace();
onFail(DownloadError.ERROR_CONNECTION_ERROR, request.getSource());
} finally {
IOUtils.closeQuietly(out);
if (httpClient != null) {
httpClient.getConnectionManager().shutdown();
}
}
}
private void onSuccess() {
if (AppConfig.DEBUG)
Log.d(TAG, "Download was successful");
result.setSuccessful();
}
private void onFail(DownloadError reason, String reasonDetailed) {
if (AppConfig.DEBUG) {
Log.d(TAG, "Download failed");
}
result.setFailed(reason, reasonDetailed);
cleanup();
}
cleanup();
}
private void onCancelled() {
if (AppConfig.DEBUG)
Log.d(TAG, "Download was cancelled");
private void onCancelled() {
if (AppConfig.DEBUG)
Log.d(TAG, "Download was cancelled");
result.setCancelled();
cleanup();
}
cleanup();
}
/** Deletes unfinished downloads. */
private void cleanup() {
if (request.getDestination() != null) {
File dest = new File(request.getDestination());
if (dest.exists()) {
boolean rc = dest.delete();
if (AppConfig.DEBUG)
Log.d(TAG, "Deleted file " + dest.getName() + "; Result: "
+ rc);
} else {
if (AppConfig.DEBUG)
Log.d(TAG, "cleanup() didn't delete file: does not exist.");
}
}
}
/**
* Deletes unfinished downloads.
*/
private void cleanup() {
if (request.getDestination() != null) {
File dest = new File(request.getDestination());
if (dest.exists()) {
boolean rc = dest.delete();
if (AppConfig.DEBUG)
Log.d(TAG, "Deleted file " + dest.getName() + "; Result: "
+ rc);
} else {
if (AppConfig.DEBUG)
Log.d(TAG, "cleanup() didn't delete file: does not exist.");
}
}
}
}

View File

@ -75,6 +75,27 @@ public final class DBReader {
return feeds;
}
/**
* Returns a list with the download URLs of all feeds.
* @param context A context that is used for opening the database connection.
* @return A list of Strings with the download URLs of all feeds.
* */
public static List<String> getFeedListDownloadUrls(final Context context) {
PodDBAdapter adapter = new PodDBAdapter(context);
List<String> result = new ArrayList<String>();
adapter.open();
Cursor feeds = adapter.getFeedCursorDownloadUrls();
if (feeds.moveToFirst()) {
do {
result.add(feeds.getString(1));
} while (feeds.moveToNext());
}
feeds.close();
adapter.close();
return result;
}
/**
* Returns a list of 'expired Feeds', i.e. Feeds that have not been updated for a certain amount of time.
*
@ -229,9 +250,11 @@ public final class DBReader {
title, item, link);
break;
}
chapter.setId(chapterCursor
.getLong(PodDBAdapter.KEY_ID_INDEX));
item.getChapters().add(chapter);
if (chapter != null) {
chapter.setId(chapterCursor
.getLong(PodDBAdapter.KEY_ID_INDEX));
item.getChapters().add(chapter);
}
} while (chapterCursor.moveToNext());
}
chapterCursor.close();

View File

@ -23,11 +23,13 @@ import de.danoeh.antennapod.feed.FeedImage;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.feed.FeedMedia;
import de.danoeh.antennapod.preferences.UserPreferences;
import de.danoeh.antennapod.service.GpodnetSyncService;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.service.download.DownloadStatus;
import de.danoeh.antennapod.util.DownloadError;
import de.danoeh.antennapod.util.NetworkUtils;
import de.danoeh.antennapod.util.QueueAccess;
import de.danoeh.antennapod.util.comparator.FeedItemPubdateComparator;
import de.danoeh.antennapod.util.exception.MediaFileNotFoundException;
/**
@ -39,6 +41,39 @@ public final class DBTasks {
private DBTasks() {
}
/**
* Removes the feed with the given download url. This method should NOT be executed on the GUI thread.
* @param context Used for accessing the db
* @param downloadUrl URL of the feed.
* */
public static void removeFeedWithDownloadUrl(Context context, String downloadUrl) {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
Cursor cursor = adapter.getFeedCursorDownloadUrls();
long feedID = 0;
if (cursor.moveToFirst()) {
do {
if (cursor.getString(1).equals(downloadUrl)) {
feedID = cursor.getLong(0);
}
} while (cursor.moveToNext());
}
cursor.close();
adapter.close();
if (feedID != 0) {
try {
DBWriter.deleteFeed(context, feedID).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
} else {
Log.w(TAG, "removeFeedWithDownloadUrl: Could not find feed with url: " + downloadUrl);
}
}
/**
* Starts playback of a FeedMedia object's file. This method will build an Intent based on the given parameters to
* start the {@link PlaybackService}.
@ -110,6 +145,8 @@ public final class DBTasks {
refreshFeeds(context, DBReader.getFeedList(context));
}
isRefreshing.set(false);
GpodnetSyncService.sendSyncIntent(context);
}
}.start();
} else {
@ -406,12 +443,13 @@ public final class DBTasks {
private static int performAutoCleanup(final Context context,
final int episodeNumber) {
List<FeedItem> candidates = DBReader.getDownloadedItems(context);
List<FeedItem> queue = DBReader.getQueue(context);
List<FeedItem> candidates = new ArrayList<FeedItem>();
List<FeedItem> downloadedItems = DBReader.getDownloadedItems(context);
QueueAccess queue = QueueAccess.IDListAccess(DBReader.getQueueIDList(context));
List<FeedItem> delete;
for (FeedItem item : candidates) {
for (FeedItem item : downloadedItems) {
if (item.hasMedia() && item.getMedia().isDownloaded()
&& !queue.contains(item) && item.isRead()) {
&& !queue.contains(item.getId()) && item.isRead()) {
candidates.add(item);
}
@ -440,7 +478,13 @@ public final class DBTasks {
}
for (FeedItem item : delete) {
DBWriter.deleteFeedMediaOfItem(context, item.getId());
try {
DBWriter.deleteFeedMediaOfItem(context, item.getId()).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
int counter = delete.size();
@ -561,6 +605,7 @@ public final class DBTasks {
Log.d(TAG, "Feed with title " + newFeed.getTitle()
+ " already exists. Syncing new with existing one.");
Collections.sort(newFeed.getItems(), new FeedItemPubdateComparator());
savedFeed.setItems(DBReader.getFeedItemList(context, savedFeed));
if (savedFeed.compareWithOther(newFeed)) {
if (AppConfig.DEBUG)
@ -578,7 +623,7 @@ public final class DBTasks {
final int i = idx;
item.setFeed(savedFeed);
savedFeed.getItems().add(i, item);
DBWriter.markItemRead(context, item.getId(), false);
item.setRead(false);
} else {
oldItem.updateFromOther(item);
}

View File

@ -4,6 +4,7 @@ import java.io.File;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@ -17,7 +18,9 @@ import android.preference.PreferenceManager;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.feed.*;
import de.danoeh.antennapod.preferences.GpodnetPreferences;
import de.danoeh.antennapod.preferences.PlaybackPreferences;
import de.danoeh.antennapod.service.GpodnetSyncService;
import de.danoeh.antennapod.service.PlaybackService;
import de.danoeh.antennapod.service.download.DownloadStatus;
import de.danoeh.antennapod.util.QueueAccess;
@ -101,6 +104,8 @@ public class DBWriter {
}
if (AppConfig.DEBUG)
Log.d(TAG, "Deleting File. Result: " + result);
EventDistributor.getInstance().sendQueueUpdateBroadcast();
EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast();
}
}
});
@ -171,6 +176,8 @@ public class DBWriter {
}
adapter.removeFeed(feed);
adapter.close();
GpodnetPreferences.addRemovedFeed(feed.getDownload_url());
EventDistributor.getInstance().sendFeedUpdateBroadcast();
}
}
@ -215,7 +222,7 @@ public class DBWriter {
media.setPlaybackCompletionDate(new Date());
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
adapter.setMedia(media);
adapter.setFeedMediaPlaybackCompletionDate(media);
adapter.close();
EventDistributor.getInstance().sendPlaybackHistoryUpdateBroadcast();
@ -685,6 +692,7 @@ public class DBWriter {
adapter.setCompleteFeed(feed);
adapter.close();
GpodnetPreferences.addAddedFeed(feed.getDownload_url());
EventDistributor.getInstance().sendFeedUpdateBroadcast();
}
});
@ -787,6 +795,26 @@ public class DBWriter {
});
}
/**
* Updates download URLs of feeds from a given Map. The key of the Map is the original URL of the feed
* and the value is the updated URL
* */
public static Future<?> updateFeedDownloadURLs(final Context context, final Map<String, String> urls) {
return dbExec.submit(new Runnable() {
@Override
public void run() {
PodDBAdapter adapter = new PodDBAdapter(context);
adapter.open();
for (String key : urls.keySet()) {
if (AppConfig.DEBUG) Log.d(TAG, "Replacing URL " + key + " with url " + urls.get(key));
adapter.setFeedDownloadUrl(key, urls.get(key));
}
adapter.close();
}
});
}
private static boolean itemListContains(List<FeedItem> items, long itemId) {
for (FeedItem item : items) {
if (item.getId() == itemId) {

View File

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

Some files were not shown because too many files have changed in this diff Show More