Merge branch 'develop' of https://github.com/danieloeh/AntennaPod into move-to-top
This commit is contained in:
commit
34a5e62339
|
@ -41,3 +41,4 @@ proguard
|
|||
libs
|
||||
*.DS_Store
|
||||
src/de/danoeh/antennapod/util/flattr/FlattrConfig.java
|
||||
gradle.properties
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" />
|
||||
|
@ -46,6 +46,7 @@
|
|||
<meta-data
|
||||
android:name="android.app.searchable"
|
||||
android:resource="@xml/searchable"/>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
@ -58,9 +59,12 @@
|
|||
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:scheme="https"/>
|
||||
<data android:host="*"/>
|
||||
<data android:pathPattern=".*\\.xml"/>
|
||||
<data android:pathPattern=".*\\.rss"/>
|
||||
|
@ -68,9 +72,12 @@
|
|||
|
||||
<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="feeds.feedburner.com"/>
|
||||
<data android:host="feedproxy.google.com"/>
|
||||
<data android:host="feeds2.feedburner.com"/>
|
||||
|
@ -79,9 +86,12 @@
|
|||
|
||||
<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"/>
|
||||
|
@ -133,6 +143,11 @@
|
|||
android:enabled="true">
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".service.GpodnetSyncService"
|
||||
android:enabled="true">
|
||||
</service>
|
||||
|
||||
<activity
|
||||
android:name=".activity.PreferenceActivity"
|
||||
android:configChanges="keyboardHidden|orientation"
|
||||
|
@ -317,6 +332,70 @@
|
|||
android:configChanges="orientation"
|
||||
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>
|
||||
|
||||
<activity
|
||||
android:name=".activity.gpoddernet.GpodnetSearchActivity"
|
||||
android:configChanges="orientation"
|
||||
android:label="@string/search_label"
|
||||
android:launchMode="singleTop">
|
||||
<intent-filter>
|
||||
<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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
44
build.gradle
44
build.gradle
|
@ -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
12
pom.xml
|
@ -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>
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
android:id="@+id/footer"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="48dp"
|
||||
android:focusableInTouchMode="true"
|
||||
android:layout_alignParentBottom="true">
|
||||
|
||||
<View
|
||||
|
@ -65,9 +66,10 @@
|
|||
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:layout_margin="16dp"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
android:textColor="@color/bright_blue"
|
||||
android:textStyle="italic"
|
||||
android:text="@string/txtvfeedurl_label"/>
|
||||
|
||||
<EditText
|
||||
|
@ -80,20 +82,32 @@
|
|||
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,13 +115,24 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/butBrowseMiroguide"
|
||||
android:layout_margin="8dp"
|
||||
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"
|
||||
|
|
|
@ -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>
|
|
@ -43,9 +43,10 @@
|
|||
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:layout_margin="16dp"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
android:textColor="@color/bright_blue"
|
||||
android:textStyle="italic"
|
||||
android:text="@string/txtvfeedurl_label"/>
|
||||
|
||||
<EditText
|
||||
|
@ -58,20 +59,32 @@
|
|||
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,13 +92,24 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/butBrowseMiroguide"
|
||||
android:layout_margin="8dp"
|
||||
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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
@ -146,6 +152,12 @@
|
|||
<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>
|
||||
<string name="no_feeds_label">You haven\'t subscribed to any feeds yet.</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>
|
|
@ -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>
|
|
@ -2,9 +2,19 @@
|
|||
<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"
|
||||
|
@ -17,7 +27,10 @@
|
|||
android:key="prefFollowQueue"
|
||||
android:summary="@string/pref_followQueue_sum"
|
||||
android:title="@string/pref_followQueue_title"/>
|
||||
|
||||
<Preference
|
||||
android:key="prefPlaybackSpeedLauncher"
|
||||
android:summary="@string/pref_playback_speed_sum"
|
||||
android:title="@string/pref_playback_speed_title" />
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory android:title="@string/network_pref">
|
||||
<ListPreference
|
||||
|
@ -34,13 +47,32 @@
|
|||
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"/>
|
||||
<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" >
|
||||
<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"
|
||||
|
@ -52,14 +84,40 @@
|
|||
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
|
||||
<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">
|
||||
</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"/>
|
||||
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,18 +24,27 @@ 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";
|
||||
public static final String ARG_FEEDURL = "arg.feedurl";
|
||||
|
||||
/** Optional argument: specify a title for the actionbar. */
|
||||
public static final String ARG_TITLE = "title";
|
||||
|
||||
public static final int RESULT_ERROR = 2;
|
||||
|
||||
|
@ -55,6 +55,11 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
|
|||
protected void onCreate(Bundle arg0) {
|
||||
setTheme(UserPreferences.getTheme());
|
||||
super.onCreate(arg0);
|
||||
|
||||
if (getIntent() != null && getIntent().hasExtra(ARG_TITLE)) {
|
||||
getSupportActionBar().setTitle(getIntent().getStringExtra(ARG_TITLE));
|
||||
}
|
||||
|
||||
StorageUtils.checkStorageAvailability(this);
|
||||
final String feedUrl = getIntent().getStringExtra(ARG_FEEDURL);
|
||||
if (feedUrl == null) {
|
||||
|
@ -75,13 +80,13 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
|
|||
}
|
||||
}
|
||||
|
||||
private DownloaderCallback downloaderCallback = new DownloaderCallback() {
|
||||
@Override
|
||||
public void onDownloadCompleted(final Downloader downloader) {
|
||||
|
||||
private void onDownloadCompleted(final Downloader downloader) {
|
||||
runOnUiThread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (AppConfig.DEBUG) Log.d(TAG, "Download was completed");
|
||||
DownloadStatus status = downloader.getResult();
|
||||
if (status != null) {
|
||||
if (!status.isCancelled()) {
|
||||
|
@ -107,7 +112,6 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
|
|||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
private void startFeedDownload(String url) {
|
||||
if (AppConfig.DEBUG)
|
||||
|
@ -118,30 +122,39 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
|
|||
FileNameGenerator.generateFileName(feed.getDownload_url()))
|
||||
.toString();
|
||||
feed.setFile_url(fileUrl);
|
||||
DownloadRequest request = new DownloadRequest(feed.getFile_url(),
|
||||
final DownloadRequest request = new DownloadRequest(feed.getFile_url(),
|
||||
feed.getDownload_url(), "OnlineFeed", 0, Feed.FEEDFILETYPE_FEED);
|
||||
/* TODO update
|
||||
HttpDownloader httpDownloader = new HttpDownloader(downloaderCallback,
|
||||
downloader = new HttpDownloader(
|
||||
request);
|
||||
httpDownloader.start();
|
||||
*/
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
loadData();
|
||||
downloader.call();
|
||||
onDownloadCompleted(downloader);
|
||||
}
|
||||
}.start();
|
||||
|
||||
|
||||
}
|
||||
|
||||
/** Displays a progress indicator. */
|
||||
/**
|
||||
* Displays a progress indicator.
|
||||
*/
|
||||
private void setLoadingLayout() {
|
||||
LinearLayout ll = new LinearLayout(this);
|
||||
LinearLayout.LayoutParams llLayoutParams = new LinearLayout.LayoutParams(
|
||||
RelativeLayout rl = new RelativeLayout(this);
|
||||
RelativeLayout.LayoutParams rlLayoutParams = new RelativeLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
LinearLayout.LayoutParams.MATCH_PARENT);
|
||||
|
||||
ProgressBar pb = new ProgressBar(this);
|
||||
pb.setIndeterminate(true);
|
||||
LinearLayout.LayoutParams pbLayoutParams = new LinearLayout.LayoutParams(
|
||||
RelativeLayout.LayoutParams pbLayoutParams = new RelativeLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
pbLayoutParams.gravity = Gravity.CENTER;
|
||||
ll.addView(pb, pbLayoutParams);
|
||||
addContentView(ll, llLayoutParams);
|
||||
pbLayoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
|
||||
rl.addView(pb, pbLayoutParams);
|
||||
addContentView(rl, rlLayoutParams);
|
||||
}
|
||||
|
||||
private void parseFeed() {
|
||||
|
@ -157,7 +170,7 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
|
|||
|
||||
@Override
|
||||
public void run() {
|
||||
String reasonDetailed = new String();
|
||||
String reasonDetailed = "";
|
||||
boolean successful = false;
|
||||
FeedHandler handler = new FeedHandler();
|
||||
try {
|
||||
|
@ -185,7 +198,7 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
|
|||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
showFeedInformation();
|
||||
showFeedInformation(feed);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
@ -206,8 +219,17 @@ public abstract class OnlineFeedViewActivity extends ActionBarActivity {
|
|||
thread.start();
|
||||
}
|
||||
|
||||
/** Called when feed parsed successfully */
|
||||
protected void showFeedInformation() {
|
||||
/**
|
||||
* Can be used to load data asynchronously.
|
||||
* */
|
||||
protected void loadData() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when feed parsed successfully
|
||||
*/
|
||||
protected void showFeedInformation(Feed feed) {
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -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")
|
||||
|
|
|
@ -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,10 +141,10 @@ 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() {
|
||||
|
@ -154,7 +155,9 @@ public class SearchActivity extends ActionBarActivity implements AdapterView.OnI
|
|||
+ " results");
|
||||
|
||||
searchAdapter.clear();
|
||||
searchAdapter.addAll(result);
|
||||
for (SearchResult s : result) {
|
||||
searchAdapter.add(s);
|
||||
}
|
||||
searchAdapter.notifyDataSetChanged();
|
||||
txtvStatus
|
||||
.setText(R.string.search_status_no_results);
|
||||
|
@ -165,7 +168,7 @@ public class SearchActivity extends ActionBarActivity implements AdapterView.OnI
|
|||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
thread.start();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -2,10 +2,14 @@ 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;
|
||||
|
@ -13,8 +17,11 @@ import de.danoeh.antennapod.util.BitmapDecoder;
|
|||
public class BitmapDecodeWorkerTask extends Thread {
|
||||
|
||||
protected int PREFERRED_LENGTH;
|
||||
public static final int FADE_DURATION = 500;
|
||||
|
||||
/** Can be thumbnail or cover */
|
||||
/**
|
||||
* Can be thumbnail or cover
|
||||
*/
|
||||
protected int imageType;
|
||||
|
||||
private static final String TAG = "BitmapDecodeWorkerTask";
|
||||
|
@ -35,10 +42,7 @@ public class BitmapDecodeWorkerTask extends Thread {
|
|||
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();
|
||||
this.defaultCoverResource = android.R.color.transparent;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,14 +50,20 @@ public class BitmapDecodeWorkerTask extends Thread {
|
|||
* before the bitmap was decoded
|
||||
*/
|
||||
protected boolean tagsMatching(ImageView target) {
|
||||
return target.getTag() == null
|
||||
|| target.getTag() == imageResource.getImageLoaderCacheKey();
|
||||
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());
|
||||
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");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,6 +106,7 @@ public class ImageLoader {
|
|||
.getContext());
|
||||
|
||||
if (source != null && source.getImageLoaderCacheKey() != null) {
|
||||
target.setTag(R.id.imageloader_key, source.getImageLoaderCacheKey());
|
||||
CachedBitmap cBitmap = getBitmapFromCoverCache(source.getImageLoaderCacheKey());
|
||||
if (cBitmap != null && cBitmap.getLength() >= length) {
|
||||
target.setImageBitmap(cBitmap.getBitmap());
|
||||
|
@ -143,6 +144,7 @@ public class ImageLoader {
|
|||
.getContext());
|
||||
|
||||
if (source != null && source.getImageLoaderCacheKey() != null) {
|
||||
target.setTag(R.id.imageloader_key, source.getImageLoaderCacheKey());
|
||||
CachedBitmap cBitmap = getBitmapFromThumbnailCache(source.getImageLoaderCacheKey());
|
||||
if (cBitmap != null && cBitmap.getLength() >= length) {
|
||||
target.setImageBitmap(cBitmap.getBitmap());
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -173,6 +173,12 @@ public class ExternalPlayerFragment extends Fragment {
|
|||
.newOnPlayButtonClickListener());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaybackSpeedChange() {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
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 (selection != null) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Selected Feed with title " + selection.getTitle());
|
||||
if (selection != null) {
|
||||
if (mActionMode != null) {
|
||||
mActionMode.finish();
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
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});
|
||||
|
|
|
@ -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;
|
||||
|
@ -117,11 +118,21 @@ public class ItemlistFragment extends ListFragment {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
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;
|
||||
if (feed == null) {
|
||||
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
new InputStreamReader(entity.getContent(),
|
||||
LangUtils.UTF_8));
|
||||
try {
|
||||
result = reader.readLine();
|
||||
in.close();
|
||||
} finally {
|
||||
reader.close();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new MiroGuideException(response.getStatusLine()
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,6 +123,11 @@ 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,18 +280,39 @@ 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);
|
||||
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;
|
||||
}
|
||||
|
@ -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,9 +691,22 @@ public class PlaybackService extends Service {
|
|||
}
|
||||
}
|
||||
|
||||
private MediaPlayer.OnPreparedListener preparedListener = new MediaPlayer.OnPreparedListener() {
|
||||
private final com.aocate.media.MediaPlayer.OnPreparedListener audioPreparedListener = new com.aocate.media.MediaPlayer.OnPreparedListener() {
|
||||
@Override
|
||||
public void onPrepared(MediaPlayer mp) {
|
||||
public void onPrepared(com.aocate.media.MediaPlayer mp) {
|
||||
genericOnPrepared(mp);
|
||||
}
|
||||
};
|
||||
|
||||
private final android.media.MediaPlayer.OnPreparedListener videoPreparedListener = new android.media.MediaPlayer.OnPreparedListener() {
|
||||
@Override
|
||||
public void onPrepared(android.media.MediaPlayer mp) {
|
||||
genericOnPrepared(mp);
|
||||
}
|
||||
};
|
||||
|
||||
private final void genericOnPrepared(Object inObj) {
|
||||
IPlayer mp = DuckType.coerce(inObj).to(IPlayer.class);
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Resource prepared");
|
||||
mp.seekTo(media.getPosition());
|
||||
|
@ -699,23 +741,43 @@ public class PlaybackService extends Service {
|
|||
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.OnSeekCompleteListener onSeekCompleteListener = new MediaPlayer.OnSeekCompleteListener() {
|
||||
|
||||
private final android.media.MediaPlayer.OnSeekCompleteListener videoSeekCompleteListener = new android.media.MediaPlayer.OnSeekCompleteListener() {
|
||||
@Override
|
||||
public void onSeekComplete(MediaPlayer mp) {
|
||||
public void onSeekComplete(android.media.MediaPlayer mp) {
|
||||
genericSeekCompleteListener();
|
||||
}
|
||||
};
|
||||
|
||||
private final void genericSeekCompleteListener() {
|
||||
if (status == PlayerStatus.SEEKING) {
|
||||
setStatus(statusBeforeSeek);
|
||||
}
|
||||
}
|
||||
|
||||
private final com.aocate.media.MediaPlayer.OnInfoListener audioInfoListener = new com.aocate.media.MediaPlayer.OnInfoListener() {
|
||||
@Override
|
||||
public boolean onInfo(com.aocate.media.MediaPlayer mp, int what,
|
||||
int extra) {
|
||||
return genericInfoListener(what);
|
||||
}
|
||||
};
|
||||
|
||||
private MediaPlayer.OnInfoListener onInfoListener = new MediaPlayer.OnInfoListener() {
|
||||
|
||||
private final android.media.MediaPlayer.OnInfoListener videoInfoListener = new android.media.MediaPlayer.OnInfoListener() {
|
||||
@Override
|
||||
public boolean onInfo(MediaPlayer mp, int what, int extra) {
|
||||
public boolean onInfo(android.media.MediaPlayer mp, int what, int extra) {
|
||||
return genericInfoListener(what);
|
||||
}
|
||||
};
|
||||
|
||||
private boolean genericInfoListener(int what) {
|
||||
switch (what) {
|
||||
case MediaPlayer.MEDIA_INFO_BUFFERING_START:
|
||||
sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_START, 0);
|
||||
|
@ -727,14 +789,26 @@ public class PlaybackService extends Service {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private final com.aocate.media.MediaPlayer.OnErrorListener audioErrorListener = new com.aocate.media.MediaPlayer.OnErrorListener() {
|
||||
@Override
|
||||
public boolean onError(com.aocate.media.MediaPlayer mp, int what,
|
||||
int extra) {
|
||||
return genericOnError(mp, what, extra);
|
||||
}
|
||||
};
|
||||
|
||||
private MediaPlayer.OnErrorListener onErrorListener = new MediaPlayer.OnErrorListener() {
|
||||
private static final String TAG = "PlaybackService.onErrorListener";
|
||||
|
||||
private final android.media.MediaPlayer.OnErrorListener videoErrorListener = new android.media.MediaPlayer.OnErrorListener() {
|
||||
@Override
|
||||
public boolean onError(MediaPlayer mp, int what, int extra) {
|
||||
Log.w(TAG, "An error has occured: " + what);
|
||||
public boolean onError(android.media.MediaPlayer mp, int what, int extra) {
|
||||
return genericOnError(mp, what, extra);
|
||||
}
|
||||
};
|
||||
|
||||
private boolean genericOnError(Object inObj, int what, int extra) {
|
||||
final String TAG = "PlaybackService.onErrorListener";
|
||||
Log.w(TAG, "An error has occured: " + what + " " + extra);
|
||||
IPlayer mp = DuckType.coerce(inObj).to(IPlayer.class);
|
||||
if (mp.isPlaying()) {
|
||||
pause(true, true);
|
||||
}
|
||||
|
@ -743,25 +817,44 @@ public class PlaybackService extends Service {
|
|||
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 MediaPlayer.OnCompletionListener completionListener = new MediaPlayer.OnCompletionListener() {
|
||||
|
||||
private final android.media.MediaPlayer.OnCompletionListener videoCompletionListener = new android.media.MediaPlayer.OnCompletionListener() {
|
||||
@Override
|
||||
public void onCompletion(MediaPlayer mp) {
|
||||
public void onCompletion(android.media.MediaPlayer mp) {
|
||||
genericOnCompletion();
|
||||
}
|
||||
};
|
||||
|
||||
private void genericOnCompletion() {
|
||||
endPlayback(true);
|
||||
}
|
||||
};
|
||||
|
||||
private MediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() {
|
||||
|
||||
private final com.aocate.media.MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener = new com.aocate.media.MediaPlayer.OnBufferingUpdateListener() {
|
||||
@Override
|
||||
public void onBufferingUpdate(MediaPlayer mp, int percent) {
|
||||
sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent);
|
||||
|
||||
public void onBufferingUpdate(com.aocate.media.MediaPlayer mp,
|
||||
int percent) {
|
||||
genericOnBufferingUpdate(percent);
|
||||
}
|
||||
};
|
||||
|
||||
private final android.media.MediaPlayer.OnBufferingUpdateListener videoBufferingUpdateListener = new android.media.MediaPlayer.OnBufferingUpdateListener() {
|
||||
@Override
|
||||
public void onBufferingUpdate(android.media.MediaPlayer mp, int percent) {
|
||||
genericOnBufferingUpdate(percent);
|
||||
}
|
||||
};
|
||||
|
||||
private void genericOnBufferingUpdate(int percent) {
|
||||
sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent);
|
||||
}
|
||||
|
||||
private void endPlayback(boolean playNextEpisode) {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Playback ended");
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -1282,6 +1375,7 @@ public class PlaybackService extends Service {
|
|||
isPlaying = true;
|
||||
}
|
||||
|
||||
if (media != null) {
|
||||
Intent i = new Intent(AVRCP_ACTION_PLAYER_STATUS_CHANGED);
|
||||
i.putExtra("id", 1);
|
||||
i.putExtra("artist", "");
|
||||
|
@ -1295,6 +1389,7 @@ public class PlaybackService extends Service {
|
|||
i.putExtra("position", media.getPosition());
|
||||
sendBroadcast(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses playback when the headset is disconnected and the preference is
|
||||
|
@ -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
|
||||
|
|
|
@ -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,44 +421,14 @@ 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) {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Removing downloader: "
|
||||
+ d.getDownloadRequest().getSource());
|
||||
|
@ -468,6 +438,8 @@ public class DownloadService extends Service {
|
|||
DownloadRequester.getInstance().removeDownload(d.getDownloadRequest());
|
||||
sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new DownloadStatus object to the list of completed downloads and
|
||||
|
@ -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,9 +811,14 @@ 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 {
|
||||
if (mediaplayer != null) {
|
||||
mediaplayer.release();
|
||||
}
|
||||
}
|
||||
|
||||
if (media.getItem().getChapters() == null) {
|
||||
ChapterUtils.loadChaptersFromFileUrl(media);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
@ -61,7 +61,7 @@ public class HttpDownloader extends Downloader {
|
|||
@Override
|
||||
protected void download() {
|
||||
DefaultHttpClient httpClient = null;
|
||||
OutputStream out = null;
|
||||
BufferedOutputStream out = null;
|
||||
InputStream connection = null;
|
||||
try {
|
||||
HttpGet httpGet = new HttpGet(request.getSource());
|
||||
|
@ -69,15 +69,34 @@ public class HttpDownloader extends Downloader {
|
|||
HttpResponse response = httpClient.execute(httpGet);
|
||||
HttpEntity httpEntity = response.getEntity();
|
||||
int responseCode = response.getStatusLine().getStatusCode();
|
||||
Header contentEncodingHeader = response.getFirstHeader("Content-Encoding");
|
||||
|
||||
final boolean isGzip = contentEncodingHeader != null &&
|
||||
contentEncodingHeader.getValue().equalsIgnoreCase("gzip");
|
||||
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Response code is " + responseCode);
|
||||
if (responseCode == HttpURLConnection.HTTP_OK && httpEntity != null) {
|
||||
if (StorageUtils.storageAvailable(PodcastApp.getInstance())) {
|
||||
|
||||
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()) {
|
||||
connection = AndroidHttpClient
|
||||
.getUngzippedContent(httpEntity);
|
||||
InputStream in = new BufferedInputStream(connection);
|
||||
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];
|
||||
|
@ -95,12 +114,17 @@ public class HttpDownloader extends Downloader {
|
|||
long freeSpace = StorageUtils.getFreeSpaceAvailable();
|
||||
if (AppConfig.DEBUG)
|
||||
Log.d(TAG, "Free space is " + freeSpace);
|
||||
if (request.getSize() == DownloadStatus.SIZE_UNKNOWN
|
||||
|| request.getSize() <= 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 = in.read(buffer)) != -1) {
|
||||
&& (count = connection.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, count);
|
||||
request.setSoFar(request.getSoFar() + count);
|
||||
request.setProgressPercent((int) (((double) request
|
||||
|
@ -110,22 +134,21 @@ public class HttpDownloader extends Downloader {
|
|||
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();
|
||||
}
|
||||
} 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());
|
||||
|
@ -171,7 +194,9 @@ public class HttpDownloader extends Downloader {
|
|||
cleanup();
|
||||
}
|
||||
|
||||
/** Deletes unfinished downloads. */
|
||||
/**
|
||||
* Deletes unfinished downloads.
|
||||
*/
|
||||
private void cleanup() {
|
||||
if (request.getDestination() != null) {
|
||||
File dest = new File(request.getDestination());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
if (chapter != null) {
|
||||
chapter.setId(chapterCursor
|
||||
.getLong(PodDBAdapter.KEY_ID_INDEX));
|
||||
item.getChapters().add(chapter);
|
||||
}
|
||||
} while (chapterCursor.moveToNext());
|
||||
}
|
||||
chapterCursor.close();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ public class FeedItemStatistics {
|
|||
this.numberOfItems = numberOfItems;
|
||||
this.numberOfNewItems = numberOfNewItems;
|
||||
this.numberOfInProgressItems = numberOfInProgressItems;
|
||||
this.lastUpdate = lastUpdate;
|
||||
this.lastUpdate = (lastUpdate != null) ? (Date) lastUpdate.clone() : null;
|
||||
}
|
||||
|
||||
public long getFeedID() {
|
||||
|
@ -37,6 +37,6 @@ public class FeedItemStatistics {
|
|||
}
|
||||
|
||||
public Date getLastUpdate() {
|
||||
return lastUpdate;
|
||||
return (lastUpdate != null) ? (Date) lastUpdate.clone() : null;
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue