Merge branch 'develop' into prevent-thrashing

This commit is contained in:
H. Lehmann 2018-06-08 20:40:57 +02:00 committed by GitHub
commit 11c3a45f6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
107 changed files with 2315 additions and 2084 deletions

36
.circleci/config.yml Normal file
View File

@ -0,0 +1,36 @@
version: 2
jobs:
build:
docker:
- image: circleci/android:api-26-alpha
working_directory: ~/AntennaPod
environment:
GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx1536m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError"'
_JAVA_OPTIONS: "-Xms256m -Xmx1280m"
steps:
- checkout
- restore_cache:
keys:
- v1-android-{{ checksum "build.gradle" }}
# fallback to using the latest cache if no exact match is found
- v1-android-
- run:
command: ./gradlew assembleDebug :core:testPlayDebugUnitTest -PdisablePreDex
no_output_timeout: 1800
- store_artifacts:
path: app/build/outputs/apk
destination: apks
- save_cache:
paths:
- ~/.android
- ~/.gradle
- ~/android
key: v1-android-{{ checksum "build.gradle" }}

View File

@ -83,7 +83,14 @@ android {
applicationIdSuffix ".debug"
resValue "string", "provider_authority", "de.danoeh.antennapod.debug.provider"
buildConfigField STRING, FLATTR_APP_KEY, mFlattrAppKey
buildConfigField STRING, FLATTR_APP_SECRET, mFlattrAppSecret
buildConfigField STRING, FLATTR_APP_SECRET, mFlattrAppSecret
dexcount {
if (project.hasProperty("enableDexcountInDebug")) {
runOnEachPackage enableDexcountInDebug.toBoolean()
} else { // default to not running dexcount
runOnEachPackage false
}
}
}
release {
resValue "string", "provider_authority", "de.danoeh.antennapod.provider"
@ -139,6 +146,7 @@ dependencies {
implementation "com.android.support:support-v4:$supportVersion"
implementation "com.android.support:appcompat-v7:$supportVersion"
implementation "com.android.support:design:$supportVersion"
implementation "com.android.support:preference-v14:$supportVersion"
implementation "com.android.support:gridlayout-v7:$supportVersion"
implementation "com.android.support:percent:$supportVersion"
implementation "com.android.support:recyclerview-v7:$supportVersion"
@ -172,6 +180,7 @@ dependencies {
implementation "com.github.AntennaPod:AntennaPod-AudioPlayer:$audioPlayerVersion"
implementation 'com.github.mfietz:fyydlin:v0.3'
implementation 'com.github.ByteHamster:SearchPreference:v1.0.8'
androidTestImplementation "com.jayway.android.robotium:robotium-solo:$robotiumSoloVersion"
}

View File

@ -17,7 +17,6 @@ import javax.xml.parsers.ParserConfigurationException;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.syndication.handler.FeedHandler;
@ -82,15 +81,7 @@ public class FeedHandlerTest extends InstrumentationTestCase {
assertEquals(feed.getLink(), parsedFeed.getLink());
assertEquals(feed.getDescription(), parsedFeed.getDescription());
assertEquals(feed.getPaymentLink(), parsedFeed.getPaymentLink());
if (feed.getImage() != null) {
FeedImage image = feed.getImage();
FeedImage parsedImage = parsedFeed.getImage();
assertNotNull(parsedImage);
assertEquals(image.getTitle(), parsedImage.getTitle());
assertEquals(image.getDownload_url(), parsedImage.getDownload_url());
}
assertEquals(feed.getImageUrl(), parsedFeed.getImageUrl());
if (feed.getItems() != null) {
assertNotNull(parsedFeed.getItems());
@ -119,14 +110,7 @@ public class FeedHandlerTest extends InstrumentationTestCase {
assertEquals(media.getMime_type(), parsedMedia.getMime_type());
}
if (item.hasItemImage()) {
assertTrue(parsedItem.hasItemImage());
FeedImage image = item.getImage();
FeedImage parsedImage = parsedItem.getImage();
assertEquals(image.getTitle(), parsedImage.getTitle());
assertEquals(image.getDownload_url(), parsedImage.getDownload_url());
}
assertEquals(item.getImageUrl(), parsedFeed.getImageUrl());
if (item.getChapters() != null) {
assertNotNull(parsedItem.getChapters());
@ -158,12 +142,8 @@ public class FeedHandlerTest extends InstrumentationTestCase {
}
private Feed createTestFeed(int numItems, boolean withImage, boolean withFeedMedia, boolean withChapters) {
FeedImage image = null;
if (withImage) {
image = new FeedImage(0, "image", null, "http://example.com/picture", false);
}
Feed feed = new Feed(0, null, "title", "http://example.com", "This is the description",
"http://example.com/payment", "Daniel", "en", null, "http://example.com/feed", image, file.getAbsolutePath(),
"http://example.com/payment", "Daniel", "en", null, "http://example.com/feed", "http://example.com/picture", file.getAbsolutePath(),
"http://example.com/feed", true);
feed.setItems(new ArrayList<>());

View File

@ -15,9 +15,7 @@ import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.SimpleChapter;
@ -124,89 +122,13 @@ public class DBWriterTest extends InstrumentationTestCase {
assertNull(media.getFile_url());
}
public void testDeleteFeed() throws IOException, ExecutionException, InterruptedException, TimeoutException {
public void testDeleteFeed() throws ExecutionException, InterruptedException, IOException, TimeoutException {
File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
assertNotNull(destFolder);
Feed feed = new Feed("url", null, "title");
feed.setItems(new ArrayList<>());
// create Feed image
File imgFile = new File(destFolder, "image");
assertTrue(imgFile.createNewFile());
FeedImage image = new FeedImage(0, "image", imgFile.getAbsolutePath(), "url", true);
image.setOwner(feed);
feed.setImage(image);
List<File> itemFiles = new ArrayList<>();
// create items with downloaded media files
for (int i = 0; i < 10; i++) {
FeedItem item = new FeedItem(0, "Item " + i, "Item" + i, "url", new Date(), FeedItem.PLAYED, feed, true);
feed.getItems().add(item);
File enc = new File(destFolder, "file " + i);
assertTrue(enc.createNewFile());
itemFiles.add(enc);
FeedMedia media = new FeedMedia(0, item, 1, 1, 1, "mime_type", enc.getAbsolutePath(), "download_url", true, null, 0, 0);
item.setMedia(media);
item.setChapters(new ArrayList<>());
item.getChapters().add(new SimpleChapter(0, "item " + i, item, "example.com"));
}
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setCompleteFeed(feed);
adapter.close();
assertTrue(feed.getId() != 0);
assertTrue(feed.getImage().getId() != 0);
for (FeedItem item : feed.getItems()) {
assertTrue(item.getId() != 0);
assertTrue(item.getMedia().getId() != 0);
assertTrue(item.getChapters().get(0).getId() != 0);
}
DBWriter.deleteFeed(getInstrumentation().getTargetContext(), feed.getId()).get(TIMEOUT, TimeUnit.SECONDS);
// check if files still exist
assertFalse(imgFile.exists());
for (File f : itemFiles) {
assertFalse(f.exists());
}
adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor c = adapter.getFeedCursor(feed.getId());
assertEquals(0, c.getCount());
c.close();
c = adapter.getImageCursor(String.valueOf(image.getId()));
assertEquals(0, c.getCount());
c.close();
for (FeedItem item : feed.getItems()) {
c = adapter.getFeedItemCursor(String.valueOf(item.getId()));
assertEquals(0, c.getCount());
c.close();
c = adapter.getSingleFeedMediaCursor(item.getMedia().getId());
assertEquals(0, c.getCount());
c.close();
c = adapter.getSimpleChaptersOfFeedItemCursor(item);
assertEquals(0, c.getCount());
c.close();
}
adapter.close();
}
public void testDeleteFeedNoImage() throws ExecutionException, InterruptedException, IOException, TimeoutException {
File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
assertNotNull(destFolder);
Feed feed = new Feed("url", null, "title");
feed.setItems(new ArrayList<>());
feed.setImage(null);
List<File> itemFiles = new ArrayList<>();
// create items with downloaded media files
for (int i = 0; i < 10; i++) {
@ -261,13 +183,7 @@ public class DBWriterTest extends InstrumentationTestCase {
Feed feed = new Feed("url", null, "title");
feed.setItems(null);
// create Feed image
File imgFile = new File(destFolder, "image");
assertTrue(imgFile.createNewFile());
FeedImage image = new FeedImage(0, "image", imgFile.getAbsolutePath(), "url", true);
image.setOwner(feed);
feed.setImage(image);
feed.setImageUrl("url");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
@ -275,21 +191,14 @@ public class DBWriterTest extends InstrumentationTestCase {
adapter.close();
assertTrue(feed.getId() != 0);
assertTrue(feed.getImage().getId() != 0);
DBWriter.deleteFeed(getInstrumentation().getTargetContext(), feed.getId()).get(TIMEOUT, TimeUnit.SECONDS);
// check if files still exist
assertFalse(imgFile.exists());
adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor c = adapter.getFeedCursor(feed.getId());
assertTrue(c.getCount() == 0);
c.close();
c = adapter.getImageCursor(String.valueOf(image.getId()));
assertTrue(c.getCount() == 0);
c.close();
adapter.close();
}
@ -300,12 +209,7 @@ public class DBWriterTest extends InstrumentationTestCase {
Feed feed = new Feed("url", null, "title");
feed.setItems(new ArrayList<>());
// create Feed image
File imgFile = new File(destFolder, "image");
assertTrue(imgFile.createNewFile());
FeedImage image = new FeedImage(0, "image", imgFile.getAbsolutePath(), "url", true);
image.setOwner(feed);
feed.setImage(image);
feed.setImageUrl("url");
// create items
for (int i = 0; i < 10; i++) {
@ -320,24 +224,18 @@ public class DBWriterTest extends InstrumentationTestCase {
adapter.close();
assertTrue(feed.getId() != 0);
assertTrue(feed.getImage().getId() != 0);
for (FeedItem item : feed.getItems()) {
assertTrue(item.getId() != 0);
}
DBWriter.deleteFeed(getInstrumentation().getTargetContext(), feed.getId()).get(TIMEOUT, TimeUnit.SECONDS);
// check if files still exist
assertFalse(imgFile.exists());
adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor c = adapter.getFeedCursor(feed.getId());
assertTrue(c.getCount() == 0);
c.close();
c = adapter.getImageCursor(String.valueOf(image.getId()));
assertTrue(c.getCount() == 0);
c.close();
for (FeedItem item : feed.getItems()) {
c = adapter.getFeedItemCursor(String.valueOf(item.getId()));
assertTrue(c.getCount() == 0);
@ -346,65 +244,6 @@ public class DBWriterTest extends InstrumentationTestCase {
adapter.close();
}
public void testDeleteFeedWithItemImages() throws InterruptedException, ExecutionException, TimeoutException, IOException {
File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
assertNotNull(destFolder);
Feed feed = new Feed("url", null, "title");
feed.setItems(new ArrayList<>());
// create Feed image
File imgFile = new File(destFolder, "image");
assertTrue(imgFile.createNewFile());
FeedImage image = new FeedImage(0, "image", imgFile.getAbsolutePath(), "url", true);
image.setOwner(feed);
feed.setImage(image);
// create items with images
for (int i = 0; i < 10; i++) {
FeedItem item = new FeedItem(0, "Item " + i, "Item" + i, "url", new Date(), FeedItem.PLAYED, feed);
feed.getItems().add(item);
File itemImageFile = new File(destFolder, "item-image-" + i);
FeedImage itemImage = new FeedImage(0, "item-image" + i, itemImageFile.getAbsolutePath(), "url", true);
item.setImage(itemImage);
}
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setCompleteFeed(feed);
adapter.close();
assertTrue(feed.getId() != 0);
assertTrue(feed.getImage().getId() != 0);
for (FeedItem item : feed.getItems()) {
assertTrue(item.getId() != 0);
assertTrue(item.getImage().getId() != 0);
}
DBWriter.deleteFeed(getInstrumentation().getTargetContext(), feed.getId()).get(TIMEOUT, TimeUnit.SECONDS);
// check if files still exist
assertFalse(imgFile.exists());
adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor c = adapter.getFeedCursor(feed.getId());
assertTrue(c.getCount() == 0);
c.close();
c = adapter.getImageCursor(String.valueOf(image.getId()));
assertTrue(c.getCount() == 0);
c.close();
for (FeedItem item : feed.getItems()) {
c = adapter.getFeedItemCursor(String.valueOf(item.getId()));
assertTrue(c.getCount() == 0);
c.close();
c = adapter.getImageCursor(String.valueOf(item.getImage().getId()));
assertEquals(0, c.getCount());
c.close();
}
adapter.close();
}
public void testDeleteFeedWithQueueItems() throws ExecutionException, InterruptedException, TimeoutException {
File destFolder = getInstrumentation().getTargetContext().getExternalFilesDir(TEST_FOLDER);
assertNotNull(destFolder);
@ -412,11 +251,7 @@ public class DBWriterTest extends InstrumentationTestCase {
Feed feed = new Feed("url", null, "title");
feed.setItems(new ArrayList<>());
// create Feed image
File imgFile = new File(destFolder, "image");
FeedImage image = new FeedImage(0, "image", imgFile.getAbsolutePath(), "url", true);
image.setOwner(feed);
feed.setImage(image);
feed.setImageUrl("url");
List<File> itemFiles = new ArrayList<>();
// create items with downloaded media files
@ -437,7 +272,6 @@ public class DBWriterTest extends InstrumentationTestCase {
adapter.close();
assertTrue(feed.getId() != 0);
assertTrue(feed.getImage().getId() != 0);
for (FeedItem item : feed.getItems()) {
assertTrue(item.getId() != 0);
assertTrue(item.getMedia().getId() != 0);
@ -460,9 +294,6 @@ public class DBWriterTest extends InstrumentationTestCase {
Cursor c = adapter.getFeedCursor(feed.getId());
assertTrue(c.getCount() == 0);
c.close();
c = adapter.getImageCursor(String.valueOf(image.getId()));
assertTrue(c.getCount() == 0);
c.close();
for (FeedItem item : feed.getItems()) {
c = adapter.getFeedItemCursor(String.valueOf(item.getId()));
assertTrue(c.getCount() == 0);
@ -484,11 +315,7 @@ public class DBWriterTest extends InstrumentationTestCase {
Feed feed = new Feed("url", null, "title");
feed.setItems(new ArrayList<>());
// create Feed image
File imgFile = new File(destFolder, "image");
FeedImage image = new FeedImage(0, "image", imgFile.getAbsolutePath(), "url", true);
image.setOwner(feed);
feed.setImage(image);
feed.setImageUrl("url");
List<File> itemFiles = new ArrayList<>();
// create items with downloaded media files
@ -509,7 +336,6 @@ public class DBWriterTest extends InstrumentationTestCase {
adapter.close();
assertTrue(feed.getId() != 0);
assertTrue(feed.getImage().getId() != 0);
for (FeedItem item : feed.getItems()) {
assertTrue(item.getId() != 0);
assertTrue(item.getMedia().getId() != 0);
@ -522,9 +348,6 @@ public class DBWriterTest extends InstrumentationTestCase {
Cursor c = adapter.getFeedCursor(feed.getId());
assertTrue(c.getCount() == 0);
c.close();
c = adapter.getImageCursor(String.valueOf(image.getId()));
assertTrue(c.getCount() == 0);
c.close();
for (FeedItem item : feed.getItems()) {
c = adapter.getFeedItemCursor(String.valueOf(item.getId()));
assertTrue(c.getCount() == 0);

View File

@ -55,6 +55,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
} else {
otherTheme = R.string.pref_theme_title_light;
}
solo.clickOnText(solo.getString(R.string.user_interface_label));
solo.clickOnText(solo.getString(R.string.pref_set_theme_title));
solo.waitForDialogToOpen();
solo.clickOnText(solo.getString(otherTheme));
@ -69,6 +70,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
} else {
otherTheme = R.string.pref_theme_title_light;
}
solo.clickOnText(solo.getString(R.string.user_interface_label));
solo.clickOnText(solo.getString(R.string.pref_set_theme_title));
solo.waitForDialogToOpen(1000);
solo.clickOnText(solo.getString(otherTheme));
@ -76,6 +78,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
}
public void testExpandNotification() {
solo.clickOnText(solo.getString(R.string.user_interface_label));
final int priority = UserPreferences.getNotifyPriority();
solo.clickOnText(solo.getString(R.string.pref_expandNotify_title));
assertTrue(solo.waitForCondition(() -> priority != UserPreferences.getNotifyPriority(), Timeout.getLargeTimeout()));
@ -84,7 +87,10 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
}
public void testEnablePersistentPlaybackControls() {
solo.clickOnText(solo.getString(R.string.user_interface_label));
final boolean persistNotify = UserPreferences.isPersistNotify();
solo.scrollDown();
solo.scrollDown();
solo.clickOnText(solo.getString(R.string.pref_persistNotify_title));
assertTrue(solo.waitForCondition(() -> persistNotify != UserPreferences.isPersistNotify(), Timeout.getLargeTimeout()));
solo.clickOnText(solo.getString(R.string.pref_persistNotify_title));
@ -92,6 +98,8 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
}
public void testSetLockscreenButtons() {
solo.clickOnText(solo.getString(R.string.user_interface_label));
solo.scrollDown();
String[] buttons = res.getStringArray(R.array.compact_notification_buttons_options);
solo.clickOnText(solo.getString(R.string.pref_compact_notification_buttons_title));
solo.waitForDialogToOpen(1000);
@ -116,7 +124,10 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
}
public void testEnqueueAtFront() {
solo.clickOnText(solo.getString(R.string.playback_pref));
final boolean enqueueAtFront = UserPreferences.enqueueAtFront();
solo.scrollDown();
solo.scrollDown();
solo.clickOnText(solo.getString(R.string.pref_queueAddToFront_title));
assertTrue(solo.waitForCondition(() -> enqueueAtFront != UserPreferences.enqueueAtFront(), Timeout.getLargeTimeout()));
solo.clickOnText(solo.getString(R.string.pref_queueAddToFront_title));
@ -124,6 +135,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
}
public void testHeadPhonesDisconnect() {
solo.clickOnText(solo.getString(R.string.playback_pref));
final boolean pauseOnHeadsetDisconnect = UserPreferences.isPauseOnHeadsetDisconnect();
solo.clickOnText(solo.getString(R.string.pref_pauseOnHeadsetDisconnect_title));
assertTrue(solo.waitForCondition(() -> pauseOnHeadsetDisconnect != UserPreferences.isPauseOnHeadsetDisconnect(), Timeout.getLargeTimeout()));
@ -132,6 +144,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
}
public void testHeadPhonesReconnect() {
solo.clickOnText(solo.getString(R.string.playback_pref));
if(UserPreferences.isPauseOnHeadsetDisconnect() == false) {
solo.clickOnText(solo.getString(R.string.pref_pauseOnHeadsetDisconnect_title));
assertTrue(solo.waitForCondition(UserPreferences::isPauseOnHeadsetDisconnect, Timeout.getLargeTimeout()));
@ -144,6 +157,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
}
public void testBluetoothReconnect() {
solo.clickOnText(solo.getString(R.string.playback_pref));
if(UserPreferences.isPauseOnHeadsetDisconnect() == false) {
solo.clickOnText(solo.getString(R.string.pref_pauseOnHeadsetDisconnect_title));
assertTrue(solo.waitForCondition(UserPreferences::isPauseOnHeadsetDisconnect, Timeout.getLargeTimeout()));
@ -156,7 +170,10 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
}
public void testContinuousPlayback() {
solo.clickOnText(solo.getString(R.string.playback_pref));
final boolean continuousPlayback = UserPreferences.isFollowQueue();
solo.scrollDown();
solo.scrollDown();
solo.clickOnText(solo.getString(R.string.pref_followQueue_title));
assertTrue(solo.waitForCondition(() -> continuousPlayback != UserPreferences.isFollowQueue(), Timeout.getLargeTimeout()));
solo.clickOnText(solo.getString(R.string.pref_followQueue_title));
@ -164,6 +181,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
}
public void testAutoDelete() {
solo.clickOnText(solo.getString(R.string.storage_pref));
final boolean autoDelete = UserPreferences.isAutoDelete();
solo.clickOnText(solo.getString(R.string.pref_auto_delete_title));
assertTrue(solo.waitForCondition(() -> autoDelete != UserPreferences.isAutoDelete(), Timeout.getLargeTimeout()));
@ -172,6 +190,9 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
}
public void testPlaybackSpeeds() {
solo.clickOnText(solo.getString(R.string.playback_pref));
solo.scrollDown();
solo.scrollDown();
solo.clickOnText(solo.getString(R.string.pref_playback_speed_title));
solo.waitForDialogToOpen(1000);
assertTrue(solo.searchText(res.getStringArray(R.array.playback_speed_values)[0]));
@ -180,6 +201,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
}
public void testPauseForInterruptions() {
solo.clickOnText(solo.getString(R.string.playback_pref));
final boolean pauseForFocusLoss = UserPreferences.shouldPauseForFocusLoss();
solo.clickOnText(solo.getString(R.string.pref_pausePlaybackForFocusLoss_title));
assertTrue(solo.waitForCondition(() -> pauseForFocusLoss != UserPreferences.shouldPauseForFocusLoss(), Timeout.getLargeTimeout()));
@ -188,6 +210,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
}
public void testDisableUpdateInterval() {
solo.clickOnText(solo.getString(R.string.network_pref));
solo.clickOnText(solo.getString(R.string.pref_autoUpdateIntervallOrTime_sum));
solo.waitForDialogToOpen();
solo.clickOnText(solo.getString(R.string.pref_autoUpdateIntervallOrTime_Disable));
@ -195,6 +218,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
}
public void testSetUpdateInterval() {
solo.clickOnText(solo.getString(R.string.network_pref));
solo.clickOnText(solo.getString(R.string.pref_autoUpdateIntervallOrTime_title));
solo.waitForDialogToOpen();
solo.clickOnText(solo.getString(R.string.pref_autoUpdateIntervallOrTime_Interval));
@ -207,6 +231,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
}
public void testMobileUpdates() {
solo.clickOnText(solo.getString(R.string.network_pref));
final boolean mobileUpdates = UserPreferences.isAllowMobileUpdate();
solo.clickOnText(solo.getString(R.string.pref_mobileUpdate_title));
assertTrue(solo.waitForCondition(() -> mobileUpdates != UserPreferences.isAllowMobileUpdate(), Timeout.getLargeTimeout()));
@ -215,6 +240,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
}
public void testSetSequentialDownload() {
solo.clickOnText(solo.getString(R.string.network_pref));
solo.clickOnText(solo.getString(R.string.pref_parallel_downloads_title));
solo.waitForDialogToOpen();
solo.clearEditText(0);
@ -224,6 +250,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
}
public void testSetParallelDownloads() {
solo.clickOnText(solo.getString(R.string.network_pref));
solo.clickOnText(solo.getString(R.string.pref_parallel_downloads_title));
solo.waitForDialogToOpen();
solo.clearEditText(0);
@ -233,6 +260,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
}
public void testSetParallelDownloadsInvalidInput() {
solo.clickOnText(solo.getString(R.string.network_pref));
solo.clickOnText(solo.getString(R.string.pref_parallel_downloads_title));
solo.waitForDialogToOpen();
solo.clearEditText(0);
@ -248,6 +276,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
String[] values = res.getStringArray(R.array.episode_cache_size_values);
String entry = entries[entries.length/2];
final int value = Integer.valueOf(values[values.length/2]);
solo.clickOnText(solo.getString(R.string.network_pref));
solo.clickOnText(solo.getString(R.string.pref_automatic_download_title));
solo.waitForText(solo.getString(R.string.pref_automatic_download_title));
solo.clickOnText(solo.getString(R.string.pref_episode_cache_title));
@ -261,6 +290,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
String[] values = res.getStringArray(R.array.episode_cache_size_values);
String minEntry = entries[0];
final int minValue = Integer.valueOf(values[0]);
solo.clickOnText(solo.getString(R.string.network_pref));
solo.clickOnText(solo.getString(R.string.pref_automatic_download_title));
solo.waitForText(solo.getString(R.string.pref_automatic_download_title));
if(!UserPreferences.isEnableAutodownload()) {
@ -278,6 +308,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
String[] values = res.getStringArray(R.array.episode_cache_size_values);
String maxEntry = entries[entries.length-1];
final int maxValue = Integer.valueOf(values[values.length-1]);
solo.clickOnText(solo.getString(R.string.network_pref));
solo.clickOnText(solo.getString(R.string.pref_automatic_download_title));
solo.waitForText(solo.getString(R.string.pref_automatic_download_title));
if(!UserPreferences.isEnableAutodownload()) {
@ -291,6 +322,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
public void testAutomaticDownload() {
final boolean automaticDownload = UserPreferences.isEnableAutodownload();
solo.clickOnText(solo.getString(R.string.network_pref));
solo.clickOnText(solo.getString(R.string.pref_automatic_download_title));
solo.waitForText(solo.getString(R.string.pref_automatic_download_title));
solo.clickOnText(solo.getString(R.string.pref_automatic_download_title));
@ -312,6 +344,8 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
}
public void testEpisodeCleanupQueueOnly() {
solo.clickOnText(solo.getString(R.string.network_pref));
solo.clickOnText(solo.getString(R.string.pref_automatic_download_title));
solo.clickOnText(solo.getString(R.string.pref_episode_cleanup_title));
solo.waitForText(solo.getString(R.string.episode_cleanup_queue_removal));
solo.clickOnText(solo.getString(R.string.episode_cleanup_queue_removal));
@ -323,6 +357,8 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
}
public void testEpisodeCleanupNeverAlg() {
solo.clickOnText(solo.getString(R.string.network_pref));
solo.clickOnText(solo.getString(R.string.pref_automatic_download_title));
solo.clickOnText(solo.getString(R.string.pref_episode_cleanup_title));
solo.waitForText(solo.getString(R.string.episode_cleanup_never));
solo.clickOnText(solo.getString(R.string.episode_cleanup_never));
@ -334,6 +370,8 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
}
public void testEpisodeCleanupClassic() {
solo.clickOnText(solo.getString(R.string.network_pref));
solo.clickOnText(solo.getString(R.string.pref_automatic_download_title));
solo.clickOnText(solo.getString(R.string.pref_episode_cleanup_title));
solo.waitForText(solo.getString(R.string.episode_cleanup_after_listening));
solo.clickOnText(solo.getString(R.string.episode_cleanup_after_listening));
@ -349,6 +387,8 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
}
public void testEpisodeCleanupNumDays() {
solo.clickOnText(solo.getString(R.string.network_pref));
solo.clickOnText(solo.getString(R.string.pref_automatic_download_title));
solo.clickOnText(solo.getString(R.string.pref_episode_cleanup_title));
solo.waitForText(solo.getString(R.string.episode_cleanup_after_listening));
solo.clickOnText("5");
@ -368,6 +408,9 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
int seconds = UserPreferences.getRewindSecs();
int deltas[] = res.getIntArray(R.array.seek_delta_values);
solo.clickOnText(solo.getString(R.string.playback_pref));
solo.scrollDown();
solo.scrollDown();
solo.clickOnText(solo.getString(R.string.pref_rewind));
solo.waitForDialogToOpen();
@ -386,6 +429,9 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
}
public void testFastForwardChange() {
solo.clickOnText(solo.getString(R.string.playback_pref));
solo.scrollDown();
solo.scrollDown();
for (int i = 2; i > 0; i--) { // repeat twice to catch any error where fastforward is tracking rewind
int seconds = UserPreferences.getFastForwardSecs();
int deltas[] = res.getIntArray(R.array.seek_delta_values);

View File

@ -22,7 +22,6 @@ import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.event.QueueEvent;
import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
@ -136,12 +135,9 @@ class UITestUtils {
public void addHostedFeedData() throws IOException {
if (feedDataHosted) throw new IllegalStateException("addHostedFeedData was called twice on the same instance");
for (int i = 0; i < NUM_FEEDS; i++) {
File bitmapFile = newBitmapFile("image" + i);
FeedImage image = new FeedImage(0, "image " + i, null, hostFile(bitmapFile), false);
Feed feed = new Feed(0, null, "Title " + i, "http://example.com/" + i, "Description of feed " + i,
"http://example.com/pay/feed" + i, "author " + i, "en", Feed.TYPE_RSS2, "feed" + i, image, null,
"http://example.com/pay/feed" + i, "author " + i, "en", Feed.TYPE_RSS2, "feed" + i, null, null,
"http://example.com/feed/src/" + i, false);
image.setOwner(feed);
// create items
List<FeedItem> items = new ArrayList<>();
@ -187,12 +183,6 @@ class UITestUtils {
List<FeedItem> queue = new ArrayList<>();
for (Feed feed : hostedFeeds) {
feed.setDownloaded(true);
if (feed.getImage() != null) {
FeedImage image = feed.getImage();
int fileId = Integer.parseInt(StringUtils.substringAfter(image.getDownload_url(), "files/"));
image.setFile_url(server.accessFile(fileId).getAbsolutePath());
image.setDownloaded(true);
}
if (downloadEpisodes) {
for (FeedItem item : feed.getItems()) {
if (item.hasMedia()) {

View File

@ -38,9 +38,6 @@ public class UITestUtilsTest extends InstrumentationTestCase {
for (Feed feed : feeds) {
testUrlReachable(feed.getDownload_url());
if (feed.getImage() != null) {
testUrlReachable(feed.getImage().getDownload_url());
}
for (FeedItem item : feed.getItems()) {
if (item.hasMedia()) {
testUrlReachable(item.getMedia().getDownload_url());
@ -66,9 +63,6 @@ public class UITestUtilsTest extends InstrumentationTestCase {
for (Feed feed : uiTestUtils.hostedFeeds) {
assertTrue(feed.getId() != 0);
if (feed.getImage() != null) {
assertTrue(feed.getImage().getId() != 0);
}
for (FeedItem item : feed.getItems()) {
assertTrue(item.getId() != 0);
if (item.hasMedia()) {

View File

@ -2,7 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="de.danoeh.antennapod"
android:installLocation="auto"
android:versionCode="1060595"
android:versionCode="1060596"
android:versionName="1.6.5">
<!--
Version code schema:
@ -118,12 +118,13 @@
</activity>
<service
android:name=".service.PlayerWidgetService"
android:name=".core.service.PlayerWidgetJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:enabled="true"
android:exported="false">
</service>
<receiver android:name=".receiver.PlayerWidget">
<receiver android:name=".core.receiver.PlayerWidget">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>

View File

@ -21,8 +21,7 @@ import java.nio.charset.Charset;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import rx.Observable;
import rx.Subscriber;
import rx.Single;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
@ -34,10 +33,8 @@ public class AboutActivity extends AppCompatActivity {
private static final String TAG = AboutActivity.class.getSimpleName();
private WebView webview;
private LinearLayout webviewContainer;
private int depth = 0;
private WebView webView;
private LinearLayout webViewContainer;
private Subscription subscription;
@Override
@ -46,28 +43,25 @@ public class AboutActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayShowHomeEnabled(true);
setContentView(R.layout.about);
webviewContainer = (LinearLayout) findViewById(R.id.webvContainer);
webview = (WebView) findViewById(R.id.webvAbout);
webview.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
webViewContainer = (LinearLayout) findViewById(R.id.webViewContainer);
webView = (WebView) findViewById(R.id.webViewAbout);
webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
if (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) {
if (Build.VERSION.SDK_INT >= 11
&& Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
webview.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
webview.setBackgroundColor(Color.TRANSPARENT);
webView.setBackgroundColor(Color.TRANSPARENT);
}
webview.setWebViewClient(new WebViewClient() {
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if(url.startsWith("http")) {
depth++;
return false;
} else {
if (!url.startsWith("http")) {
url = url.replace("file:///android_asset/", "");
loadAsset(url);
return true;
}
return false;
}
});
@ -75,7 +69,7 @@ public class AboutActivity extends AppCompatActivity {
}
private void loadAsset(String filename) {
subscription = Observable.create((Observable.OnSubscribe<String>) subscriber -> {
subscription = Single.create(subscriber -> {
InputStream input = null;
try {
TypedArray res = AboutActivity.this.getTheme().obtainStyledAttributes(
@ -85,8 +79,7 @@ public class AboutActivity extends AppCompatActivity {
res.recycle();
input = getAssets().open(filename);
String webViewData = IOUtils.toString(input, Charset.defaultCharset());
if(!webViewData.startsWith("<!DOCTYPE html>")) {
//webViewData = webViewData.replace("\n\n", "</p><p>");
if (!webViewData.startsWith("<!DOCTYPE html>")) {
webViewData = webViewData.replace("%", "&#37;");
webViewData =
"<!DOCTYPE html>" +
@ -106,35 +99,29 @@ public class AboutActivity extends AppCompatActivity {
" </style>" +
"</head><body><p>" + webViewData + "</p></body></html>";
webViewData = webViewData.replace("\n", "<br/>");
depth++;
} else {
depth = 0;
}
webViewData = String.format(webViewData, colorString);
subscriber.onNext(webViewData);
subscriber.onSuccess(webViewData);
} catch (IOException e) {
Log.e(TAG, Log.getStackTraceString(e));
subscriber.onError(e);
} finally {
IOUtils.closeQuietly(input);
}
subscriber.onCompleted();
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
webviewData ->
webview.loadDataWithBaseURL("file:///android_asset/", webviewData, "text/html", "utf-8", "about:blank"),
webViewData ->
webView.loadDataWithBaseURL("file:///android_asset/", webViewData.toString(), "text/html", "utf-8", "file:///android_asset/" + filename.toString()),
error -> Log.e(TAG, Log.getStackTraceString(error))
);
}
@Override
public void onBackPressed() {
Log.d(TAG, "depth: " + depth);
if(depth == 1) {
loadAsset("about.html");
} else if(depth > 1) {
webview.goBack();
if (webView.canGoBack()) {
webView.goBack();
} else {
super.onBackPressed();
}
@ -156,9 +143,9 @@ public class AboutActivity extends AppCompatActivity {
if(subscription != null) {
subscription.unsubscribe();
}
if (webviewContainer != null && webview != null) {
webviewContainer.removeAllViews();
webview.destroy();
if (webViewContainer != null && webView != null) {
webViewContainer.removeAllViews();
webView.destroy();
}
}
}

View File

@ -13,6 +13,7 @@ import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.util.playback.ExternalMedia;
import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
import de.danoeh.antennapod.dialog.VariableSpeedDialog;
/**
@ -34,14 +35,13 @@ public class AudioplayerActivity extends MediaplayerInfoActivity {
Log.d(TAG, "Received VIEW intent: " + intent.getData().getPath());
ExternalMedia media = new ExternalMedia(intent.getData().getPath(),
MediaType.AUDIO);
Intent launchIntent = new Intent(this, PlaybackService.class);
launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED,
true);
launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, false);
launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY,
true);
startService(launchIntent);
new PlaybackServiceStarter(this, media)
.startWhenPrepared(true)
.shouldStream(false)
.prepareImmediately(true)
.start();
} else if (PlaybackService.isCasting()) {
Intent intent = PlaybackService.getPlayerActivityIntent(this);
if (intent.getComponent() != null &&

View File

@ -8,7 +8,7 @@ import android.os.Bundle;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.support.design.widget.Snackbar;
import android.support.v4.content.IntentCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
@ -39,7 +39,10 @@ public class ImportExportActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayShowHomeEnabled(true);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayShowHomeEnabled(true);
}
setContentView(R.layout.import_export_activity);
findViewById(R.id.button_export).setOnClickListener(view -> backup());
@ -125,7 +128,7 @@ public class ImportExportActivity extends AppCompatActivity {
d.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {
Intent intent = new Intent(getApplicationContext(), SplashActivity.class);
ComponentName cn = intent.getComponent();
Intent mainIntent = IntentCompat.makeRestartActivityTask(cn);
Intent mainIntent = Intent.makeRestartActivityTask(cn);
startActivity(mainIntent);
});
d.show();

View File

@ -30,6 +30,8 @@ import android.widget.ListView;
import com.bumptech.glide.Glide;
import de.danoeh.antennapod.core.event.ServiceEvent;
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.Validate;
@ -200,6 +202,8 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi
transaction.commit();
checkFirstLaunch();
NotificationUtils.createChannels(this);
UserPreferences.restartUpdateAlarm(false);
}
private void saveLastNavFragment(String tag) {
@ -739,6 +743,15 @@ public class MainActivity extends CastEnabledActivity implements NavDrawerActivi
loadData();
}
public void onEventMainThread(ServiceEvent event) {
Log.d(TAG, "onEvent(" + event + ")");
switch(event.action) {
case SERVICE_STARTED:
externalPlayerFragment.connectToPlaybackService();
break;
}
}
public void onEventMainThread(ProgressEvent event) {
Log.d(TAG, "onEvent(" + event + ")");
switch(event.action) {

View File

@ -34,6 +34,7 @@ import com.joanzapata.iconify.fonts.FontAwesomeIcons;
import java.util.Locale;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.event.ServiceEvent;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.preferences.UserPreferences;
@ -42,6 +43,7 @@ import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.Flavors;
import de.danoeh.antennapod.core.util.ShareUtils;
import de.danoeh.antennapod.core.util.StorageUtils;
@ -270,6 +272,9 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
controller.release();
}
controller = newPlaybackController();
setupGUI();
loadMediaInfo();
onPositionObserverUpdate();
}
@Override
@ -320,11 +325,11 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
((FeedMedia) media).getItem().getFlattrStatus().flattrable()
);
boolean hasWebsiteLink = media != null && media.getWebsiteLink() != null;
boolean hasWebsiteLink = ( getWebsiteLinkWithFallback(media) != null );
menu.findItem(R.id.visit_website_item).setVisible(hasWebsiteLink);
boolean isItemAndHasLink = isFeedMedia &&
((FeedMedia) media).getItem() != null && ((FeedMedia) media).getItem().getLink() != null;
ShareUtils.hasLinkToShare(((FeedMedia) media).getItem());
menu.findItem(R.id.share_link_item).setVisible(isItemAndHasLink);
menu.findItem(R.id.share_link_with_position_item).setVisible(isItemAndHasLink);
@ -560,7 +565,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
});
break;
case R.id.visit_website_item:
Uri uri = Uri.parse(media.getWebsiteLink());
Uri uri = Uri.parse(getWebsiteLinkWithFallback(media));
startActivity(new Intent(Intent.ACTION_VIEW, uri));
break;
case R.id.support_item:
@ -603,16 +608,37 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
}
}
private static String getWebsiteLinkWithFallback(Playable media) {
if (media == null) {
return null;
} else if (media.getWebsiteLink() != null) {
return media.getWebsiteLink();
} else if (media instanceof FeedMedia) {
return FeedItemUtil.getLinkWithFallback(((FeedMedia)media).getItem());
}
return null;
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume()");
StorageUtils.checkStorageAvailability(this);
if(controller != null) {
if (controller != null) {
controller.init();
}
}
public void onEventMainThread(ServiceEvent event) {
Log.d(TAG, "onEvent(" + event + ")");
if (event.action == ServiceEvent.Action.SERVICE_STARTED) {
if (controller != null) {
controller.init();
}
}
}
/**
* Called by 'handleStatus()' when the PlaybackService is waiting for
* a video surface.
@ -853,6 +879,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements
if(controller == null) {
return;
}
controller.init();
controller.playPause();
}

View File

@ -393,9 +393,9 @@ public class OnlineFeedViewActivity extends AppCompatActivity {
subscribeButton = (Button) header.findViewById(R.id.butSubscribe);
if (feed.getImage() != null && StringUtils.isNotBlank(feed.getImage().getDownload_url())) {
if (StringUtils.isNotBlank(feed.getImageUrl())) {
Glide.with(this)
.load(feed.getImage().getDownload_url())
.load(feed.getImageUrl())
.placeholder(R.color.light_gray)
.error(R.color.light_gray)
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)

View File

@ -1,13 +1,12 @@
package de.danoeh.antennapod.activity;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceFragmentCompat;
import android.support.v7.preference.PreferenceScreen;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewGroup;
@ -15,6 +14,9 @@ import android.widget.FrameLayout;
import java.lang.ref.WeakReference;
import com.bytehamster.lib.preferencesearch.SearchPreference;
import com.bytehamster.lib.preferencesearch.SearchPreferenceResult;
import com.bytehamster.lib.preferencesearch.SearchPreferenceResultListener;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.preferences.PreferenceController;
@ -23,19 +25,24 @@ import de.danoeh.antennapod.preferences.PreferenceController;
* PreferenceActivity for API 11+. In order to change the behavior of the preference UI, see
* PreferenceController.
*/
public class PreferenceActivity extends AppCompatActivity {
public class PreferenceActivity extends AppCompatActivity implements SearchPreferenceResultListener {
public static final String PARAM_RESOURCE = "resource";
private static WeakReference<PreferenceActivity> instance;
private PreferenceController preferenceController;
private final PreferenceController.PreferenceUI preferenceUI = new PreferenceController.PreferenceUI() {
private PreferenceFragment fragment;
private PreferenceFragmentCompat fragment;
@Override
public void setFragment(PreferenceFragment fragment) {
public void setFragment(PreferenceFragmentCompat fragment) {
this.fragment = fragment;
}
@Override
public PreferenceFragmentCompat getFragment() {
return fragment;
}
@Override
public Preference findPreference(CharSequence key) {
return fragment.findPreference(key);
@ -47,7 +54,7 @@ public class PreferenceActivity extends AppCompatActivity {
}
@Override
public Activity getActivity() {
public AppCompatActivity getActivity() {
return PreferenceActivity.this;
}
};
@ -77,12 +84,21 @@ public class PreferenceActivity extends AppCompatActivity {
// since the MainFragment depends on the preferenceController already being created
preferenceController = new PreferenceController(preferenceUI);
PreferenceFragment prefFragment = new MainFragment();
showPreferenceScreen(R.xml.preferences, false);
}
private void showPreferenceScreen(int screen, boolean addHistory) {
PreferenceFragmentCompat prefFragment = new MainFragment();
preferenceUI.setFragment(prefFragment);
Bundle args = new Bundle();
args.putInt(PARAM_RESOURCE, R.xml.preferences);
args.putInt(PARAM_RESOURCE, screen);
prefFragment.setArguments(args);
getFragmentManager().beginTransaction().replace(R.id.content, prefFragment).commit();
if (addHistory) {
getSupportFragmentManager().beginTransaction().replace(R.id.content, prefFragment)
.addToBackStack(getString(PreferenceController.getTitleOfPage(screen))).commit();
} else {
getSupportFragmentManager().beginTransaction().replace(R.id.content, prefFragment).commit();
}
}
@Override
@ -101,10 +117,10 @@ public class PreferenceActivity extends AppCompatActivity {
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
if (getFragmentManager().getBackStackEntryCount() == 0) {
if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
finish();
} else {
getFragmentManager().popBackStack();
getSupportFragmentManager().popBackStack();
}
return true;
default:
@ -112,13 +128,23 @@ public class PreferenceActivity extends AppCompatActivity {
}
}
public static class MainFragment extends PreferenceFragment {
@Override
public void onSearchResultClicked(SearchPreferenceResult result) {
showPreferenceScreen(result.getResourceFile(), true);
result.highlight(preferenceUI.getFragment());
}
public static class MainFragment extends PreferenceFragmentCompat {
private int screen;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
screen = getArguments().getInt(PARAM_RESOURCE);
addPreferencesFromResource(screen);
PreferenceActivity activity = instance.get();
@ -133,35 +159,16 @@ public class PreferenceActivity extends AppCompatActivity {
super.onResume();
PreferenceActivity activity = instance.get();
if(activity != null && activity.preferenceController != null) {
activity.setTitle(getTitle(screen));
activity.setTitle(PreferenceController.getTitleOfPage(screen));
activity.preferenceUI.setFragment(this);
activity.preferenceController.onResume(screen);
}
}
private int getTitle(int preferences) {
switch (preferences) {
case R.xml.preferences_network:
return R.string.network_pref;
case R.xml.preferences_autodownload:
return R.string.pref_automatic_download_title;
case R.xml.preferences_playback:
return R.string.playback_pref;
case R.xml.preferences_storage:
return R.string.storage_pref;
case R.xml.preferences_user_interface:
return R.string.user_interface_label;
case R.xml.preferences_integrations:
return R.string.integrations_label;
default:
return R.string.settings_label;
}
}
@Override
public void onPause() {
PreferenceActivity activity = instance.get();
if (screen == R.xml.preferences_integrations) {
if (screen == R.xml.preferences_gpodder) {
activity.preferenceController.unregisterGpodnet();
}
super.onPause();

View File

@ -30,6 +30,7 @@ import de.danoeh.antennapod.core.service.playback.PlayerStatus;
import de.danoeh.antennapod.core.util.gui.PictureInPictureUtil;
import de.danoeh.antennapod.core.util.playback.ExternalMedia;
import de.danoeh.antennapod.core.util.playback.Playable;
import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
import de.danoeh.antennapod.view.AspectRatioVideoView;
import java.lang.ref.WeakReference;
@ -82,14 +83,12 @@ public class VideoplayerActivity extends MediaplayerActivity {
Log.d(TAG, "Received VIEW intent: " + intent.getData().getPath());
ExternalMedia media = new ExternalMedia(intent.getData().getPath(),
MediaType.VIDEO);
Intent launchIntent = new Intent(this, PlaybackService.class);
launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED,
true);
launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, false);
launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY,
true);
startService(launchIntent);
new PlaybackServiceStarter(this, media)
.startWhenPrepared(true)
.shouldStream(false)
.prepareImmediately(true)
.start();
} else if (PlaybackService.isCasting()) {
Intent intent = PlaybackService.getPlayerActivityIntent(this);
if (!intent.getComponent().getClassName().equals(VideoplayerActivity.class.getName())) {

View File

@ -29,12 +29,12 @@ import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.glide.ApGlideSettings;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.DateUtils;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.core.util.ThemeUtils;
import de.danoeh.antennapod.fragment.ItemFragment;
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
@ -67,11 +67,7 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR
this.actionButtonCallback = actionButtonCallback;
this.showOnlyNewEpisodes = showOnlyNewEpisodes;
if(UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) {
playingBackGroundColor = ContextCompat.getColor(mainActivity, R.color.highlight_dark);
} else {
playingBackGroundColor = ContextCompat.getColor(mainActivity, R.color.highlight_light);
}
playingBackGroundColor = ThemeUtils.getColorFromAttr(mainActivity, R.attr.currently_playing_background);
normalBackGroundColor = ContextCompat.getColor(mainActivity, android.R.color.transparent);
}

View File

@ -19,9 +19,9 @@ import android.widget.TextView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.util.ChapterUtils;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.ThemeUtils;
import de.danoeh.antennapod.core.util.playback.Playable;
public class ChaptersListAdapter extends ArrayAdapter<Chapter> {
@ -143,9 +143,7 @@ public class ChaptersListAdapter extends ArrayAdapter<Chapter> {
Chapter current = ChapterUtils.getCurrentChapter(media);
if (current == sc) {
boolean darkTheme = UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark;
int highlight = darkTheme ? R.color.highlight_dark : R.color.highlight_light;
int playingBackGroundColor = ContextCompat.getColor(getContext(), highlight);
int playingBackGroundColor = ThemeUtils.getColorFromAttr(getContext(), R.attr.currently_playing_background);
holder.view.setBackgroundColor(playingBackGroundColor);
} else {
holder.view.setBackgroundColor(ContextCompat.getColor(getContext(), android.R.color.transparent));

View File

@ -6,6 +6,7 @@ import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog;
import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
import org.apache.commons.lang3.Validate;
import de.danoeh.antennapod.R;
@ -80,13 +81,19 @@ public class DefaultActionButtonCallback implements ActionButtonCallback {
Toast.makeText(context, R.string.download_canceled_msg, Toast.LENGTH_LONG).show();
}
} else { // media is downloaded
if (item.hasMedia() && item.getMedia().isCurrentlyPlaying()) {
if (media.isCurrentlyPlaying()) {
new PlaybackServiceStarter(context, media)
.startWhenPrepared(true)
.shouldStream(false)
.start();
context.sendBroadcast(new Intent(PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE));
}
else if (item.hasMedia() && item.getMedia().isCurrentlyPaused()) {
} else if (media.isCurrentlyPaused()) {
new PlaybackServiceStarter(context, media)
.startWhenPrepared(true)
.shouldStream(false)
.start();
context.sendBroadcast(new Intent(PlaybackService.ACTION_RESUME_PLAY_CURRENT_EPISODE));
}
else {
} else {
DBTasks.playMedia(context, media, false, true, false);
}
}

View File

@ -19,7 +19,6 @@ import com.joanzapata.iconify.widget.IconTextView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.dialog.DownloadRequestErrorDialogCreator;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.storage.DBReader;
@ -67,8 +66,6 @@ public class DownloadLogAdapter extends BaseAdapter {
holder.type.setText(R.string.download_type_feed);
} else if (status.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
holder.type.setText(R.string.download_type_media);
} else if (status.getFeedfileType() == FeedImage.FEEDFILETYPE_FEEDIMAGE) {
holder.type.setText(R.string.download_type_image);
}
if (status.getTitle() != null) {
holder.title.setText(status.getTitle());
@ -94,8 +91,7 @@ public class DownloadLogAdapter extends BaseAdapter {
}
holder.reason.setText(reasonText);
holder.reason.setVisibility(View.VISIBLE);
if(status.getFeedfileType() != FeedImage.FEEDFILETYPE_FEEDIMAGE &&
!newerWasSuccessful(position, status.getFeedfileType(), status.getFeedfileId())) {
if(!newerWasSuccessful(position, status.getFeedfileType(), status.getFeedfileId())) {
holder.retry.setVisibility(View.VISIBLE);
holder.retry.setOnClickListener(clickListener);
ButtonHolder btnHolder;

View File

@ -100,8 +100,10 @@ public class DownloadedEpisodesListAdapter extends BaseAdapter {
FeedItem.State state = item.getState();
if (state == FeedItem.State.PLAYING) {
holder.butSecondary.setEnabled(false);
holder.butSecondary.setAlpha(0.5f);
} else {
holder.butSecondary.setEnabled(true);
holder.butSecondary.setAlpha(1.0f);
}
holder.butSecondary.setFocusable(false);
holder.butSecondary.setTag(item);

View File

@ -21,7 +21,6 @@ import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.DateUtils;
import de.danoeh.antennapod.core.util.LongList;
@ -60,11 +59,7 @@ public class FeedItemlistAdapter extends BaseAdapter {
this.actionButtonUtils = new ActionButtonUtils(context);
this.makePlayedItemsTransparent = makePlayedItemsTransparent;
if(UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) {
playingBackGroundColor = ContextCompat.getColor(context, R.color.highlight_dark);
} else {
playingBackGroundColor = ContextCompat.getColor(context, R.color.highlight_light);
}
playingBackGroundColor = ThemeUtils.getColorFromAttr(context, R.attr.currently_playing_background);
normalBackGroundColor = ContextCompat.getColor(context, android.R.color.transparent);
}

View File

@ -25,6 +25,7 @@ import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.joanzapata.iconify.Iconify;
import de.danoeh.antennapod.core.util.ThemeUtils;
import org.apache.commons.lang3.ArrayUtils;
import java.lang.ref.WeakReference;
@ -75,11 +76,7 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap
this.itemTouchHelper = itemTouchHelper;
locked = UserPreferences.isQueueLocked();
if(UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) {
playingBackGroundColor = ContextCompat.getColor(mainActivity, R.color.highlight_dark);
} else {
playingBackGroundColor = ContextCompat.getColor(mainActivity, R.color.highlight_light);
}
playingBackGroundColor = ThemeUtils.getColorFromAttr(mainActivity, R.attr.currently_playing_background);
normalBackGroundColor = ContextCompat.getColor(mainActivity, android.R.color.transparent);
}

View File

@ -37,7 +37,6 @@ public class ExternalPlayerFragment extends Fragment {
private ImageButton butPlay;
private TextView mFeedName;
private ProgressBar mProgressBar;
private PlaybackController controller;
public ExternalPlayerFragment() {
@ -83,6 +82,11 @@ public class ExternalPlayerFragment extends Fragment {
controller.playPause();
}
});
loadMediaInfo();
}
public void connectToPlaybackService() {
controller.init();
}
private PlaybackController setupPlaybackController() {
@ -164,36 +168,35 @@ public class ExternalPlayerFragment extends Fragment {
private boolean loadMediaInfo() {
Log.d(TAG, "Loading media info");
if (controller != null && controller.serviceAvailable()) {
Playable media = controller.getMedia();
if (media != null) {
txtvTitle.setText(media.getEpisodeTitle());
mFeedName.setText(media.getFeedTitle());
mProgressBar.setProgress((int)
((double) controller.getPosition() / controller.getDuration() * 100));
if (controller == null) {
Log.w(TAG, "loadMediaInfo was called while PlaybackController was null!");
return false;
}
Glide.with(getActivity())
.load(media.getImageLocation())
.placeholder(R.color.light_gray)
.error(R.color.light_gray)
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
.fitCenter()
.dontAnimate()
.into(imgvCover);
Playable media = controller.getMedia();
if (media != null) {
txtvTitle.setText(media.getEpisodeTitle());
mFeedName.setText(media.getFeedTitle());
onPositionObserverUpdate();
fragmentLayout.setVisibility(View.VISIBLE);
if (controller.isPlayingVideoLocally()) {
butPlay.setVisibility(View.GONE);
} else {
butPlay.setVisibility(View.VISIBLE);
}
return true;
Glide.with(getActivity())
.load(media.getImageLocation())
.placeholder(R.color.light_gray)
.error(R.color.light_gray)
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
.fitCenter()
.dontAnimate()
.into(imgvCover);
fragmentLayout.setVisibility(View.VISIBLE);
if (controller.isPlayingVideoLocally()) {
butPlay.setVisibility(View.GONE);
} else {
Log.w(TAG, "loadMediaInfo was called while the media object of playbackService was null!");
return false;
butPlay.setVisibility(View.VISIBLE);
}
return true;
} else {
Log.w(TAG, "loadMediaInfo was called while playbackService was null!");
Log.w(TAG, "loadMediaInfo was called while the media object of playbackService was null!");
return false;
}
}

View File

@ -113,10 +113,13 @@ public class ItemDescriptionFragment extends Fragment implements MediaplayerInfo
Log.d(TAG, "Creating view");
webvDescription = new WebView(getActivity().getApplicationContext());
webvDescription.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
TypedArray ta = getActivity().getTheme().obtainStyledAttributes(new int[]
{android.R.attr.colorBackground});
int backgroundColor = ta.getColor(0, UserPreferences.getTheme() ==
R.style.Theme_AntennaPod_Dark ? Color.BLACK : Color.WHITE);
boolean black = UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark
|| UserPreferences.getTheme() == R.style.Theme_AntennaPod_TrueBlack;
int backgroundColor = ta.getColor(0, black ? Color.BLACK : Color.WHITE);
ta.recycle();
webvDescription.setBackgroundColor(backgroundColor);
if (!NetworkUtils.networkAvailable()) {

View File

@ -34,6 +34,7 @@ import com.bumptech.glide.Glide;
import com.joanzapata.iconify.Iconify;
import com.joanzapata.iconify.widget.IconButton;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.util.NetworkUtils;
import org.apache.commons.lang3.ArrayUtils;
@ -184,7 +185,8 @@ public class ItemFragment extends Fragment implements OnSwipeGesture {
txtvTitle.setEllipsize(TextUtils.TruncateAt.END);
}
webvDescription = (WebView) layout.findViewById(R.id.webvDescription);
if (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark) {
if (UserPreferences.getTheme() == R.style.Theme_AntennaPod_Dark ||
UserPreferences.getTheme() == R.style.Theme_AntennaPod_TrueBlack) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
webvDescription.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
@ -432,6 +434,16 @@ public class ItemFragment extends Fragment implements OnSwipeGesture {
butAction1Text = R.string.download_label;
}
}
FeedItem.State state = item.getState();
if (butAction2Text == R.string.delete_label && state == FeedItem.State.PLAYING) {
butAction2.setEnabled(false);
butAction2.setAlpha(0.5f);
} else {
butAction2.setEnabled(true);
butAction2.setAlpha(1.0f);
}
if(butAction1Icon != null && butAction1Text != 0) {
butAction1.setText(butAction1Icon +"\u0020\u0020" + getActivity().getString(butAction1Text));
Iconify.addIcons(butAction1);

View File

@ -534,14 +534,15 @@ public class QueueFragment extends Fragment {
private void refreshInfoBar() {
String info = queue.size() + getString(R.string.episodes_suffix);
if(queue.size() > 0) {
long duration = 0;
long timeLeft = 0;
for(FeedItem item : queue) {
if(item.getMedia() != null) {
duration += item.getMedia().getDuration();
timeLeft += item.getMedia().getDuration() - item.getMedia().getPosition();
}
}
info += " \u2022 ";
info += Converter.getDurationStringLocalized(getActivity(), duration);
info += getString(R.string.time_left_label);
info += Converter.getDurationStringLocalized(getActivity(), timeLeft);
}
infoBar.setText(info);
}

View File

@ -17,6 +17,7 @@ import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.FeedItemUtil;
import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.ShareUtils;
@ -86,7 +87,7 @@ public class FeedItemMenuHandler {
mi.setItemVisibility(R.id.add_to_queue_item, false);
}
if (!showExtendedMenu || selectedItem.getLink() == null) {
if (!showExtendedMenu || !ShareUtils.hasLinkToShare(selectedItem)) {
mi.setItemVisibility(R.id.visit_website_item, false);
mi.setItemVisibility(R.id.share_link_item, false);
mi.setItemVisibility(R.id.share_link_with_position_item, false);
@ -216,7 +217,7 @@ public class FeedItemMenuHandler {
DBWriter.setFeedItemAutoDownload(selectedItem, false);
break;
case R.id.visit_website_item:
Uri uri = Uri.parse(selectedItem.getLink());
Uri uri = Uri.parse(FeedItemUtil.getLinkWithFallback(selectedItem));
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
if(IntentUtils.isCallable(context, intent)) {
context.startActivity(intent);

View File

@ -20,7 +20,8 @@ public class MenuItemUtils extends de.danoeh.antennapod.core.menuhandler.MenuIte
public static void adjustTextColor(Context context, SearchView sv) {
if(Build.VERSION.SDK_INT < 14) {
EditText searchEditText = (EditText) sv.findViewById(R.id.search_src_text);
if(UserPreferences.getTheme() == de.danoeh.antennapod.R.style.Theme_AntennaPod_Dark) {
if (UserPreferences.getTheme() == de.danoeh.antennapod.R.style.Theme_AntennaPod_Dark
|| UserPreferences.getTheme() == R.style.Theme_AntennaPod_TrueBlack) {
searchEditText.setTextColor(Color.WHITE);
} else {
searchEditText.setTextColor(Color.BLACK);

View File

@ -4,13 +4,14 @@ import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Typeface;
import android.os.Build;
import android.support.v14.preference.SwitchPreference;
import android.support.v7.preference.PreferenceViewHolder;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.widget.TextView;
import de.danoeh.antennapod.R;
public class MasterSwitchPreference extends SwitchCompatPreference {
public class MasterSwitchPreference extends SwitchPreference {
public MasterSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
@ -29,15 +30,16 @@ public class MasterSwitchPreference extends SwitchCompatPreference {
super(context);
}
@Override
protected void onBindView(View view) {
super.onBindView(view);
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
TypedValue typedValue = new TypedValue();
getContext().getTheme().resolveAttribute(R.attr.master_switch_background, typedValue, true);
view.setBackgroundColor(typedValue.data);
holder.itemView.setBackgroundColor(typedValue.data);
TextView title = (TextView) view.findViewById(android.R.id.title);
TextView title = (TextView) holder.findViewById(android.R.id.title);
if (title != null) {
title.setTypeface(title.getTypeface(), Typeface.BOLD);
}

View File

@ -3,7 +3,6 @@ package de.danoeh.antennapod.preferences;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Fragment;
import android.app.ProgressDialog;
import android.app.TimePickerDialog;
import android.content.ActivityNotFoundException;
@ -18,31 +17,29 @@ import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AlertDialog;
import android.text.Editable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.preference.CheckBoxPreference;
import android.support.v7.preference.ListPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceFragmentCompat;
import android.support.v7.preference.PreferenceManager;
import android.support.v7.preference.PreferenceScreen;
import android.text.Html;
import android.text.TextWatcher;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.util.Log;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog;
import com.bytehamster.lib.preferencesearch.SearchConfiguration;
import com.bytehamster.lib.preferencesearch.SearchPreference;
import de.danoeh.antennapod.activity.AboutActivity;
import com.afollestad.materialdialogs.prefs.MaterialListPreference;
import de.danoeh.antennapod.activity.ImportExportActivity;
import de.danoeh.antennapod.activity.MediaplayerActivity;
import de.danoeh.antennapod.activity.OpmlImportFromPathActivity;
@ -99,8 +96,9 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc
private static final String PREF_SCREEN_INTEGRATIONS = "prefScreenIntegrations";
private static final String PREF_SCREEN_STORAGE = "prefScreenStorage";
private static final String PREF_SCREEN_AUTODL = "prefAutoDownloadSettings";
private static final String PREF_SCREEN_FLATTR = "prefFlattrSettings";
private static final String PREF_SCREEN_GPODDER = "prefGpodderSettings";
private static final String PREF_FLATTR_SETTINGS = "prefFlattrSettings";
private static final String PREF_FLATTR_AUTH = "pref_flattr_authenticate";
private static final String PREF_FLATTR_REVOKE = "prefRevokeAccess";
private static final String PREF_AUTO_FLATTR_PREFS = "prefAutoFlattrPrefs";
@ -181,6 +179,12 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc
case R.xml.preferences_integrations:
setupIntegrationsScreen();
break;
case R.xml.preferences_flattr:
setupFlattrScreen();
break;
case R.xml.preferences_gpodder:
setupGpodderScreen();
break;
case R.xml.preferences_storage:
setupStorageScreen();
break;
@ -300,7 +304,20 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc
}
private void setupIntegrationsScreen() {
final Activity activity = ui.getActivity();
final AppCompatActivity activity = ui.getActivity();
ui.findPreference(PREF_SCREEN_FLATTR).setOnPreferenceClickListener(preference -> {
openScreen(R.xml.preferences_flattr, activity);
return true;
});
ui.findPreference(PREF_SCREEN_GPODDER).setOnPreferenceClickListener(preference -> {
openScreen(R.xml.preferences_gpodder, activity);
return true;
});
}
private void setupFlattrScreen() {
final AppCompatActivity activity = ui.getActivity();
ui.findPreference(PreferenceController.PREF_FLATTR_REVOKE).setOnPreferenceClickListener(
preference -> {
@ -309,6 +326,29 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc
return true;
}
);
ui.findPreference(PreferenceController.PREF_AUTO_FLATTR_PREFS)
.setOnPreferenceClickListener(preference -> {
AutoFlattrPreferenceDialog.newAutoFlattrPreferenceDialog(activity,
new AutoFlattrPreferenceDialog.AutoFlattrPreferenceDialogInterface() {
@Override
public void onCancelled() {
}
@Override
public void onConfirmed(boolean autoFlattrEnabled, float autoFlattrValue) {
UserPreferences.setAutoFlattrSettings(autoFlattrEnabled, autoFlattrValue);
checkFlattrItemVisibility();
}
});
return true;
});
}
private void setupGpodderScreen() {
final AppCompatActivity activity = ui.getActivity();
ui.findPreference(PreferenceController.PREF_GPODNET_SETLOGIN_INFORMATION)
.setOnPreferenceClickListener(preference -> {
AuthenticationDialog dialog = new AuthenticationDialog(activity,
@ -356,24 +396,6 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc
GpodnetSetHostnameDialog.createDialog(activity).setOnDismissListener(dialog -> updateGpodnetPreferenceScreen());
return true;
});
ui.findPreference(PreferenceController.PREF_AUTO_FLATTR_PREFS)
.setOnPreferenceClickListener(preference -> {
AutoFlattrPreferenceDialog.newAutoFlattrPreferenceDialog(activity,
new AutoFlattrPreferenceDialog.AutoFlattrPreferenceDialogInterface() {
@Override
public void onCancelled() {
}
@Override
public void onConfirmed(boolean autoFlattrEnabled, float autoFlattrValue) {
UserPreferences.setAutoFlattrSettings(autoFlattrEnabled, autoFlattrValue);
checkFlattrItemVisibility();
}
});
return true;
});
}
private void setupPlaybackScreen() {
@ -395,7 +417,7 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc
return true;
});
if (!PictureInPictureUtil.supportsPictureInPicture(activity)) {
MaterialListPreference behaviour = (MaterialListPreference) ui.findPreference(UserPreferences.PREF_VIDEO_BEHAVIOR);
ListPreference behaviour = (ListPreference) ui.findPreference(UserPreferences.PREF_VIDEO_BEHAVIOR);
behaviour.setEntries(R.array.video_background_behavior_options_without_pip);
behaviour.setEntryValues(R.array.video_background_behavior_values_without_pip);
}
@ -432,9 +454,11 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc
}
private void setupNetworkScreen() {
final Activity activity = ui.getActivity();
ui.findPreference(PREF_SCREEN_AUTODL).setOnPreferenceClickListener(preference ->
openScreen(R.xml.preferences_autodownload, activity));
final AppCompatActivity activity = ui.getActivity();
ui.findPreference(PREF_SCREEN_AUTODL).setOnPreferenceClickListener(preference -> {
openScreen(R.xml.preferences_autodownload, activity);
return true;
});
ui.findPreference(UserPreferences.PREF_UPDATE_INTERVAL)
.setOnPreferenceClickListener(preference -> {
showUpdateIntervalTimePreferencesDialog();
@ -458,33 +482,6 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc
}
);
// validate and set correct value: number of downloads between 1 and 50 (inclusive)
final EditText ev = ((EditTextPreference) ui.findPreference(UserPreferences.PREF_PARALLEL_DOWNLOADS)).getEditText();
ev.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (s.length() > 0) {
try {
int value = Integer.parseInt(s.toString());
if (value <= 0) {
ev.setText("1");
} else if (value > 50) {
ev.setText("50");
}
} catch (NumberFormatException e) {
ev.setText("6");
}
ev.setSelection(ev.getText().length());
}
}
});
ui.findPreference(PREF_PROXY).setOnPreferenceClickListener(preference -> {
ProxyDialog dialog = new ProxyDialog(ui.getActivity());
dialog.createDialog().show();
@ -493,17 +490,28 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc
}
private void setupMainScreen() {
final Activity activity = ui.getActivity();
ui.findPreference(PREF_SCREEN_USER_INTERFACE).setOnPreferenceClickListener(preference ->
openScreen(R.xml.preferences_user_interface, activity));
ui.findPreference(PREF_SCREEN_PLAYBACK).setOnPreferenceClickListener(preference ->
openScreen(R.xml.preferences_playback, activity));
ui.findPreference(PREF_SCREEN_NETWORK).setOnPreferenceClickListener(preference ->
openScreen(R.xml.preferences_network, activity));
ui.findPreference(PREF_SCREEN_INTEGRATIONS).setOnPreferenceClickListener(preference ->
openScreen(R.xml.preferences_integrations, activity));
ui.findPreference(PREF_SCREEN_STORAGE).setOnPreferenceClickListener(preference ->
openScreen(R.xml.preferences_storage, activity));
final AppCompatActivity activity = ui.getActivity();
setupSearch();
ui.findPreference(PREF_SCREEN_USER_INTERFACE).setOnPreferenceClickListener(preference -> {
openScreen(R.xml.preferences_user_interface, activity);
return true;
});
ui.findPreference(PREF_SCREEN_PLAYBACK).setOnPreferenceClickListener(preference -> {
openScreen(R.xml.preferences_playback, activity);
return true;
});
ui.findPreference(PREF_SCREEN_NETWORK).setOnPreferenceClickListener(preference -> {
openScreen(R.xml.preferences_network, activity);
return true;
});
ui.findPreference(PREF_SCREEN_INTEGRATIONS).setOnPreferenceClickListener(preference -> {
openScreen(R.xml.preferences_integrations, activity);
return true;
});
ui.findPreference(PREF_SCREEN_STORAGE).setOnPreferenceClickListener(preference -> {
openScreen(R.xml.preferences_storage, activity);
return true;
});
ui.findPreference(PreferenceController.PREF_ABOUT).setOnPreferenceClickListener(
preference -> {
@ -550,15 +558,74 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc
});
}
private boolean openScreen(int preferences, Activity activity) {
Fragment prefFragment = new PreferenceActivity.MainFragment();
private void setupSearch() {
final AppCompatActivity activity = ui.getActivity();
SearchPreference searchPreference = (SearchPreference) ui.findPreference("searchPreference");
SearchConfiguration config = searchPreference.getSearchConfiguration();
config.setActivity(activity);
config.setFragmentContainerViewId(R.id.content);
config.setBreadcrumbsEnabled(true);
config.index()
.addBreadcrumb(getTitleOfPage(R.xml.preferences_user_interface))
.addFile(R.xml.preferences_user_interface);
config.index()
.addBreadcrumb(getTitleOfPage(R.xml.preferences_playback))
.addFile(R.xml.preferences_playback);
config.index()
.addBreadcrumb(getTitleOfPage(R.xml.preferences_network))
.addFile(R.xml.preferences_network);
config.index()
.addBreadcrumb(getTitleOfPage(R.xml.preferences_storage))
.addFile(R.xml.preferences_storage);
config.index()
.addBreadcrumb(getTitleOfPage(R.xml.preferences_network))
.addBreadcrumb(R.string.automation)
.addBreadcrumb(getTitleOfPage(R.xml.preferences_autodownload))
.addFile(R.xml.preferences_autodownload);
config.index()
.addBreadcrumb(getTitleOfPage(R.xml.preferences_integrations))
.addBreadcrumb(getTitleOfPage(R.xml.preferences_gpodder))
.addFile(R.xml.preferences_gpodder);
config.index()
.addBreadcrumb(getTitleOfPage(R.xml.preferences_integrations))
.addBreadcrumb(getTitleOfPage(R.xml.preferences_flattr))
.addFile(R.xml.preferences_flattr);
}
public PreferenceFragmentCompat openScreen(int preferences, AppCompatActivity activity) {
PreferenceFragmentCompat prefFragment = new PreferenceActivity.MainFragment();
Bundle args = new Bundle();
args.putInt(PARAM_RESOURCE, preferences);
prefFragment.setArguments(args);
activity.getFragmentManager().beginTransaction()
activity.getSupportFragmentManager().beginTransaction()
.replace(R.id.content, prefFragment)
.addToBackStack(TAG).commit();
return true;
return prefFragment;
}
public static int getTitleOfPage(int preferences) {
switch (preferences) {
case R.xml.preferences_network:
return R.string.network_pref;
case R.xml.preferences_autodownload:
return R.string.pref_automatic_download_title;
case R.xml.preferences_playback:
return R.string.playback_pref;
case R.xml.preferences_storage:
return R.string.storage_pref;
case R.xml.preferences_user_interface:
return R.string.user_interface_label;
case R.xml.preferences_integrations:
return R.string.integrations_label;
case R.xml.preferences_flattr:
return R.string.flattr_label;
case R.xml.preferences_gpodder:
return R.string.gpodnet_main_label;
default:
return R.string.settings_label;
}
}
private boolean export(ExportWriter exportWriter) {
@ -628,9 +695,14 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc
setDataFolderText();
break;
case R.xml.preferences_integrations:
setIntegrationsItemVisibility();
return;
case R.xml.preferences_flattr:
checkFlattrItemVisibility();
break;
case R.xml.preferences_gpodder:
GpodnetPreferences.registerOnSharedPreferenceChangeListener(gpoddernetListener);
updateGpodnetPreferenceScreen();
checkFlattrItemVisibility();
break;
case R.xml.preferences_playback:
checkSonicItemVisibility();
@ -797,10 +869,13 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc
}
}
private void setIntegrationsItemVisibility() {
ui.findPreference(PreferenceController.PREF_SCREEN_FLATTR).setEnabled(FlattrUtils.hasAPICredentials());
}
@SuppressWarnings("deprecation")
private void checkFlattrItemVisibility() {
boolean hasFlattrToken = FlattrUtils.hasToken();
ui.findPreference(PreferenceController.PREF_FLATTR_SETTINGS).setEnabled(FlattrUtils.hasAPICredentials());
ui.findPreference(PreferenceController.PREF_FLATTR_AUTH).setEnabled(!hasFlattrToken);
ui.findPreference(PreferenceController.PREF_FLATTR_REVOKE).setEnabled(hasFlattrToken);
ui.findPreference(PreferenceController.PREF_AUTO_FLATTR_PREFS).setEnabled(hasFlattrToken);
@ -1115,7 +1190,8 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc
public interface PreferenceUI {
void setFragment(PreferenceFragment fragment);
void setFragment(PreferenceFragmentCompat fragment);
PreferenceFragmentCompat getFragment();
/**
* Finds a preference based on its key.
@ -1124,6 +1200,6 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc
PreferenceScreen getPreferenceScreen();
Activity getActivity();
AppCompatActivity getActivity();
}
}

View File

@ -1,37 +0,0 @@
package de.danoeh.antennapod.preferences;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.preference.CheckBoxPreference;
import android.util.AttributeSet;
import de.danoeh.antennapod.R;
public class SwitchCompatPreference extends CheckBoxPreference {
public SwitchCompatPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public SwitchCompatPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
public SwitchCompatPreference(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public SwitchCompatPreference(Context context) {
super(context);
init();
}
private void init() {
setWidgetLayoutResource(R.layout.preference_switch_layout);
}
}

View File

@ -1,244 +0,0 @@
package de.danoeh.antennapod.service;
import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Build;
import android.os.IBinder;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.RemoteViews;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.service.playback.PlayerStatus;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.playback.Playable;
import de.danoeh.antennapod.fragment.QueueFragment;
import de.danoeh.antennapod.receiver.PlayerWidget;
/**
* Updates the state of the player widget
*/
public class PlayerWidgetService extends Service {
private static final String TAG = "PlayerWidgetService";
private PlaybackService playbackService;
/**
* Controls write access to playbackservice reference
*/
private final Object psLock = new Object();
/**
* True while service is updating the widget
*/
private volatile boolean isUpdating;
public PlayerWidgetService() {
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "Service created");
isUpdating = false;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "Service is about to be destroyed");
if (playbackService != null) {
Playable playable = playbackService.getPlayable();
if (playable != null && playable instanceof FeedMedia) {
FeedMedia media = (FeedMedia) playable;
if (media.hasAlmostEnded()) {
Log.d(TAG, "smart mark as read");
FeedItem item = media.getItem();
DBWriter.markItemPlayed(item, FeedItem.PLAYED, false);
DBWriter.removeQueueItem(this, item, false);
DBWriter.addItemToPlaybackHistory(media);
if (item.getFeed().getPreferences().getCurrentAutoDelete() &&
(!item.isTagged(FeedItem.TAG_FAVORITE) || !UserPreferences.shouldFavoriteKeepEpisode())) {
Log.d(TAG, "Delete " + media.toString());
DBWriter.deleteFeedMediaOfItem(this, media.getId());
}
}
}
}
try {
unbindService(mConnection);
} catch (IllegalArgumentException e) {
Log.w(TAG, "IllegalArgumentException when trying to unbind service");
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!isUpdating) {
if (playbackService == null && PlaybackService.isRunning) {
bindService(new Intent(this, PlaybackService.class),
mConnection, 0);
} else {
startViewUpdaterIfNotRunning();
}
} else {
Log.d(TAG, "Service was called while updating. Ignoring update request");
}
return Service.START_NOT_STICKY;
}
private void updateViews() {
isUpdating = true;
ComponentName playerWidget = new ComponentName(this, PlayerWidget.class);
AppWidgetManager manager = AppWidgetManager.getInstance(this);
RemoteViews views = new RemoteViews(getPackageName(),
R.layout.player_widget);
PendingIntent startMediaplayer = PendingIntent.getActivity(this, 0,
PlaybackService.getPlayerActivityIntent(this), 0);
Intent startApp = new Intent(getBaseContext(), MainActivity.class);
startApp.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startApp.putExtra(MainActivity.EXTRA_FRAGMENT_TAG, QueueFragment.TAG);
PendingIntent startAppPending = PendingIntent.getActivity(getBaseContext(), 0, startApp, PendingIntent.FLAG_UPDATE_CURRENT);
boolean nothingPlaying = false;
if (playbackService != null) {
final Playable media = playbackService.getPlayable();
if (media != null) {
PlayerStatus status = playbackService.getStatus();
views.setOnClickPendingIntent(R.id.layout_left, startMediaplayer);
views.setTextViewText(R.id.txtvTitle, media.getEpisodeTitle());
String progressString = getProgressString();
if (progressString != null) {
views.setViewVisibility(R.id.txtvProgress, View.VISIBLE);
views.setTextViewText(R.id.txtvProgress, progressString);
}
if (status == PlayerStatus.PLAYING) {
views.setImageViewResource(R.id.butPlay, R.drawable.ic_pause_white_24dp);
if (Build.VERSION.SDK_INT >= 15) {
views.setContentDescription(R.id.butPlay, getString(R.string.pause_label));
}
} else {
views.setImageViewResource(R.id.butPlay, R.drawable.ic_play_arrow_white_24dp);
if (Build.VERSION.SDK_INT >= 15) {
views.setContentDescription(R.id.butPlay, getString(R.string.play_label));
}
}
views.setOnClickPendingIntent(R.id.butPlay,
createMediaButtonIntent());
} else {
nothingPlaying = true;
}
} else {
nothingPlaying = true;
}
if (nothingPlaying) {
// start the app if they click anything
views.setOnClickPendingIntent(R.id.layout_left, startAppPending);
views.setOnClickPendingIntent(R.id.butPlay, startAppPending);
views.setViewVisibility(R.id.txtvProgress, View.INVISIBLE);
views.setTextViewText(R.id.txtvTitle,
this.getString(R.string.no_media_playing_label));
views.setImageViewResource(R.id.butPlay, R.drawable.ic_play_arrow_white_24dp);
}
manager.updateAppWidget(playerWidget, views);
isUpdating = false;
}
/**
* Creates an intent which fakes a mediabutton press
*/
private PendingIntent createMediaButtonIntent() {
KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
Intent startingIntent = new Intent(
MediaButtonReceiver.NOTIFY_BUTTON_RECEIVER);
startingIntent.putExtra(Intent.EXTRA_KEY_EVENT, event);
return PendingIntent.getBroadcast(this, 0, startingIntent, 0);
}
private String getProgressString() {
int position = playbackService.getCurrentPosition();
int duration = playbackService.getDuration();
if (position > 0 && duration > 0) {
return Converter.getDurationStringLong(position) + " / "
+ Converter.getDurationStringLong(duration);
} else {
return null;
}
}
private final ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
Log.d(TAG, "Connection to service established");
synchronized (psLock) {
if(service instanceof PlaybackService.LocalBinder) {
playbackService = ((PlaybackService.LocalBinder) service).getService();
startViewUpdaterIfNotRunning();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
synchronized (psLock) {
playbackService = null;
Log.d(TAG, "Disconnected from service");
}
}
};
private void startViewUpdaterIfNotRunning() {
if (!isUpdating) {
ViewUpdater updateThread = new ViewUpdater(this);
updateThread.start();
}
}
class ViewUpdater extends Thread {
private static final String THREAD_NAME = "ViewUpdater";
private final PlayerWidgetService service;
public ViewUpdater(PlayerWidgetService service) {
super();
setName(THREAD_NAME);
this.service = service;
}
@Override
public void run() {
synchronized (psLock) {
service.updateViews();
}
}
}
}

View File

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

View File

@ -44,6 +44,7 @@
android:layout_centerVertical="true"
android:contentDescription="@string/pause_label"
android:background="?attr/selectableItemBackground"
android:src="?attr/av_play_big"
tools:src="@drawable/ic_play_arrow_white_36dp"/>
<TextView

View File

@ -110,9 +110,9 @@
android:layout_centerHorizontal="true"
android:background="?attr/selectableItemBackground"
android:contentDescription="@string/pause_label"
android:src="?attr/av_pause"
android:src="?attr/av_play"
android:scaleType="fitCenter"
tools:src="@drawable/ic_pause_white_36dp"
tools:src="@drawable/ic_play_arrow_white_24dp"
tools:background="@android:color/holo_green_dark" />
<ImageButton

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.SwitchCompat
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:clickable="false"
android:focusable="false" />

View File

@ -1,10 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<com.bytehamster.lib.preferencesearch.SearchPreference
android:key="searchPreference" />
<Preference
android:key="prefScreenInterface"
android:title="@string/user_interface_label"
android:icon="?attr/type_video" />
android:key="prefScreenInterface"
android:title="@string/user_interface_label"
android:icon="?attr/type_video" />
<Preference
android:key="prefScreenPlayback"

View File

@ -1,20 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:search="http://schemas.android.com/apk/com.bytehamster.lib.preferencesearch">
<de.danoeh.antennapod.preferences.MasterSwitchPreference
android:key="prefEnableAutoDl"
android:title="@string/pref_automatic_download_title"
search:summary="@string/pref_automatic_download_sum"
android:defaultValue="false"/>
<com.afollestad.materialdialogs.prefs.MaterialListPreference
<ListPreference
android:defaultValue="25"
android:entries="@array/episode_cache_size_entries"
android:key="prefEpisodeCacheSize"
android:title="@string/pref_episode_cache_title"
android:entryValues="@array/episode_cache_size_values"
app:useStockLayout="true"/>
<com.afollestad.materialdialogs.prefs.MaterialListPreference
<ListPreference
android:defaultValue="-1"
android:entries="@array/episode_cleanup_entries"
android:key="prefEpisodeCleanup"
@ -22,17 +24,17 @@
android:summary="@string/pref_episode_cleanup_summary"
android:entryValues="@array/episode_cleanup_values"
app:useStockLayout="true"/>
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:key="prefEnableAutoDownloadOnBattery"
android:title="@string/pref_automatic_download_on_battery_title"
android:summary="@string/pref_automatic_download_on_battery_sum"
android:defaultValue="true"/>
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:key="prefEnableAutoDownloadOnMobile"
android:title="@string/pref_autodl_allow_on_mobile_title"
android:summary="@string/pref_autodl_allow_on_mobile_sum"
android:defaultValue="false"/>
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:key="prefEnableAutoDownloadWifiFilter"
android:title="@string/pref_autodl_wifi_filter_title"
android:summary="@string/pref_autodl_wifi_filter_sum"/>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceScreen
android:key="pref_flattr_authenticate"
android:summary="@string/pref_flattr_auth_sum"
android:title="@string/pref_flattr_auth_title">
<intent android:action=".activities.FlattrAuthActivity"/>
</PreferenceScreen>
<Preference
android:key="prefAutoFlattrPrefs"
android:summary="@string/pref_auto_flattr_sum"
android:title="@string/pref_auto_flattr_title"/>
<Preference
android:key="prefRevokeAccess"
android:summary="@string/pref_revokeAccess_sum"
android:title="@string/pref_revokeAccess_title"/>
</PreferenceScreen>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<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_sync"
android:title="@string/pref_gpodnet_sync_changes_title"
android:summary="@string/pref_gpodnet_sync_changes_sum"/>
<Preference
android:key="pref_gpodnet_force_full_sync"
android:title="@string/pref_gpodnet_full_sync_title"
android:summary="@string/pref_gpodnet_full_sync_sum"/>
<Preference
android:key="pref_gpodnet_logout"
android:title="@string/pref_gpodnet_logout_title"/>
<Preference
android:key="pref_gpodnet_hostname"
android:title="@string/pref_gpodnet_sethostname_title"/>
<SwitchPreference
android:key="pref_gpodnet_notifications"
android:title="@string/pref_gpodnet_notifications_title"
android:summary="@string/pref_gpodnet_notifications_sum"
android:defaultValue="true"/>
</PreferenceScreen>

View File

@ -3,61 +3,14 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceScreen
<Preference
android:key="prefFlattrSettings"
android:title="@string/flattr_label"
android:summary="@string/flattr_summary">
<PreferenceScreen
android:key="pref_flattr_authenticate"
android:summary="@string/pref_flattr_auth_sum"
android:title="@string/pref_flattr_auth_title">
<intent android:action=".activities.FlattrAuthActivity"/>
</PreferenceScreen>
android:summary="@string/flattr_summary" />
<Preference
android:key="prefAutoFlattrPrefs"
android:summary="@string/pref_auto_flattr_sum"
android:title="@string/pref_auto_flattr_title"/>
<Preference
android:key="prefRevokeAccess"
android:summary="@string/pref_revokeAccess_sum"
android:title="@string/pref_revokeAccess_title"/>
</PreferenceScreen>
<PreferenceScreen
<Preference
android:key="prefGpodderSettings"
android:title="@string/gpodnet_main_label"
android:summary="@string/gpodnet_summary">
<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_sync"
android:title="@string/pref_gpodnet_sync_changes_title"
android:summary="@string/pref_gpodnet_sync_changes_sum"/>
<Preference
android:key="pref_gpodnet_force_full_sync"
android:title="@string/pref_gpodnet_full_sync_title"
android:summary="@string/pref_gpodnet_full_sync_sum"/>
<Preference
android:key="pref_gpodnet_logout"
android:title="@string/pref_gpodnet_logout_title"/>
<Preference
android:key="pref_gpodnet_hostname"
android:title="@string/pref_gpodnet_sethostname_title"/>
<de.danoeh.antennapod.preferences.SwitchCompatPreference
android:key="pref_gpodnet_notifications"
android:title="@string/pref_gpodnet_notifications_title"
android:summary="@string/pref_gpodnet_notifications_sum"
android:defaultValue="true"/>
</PreferenceScreen>
android:summary="@string/gpodnet_summary" />
</PreferenceScreen>

View File

@ -1,13 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:search="http://schemas.android.com/apk/com.bytehamster.lib.preferencesearch">
<PreferenceCategory android:title="@string/automation">
<Preference
android:key="prefAutoUpdateIntervall"
android:summary="@string/pref_autoUpdateIntervallOrTime_sum"
android:title="@string/pref_autoUpdateIntervallOrTime_title"/>
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:defaultValue="false"
android:enabled="true"
android:key="prefMobileUpdate"
@ -16,17 +17,18 @@
<Preference
android:summary="@string/pref_automatic_download_sum"
android:key="prefAutoDownloadSettings"
android:title="@string/pref_automatic_download_title" />
android:title="@string/pref_automatic_download_title"
search:ignore="true" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/download_pref_details">
<com.afollestad.materialdialogs.prefs.MaterialEditTextPreference
<EditTextPreference
android:defaultValue="4"
android:inputType="number"
android:key="prefParallelDownloads"
android:title="@string/pref_parallel_downloads_title"
app:useStockLayout="true"/>
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:defaultValue="true"
android:enabled="true"
android:key="prefShowDownloadReport"

View File

@ -4,39 +4,39 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory android:title="@string/interruptions">
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:defaultValue="true"
android:enabled="true"
android:key="prefPauseOnHeadsetDisconnect"
android:summary="@string/pref_pauseOnDisconnect_sum"
android:title="@string/pref_pauseOnHeadsetDisconnect_title"/>
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:defaultValue="true"
android:enabled="true"
android:dependency="prefPauseOnHeadsetDisconnect"
android:key="prefUnpauseOnHeadsetReconnect"
android:summary="@string/pref_unpauseOnHeadsetReconnect_sum"
android:title="@string/pref_unpauseOnHeadsetReconnect_title"/>
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:defaultValue="false"
android:enabled="true"
android:dependency="prefPauseOnHeadsetDisconnect"
android:key="prefUnpauseOnBluetoothReconnect"
android:summary="@string/pref_unpauseOnBluetoothReconnect_sum"
android:title="@string/pref_unpauseOnBluetoothReconnect_title"/>
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:defaultValue="false"
android:enabled="true"
android:key="prefPauseForFocusLoss"
android:summary="@string/pref_pausePlaybackForFocusLoss_sum"
android:title="@string/pref_pausePlaybackForFocusLoss_title"/>
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:defaultValue="true"
android:enabled="true"
android:key="prefResumeAfterCall"
android:summary="@string/pref_resumeAfterCall_sum"
android:title="@string/pref_resumeAfterCall_title"/>
<com.afollestad.materialdialogs.prefs.MaterialListPreference
<ListPreference
android:defaultValue="stop"
android:entries="@array/video_background_behavior_options"
android:entryValues="@array/video_background_behavior_values"
@ -47,13 +47,13 @@
</PreferenceCategory>
<PreferenceCategory android:title="@string/buttons">
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:defaultValue="false"
android:enabled="true"
android:key="prefHardwareForwardButtonSkips"
android:summary="@string/pref_hardwareForwardButtonSkips_sum"
android:title="@string/pref_hardwareForwardButtonSkips_title"/>
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:defaultValue="false"
android:enabled="true"
android:key="prefHardwarePreviousButtonRestarts"
@ -74,25 +74,25 @@
</PreferenceCategory>
<PreferenceCategory android:title="@string/queue_label">
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:defaultValue="true"
android:enabled="true"
android:key="prefEnqueueDownloaded"
android:summary="@string/pref_enqueue_downloaded_summary"
android:title="@string/pref_enqueue_downloaded_title" />
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:defaultValue="false"
android:enabled="true"
android:key="prefQueueAddToFront"
android:summary="@string/pref_queueAddToFront_sum"
android:title="@string/pref_queueAddToFront_title"/>
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:defaultValue="true"
android:enabled="true"
android:key="prefFollowQueue"
android:summary="@string/pref_followQueue_sum"
android:title="@string/pref_followQueue_title"/>
<com.afollestad.materialdialogs.prefs.MaterialListPreference
<ListPreference
android:defaultValue="30"
android:entries="@array/smart_mark_as_played_values"
android:entryValues="@array/smart_mark_as_played_values"
@ -100,7 +100,7 @@
android:summary="@string/pref_smart_mark_as_played_sum"
android:title="@string/pref_smart_mark_as_played_title"
app:useStockLayout="true"/>
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:defaultValue="true"
android:enabled="true"
android:key="prefSkipKeepsEpisode"
@ -109,7 +109,7 @@
</PreferenceCategory>
<PreferenceCategory android:title="@string/media_player">
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:defaultValue="true"
android:enabled="false"
android:key="prefSonic"
@ -118,7 +118,7 @@
</PreferenceCategory>
<PreferenceCategory android:title="@string/experimental_pref">
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:defaultValue="false"
android:enabled="true"
android:key="prefCast"

View File

@ -13,13 +13,13 @@
android:key="prefImageCacheSize"
android:summary="@string/pref_image_cache_size_sum"
android:defaultValue="100"/>
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:defaultValue="false"
android:enabled="true"
android:key="prefAutoDelete"
android:summary="@string/pref_auto_delete_sum"
android:title="@string/pref_auto_delete_title"/>
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:defaultValue="true"
android:enabled="true"
android:key="prefFavoriteKeepsEpisode"

View File

@ -4,7 +4,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory android:title="@string/appearance">
<com.afollestad.materialdialogs.prefs.MaterialListPreference
<ListPreference
android:entryValues="@array/theme_values"
android:entries="@array/theme_options"
android:title="@string/pref_set_theme_title"
@ -16,7 +16,7 @@
android:key="prefHiddenDrawerItems"
android:summary="@string/pref_nav_drawer_items_sum"
android:title="@string/pref_nav_drawer_items_title"/>
<com.afollestad.materialdialogs.prefs.MaterialListPreference
<ListPreference
android:entryValues="@array/nav_drawer_feed_order_values"
android:entries="@array/nav_drawer_feed_order_options"
android:title="@string/pref_nav_drawer_feed_order_title"
@ -24,7 +24,7 @@
android:summary="@string/pref_nav_drawer_feed_order_sum"
android:defaultValue="0"
app:useStockLayout="true"/>
<com.afollestad.materialdialogs.prefs.MaterialListPreference
<ListPreference
android:entryValues="@array/nav_drawer_feed_counter_values"
android:entries="@array/nav_drawer_feed_counter_options"
android:title="@string/pref_nav_drawer_feed_counter_title"
@ -34,13 +34,13 @@
app:useStockLayout="true"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/external_elements">
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:defaultValue="false"
android:enabled="true"
android:key="prefExpandNotify"
android:summary="@string/pref_expandNotify_sum"
android:title="@string/pref_expandNotify_title"/>
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:defaultValue="true"
android:enabled="true"
android:key="prefPersistNotify"
@ -50,7 +50,7 @@
android:key="prefCompactNotificationButtons"
android:summary="@string/pref_compact_notification_buttons_sum"
android:title="@string/pref_compact_notification_buttons_title"/>
<de.danoeh.antennapod.preferences.SwitchCompatPreference
<SwitchPreference
android:defaultValue="true"
android:enabled="true"
android:key="prefLockscreenBackground"

View File

@ -37,12 +37,12 @@ subprojects {
}
project.ext {
compileSdkVersion = 25
compileSdkVersion = 26
buildToolsVersion = "27.0.3"
minSdkVersion = 14
targetSdkVersion = 26
supportVersion = "25.3.1"
supportVersion = "26.1.0"
commonsioVersion = "2.5"
commonslangVersion = "3.6"
commonstextVersion = "1.3"
@ -66,7 +66,7 @@ project.ext {
castCompanionLibVer = "2.9.1"
playServicesVersion = "8.4.0"
wearableSupportVersion = "2.0.3"
wearableSupportVersion = "2.2.0"
}
task wrapper(type: Wrapper) {

View File

@ -1,23 +0,0 @@
general:
artifacts:
- app/build/outputs/apk
machine:
environment:
GRADLE_OPTS: '-Dorg.gradle.jvmargs="-Xmx1536m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError"'
_JAVA_OPTIONS: "-Xms256m -Xmx1280m -XX:MaxPermSize=350m"
java:
version: oraclejdk8
dependencies:
cache_directories:
- ~/.android
- ~/android
pre:
- echo y | android update sdk --no-ui --all --filter "tool,extra-android-m2repository,extra-android-support,extra-google-google_play_services,extra-google-m2repository,android-25"
- echo y | android update sdk --no-ui --all --filter "build-tools-27.0.3"
override:
- echo override dependencies
test:
override:
- ./gradlew assembleDebug -PdisablePreDex:
timeout: 1800

View File

@ -10,7 +10,7 @@ android {
versionCode 1
versionName "1.0"
testApplicationId "de.danoeh.antennapod.core.tests"
testInstrumentationRunner "de.danoeh.antennapod.core.tests.AntennaPodTestRunner"
testInstrumentationRunner "de.danoeh.antennapod.core.AntennaPodTestRunner"
}
buildTypes {
release {
@ -49,6 +49,8 @@ repositories {
dependencies {
implementation "com.android.support:support-v4:$supportVersion"
implementation "com.android.support:appcompat-v7:$supportVersion"
implementation "com.android.support:preference-v14:$supportVersion"
implementation "com.android.support:percent:$supportVersion"
implementation "org.apache.commons:commons-lang3:$commonslangVersion"
implementation "org.apache.commons:commons-text:$commonstextVersion"
implementation ("org.shredzone.flattr4j:flattr4j-core:$flattr4jVersion") {
@ -77,6 +79,18 @@ dependencies {
} else {
System.out.println("core: free build hack, skipping some dependencies")
}
testImplementation 'junit:junit:4.12'
}
tasks.withType(Test) {
testLogging {
exceptionFormat "full"
events "skipped", "passed", "failed"
showStandardStreams true
displayGranularity 2
}
}
allprojects {

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.tests;
package de.danoeh.antennapod.core;
import android.test.InstrumentationTestRunner;
import android.test.suitebuilder.TestSuiteBuilder;

View File

@ -1,9 +0,0 @@
package de.danoeh.antennapod.core.feed;
class FeedImageMother {
public static FeedImage anyFeedImage() {
return new FeedImage(0, "image", null, "http://example.com/picture", false);
}
}

View File

@ -1,35 +0,0 @@
package de.danoeh.antennapod.core.tests.util.service.download;
import android.test.AndroidTestCase;
import java.util.ArrayList;
import java.util.List;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.service.download.DownloadService;
public class DownloadServiceTest extends AndroidTestCase {
public void testRemoveDuplicateImages() {
List<FeedItem> items = new ArrayList<>();
for (int i = 0; i < 50; i++) {
FeedItem item = new FeedItem();
String url = (i % 5 == 0) ? "dupe_url" : String.format("url_%d", i);
item.setImage(new FeedImage(null, url, ""));
items.add(item);
}
Feed feed = new Feed();
feed.setItems(items);
DownloadService.removeDuplicateImages(feed);
assertEquals(50, items.size());
for (int i = 0; i < items.size(); i++) {
FeedItem item = items.get(i);
String want = (i == 0) ? "dupe_url" : (i % 5 == 0) ? null : String.format("url_%d", i);
assertEquals(want, item.getImageLocation());
}
}
}

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.tests.util;
package de.danoeh.antennapod.core.util;
import android.test.AndroidTestCase;
@ -7,8 +7,14 @@ import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import de.danoeh.antennapod.core.util.DateUtils;
/**
* Unit test for {@link DateUtils}.
*
* Note: It NEEDS to be run in android devices, i.e., it cannot be run in standard JDK, because
* the test invokes some android platform-specific behavior in the underlying
* {@link java.text.SimpleDateFormat} used by {@link DateUtils}.
*
*/
public class DateUtilsTest extends AndroidTestCase {
public void testParseDateWithMicroseconds() throws Exception {
@ -101,6 +107,12 @@ public class DateUtilsTest extends AndroidTestCase {
assertEquals(expected, actual);
}
/**
* Requires Android platform.
*
* Reason: Standard JDK cannot parse timezone <code>-08:00</code> (ISO 8601 format). It only accepts
* <code>-0800</code> (RFC 822 format)
*/
public void testParseDateWithNoTimezonePadding() throws Exception {
GregorianCalendar exp = new GregorianCalendar(2017, 1, 22, 22, 28, 0);
exp.setTimeZone(TimeZone.getTimeZone("UTC"));
@ -109,6 +121,12 @@ public class DateUtilsTest extends AndroidTestCase {
assertEquals(expected, actual);
}
/**
* Requires Android platform. Root cause: {@link DateUtils} implementation makes
* use of ISO 8601 time zone, which does not work on standard JDK.
*
* @see #testParseDateWithNoTimezonePadding()
*/
public void testParseDateWithForCest() throws Exception {
GregorianCalendar exp1 = new GregorianCalendar(2017, 0, 28, 22, 0, 0);
exp1.setTimeZone(TimeZone.getTimeZone("UTC"));

View File

@ -27,6 +27,7 @@
</service>
<service
android:name=".service.GpodnetSyncService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:enabled="true" />
<receiver
@ -52,9 +53,17 @@
</intent-filter>
</receiver>
<receiver android:name=".receiver.FeedUpdateReceiver">
<receiver android:name=".receiver.FeedUpdateReceiver"
android:label="@string/feed_update_receiver_name"
android:exported="true"> <!-- allow feeds update to be triggered by external apps -->
</receiver>
<service
android:name=".service.FeedUpdateJobService"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
</application>
</manifest>

View File

@ -14,7 +14,6 @@ import java.io.File;
import java.util.List;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBReader;
@ -64,29 +63,6 @@ class UpdateManager {
}
private static void onUpgrade(final int oldVersionCode, final int newVersionCode) {
if(oldVersionCode < 1030099) {
// delete the now obsolete image cache
// from now on, Glide will handle caching images
new Thread() {
public void run() {
List<Feed> feeds = DBReader.getFeedList();
for (Feed podcast : feeds) {
List<FeedItem> episodes = DBReader.getFeedItemList(podcast);
for (FeedItem episode : episodes) {
FeedImage image = episode.getImage();
if (image != null && image.isDownloaded() && image.getFile_url() != null) {
File imageFile = new File(image.getFile_url());
if (imageFile.exists()) {
imageFile.delete();
}
image.setFile_url(null); // calls setDownloaded(false)
DBWriter.setFeedImage(image);
}
}
}
}
}.start();
}
if(oldVersionCode < 1050004) {
if(MediaPlayer.isPrestoLibraryInstalled(context) && Build.VERSION.SDK_INT >= 16) {
UserPreferences.enableSonic(true);

View File

@ -10,6 +10,7 @@ import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
import org.shredzone.flattr4j.exception.FlattrException;
import java.util.LinkedList;
@ -175,7 +176,7 @@ public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorke
PendingIntent contentIntent = PendingIntent.getActivity(context, 0,
ClientConfig.flattrCallbacks.getFlattrAuthenticationActivityIntent(context), 0);
Notification notification = new NotificationCompat.Builder(context)
Notification notification = new NotificationCompat.Builder(context, NotificationUtils.CHANNEL_ID_ERROR)
.setStyle(new NotificationCompat.BigTextStyle().bigText(context.getString(R.string.no_flattr_token_notification_msg)))
.setContentIntent(contentIntent)
.setContentTitle(context.getString(R.string.no_flattr_token_title))
@ -208,7 +209,7 @@ public class FlattrClickWorker extends AsyncTask<Void, Integer, FlattrClickWorke
+ context.getString(R.string.flattr_click_failure_count, failed);
}
Notification notification = new NotificationCompat.Builder(context)
Notification notification = new NotificationCompat.Builder(context, NotificationUtils.CHANNEL_ID_ERROR)
.setStyle(new NotificationCompat.BigTextStyle().bigText(subtext))
.setContentIntent(contentIntent)
.setContentTitle(title)

View File

@ -0,0 +1,13 @@
package de.danoeh.antennapod.core.event;
public class ServiceEvent {
public enum Action {
SERVICE_STARTED
}
public final Action action;
public ServiceEvent(Action action) {
this.action = action;
}
}

View File

@ -44,7 +44,7 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
* Name of the author
*/
private String author;
private FeedImage image;
private String imageUrl;
private List<FeedItem> items;
/**
@ -96,7 +96,7 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
* This constructor is used for restoring a feed from the database.
*/
public Feed(long id, String lastUpdate, String title, String customTitle, String link, String description, String paymentLink,
String author, String language, String type, String feedIdentifier, FeedImage image, String fileUrl,
String author, String language, String type, String feedIdentifier, String imageUrl, String fileUrl,
String downloadUrl, boolean downloaded, FlattrStatus status, boolean paged, String nextPageLink,
String filter, boolean lastUpdateFailed) {
super(fileUrl, downloadUrl, downloaded);
@ -111,7 +111,7 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
this.language = language;
this.type = type;
this.feedIdentifier = feedIdentifier;
this.image = image;
this.imageUrl = imageUrl;
this.flattrStatus = status;
this.paged = paged;
this.nextPageLink = nextPageLink;
@ -128,9 +128,9 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
* This constructor is used for test purposes and uses a default flattr status object.
*/
public Feed(long id, String lastUpdate, String title, String link, String description, String paymentLink,
String author, String language, String type, String feedIdentifier, FeedImage image, String fileUrl,
String author, String language, String type, String feedIdentifier, String imageUrl, String fileUrl,
String downloadUrl, boolean downloaded) {
this(id, lastUpdate, title, null, link, description, paymentLink, author, language, type, feedIdentifier, image,
this(id, lastUpdate, title, null, link, description, paymentLink, author, language, type, feedIdentifier, imageUrl,
fileUrl, downloadUrl, downloaded, new FlattrStatus(), false, null, null, false);
}
@ -191,6 +191,7 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
int indexNextPageLink = cursor.getColumnIndex(PodDBAdapter.KEY_NEXT_PAGE_LINK);
int indexHide = cursor.getColumnIndex(PodDBAdapter.KEY_HIDE);
int indexLastUpdateFailed = cursor.getColumnIndex(PodDBAdapter.KEY_LAST_UPDATE_FAILED);
int indexImageUrl = cursor.getColumnIndex(PodDBAdapter.KEY_IMAGE_URL);
Feed feed = new Feed(
cursor.getLong(indexId),
@ -204,7 +205,7 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
cursor.getString(indexLanguage),
cursor.getString(indexType),
cursor.getString(indexFeedIdentifier),
null,
cursor.getString(indexImageUrl),
cursor.getString(indexFileUrl),
cursor.getString(indexDownloadUrl),
cursor.getInt(indexDownloaded) > 0,
@ -266,8 +267,8 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
public void updateFromOther(Feed other) {
// don't update feed's download_url, we do that manually if redirected
// see AntennapodHttpClient
if (other.image != null) {
this.image = other.image;
if (other.imageUrl != null) {
this.imageUrl = other.imageUrl;
}
if (other.feedTitle != null) {
feedTitle = other.feedTitle;
@ -305,8 +306,8 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
if (super.compareWithOther(other)) {
return true;
}
if (other.image != null) {
if (image == null || !TextUtils.equals(image.download_url, other.image.download_url)) {
if (other.imageUrl != null) {
if (imageUrl == null || !TextUtils.equals(imageUrl, other.imageUrl)) {
return true;
}
}
@ -411,12 +412,12 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
this.description = description;
}
public FeedImage getImage() {
return image;
public String getImageUrl() {
return imageUrl;
}
public void setImage(FeedImage image) {
this.image = image;
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
public List<FeedItem> getItems() {
@ -505,11 +506,7 @@ public class Feed extends FeedFile implements FlattrThing, ImageResource {
@Override
public String getImageLocation() {
if (image != null) {
return image.getImageLocation();
} else {
return null;
}
return imageUrl;
}
public int getPageNr() {

View File

@ -1,92 +0,0 @@
package de.danoeh.antennapod.core.feed;
import android.database.Cursor;
import java.io.File;
import de.danoeh.antennapod.core.asynctask.ImageResource;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
public class FeedImage extends FeedFile implements ImageResource {
public static final int FEEDFILETYPE_FEEDIMAGE = 1;
private String title;
private FeedComponent owner;
public FeedImage(FeedComponent owner, String download_url, String title) {
super(null, download_url, false);
this.download_url = download_url;
this.title = title;
this.owner = owner;
}
public FeedImage(long id, String title, String file_url,
String download_url, boolean downloaded) {
super(file_url, download_url, downloaded);
this.id = id;
this.title = title;
}
public FeedImage() {
super();
}
public static FeedImage fromCursor(Cursor cursor) {
int indexId = cursor.getColumnIndex(PodDBAdapter.KEY_ID);
int indexTitle = cursor.getColumnIndex(PodDBAdapter.KEY_TITLE);
int indexFileUrl = cursor.getColumnIndex(PodDBAdapter.KEY_FILE_URL);
int indexDownloadUrl = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOAD_URL);
int indexDownloaded = cursor.getColumnIndex(PodDBAdapter.KEY_DOWNLOADED);
return new FeedImage(
cursor.getLong(indexId),
cursor.getString(indexTitle),
cursor.getString(indexFileUrl),
cursor.getString(indexDownloadUrl),
cursor.getInt(indexDownloaded) > 0
);
}
@Override
public String getHumanReadableIdentifier() {
if (owner != null && owner.getHumanReadableIdentifier() != null) {
return owner.getHumanReadableIdentifier();
} else {
return download_url;
}
}
@Override
public int getTypeAsInt() {
return FEEDFILETYPE_FEEDIMAGE;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public FeedComponent getOwner() {
return owner;
}
public void setOwner(FeedComponent owner) {
this.owner = owner;
}
@Override
public String getImageLocation() {
if (file_url != null && downloaded) {
return new File(file_url).getAbsolutePath();
} else if(download_url != null) {
return download_url;
} else {
return null;
}
}
}

View File

@ -4,6 +4,7 @@ import android.database.Cursor;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import de.danoeh.antennapod.core.asynctask.ImageResource;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
@ -14,7 +15,6 @@ import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.core.asynctask.ImageResource;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.ShownotesProvider;
@ -75,7 +75,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
* in the database. The 'hasChapters' attribute should be used to check if this item has any chapters.
* */
private List<Chapter> chapters;
private FeedImage image;
private String imageUrl;
/*
* 0: auto download disabled
@ -100,7 +100,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
* This constructor is used by DBReader.
* */
public FeedItem(long id, String title, String link, Date pubDate, String paymentLink, long feedId,
FlattrStatus flattrStatus, boolean hasChapters, FeedImage image, int state,
FlattrStatus flattrStatus, boolean hasChapters, String imageUrl, int state,
String itemIdentifier, long autoDownload) {
this.id = id;
this.title = title;
@ -110,7 +110,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
this.feedId = feedId;
this.flattrStatus = flattrStatus;
this.hasChapters = hasChapters;
this.image = image;
this.imageUrl = imageUrl;
this.state = state;
this.itemIdentifier = itemIdentifier;
this.autoDownload = autoDownload;
@ -158,6 +158,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
int indexRead = cursor.getColumnIndex(PodDBAdapter.KEY_READ);
int indexItemIdentifier = cursor.getColumnIndex(PodDBAdapter.KEY_ITEM_IDENTIFIER);
int indexAutoDownload = cursor.getColumnIndex(PodDBAdapter.KEY_AUTO_DOWNLOAD);
int indexImageUrl = cursor.getColumnIndex(PodDBAdapter.KEY_IMAGE_URL);
long id = cursor.getInt(indexId);
String title = cursor.getString(indexTitle);
@ -170,15 +171,16 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
int state = cursor.getInt(indexRead);
String itemIdentifier = cursor.getString(indexItemIdentifier);
long autoDownload = cursor.getLong(indexAutoDownload);
String imageUrl = cursor.getString(indexImageUrl);
return new FeedItem(id, title, link, pubDate, paymentLink, feedId, flattrStatus,
hasChapters, null, state, itemIdentifier, autoDownload);
hasChapters, imageUrl, state, itemIdentifier, autoDownload);
}
public void updateFromOther(FeedItem other) {
super.updateFromOther(other);
if (other.image != null) {
this.image = other.image;
if (other.imageUrl != null) {
this.imageUrl = other.imageUrl;
}
if (other.title != null) {
title = other.title;
@ -212,9 +214,6 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
chapters = other.chapters;
}
}
if (image == null) {
image = other.image;
}
}
/**
@ -389,8 +388,8 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
public String getImageLocation() {
if(media != null && media.hasEmbeddedPicture()) {
return media.getImageLocation();
} else if (image != null) {
return image.getImageLocation();
} else if (imageUrl != null) {
return imageUrl;
} else if (feed != null) {
return feed.getImageLocation();
} else {
@ -426,29 +425,12 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr
* Returns the image of this item or the image of the feed if this item does
* not have its own image.
*/
public FeedImage getImage() {
return (hasItemImage()) ? image : feed.getImage();
public String getImageUrl() {
return (imageUrl != null) ? imageUrl : feed.getImageUrl();
}
public void setImage(FeedImage image) {
this.image = image;
if (image != null) {
image.setOwner(this);
}
}
/**
* Returns true if this FeedItem has its own image, false otherwise.
*/
public boolean hasItemImage() {
return image != null;
}
/**
* Returns true if this FeedItem has its own image and the image has been downloaded.
*/
public boolean hasItemImageDownloaded() {
return image != null && image.isDownloaded();
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
@Override

View File

@ -554,15 +554,9 @@ public class FeedMedia extends FeedFile implements Playable {
public Callable<String> loadShownotes() {
return () -> {
if (item == null) {
item = DBReader.getFeedItem(
itemID);
item = DBReader.getFeedItem(itemID);
}
if (item.getContentEncoded() == null || item.getDescription() == null) {
DBReader.loadExtraInformationOfFeedItem(
item);
}
return (item.getContentEncoded() != null) ? item.getContentEncoded() : item.getDescription();
return item.loadShownotes().call();
};
}

View File

@ -1,18 +1,21 @@
package de.danoeh.antennapod.core.preferences;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
import android.text.TextUtils;
import android.util.Log;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.service.download.ProxyConfig;
import de.danoeh.antennapod.core.storage.APCleanupAlgorithm;
import de.danoeh.antennapod.core.storage.APNullCleanupAlgorithm;
import de.danoeh.antennapod.core.storage.APQueueCleanupAlgorithm;
import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
import org.json.JSONArray;
import org.json.JSONException;
@ -21,19 +24,9 @@ import java.io.IOException;
import java.net.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.receiver.FeedUpdateReceiver;
import de.danoeh.antennapod.core.service.download.ProxyConfig;
import de.danoeh.antennapod.core.storage.APCleanupAlgorithm;
import de.danoeh.antennapod.core.storage.APNullCleanupAlgorithm;
import de.danoeh.antennapod.core.storage.APQueueCleanupAlgorithm;
import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
import de.danoeh.antennapod.core.util.Converter;
/**
* Provides access to preferences set by the user in the settings screen. A
* private instance of this class must first be instantiated via
@ -168,6 +161,8 @@ public class UserPreferences {
int theme = getTheme();
if (theme == R.style.Theme_AntennaPod_Dark) {
return R.style.Theme_AntennaPod_Dark_NoTitle;
} else if (theme == R.style.Theme_AntennaPod_TrueBlack) {
return R.style.Theme_AntennaPod_TrueBlack_NoTitle;
} else {
return R.style.Theme_AntennaPod_Light_NoTitle;
}
@ -604,6 +599,8 @@ public class UserPreferences {
return R.style.Theme_AntennaPod_Light;
case 1:
return R.style.Theme_AntennaPod_Dark;
case 2:
return R.style.Theme_AntennaPod_TrueBlack;
default:
return R.style.Theme_AntennaPod_Light;
}
@ -781,60 +778,17 @@ public class UserPreferences {
int[] timeOfDay = getUpdateTimeOfDay();
Log.d(TAG, "timeOfDay: " + Arrays.toString(timeOfDay));
if (timeOfDay.length == 2) {
restartUpdateTimeOfDayAlarm(timeOfDay[0], timeOfDay[1]);
AutoUpdateManager.restartUpdateTimeOfDayAlarm(context, timeOfDay[0], timeOfDay[1]);
} else {
long milliseconds = getUpdateInterval();
long startTrigger = milliseconds;
if (now) {
startTrigger = TimeUnit.SECONDS.toMillis(10);
}
restartUpdateIntervalAlarm(startTrigger, milliseconds);
AutoUpdateManager.restartUpdateIntervalAlarm(context, startTrigger, milliseconds);
}
}
/**
* Sets the interval in which the feeds are refreshed automatically
*/
private static void restartUpdateIntervalAlarm(long triggerAtMillis, long intervalMillis) {
Log.d(TAG, "Restarting update alarm.");
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, FeedUpdateReceiver.class);
PendingIntent updateIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
alarmManager.cancel(updateIntent);
if (intervalMillis > 0) {
alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + triggerAtMillis,
updateIntent);
Log.d(TAG, "Changed alarm to new interval " + TimeUnit.MILLISECONDS.toHours(intervalMillis) + " h");
} else {
Log.d(TAG, "Automatic update was deactivated");
}
}
/**
* Sets time of day the feeds are refreshed automatically
*/
private static void restartUpdateTimeOfDayAlarm(int hoursOfDay, int minute) {
Log.d(TAG, "Restarting update alarm.");
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
PendingIntent updateIntent = PendingIntent.getBroadcast(context, 0,
new Intent(context, FeedUpdateReceiver.class), 0);
alarmManager.cancel(updateIntent);
Calendar now = Calendar.getInstance();
Calendar alarm = (Calendar)now.clone();
alarm.set(Calendar.HOUR_OF_DAY, hoursOfDay);
alarm.set(Calendar.MINUTE, minute);
if (alarm.before(now) || alarm.equals(now)) {
alarm.add(Calendar.DATE, 1);
}
Log.d(TAG, "Alarm set for: " + alarm.toString() + " : " + alarm.getTimeInMillis());
alarmManager.set(AlarmManager.RTC_WAKEUP,
alarm.getTimeInMillis(),
updateIntent);
Log.d(TAG, "Changed alarm to new time of day " + hoursOfDay + ":" + minute);
}
/**
* Reads episode cache size as it is saved in the episode_cache_size_values array.
*/

View File

@ -7,8 +7,7 @@ import android.util.Log;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.core.util.FeedUpdateUtils;
/**
* Refreshes all feeds when it receives an intent
@ -21,11 +20,7 @@ public class FeedUpdateReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Received intent");
ClientConfig.initialize(context);
if (NetworkUtils.networkAvailable() && NetworkUtils.isDownloadAllowed()) {
DBTasks.refreshAllFeeds(context, null);
} else {
Log.d(TAG, "Blocking automatic update: no wifi available / no mobile updates allowed");
}
FeedUpdateUtils.startAutoUpdate(context, null);
UserPreferences.restartUpdateAlarm(false);
}

View File

@ -3,6 +3,7 @@ package de.danoeh.antennapod.core.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.KeyEvent;
@ -29,7 +30,7 @@ public class MediaButtonReceiver extends BroadcastReceiver {
Intent serviceIntent = new Intent(context, PlaybackService.class);
serviceIntent.putExtra(EXTRA_KEYCODE, event.getKeyCode());
serviceIntent.putExtra(EXTRA_SOURCE, event.getSource());
context.startService(serviceIntent);
ContextCompat.startForegroundService(context, serviceIntent);
}
}

View File

@ -1,17 +1,15 @@
package de.danoeh.antennapod.receiver;
package de.danoeh.antennapod.core.receiver;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.text.TextUtils;
import android.util.Log;
import de.danoeh.antennapod.core.service.PlayerWidgetJobService;
import java.util.Arrays;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.service.PlayerWidgetService;
public class PlayerWidget extends AppWidgetProvider {
private static final String TAG = "PlayerWidget";
@ -22,17 +20,7 @@ public class PlayerWidget extends AppWidgetProvider {
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "onReceive");
super.onReceive(context, intent);
// don't do anything if we're not enabled
if (!isEnabled(context)) {
return;
}
// these come from the PlaybackService when things should get updated
if (TextUtils.equals(intent.getAction(), PlaybackService.FORCE_WIDGET_UPDATE)) {
startUpdate(context);
} else if (TextUtils.equals(intent.getAction(), PlaybackService.STOP_WIDGET_UPDATE)) {
stopUpdate(context);
}
PlayerWidgetJobService.updateWidget(context);
}
@Override
@ -40,14 +28,13 @@ public class PlayerWidget extends AppWidgetProvider {
super.onEnabled(context);
Log.d(TAG, "Widget enabled");
setEnabled(context, true);
startUpdate(context);
PlayerWidgetJobService.updateWidget(context);
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
Log.d(TAG, "onUpdate() called with: " + "context = [" + context + "], appWidgetManager = [" + appWidgetManager + "], appWidgetIds = [" + Arrays.toString(appWidgetIds) + "]");
startUpdate(context);
PlayerWidgetJobService.updateWidget(context);
}
@Override
@ -55,20 +42,9 @@ public class PlayerWidget extends AppWidgetProvider {
super.onDisabled(context);
Log.d(TAG, "Widget disabled");
setEnabled(context, false);
stopUpdate(context);
}
private void startUpdate(Context context) {
Log.d(TAG, "startUpdate() called with: " + "context = [" + context + "]");
context.startService(new Intent(context, PlayerWidgetService.class));
}
private void stopUpdate(Context context) {
Log.d(TAG, "stopUpdate() called with: " + "context = [" + context + "]");
context.stopService(new Intent(context, PlayerWidgetService.class));
}
private boolean isEnabled(Context context) {
public static boolean isEnabled(Context context) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
return prefs.getBoolean(KEY_ENABLED, false);
}

View File

@ -0,0 +1,34 @@
package de.danoeh.antennapod.core.service;
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.Log;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.util.FeedUpdateUtils;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class FeedUpdateJobService extends JobService {
private static final String TAG = "FeedUpdateJobService";
@Override
public boolean onStartJob(JobParameters params) {
Log.d(TAG, "Job started");
ClientConfig.initialize(getApplicationContext());
FeedUpdateUtils.startAutoUpdate(getApplicationContext(), () -> {
UserPreferences.restartUpdateAlarm(false);
jobFinished(params, false); // needsReschedule = false
});
return true;
}
@Override
public boolean onStopJob(JobParameters params) {
return true;
}
}

View File

@ -3,10 +3,10 @@ package de.danoeh.antennapod.core.service;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.NonNull;
import android.support.v4.app.JobIntentService;
import android.support.v4.app.NotificationCompat;
import android.support.v4.util.ArrayMap;
import android.util.Log;
@ -15,6 +15,7 @@ import android.util.Pair;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
@ -37,12 +38,13 @@ import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.DownloadRequestException;
import de.danoeh.antennapod.core.storage.DownloadRequester;
import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
/**
* 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 {
public class GpodnetSyncService extends JobIntentService {
private static final String TAG = "GpodnetSyncService";
private static final long WAIT_INTERVAL = 5000L;
@ -55,12 +57,17 @@ public class GpodnetSyncService extends Service {
private GpodnetService service;
private boolean syncSubscriptions = false;
private boolean syncActions = false;
private static final AtomicInteger syncActionCount = new AtomicInteger(0);
private static boolean syncSubscriptions = false;
private static boolean syncActions = false;
private static void enqueueWork(Context context, Intent intent) {
enqueueWork(context, GpodnetSyncService.class, 0, intent);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
final String action = (intent != null) ? intent.getStringExtra(ARG_ACTION) : null;
protected void onHandleWork(@NonNull Intent intent) {
final String action = intent.getStringExtra(ARG_ACTION);
if (action != null) {
switch(action) {
case ACTION_SYNC:
@ -78,24 +85,20 @@ public class GpodnetSyncService extends Service {
}
if(syncSubscriptions || syncActions) {
Log.d(TAG, String.format("Waiting %d milliseconds before uploading changes", WAIT_INTERVAL));
syncWaiterThread.restart();
int syncActionId = syncActionCount.incrementAndGet();
try {
Thread.sleep(WAIT_INTERVAL);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (syncActionId == syncActionCount.get()) {
// onHandleWork was not called again in the meantime
sync();
}
}
} else {
Log.e(TAG, "Received invalid intent: action argument is null");
}
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
syncWaiterThread.interrupt();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
private synchronized GpodnetService tryLogin() throws GpodnetServiceException {
@ -109,6 +112,7 @@ public class GpodnetSyncService extends Service {
private synchronized void sync() {
if (!GpodnetPreferences.loggedIn() || !NetworkUtils.networkAvailable()) {
stopForeground(true);
stopSelf();
return;
}
@ -125,7 +129,6 @@ public class GpodnetSyncService extends Service {
}
syncActions = false;
}
stopSelf();
}
private synchronized void syncSubscriptionChanges() {
@ -319,7 +322,7 @@ public class GpodnetSyncService extends Service {
}
PendingIntent activityIntent = ClientConfig.gpodnetCallbacks.getGpodnetSyncServiceErrorNotificationPendingIntent(this);
Notification notification = new NotificationCompat.Builder(this)
Notification notification = new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_ERROR)
.setContentTitle(title)
.setContentText(description)
.setContentIntent(activityIntent)
@ -331,69 +334,11 @@ public class GpodnetSyncService extends Service {
nm.notify(id, notification);
}
private final WaiterThread syncWaiterThread = new WaiterThread(WAIT_INTERVAL) {
@Override
public void onWaitCompleted() {
sync();
}
};
private abstract class WaiterThread {
private final 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);
enqueueWork(context, intent);
}
}
@ -401,7 +346,7 @@ public class GpodnetSyncService extends Service {
if (GpodnetPreferences.loggedIn()) {
Intent intent = new Intent(context, GpodnetSyncService.class);
intent.putExtra(ARG_ACTION, ACTION_SYNC_SUBSCRIPTIONS);
context.startService(intent);
enqueueWork(context, intent);
}
}
@ -409,7 +354,7 @@ public class GpodnetSyncService extends Service {
if (GpodnetPreferences.loggedIn()) {
Intent intent = new Intent(context, GpodnetSyncService.class);
intent.putExtra(ARG_ACTION, ACTION_SYNC_ACTIONS);
context.startService(intent);
enqueueWork(context, intent);
}
}
}

View File

@ -0,0 +1,176 @@
package de.danoeh.antennapod.core.service;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Build;
import android.os.IBinder;
import android.support.annotation.NonNull;
import android.support.v4.app.JobIntentService;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.RemoteViews;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
import de.danoeh.antennapod.core.service.playback.PlayerStatus;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.playback.Playable;
import de.danoeh.antennapod.core.receiver.PlayerWidget;
/**
* Updates the state of the player widget
*/
public class PlayerWidgetJobService extends JobIntentService {
private static final String TAG = "PlayerWidgetJobService";
private PlaybackService playbackService;
private final Object waitForService = new Object();
public static void updateWidget(Context context) {
enqueueWork(context, PlayerWidgetJobService.class, 0, new Intent(context, PlayerWidgetJobService.class));
}
@Override
protected void onHandleWork(@NonNull Intent intent) {
if (!PlayerWidget.isEnabled(getApplicationContext())) {
return;
}
if (PlaybackService.isRunning && playbackService == null) {
synchronized (waitForService) {
bindService(new Intent(this, PlaybackService.class), mConnection, 0);
while (playbackService == null) {
try {
waitForService.wait();
} catch (InterruptedException e) {
return;
}
}
}
}
updateViews();
if (playbackService != null) {
try {
unbindService(mConnection);
} catch (IllegalArgumentException e) {
Log.w(TAG, "IllegalArgumentException when trying to unbind service");
}
}
}
private void updateViews() {
ComponentName playerWidget = new ComponentName(this, PlayerWidget.class);
AppWidgetManager manager = AppWidgetManager.getInstance(this);
RemoteViews views = new RemoteViews(getPackageName(), R.layout.player_widget);
PendingIntent startMediaplayer = PendingIntent.getActivity(this, 0,
PlaybackService.getPlayerActivityIntent(this), 0);
final PendingIntent startAppPending = PendingIntent.getActivity(this, 0,
PlaybackService.getPlayerActivityIntent(this),
PendingIntent.FLAG_UPDATE_CURRENT);
boolean nothingPlaying = false;
Playable media;
PlayerStatus status;
if (playbackService != null) {
media = playbackService.getPlayable();
status = playbackService.getStatus();
} else {
media = Playable.PlayableUtils.createInstanceFromPreferences(getApplicationContext());
status = PlayerStatus.STOPPED;
}
if (media != null) {
views.setOnClickPendingIntent(R.id.layout_left, startMediaplayer);
views.setTextViewText(R.id.txtvTitle, media.getEpisodeTitle());
String progressString;
if (playbackService != null) {
progressString = getProgressString(playbackService.getCurrentPosition(), playbackService.getDuration());
} else {
progressString = getProgressString(media.getPosition(), media.getDuration());
}
if (progressString != null) {
views.setViewVisibility(R.id.txtvProgress, View.VISIBLE);
views.setTextViewText(R.id.txtvProgress, progressString);
}
if (status == PlayerStatus.PLAYING) {
views.setImageViewResource(R.id.butPlay, R.drawable.ic_pause_white_24dp);
if (Build.VERSION.SDK_INT >= 15) {
views.setContentDescription(R.id.butPlay, getString(R.string.pause_label));
}
} else {
views.setImageViewResource(R.id.butPlay, R.drawable.ic_play_arrow_white_24dp);
if (Build.VERSION.SDK_INT >= 15) {
views.setContentDescription(R.id.butPlay, getString(R.string.play_label));
}
}
views.setOnClickPendingIntent(R.id.butPlay, createMediaButtonIntent());
} else {
nothingPlaying = true;
}
if (nothingPlaying) {
// start the app if they click anything
views.setOnClickPendingIntent(R.id.layout_left, startAppPending);
views.setOnClickPendingIntent(R.id.butPlay, startAppPending);
views.setViewVisibility(R.id.txtvProgress, View.INVISIBLE);
views.setTextViewText(R.id.txtvTitle,
this.getString(R.string.no_media_playing_label));
views.setImageViewResource(R.id.butPlay, R.drawable.ic_play_arrow_white_24dp);
}
manager.updateAppWidget(playerWidget, views);
}
/**
* Creates an intent which fakes a mediabutton press
*/
private PendingIntent createMediaButtonIntent() {
KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
Intent startingIntent = new Intent(getBaseContext(), MediaButtonReceiver.class);
startingIntent.setAction(MediaButtonReceiver.NOTIFY_BUTTON_RECEIVER);
startingIntent.putExtra(Intent.EXTRA_KEY_EVENT, event);
return PendingIntent.getBroadcast(this, 0, startingIntent, 0);
}
private String getProgressString(int position, int duration) {
if (position > 0 && duration > 0) {
return Converter.getDurationStringLong(position) + " / "
+ Converter.getDurationStringLong(duration);
} else {
return null;
}
}
private final ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
Log.d(TAG, "Connection to service established");
if (service instanceof PlaybackService.LocalBinder) {
synchronized (waitForService) {
playbackService = ((PlaybackService.LocalBinder) service).getService();
waitForService.notifyAll();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
playbackService = null;
Log.d(TAG, "Disconnected from service");
}
};
}

View File

@ -7,8 +7,6 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaMetadataRetriever;
import android.os.Binder;
import android.os.Build;
@ -23,6 +21,7 @@ import android.util.Pair;
import android.webkit.URLUtil;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
import org.apache.commons.io.FileUtils;
import org.xml.sax.SAXException;
@ -57,7 +56,6 @@ import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.event.DownloadEvent;
import de.danoeh.antennapod.core.event.FeedItemEvent;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
@ -297,6 +295,7 @@ public class DownloadService extends Service {
setupNotificationBuilders();
requester = DownloadRequester.getInstance();
startForeground(NOTIFICATION_ID, updateNotifications());
}
@Override
@ -342,7 +341,7 @@ public class DownloadService extends Service {
}
private void setupNotificationBuilders() {
notificationCompatBuilder = new NotificationCompat.Builder(this)
notificationCompatBuilder = new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_DOWNLOADING)
.setOngoing(true)
.setContentIntent(ClientConfig.downloadServiceCallbacks.getNotificationContentIntent(this))
.setSmallIcon(R.drawable.stat_notify_sync);
@ -355,7 +354,7 @@ public class DownloadService extends Service {
/**
* Updates the contents of the service's notifications. Should be called
* before setupNotificationBuilders.
* after setupNotificationBuilders.
*/
private Notification updateNotifications() {
if (notificationCompatBuilder == null) {
@ -492,9 +491,7 @@ public class DownloadService extends Service {
if (status.isSuccessful()) {
successfulDownloads++;
} else if (!status.isCancelled()) {
if (status.getFeedfileType() != FeedImage.FEEDFILETYPE_FEEDIMAGE) {
createReport = true;
}
createReport = true;
failedDownloads++;
}
}
@ -502,7 +499,7 @@ public class DownloadService extends Service {
if (createReport) {
Log.d(TAG, "Creating report");
// create notification object
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_ERROR)
.setTicker(getString(R.string.download_report_title))
.setContentTitle(getString(R.string.download_report_content_title))
.setContentText(
@ -554,7 +551,7 @@ public class DownloadService extends Service {
final String resourceTitle = (downloadRequest.getTitle() != null) ?
downloadRequest.getTitle() : downloadRequest.getSource();
NotificationCompat.Builder builder = new NotificationCompat.Builder(DownloadService.this);
NotificationCompat.Builder builder = new NotificationCompat.Builder(DownloadService.this, NotificationUtils.CHANNEL_ID_USER_ACTION);
builder.setTicker(getText(R.string.authentication_notification_title))
.setContentTitle(getText(R.string.authentication_notification_title))
.setContentText(getText(R.string.authentication_notification_msg))
@ -691,10 +688,6 @@ public class DownloadService extends Service {
Log.d(TAG, "Bundling " + results.size() + " feeds");
for (Pair<DownloadRequest, FeedHandlerResult> result : results) {
removeDuplicateImages(result.second.feed); // duplicate images have to removed because the DownloadRequester does not accept two downloads with the same download URL yet.
}
// Save information of feed in DB
if (dbUpdateFuture != null) {
try {
@ -1104,26 +1097,6 @@ public class DownloadService extends Service {
}
}
/**
* Checks if the FeedItems of this feed have images that point to the same URL. If two FeedItems
* have an image that points to the same URL, the reference of the second item is removed, so
* that every image reference is unique.
*/
@VisibleForTesting
public static void removeDuplicateImages(Feed feed) {
Set<String> known = new HashSet<>();
for (FeedItem item : feed.getItems()) {
String url = item.hasItemImage() ? item.getImage().getDownload_url() : null;
if (url != null) {
if (known.contains(url)) {
item.setImage(null);
} else {
known.add(url);
}
}
}
}
private static String compileNotificationString(List<Downloader> downloads) {
List<String> lines = new ArrayList<>(downloads.size());
for (Downloader downloader : downloads) {

View File

@ -20,7 +20,6 @@ import java.util.Date;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.util.DateUtils;
import de.danoeh.antennapod.core.util.DownloadError;
@ -50,13 +49,8 @@ public class HttpDownloader extends Downloader {
if (request.isDeleteOnFailure() && fileExists) {
Log.w(TAG, "File already exists");
if (request.getFeedfileType() != FeedImage.FEEDFILETYPE_FEEDIMAGE) {
onFail(DownloadError.ERROR_FILE_EXISTS, null);
return;
} else {
onSuccess();
return;
}
onSuccess();
return;
}
OkHttpClient.Builder httpClientBuilder = AntennapodHttpClient.newBuilder();

View File

@ -609,6 +609,9 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer {
public void shutdown() {
executor.shutdown();
if (mediaPlayer != null) {
try {
mediaPlayer.stop();
} catch (Exception ignore) { }
mediaPlayer.release();
}
releaseWifiLockIfNecessary();

View File

@ -30,12 +30,10 @@ import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.support.v4.view.InputDeviceCompat;
import android.support.v7.app.NotificationCompat;
import android.support.v4.app.NotificationCompat;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
import android.widget.Toast;
@ -49,6 +47,7 @@ import java.util.List;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.event.MessageEvent;
import de.danoeh.antennapod.core.event.ServiceEvent;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
@ -60,11 +59,13 @@ import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.preferences.SleepTimerPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
import de.danoeh.antennapod.core.service.PlayerWidgetJobService;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBTasks;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.FeedSearcher;
import de.danoeh.antennapod.core.util.IntList;
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
import de.danoeh.antennapod.core.util.QueueAccess;
import de.danoeh.antennapod.core.util.playback.ExternalMedia;
import de.danoeh.antennapod.core.util.playback.Playable;
@ -74,8 +75,6 @@ import de.greenrobot.event.EventBus;
* Controls the MediaPlayer that plays a FeedMedia-file
*/
public class PlaybackService extends MediaBrowserServiceCompat {
public static final String FORCE_WIDGET_UPDATE = "de.danoeh.antennapod.FORCE_WIDGET_UPDATE";
public static final String STOP_WIDGET_UPDATE = "de.danoeh.antennapod.STOP_WIDGET_UPDATE";
/**
* Logging tag
*/
@ -313,6 +312,31 @@ public class PlaybackService extends MediaBrowserServiceCompat {
flavorHelper.initializeMediaPlayer(PlaybackService.this);
mediaSession.setActive(true);
NotificationCompat.Builder notificationBuilder = createBasicNotification();
startForeground(NOTIFICATION_ID, notificationBuilder.build());
EventBus.getDefault().post(new ServiceEvent(ServiceEvent.Action.SERVICE_STARTED));
setupNotification(Playable.PlayableUtils.createInstanceFromPreferences(getApplicationContext()));
}
private NotificationCompat.Builder createBasicNotification() {
final int smallIcon = ClientConfig.playbackServiceCallbacks.getNotificationIconResource(getApplicationContext());
final PendingIntent pIntent = PendingIntent.getActivity(this, 0,
PlaybackService.getPlayerActivityIntent(this),
PendingIntent.FLAG_UPDATE_CURRENT);
return new NotificationCompat.Builder(
this, NotificationUtils.CHANNEL_ID_PLAYING)
.setContentTitle(getString(R.string.app_name))
.setContentText("Service is running") // Just in case the notification is not updated (should not occur)
.setOngoing(false)
.setContentIntent(pIntent)
.setWhen(0) // we don't need the time
.setSmallIcon(smallIcon)
.setPriority(NotificationCompat.PRIORITY_MIN);
}
@Override
@ -447,8 +471,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
if (keycode != -1) {
Log.d(TAG, "Received media button event");
handleKeycode(keycode, intent.getIntExtra(MediaButtonReceiver.EXTRA_SOURCE,
InputDeviceCompat.SOURCE_CLASS_NONE));
handleKeycode(keycode, true);
} else if (!flavorHelper.castDisconnect(castDisconnect) && playable != null) {
started = true;
boolean stream = intent.getBooleanExtra(EXTRA_SHOULD_STREAM,
@ -472,7 +495,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
* Handles media button events
* return: keycode was handled
*/
private boolean handleKeycode(int keycode, int source) {
private boolean handleKeycode(int keycode, boolean notificationButton) {
Log.d(TAG, "Handling keycode: " + keycode);
final PlaybackServiceMediaPlayer.PSMPInfo info = mediaPlayer.getPSMPInfo();
final PlayerStatus status = info.playerStatus;
@ -488,6 +511,8 @@ public class PlaybackService extends MediaBrowserServiceCompat {
} else if (status == PlayerStatus.INITIALIZED) {
mediaPlayer.setStartWhenPrepared(true);
mediaPlayer.prepare();
} else if (mediaPlayer.getPlayable() == null) {
startPlayingFromPreferences();
}
return true;
case KeyEvent.KEYCODE_MEDIA_PLAY:
@ -496,6 +521,8 @@ public class PlaybackService extends MediaBrowserServiceCompat {
} else if (status == PlayerStatus.INITIALIZED) {
mediaPlayer.setStartWhenPrepared(true);
mediaPlayer.prepare();
} else if (mediaPlayer.getPlayable() == null) {
startPlayingFromPreferences();
}
return true;
case KeyEvent.KEYCODE_MEDIA_PAUSE:
@ -505,7 +532,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
return true;
case KeyEvent.KEYCODE_MEDIA_NEXT:
if (source == InputDevice.SOURCE_CLASS_NONE ||
if (notificationButton ||
UserPreferences.shouldHardwareButtonSkip()) {
// assume the skip command comes from a notification or the lockscreen
// a >| skip button should actually skip
@ -549,6 +576,15 @@ public class PlaybackService extends MediaBrowserServiceCompat {
return false;
}
private void startPlayingFromPreferences() {
Playable playable = Playable.PlayableUtils.createInstanceFromPreferences(getApplicationContext());
if (playable != null) {
mediaPlayer.playMediaObject(playable, false, true, true);
started = true;
PlaybackService.this.updateMediaSessionMetadata(playable);
}
}
/**
* Called by a mediaplayer Activity as soon as it has prepared its
* mediaplayer.
@ -567,8 +603,10 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
public void notifyVideoSurfaceAbandoned() {
stopForeground(!UserPreferences.isPersistNotify());
mediaPlayer.pause(true, false);
mediaPlayer.resetVideoSurface();
setupNotification(getPlayable());
stopForeground(!UserPreferences.isPersistNotify());
}
private final PlaybackServiceTaskManager.PSTMCallback taskManagerCallback = new PlaybackServiceTaskManager.PSTMCallback() {
@ -602,7 +640,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
@Override
public void onWidgetUpdaterTick() {
updateWidget();
PlayerWidgetJobService.updateWidget(getBaseContext());
}
@Override
@ -664,7 +702,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
Intent statusUpdate = new Intent(ACTION_PLAYER_STATUS_CHANGED);
// statusUpdate.putExtra(EXTRA_NEW_PLAYER_STATUS, newInfo.playerStatus.ordinal());
sendBroadcast(statusUpdate);
updateWidget();
PlayerWidgetJobService.updateWidget(getBaseContext());
bluetoothNotifyChange(newInfo, AVRCP_ACTION_PLAYER_STATUS_CHANGED);
bluetoothNotifyChange(newInfo, AVRCP_ACTION_META_CHANGED);
}
@ -807,7 +845,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
if (!isCasting) {
stopForeground(true);
}
stopWidgetUpdater();
}
if (mediaType == null) {
sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_END, 0);
@ -1171,10 +1208,10 @@ public class PlaybackService extends MediaBrowserServiceCompat {
* Prepares notification and starts the service in the foreground.
*/
private void setupNotification(final PlaybackServiceMediaPlayer.PSMPInfo info) {
final PendingIntent pIntent = PendingIntent.getActivity(this, 0,
PlaybackService.getPlayerActivityIntent(this),
PendingIntent.FLAG_UPDATE_CURRENT);
setupNotification(info.playable);
}
private synchronized void setupNotification(final Playable playable) {
if (notificationSetupThread != null) {
notificationSetupThread.interrupt();
}
@ -1184,12 +1221,12 @@ public class PlaybackService extends MediaBrowserServiceCompat {
@Override
public void run() {
Log.d(TAG, "Starting background work");
if (info.playable != null) {
if (playable != null) {
int iconSize = getResources().getDimensionPixelSize(
android.R.dimen.notification_large_icon_width);
try {
icon = Glide.with(PlaybackService.this)
.load(info.playable.getImageLocation())
.load(playable.getImageLocation())
.asBitmap()
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
.centerCrop()
@ -1208,24 +1245,18 @@ public class PlaybackService extends MediaBrowserServiceCompat {
return;
}
PlayerStatus playerStatus = mediaPlayer.getPlayerStatus();
final int smallIcon = ClientConfig.playbackServiceCallbacks.getNotificationIconResource(getApplicationContext());
if (!Thread.currentThread().isInterrupted() && started && info.playable != null) {
String contentText = info.playable.getEpisodeTitle();
String contentTitle = info.playable.getFeedTitle();
if (!Thread.currentThread().isInterrupted() && started && playable != null) {
String contentText = playable.getEpisodeTitle();
String contentTitle = playable.getFeedTitle();
Notification notification;
// Builder is v7, even if some not overwritten methods return its parent's v4 interface
NotificationCompat.Builder notificationBuilder = (NotificationCompat.Builder) new NotificationCompat.Builder(
PlaybackService.this)
.setContentTitle(contentTitle)
NotificationCompat.Builder notificationBuilder = createBasicNotification();
notificationBuilder.setContentTitle(contentTitle)
.setContentText(contentText)
.setOngoing(false)
.setContentIntent(pIntent)
.setLargeIcon(icon)
.setSmallIcon(smallIcon)
.setWhen(0) // we don't need the time
.setPriority(UserPreferences.getNotifyPriority()); // set notification priority
.setPriority(UserPreferences.getNotifyPriority())
.setLargeIcon(icon); // set notification priority
IntList compactActionList = new IntList();
int numActions = 0; // we start and 0 and then increment by 1 for each call to addAction
@ -1293,7 +1324,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
PendingIntent stopButtonPendingIntent = getPendingIntentForMediaAction(
KeyEvent.KEYCODE_MEDIA_STOP, numActions);
notificationBuilder.setStyle(new android.support.v7.app.NotificationCompat.MediaStyle()
notificationBuilder.setStyle(new android.support.v4.media.app.NotificationCompat.MediaStyle()
.setMediaSession(mediaSession.getSessionToken())
.setShowActionsInCompactView(compactActionList.toArray())
.setShowCancelButton(true)
@ -1360,16 +1391,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
}
private void stopWidgetUpdater() {
taskManager.cancelWidgetUpdater();
sendBroadcast(new Intent(STOP_WIDGET_UPDATE));
}
private void updateWidget() {
PlaybackService.this.sendBroadcast(new Intent(
FORCE_WIDGET_UPDATE));
}
public boolean sleepTimerActive() {
return taskManager.isSleepTimerActive();
}
@ -1762,11 +1783,11 @@ public class PlaybackService extends MediaBrowserServiceCompat {
public boolean onMediaButtonEvent(final Intent mediaButton) {
Log.d(TAG, "onMediaButtonEvent(" + mediaButton + ")");
if (mediaButton != null) {
KeyEvent keyEvent = (KeyEvent) mediaButton.getExtras().get(Intent.EXTRA_KEY_EVENT);
KeyEvent keyEvent = (KeyEvent) mediaButton.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
if (keyEvent != null &&
keyEvent.getAction() == KeyEvent.ACTION_DOWN &&
keyEvent.getRepeatCount() == 0) {
return handleKeycode(keyEvent.getKeyCode(), keyEvent.getSource());
return handleKeycode(keyEvent.getKeyCode(), false);
}
}
return false;

View File

@ -13,7 +13,6 @@ import java.util.Map;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
@ -201,25 +200,15 @@ public final class DBReader {
private static List<FeedItem> extractItemlistFromCursor(PodDBAdapter adapter, Cursor cursor) {
List<FeedItem> result = new ArrayList<>(cursor.getCount());
LongList imageIds = new LongList(cursor.getCount());
LongList itemIds = new LongList(cursor.getCount());
if (cursor.moveToFirst()) {
do {
int indexImage = cursor.getColumnIndex(PodDBAdapter.KEY_IMAGE);
long imageId = cursor.getLong(indexImage);
imageIds.add(imageId);
FeedItem item = FeedItem.fromCursor(cursor);
result.add(item);
itemIds.add(item.getId());
} while (cursor.moveToNext());
Map<Long, FeedImage> images = getFeedImages(adapter, imageIds.toArray());
Map<Long, FeedMedia> medias = getFeedMedia(adapter, itemIds);
for (int i = 0; i < result.size(); i++) {
FeedItem item = result.get(i);
long imageId = imageIds.get(i);
FeedImage image = images.get(imageId);
item.setImage(image);
for (FeedItem item : result) {
FeedMedia media = medias.get(item.getId());
item.setMedia(media);
if (media != null) {
@ -254,24 +243,9 @@ public final class DBReader {
}
private static Feed extractFeedFromCursorRow(PodDBAdapter adapter, Cursor cursor) {
final FeedImage image;
int indexImage = cursor.getColumnIndex(PodDBAdapter.KEY_IMAGE);
long imageId = cursor.getLong(indexImage);
if (imageId != 0) {
image = getFeedImage(adapter, imageId);
} else {
image = null;
}
Feed feed = Feed.fromCursor(cursor);
if (image != null) {
feed.setImage(image);
image.setOwner(feed);
}
FeedPreferences preferences = FeedPreferences.fromCursor(cursor);
feed.setPreferences(preferences);
return feed;
}
@ -838,62 +812,6 @@ public final class DBReader {
}
}
/**
* Searches the DB for a FeedImage of the given id.
*
* @param imageId The id of the object
* @return The found object
*/
public static FeedImage getFeedImage(final long imageId) {
Log.d(TAG, "getFeedImage() called with: " + "imageId = [" + imageId + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
try {
return getFeedImage(adapter, imageId);
} finally {
adapter.close();
}
}
/**
* Searches the DB for a FeedImage of the given id.
*
* @param imageId The id of the object
* @return The found object
*/
private static FeedImage getFeedImage(PodDBAdapter adapter, final long imageId) {
return getFeedImages(adapter, imageId).get(imageId);
}
/**
* Searches the DB for a FeedImage of the given id.
*
* @param imageIds The ids of the images
* @return Map that associates the id of an image with the image itself
*/
private static Map<Long, FeedImage> getFeedImages(PodDBAdapter adapter, final long... imageIds) {
String[] ids = new String[imageIds.length];
for (int i = 0, len = imageIds.length; i < len; i++) {
ids[i] = String.valueOf(imageIds[i]);
}
Cursor cursor = adapter.getImageCursor(ids);
int imageCount = cursor.getCount();
if (imageCount == 0) {
cursor.close();
return Collections.emptyMap();
}
Map<Long, FeedImage> result = new ArrayMap<>(imageCount);
try {
while (cursor.moveToNext()) {
FeedImage image = FeedImage.fromCursor(cursor);
result.put(image.getId(), image);
}
} finally {
cursor.close();
}
return result;
}
/**
* Searches the DB for a FeedMedia of the given id.
*

View File

@ -4,6 +4,8 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import java.util.ArrayList;
@ -38,6 +40,7 @@ import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator;
import de.danoeh.antennapod.core.util.exception.MediaFileNotFoundException;
import de.danoeh.antennapod.core.util.flattr.FlattrUtils;
import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter;
import static android.content.Context.MODE_PRIVATE;
@ -123,16 +126,13 @@ public final class DBTasks {
media);
}
}
// Start playback Service
Intent launchIntent = new Intent(context, PlaybackService.class);
launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED,
startWhenPrepared);
launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM,
shouldStream);
launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY,
true);
context.startService(launchIntent);
new PlaybackServiceStarter(context, media)
.callEvenIfRunning(true)
.startWhenPrepared(startWhenPrepared)
.shouldStream(shouldStream)
.start();
if (showPlayer) {
// Launch media player
context.startActivity(PlaybackService.getPlayerActivityIntent(
@ -155,42 +155,56 @@ public final class DBTasks {
* Refreshes a given list of Feeds in a separate Thread. This method might ignore subsequent calls if it is still
* enqueuing Feeds for download from a previous call
*
* @param context Might be used for accessing the database
* @param feeds List of Feeds that should be refreshed.
* @param context Might be used for accessing the database
* @param feeds List of Feeds that should be refreshed.
*/
public static void refreshAllFeeds(final Context context,
final List<Feed> feeds) {
if (isRefreshing.compareAndSet(false, true)) {
new Thread() {
public void run() {
if (feeds != null) {
refreshFeeds(context, feeds);
} else {
refreshFeeds(context, DBReader.getFeedList());
}
isRefreshing.set(false);
public static void refreshAllFeeds(final Context context, final List<Feed> feeds) {
refreshAllFeeds(context, feeds, null);
}
SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, MODE_PRIVATE);
prefs.edit().putLong(PREF_LAST_REFRESH, System.currentTimeMillis()).apply();
if (FlattrUtils.hasToken()) {
Log.d(TAG, "Flattring all pending things.");
new FlattrClickWorker(context).executeAsync(); // flattr pending things
Log.d(TAG, "Fetching flattr status.");
new FlattrStatusFetcher(context).start();
}
if (ClientConfig.gpodnetCallbacks.gpodnetEnabled()) {
GpodnetSyncService.sendSyncIntent(context);
}
Log.d(TAG, "refreshAllFeeds autodownload");
autodownloadUndownloadedItems(context);
}
}.start();
} else {
/**
* Refreshes a given list of Feeds in a separate Thread. This method might ignore subsequent calls if it is still
* enqueuing Feeds for download from a previous call
*
* @param context Might be used for accessing the database
* @param feeds List of Feeds that should be refreshed.
* @param callback Called after everything was added enqueued for download. Might be null.
*/
public static void refreshAllFeeds(final Context context, final List<Feed> feeds, @Nullable Runnable callback) {
if (!isRefreshing.compareAndSet(false, true)) {
Log.d(TAG, "Ignoring request to refresh all feeds: Refresh lock is locked");
return;
}
new Thread(() -> {
if (feeds != null) {
refreshFeeds(context, feeds);
} else {
refreshFeeds(context, DBReader.getFeedList());
}
isRefreshing.set(false);
SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, MODE_PRIVATE);
prefs.edit().putLong(PREF_LAST_REFRESH, System.currentTimeMillis()).apply();
if (FlattrUtils.hasToken()) {
Log.d(TAG, "Flattring all pending things.");
new FlattrClickWorker(context).executeAsync(); // flattr pending things
Log.d(TAG, "Fetching flattr status.");
new FlattrStatusFetcher(context).start();
}
if (ClientConfig.gpodnetCallbacks.gpodnetEnabled()) {
GpodnetSyncService.sendSyncIntent(context);
}
Log.d(TAG, "refreshAllFeeds autodownload");
autodownloadUndownloadedItems(context);
if (callback != null) {
callback.run();
}
}).start();
}
/**

View File

@ -0,0 +1,292 @@
package de.danoeh.antennapod.core.storage;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.media.MediaMetadataRetriever;
import android.util.Log;
import de.danoeh.antennapod.core.feed.FeedItem;
class DBUpgrader {
/**
* Upgrades the given database to a new schema version
*/
static void upgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) {
if (oldVersion <= 1) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + " ADD COLUMN "
+ PodDBAdapter.KEY_TYPE + " TEXT");
}
if (oldVersion <= 2) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS
+ " ADD COLUMN " + PodDBAdapter.KEY_LINK + " TEXT");
}
if (oldVersion <= 3) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ " ADD COLUMN " + PodDBAdapter.KEY_ITEM_IDENTIFIER + " TEXT");
}
if (oldVersion <= 4) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + " ADD COLUMN "
+ PodDBAdapter.KEY_FEED_IDENTIFIER + " TEXT");
}
if (oldVersion <= 5) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_DOWNLOAD_LOG
+ " ADD COLUMN " + PodDBAdapter.KEY_REASON_DETAILED + " TEXT");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_DOWNLOAD_LOG
+ " ADD COLUMN " + PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE + " TEXT");
}
if (oldVersion <= 6) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS
+ " ADD COLUMN " + PodDBAdapter.KEY_CHAPTER_TYPE + " INTEGER");
}
if (oldVersion <= 7) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ " ADD COLUMN " + PodDBAdapter.KEY_PLAYBACK_COMPLETION_DATE
+ " INTEGER");
}
if (oldVersion <= 8) {
final int KEY_ID_POSITION = 0;
final int KEY_MEDIA_POSITION = 1;
// Add feeditem column to feedmedia table
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ " ADD COLUMN " + PodDBAdapter.KEY_FEEDITEM
+ " INTEGER");
Cursor feeditemCursor = db.query(PodDBAdapter.TABLE_NAME_FEED_ITEMS,
new String[]{PodDBAdapter.KEY_ID, PodDBAdapter.KEY_MEDIA}, "? > 0",
new String[]{PodDBAdapter.KEY_MEDIA}, null, null, null);
if (feeditemCursor.moveToFirst()) {
db.beginTransaction();
ContentValues contentValues = new ContentValues();
do {
long mediaId = feeditemCursor.getLong(KEY_MEDIA_POSITION);
contentValues.put(PodDBAdapter.KEY_FEEDITEM, feeditemCursor.getLong(KEY_ID_POSITION));
db.update(PodDBAdapter.TABLE_NAME_FEED_MEDIA, contentValues, PodDBAdapter.KEY_ID + "=?", new String[]{String.valueOf(mediaId)});
contentValues.clear();
} while (feeditemCursor.moveToNext());
db.setTransactionSuccessful();
db.endTransaction();
}
feeditemCursor.close();
}
if (oldVersion <= 9) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DOWNLOAD
+ " INTEGER DEFAULT 1");
}
if (oldVersion <= 10) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_FLATTR_STATUS
+ " INTEGER");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ " ADD COLUMN " + PodDBAdapter.KEY_FLATTR_STATUS
+ " INTEGER");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ " ADD COLUMN " + PodDBAdapter.KEY_PLAYED_DURATION
+ " INTEGER");
}
if (oldVersion <= 11) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_USERNAME
+ " TEXT");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_PASSWORD
+ " TEXT");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ " ADD COLUMN " + PodDBAdapter.KEY_IMAGE
+ " INTEGER");
}
if (oldVersion <= 12) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_IS_PAGED + " INTEGER DEFAULT 0");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_NEXT_PAGE_LINK + " TEXT");
}
if (oldVersion <= 13) {
// remove duplicate rows in "Chapters" table that were created because of a bug.
db.execSQL(String.format("DELETE FROM %s WHERE %s NOT IN " +
"(SELECT MIN(%s) as %s FROM %s GROUP BY %s,%s,%s,%s,%s)",
PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS,
PodDBAdapter.KEY_ID,
PodDBAdapter.KEY_ID,
PodDBAdapter.KEY_ID,
PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS,
PodDBAdapter.KEY_TITLE,
PodDBAdapter.KEY_START,
PodDBAdapter.KEY_FEEDITEM,
PodDBAdapter.KEY_LINK,
PodDBAdapter.KEY_CHAPTER_TYPE));
}
if (oldVersion <= 14) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DOWNLOAD + " INTEGER");
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ " SET " + PodDBAdapter.KEY_AUTO_DOWNLOAD + " = "
+ "(SELECT " + PodDBAdapter.KEY_AUTO_DOWNLOAD
+ " FROM " + PodDBAdapter.TABLE_NAME_FEEDS
+ " WHERE " + PodDBAdapter.TABLE_NAME_FEEDS + "." + PodDBAdapter.KEY_ID
+ " = " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_FEED + ")");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_HIDE + " TEXT");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_LAST_UPDATE_FAILED + " INTEGER DEFAULT 0");
// create indexes
db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_FEED);
db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDMEDIA_FEEDITEM);
db.execSQL(PodDBAdapter.CREATE_INDEX_QUEUE_FEEDITEM);
db.execSQL(PodDBAdapter.CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM);
}
if (oldVersion <= 15) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ " ADD COLUMN " + PodDBAdapter.KEY_HAS_EMBEDDED_PICTURE + " INTEGER DEFAULT -1");
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ " SET " + PodDBAdapter.KEY_HAS_EMBEDDED_PICTURE + "=0"
+ " WHERE " + PodDBAdapter.KEY_DOWNLOADED + "=0");
Cursor c = db.rawQuery("SELECT " + PodDBAdapter.KEY_FILE_URL
+ " FROM " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ " WHERE " + PodDBAdapter.KEY_DOWNLOADED + "=1 "
+ " AND " + PodDBAdapter.KEY_HAS_EMBEDDED_PICTURE + "=-1", null);
if (c.moveToFirst()) {
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
do {
String fileUrl = c.getString(0);
try {
mmr.setDataSource(fileUrl);
byte[] image = mmr.getEmbeddedPicture();
if (image != null) {
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ " SET " + PodDBAdapter.KEY_HAS_EMBEDDED_PICTURE + "=1"
+ " WHERE " + PodDBAdapter.KEY_FILE_URL + "='" + fileUrl + "'");
} else {
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ " SET " + PodDBAdapter.KEY_HAS_EMBEDDED_PICTURE + "=0"
+ " WHERE " + PodDBAdapter.KEY_FILE_URL + "='" + fileUrl + "'");
}
} catch (Exception e) {
e.printStackTrace();
}
} while (c.moveToNext());
}
c.close();
}
if (oldVersion <= 16) {
String selectNew = "SELECT " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_ID
+ " FROM " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ " INNER JOIN " + PodDBAdapter.TABLE_NAME_FEED_MEDIA + " ON "
+ PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_ID + "="
+ PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_FEEDITEM
+ " LEFT OUTER JOIN " + PodDBAdapter.TABLE_NAME_QUEUE + " ON "
+ PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_ID + "="
+ PodDBAdapter.TABLE_NAME_QUEUE + "." + PodDBAdapter.KEY_FEEDITEM
+ " WHERE "
+ PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_READ + " = 0 AND " // unplayed
+ PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_DOWNLOADED + " = 0 AND " // undownloaded
+ PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_POSITION + " = 0 AND " // not partially played
+ PodDBAdapter.TABLE_NAME_QUEUE + "." + PodDBAdapter.KEY_ID + " IS NULL"; // not in queue
String sql = "UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ " SET " + PodDBAdapter.KEY_READ + "=" + FeedItem.NEW
+ " WHERE " + PodDBAdapter.KEY_ID + " IN (" + selectNew + ")";
Log.d("Migration", "SQL: " + sql);
db.execSQL(sql);
}
if (oldVersion <= 17) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DELETE_ACTION + " INTEGER DEFAULT 0");
}
if (oldVersion < 1030005) {
db.execSQL("UPDATE FeedItems SET auto_download=0 WHERE " +
"(read=1 OR id IN (SELECT feeditem FROM FeedMedia WHERE position>0 OR downloaded=1)) " +
"AND id NOT IN (SELECT feeditem FROM Queue)");
}
if (oldVersion < 1040001) {
db.execSQL(PodDBAdapter.CREATE_TABLE_FAVORITES);
}
if (oldVersion < 1040002) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ " ADD COLUMN " + PodDBAdapter.KEY_LAST_PLAYED_TIME + " INTEGER DEFAULT 0");
}
if (oldVersion < 1040013) {
db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_PUBDATE);
db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_READ);
}
if (oldVersion < 1050003) {
// Migrates feed list filter data
db.beginTransaction();
// Change to intermediate values to avoid overwriting in the following find/replace
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
"SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'unplayed', 'noplay')");
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
"SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'not_queued', 'noqueue')");
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
"SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'not_downloaded', 'nodl')");
// Replace played, queued, and downloaded with their opposites
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
"SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'played', 'unplayed')");
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
"SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'queued', 'not_queued')");
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
"SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'downloaded', 'not_downloaded')");
// Now replace intermediates for unplayed, not queued, etc. with their opposites
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
"SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'noplay', 'played')");
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
"SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'noqueue', 'queued')");
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
"SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'nodl', 'downloaded')");
// Paused doesn't have an opposite, so unplayed is the next best option
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + "\n" +
"SET " + PodDBAdapter.KEY_HIDE + " = replace(" + PodDBAdapter.KEY_HIDE + ", 'paused', 'unplayed')");
db.setTransactionSuccessful();
db.endTransaction();
// and now get ready for autodownload filters
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_INCLUDE_FILTER + " TEXT DEFAULT ''");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_EXCLUDE_FILTER + " TEXT DEFAULT ''");
// and now auto refresh
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_KEEP_UPDATED + " INTEGER DEFAULT 1");
}
if (oldVersion < 1050004) {
// prevent old timestamps to be misinterpreted as ETags
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " SET " + PodDBAdapter.KEY_LASTUPDATE + "=NULL");
}
if (oldVersion < 1060200) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_CUSTOM_TITLE + " TEXT");
}
if (oldVersion < 1060596) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_IMAGE_URL + " TEXT");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ " ADD COLUMN " + PodDBAdapter.KEY_IMAGE_URL + " TEXT");
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + " SET " + PodDBAdapter.KEY_IMAGE_URL + " = ("
+ " SELECT " + PodDBAdapter.KEY_DOWNLOAD_URL
+ " FROM " + PodDBAdapter.TABLE_NAME_FEED_IMAGES
+ " WHERE " + PodDBAdapter.TABLE_NAME_FEED_IMAGES + "." + PodDBAdapter.KEY_ID
+ " = " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_IMAGE + ")");
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS + " SET " + PodDBAdapter.KEY_IMAGE_URL + " = ("
+ " SELECT " + PodDBAdapter.KEY_DOWNLOAD_URL
+ " FROM " + PodDBAdapter.TABLE_NAME_FEED_IMAGES
+ " WHERE " + PodDBAdapter.TABLE_NAME_FEED_IMAGES + "." + PodDBAdapter.KEY_ID
+ " = " + PodDBAdapter.TABLE_NAME_FEEDS + "." + PodDBAdapter.KEY_IMAGE + ")");
db.execSQL("DROP TABLE " + PodDBAdapter.TABLE_NAME_FEED_IMAGES);
}
}
}

View File

@ -32,7 +32,6 @@ import de.danoeh.antennapod.core.event.QueueEvent;
import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedEvent;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
@ -166,17 +165,6 @@ public class DBWriter {
editor.commit();
}
// delete image file
if (feed.getImage() != null) {
if (feed.getImage().isDownloaded()
&& feed.getImage().getFile_url() != null) {
File imageFile = new File(feed.getImage()
.getFile_url());
imageFile.delete();
} else if (requester.isDownloadingFile(feed.getImage())) {
requester.cancelDownload(context, feed.getImage());
}
}
// delete stored media files and mark them as read
List<FeedItem> queue = DBReader.getQueue();
List<FeedItem> removed = new ArrayList<>();
@ -188,6 +176,9 @@ public class DBWriter {
if(queue.remove(item)) {
removed.add(item);
}
if (item.getState() == FeedItem.State.PLAYING && PlaybackService.isRunning) {
context.stopService(new Intent(context, PlaybackService.class));
}
if (item.getMedia() != null
&& item.getMedia().isDownloaded()) {
File mediaFile = new File(item.getMedia()
@ -197,16 +188,6 @@ public class DBWriter {
&& requester.isDownloadingFile(item.getMedia())) {
requester.cancelDownload(context, item.getMedia());
}
if (item.hasItemImage()) {
FeedImage image = item.getImage();
if (image.isDownloaded() && image.getFile_url() != null) {
File imgFile = new File(image.getFile_url());
imgFile.delete();
} else if (requester.isDownloadingFile(image)) {
requester.cancelDownload(context, item.getImage());
}
}
}
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
@ -782,21 +763,6 @@ public class DBWriter {
});
}
/**
* Saves a FeedImage object in the database. This method will save all attributes of the FeedImage object. The
* contents of FeedComponent-attributes (e.g. the FeedImages's 'feed'-attribute) will not be saved.
*
* @param image The FeedImage object.
*/
public static Future<?> setFeedImage(final FeedImage image) {
return dbExec.submit(() -> {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
adapter.setImage(image);
adapter.close();
});
}
/**
* Updates download URL of a feed
*/

View File

@ -4,6 +4,7 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.URLUtil;
@ -81,7 +82,7 @@ public class DownloadRequester {
Intent launchIntent = new Intent(context, DownloadService.class);
launchIntent.putExtra(DownloadService.EXTRA_REQUEST, request);
context.startService(launchIntent);
ContextCompat.startForegroundService(context, launchIntent);
return true;
}
@ -176,8 +177,8 @@ public class DownloadRequester {
args.putInt(REQUEST_ARG_PAGE_NR, feed.getPageNr());
args.putBoolean(REQUEST_ARG_LOAD_ALL_PAGES, loadAllPages);
download(context, feed, null, new File(getFeedfilePath(context),
getFeedfileName(feed)), true, username, password, lastModified, true, args);
download(context, feed, null, new File(getFeedfilePath(), getFeedfileName(feed)),
true, username, password, lastModified, true, args);
}
}
@ -203,8 +204,7 @@ public class DownloadRequester {
if (feedmedia.getFile_url() != null) {
dest = new File(feedmedia.getFile_url());
} else {
dest = new File(getMediafilePath(context, feedmedia),
getMediafilename(feedmedia));
dest = new File(getMediafilePath(feedmedia), getMediafilename(feedmedia));
}
download(context, feedmedia, feed,
dest, false, username, password, null, false, null);
@ -305,10 +305,8 @@ public class DownloadRequester {
return downloads.size();
}
private synchronized String getFeedfilePath(Context context)
throws DownloadRequestException {
return getExternalFilesDirOrThrowException(context, FEED_DOWNLOADPATH)
.toString() + "/";
private synchronized String getFeedfilePath() throws DownloadRequestException {
return getExternalFilesDirOrThrowException(FEED_DOWNLOADPATH).toString() + "/";
}
private synchronized String getFeedfileName(Feed feed) {
@ -319,10 +317,8 @@ public class DownloadRequester {
return "feed-" + FileNameGenerator.generateFileName(filename);
}
private synchronized String getMediafilePath(Context context, FeedMedia media)
throws DownloadRequestException {
private synchronized String getMediafilePath(FeedMedia media) throws DownloadRequestException {
File externalStorage = getExternalFilesDirOrThrowException(
context,
MEDIA_DOWNLOADPATH
+ FileNameGenerator.generateFileName(media.getItem()
.getFeed().getTitle()) + "/"
@ -330,8 +326,7 @@ public class DownloadRequester {
return externalStorage.toString();
}
private File getExternalFilesDirOrThrowException(Context context,
String type) throws DownloadRequestException {
private File getExternalFilesDirOrThrowException(String type) throws DownloadRequestException {
File result = UserPreferences.getDataFolder(type);
if (result == null) {
throw new DownloadRequestException(

View File

@ -14,20 +14,10 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.media.MediaMetadataRetriever;
import android.text.TextUtils;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.event.ProgressEvent;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedComponent;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
@ -38,6 +28,13 @@ import de.danoeh.antennapod.core.util.flattr.FlattrStatus;
import de.greenrobot.event.EventBus;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
// TODO Remove media column from feeditem table
/**
@ -74,8 +71,9 @@ public class PodDBAdapter {
public static final String KEY_SIZE = "filesize";
public static final String KEY_MIME_TYPE = "mime_type";
public static final String KEY_IMAGE = "image";
public static final String KEY_IMAGE_URL = "image_url";
public static final String KEY_FEED = "feed";
private static final String KEY_MEDIA = "media";
public static final String KEY_MEDIA = "media";
public static final String KEY_DOWNLOADED = "downloaded";
public static final String KEY_LASTUPDATE = "last_update";
public static final String KEY_FEEDFILE = "feedfile";
@ -114,14 +112,14 @@ public class PodDBAdapter {
public static final String KEY_EXCLUDE_FILTER = "exclude_filter";
// Table names
private static final String TABLE_NAME_FEEDS = "Feeds";
private static final String TABLE_NAME_FEED_ITEMS = "FeedItems";
private static final String TABLE_NAME_FEED_IMAGES = "FeedImages";
private static final String TABLE_NAME_FEED_MEDIA = "FeedMedia";
private static final String TABLE_NAME_DOWNLOAD_LOG = "DownloadLog";
private static final String TABLE_NAME_QUEUE = "Queue";
private static final String TABLE_NAME_SIMPLECHAPTERS = "SimpleChapters";
private static final String TABLE_NAME_FAVORITES = "Favorites";
static final String TABLE_NAME_FEEDS = "Feeds";
static final String TABLE_NAME_FEED_ITEMS = "FeedItems";
static final String TABLE_NAME_FEED_IMAGES = "FeedImages";
static final String TABLE_NAME_FEED_MEDIA = "FeedMedia";
static final String TABLE_NAME_DOWNLOAD_LOG = "DownloadLog";
static final String TABLE_NAME_QUEUE = "Queue";
static final String TABLE_NAME_SIMPLECHAPTERS = "SimpleChapters";
static final String TABLE_NAME_FAVORITES = "Favorites";
// SQL Statements for creating new tables
private static final String TABLE_PRIMARY_KEY = KEY_ID
@ -133,7 +131,7 @@ public class PodDBAdapter {
+ KEY_DOWNLOADED + " INTEGER," + KEY_LINK + " TEXT,"
+ KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT,"
+ KEY_LASTUPDATE + " TEXT," + KEY_LANGUAGE + " TEXT," + KEY_AUTHOR
+ " TEXT," + KEY_IMAGE + " INTEGER," + KEY_TYPE + " TEXT,"
+ " TEXT," + KEY_IMAGE_URL + " TEXT," + KEY_TYPE + " TEXT,"
+ KEY_FEED_IDENTIFIER + " TEXT," + KEY_AUTO_DOWNLOAD + " INTEGER DEFAULT 1,"
+ KEY_FLATTR_STATUS + " INTEGER,"
+ KEY_USERNAME + " TEXT,"
@ -155,14 +153,9 @@ public class PodDBAdapter {
+ KEY_MEDIA + " INTEGER," + KEY_FEED + " INTEGER,"
+ KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT,"
+ KEY_FLATTR_STATUS + " INTEGER,"
+ KEY_IMAGE + " INTEGER,"
+ KEY_IMAGE_URL + " TEXT,"
+ KEY_AUTO_DOWNLOAD + " INTEGER)";
private static final String CREATE_TABLE_FEED_IMAGES = "CREATE TABLE "
+ TABLE_NAME_FEED_IMAGES + " (" + TABLE_PRIMARY_KEY + KEY_TITLE
+ " TEXT," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL + " TEXT,"
+ KEY_DOWNLOADED + " INTEGER)";
private static final String CREATE_TABLE_FEED_MEDIA = "CREATE TABLE "
+ TABLE_NAME_FEED_MEDIA + " (" + TABLE_PRIMARY_KEY + KEY_DURATION
+ " INTEGER," + KEY_FILE_URL + " TEXT," + KEY_DOWNLOAD_URL
@ -191,36 +184,31 @@ public class PodDBAdapter {
+ KEY_LINK + " TEXT," + KEY_CHAPTER_TYPE + " INTEGER)";
// SQL Statements for creating indexes
private static final String CREATE_INDEX_FEEDITEMS_FEED = "CREATE INDEX "
static final String CREATE_INDEX_FEEDITEMS_FEED = "CREATE INDEX "
+ TABLE_NAME_FEED_ITEMS + "_" + KEY_FEED + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ KEY_FEED + ")";
private static final String CREATE_INDEX_FEEDITEMS_IMAGE = "CREATE INDEX "
+ TABLE_NAME_FEED_ITEMS + "_" + KEY_IMAGE + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ KEY_IMAGE + ")";
private static final String CREATE_INDEX_FEEDITEMS_PUBDATE = "CREATE INDEX IF NOT EXISTS "
static final String CREATE_INDEX_FEEDITEMS_PUBDATE = "CREATE INDEX IF NOT EXISTS "
+ TABLE_NAME_FEED_ITEMS + "_" + KEY_PUBDATE + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ KEY_PUBDATE + ")";
private static final String CREATE_INDEX_FEEDITEMS_READ = "CREATE INDEX IF NOT EXISTS "
static final String CREATE_INDEX_FEEDITEMS_READ = "CREATE INDEX IF NOT EXISTS "
+ TABLE_NAME_FEED_ITEMS + "_" + KEY_READ + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ KEY_READ + ")";
private static final String CREATE_INDEX_QUEUE_FEEDITEM = "CREATE INDEX "
static final String CREATE_INDEX_QUEUE_FEEDITEM = "CREATE INDEX "
+ TABLE_NAME_QUEUE + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_QUEUE + " ("
+ KEY_FEEDITEM + ")";
private static final String CREATE_INDEX_FEEDMEDIA_FEEDITEM = "CREATE INDEX "
static final String CREATE_INDEX_FEEDMEDIA_FEEDITEM = "CREATE INDEX "
+ TABLE_NAME_FEED_MEDIA + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_FEED_MEDIA + " ("
+ KEY_FEEDITEM + ")";
private static final String CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM = "CREATE INDEX "
static final String CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM = "CREATE INDEX "
+ TABLE_NAME_SIMPLECHAPTERS + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_SIMPLECHAPTERS + " ("
+ KEY_FEEDITEM + ")";
private static final String CREATE_TABLE_FAVORITES = "CREATE TABLE "
static final String CREATE_TABLE_FAVORITES = "CREATE TABLE "
+ TABLE_NAME_FAVORITES + "(" + KEY_ID + " INTEGER PRIMARY KEY,"
+ KEY_FEEDITEM + " INTEGER," + KEY_FEED + " INTEGER)";
@ -240,7 +228,7 @@ public class PodDBAdapter {
TABLE_NAME_FEEDS + "." + KEY_LASTUPDATE,
TABLE_NAME_FEEDS + "." + KEY_LANGUAGE,
TABLE_NAME_FEEDS + "." + KEY_AUTHOR,
TABLE_NAME_FEEDS + "." + KEY_IMAGE,
TABLE_NAME_FEEDS + "." + KEY_IMAGE_URL,
TABLE_NAME_FEEDS + "." + KEY_TYPE,
TABLE_NAME_FEEDS + "." + KEY_FEED_IDENTIFIER,
TABLE_NAME_FEEDS + "." + KEY_AUTO_DOWNLOAD,
@ -273,7 +261,7 @@ public class PodDBAdapter {
TABLE_NAME_FEED_ITEMS + "." + KEY_HAS_CHAPTERS,
TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER,
TABLE_NAME_FEED_ITEMS + "." + KEY_FLATTR_STATUS,
TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE,
TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE_URL,
TABLE_NAME_FEED_ITEMS + "." + KEY_AUTO_DOWNLOAD
};
@ -283,7 +271,6 @@ public class PodDBAdapter {
private static final String[] ALL_TABLES = {
TABLE_NAME_FEEDS,
TABLE_NAME_FEED_ITEMS,
TABLE_NAME_FEED_IMAGES,
TABLE_NAME_FEED_MEDIA,
TABLE_NAME_DOWNLOAD_LOG,
TABLE_NAME_QUEUE,
@ -388,12 +375,7 @@ public class PodDBAdapter {
values.put(KEY_PAYMENT_LINK, feed.getPaymentLink());
values.put(KEY_AUTHOR, feed.getAuthor());
values.put(KEY_LANGUAGE, feed.getLanguage());
if (feed.getImage() != null) {
if (feed.getImage().getId() == 0) {
setImage(feed.getImage());
}
values.put(KEY_IMAGE, feed.getImage().getId());
}
values.put(KEY_IMAGE_URL, feed.getImageUrl());
values.put(KEY_FILE_URL, feed.getFile_url());
values.put(KEY_DOWNLOAD_URL, feed.getDownload_url());
@ -450,54 +432,7 @@ public class PodDBAdapter {
}
/**
* Inserts or updates an image entry
*
* @return the id of the entry
*/
public long setImage(FeedImage image) {
boolean startedTransaction = false;
try {
if (!db.inTransaction()) {
db.beginTransactionNonExclusive();
startedTransaction = true;
}
ContentValues values = new ContentValues();
values.put(KEY_TITLE, image.getTitle());
values.put(KEY_DOWNLOAD_URL, image.getDownload_url());
values.put(KEY_DOWNLOADED, image.isDownloaded());
values.put(KEY_FILE_URL, image.getFile_url());
if (image.getId() == 0) {
image.setId(db.insert(TABLE_NAME_FEED_IMAGES, null, values));
} else {
db.update(TABLE_NAME_FEED_IMAGES, values, KEY_ID + "=?",
new String[]{String.valueOf(image.getId())});
}
final FeedComponent owner = image.getOwner();
if (owner != null && owner.getId() != 0) {
values.clear();
values.put(KEY_IMAGE, image.getId());
if (owner instanceof Feed) {
db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(image.getOwner().getId())});
}
}
if (startedTransaction) {
db.setTransactionSuccessful();
}
} catch (SQLException e) {
Log.e(TAG, Log.getStackTraceString(e));
} finally {
if (startedTransaction) {
db.endTransaction();
}
}
return image.getId();
}
/**
* Inserts or updates an image entry
* Inserts or updates a media entry
*
* @return the id of the entry
*/
@ -759,12 +694,7 @@ public class PodDBAdapter {
values.put(KEY_ITEM_IDENTIFIER, item.getItemIdentifier());
values.put(KEY_FLATTR_STATUS, item.getFlattrStatus().toLong());
values.put(KEY_AUTO_DOWNLOAD, item.getAutoDownload());
if (item.hasItemImage()) {
if (item.getImage().getId() == 0) {
setImage(item.getImage());
}
values.put(KEY_IMAGE, item.getImage().getId());
}
values.put(KEY_IMAGE_URL, item.getImageUrl());
if (item.getId() == 0) {
item.setId(db.insert(TABLE_NAME_FEED_ITEMS, null, values));
@ -993,11 +923,6 @@ public class PodDBAdapter {
new String[]{String.valueOf(item.getId())});
}
private void removeFeedImage(FeedImage image) {
db.delete(TABLE_NAME_FEED_IMAGES, KEY_ID + "=?",
new String[]{String.valueOf(image.getId())});
}
/**
* Remove a FeedItem and its FeedMedia entry.
*/
@ -1008,9 +933,6 @@ public class PodDBAdapter {
if (item.hasChapters() || item.getChapters() != null) {
removeChaptersOfItem(item);
}
if (item.hasItemImage()) {
removeFeedImage(item.getImage());
}
db.delete(TABLE_NAME_FEED_ITEMS, KEY_ID + "=?",
new String[]{String.valueOf(item.getId())});
}
@ -1021,9 +943,6 @@ public class PodDBAdapter {
public void removeFeed(Feed feed) {
try {
db.beginTransactionNonExclusive();
if (feed.getImage() != null) {
removeFeedImage(feed.getImage());
}
if (feed.getItems() != null) {
for (FeedItem item : feed.getItems()) {
removeFeedItem(item);
@ -1363,17 +1282,12 @@ public class PodDBAdapter {
public Cursor getImageAuthenticationCursor(final String imageUrl) {
String downloadUrl = DatabaseUtils.sqlEscapeString(imageUrl);
final String query = ""
+ "SELECT " + KEY_USERNAME + "," + KEY_PASSWORD + " FROM " + TABLE_NAME_FEED_IMAGES
+ "SELECT " + KEY_USERNAME + "," + KEY_PASSWORD + " FROM " + TABLE_NAME_FEED_ITEMS
+ " INNER JOIN " + TABLE_NAME_FEEDS
+ " ON " + TABLE_NAME_FEED_IMAGES + "." + KEY_ID + "=" + TABLE_NAME_FEEDS + "." + KEY_IMAGE
+ " WHERE " + TABLE_NAME_FEED_IMAGES + "." + KEY_DOWNLOAD_URL + "=" + downloadUrl
+ " UNION SELECT " + KEY_USERNAME + "," + KEY_PASSWORD
+ " FROM " + TABLE_NAME_FEED_IMAGES
+ " INNER JOIN " + TABLE_NAME_FEED_ITEMS
+ " ON " + TABLE_NAME_FEED_IMAGES + "." + KEY_ID + "=" + TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE
+ " INNER JOIN " + TABLE_NAME_FEEDS
+ " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + "=" + TABLE_NAME_FEEDS + "." + KEY_ID
+ " WHERE " + TABLE_NAME_FEED_IMAGES + "." + KEY_DOWNLOAD_URL + "=" + downloadUrl;
+ " ON " + TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + " = " + TABLE_NAME_FEEDS + "." + KEY_ID
+ " WHERE " + TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE_URL + "=" + downloadUrl
+ " UNION SELECT " + KEY_USERNAME + "," + KEY_PASSWORD + " FROM " + TABLE_NAME_FEEDS
+ " WHERE " + TABLE_NAME_FEEDS + "." + KEY_IMAGE_URL + "=" + downloadUrl;
return db.rawQuery(query, null);
}
@ -1672,7 +1586,7 @@ public class PodDBAdapter {
*/
private static class PodDBHelper extends SQLiteOpenHelper {
private static final int VERSION = 1060200;
private static final int VERSION = 1060596;
private final Context context;
@ -1693,7 +1607,6 @@ public class PodDBAdapter {
public void onCreate(final SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_FEEDS);
db.execSQL(CREATE_TABLE_FEED_ITEMS);
db.execSQL(CREATE_TABLE_FEED_IMAGES);
db.execSQL(CREATE_TABLE_FEED_MEDIA);
db.execSQL(CREATE_TABLE_DOWNLOAD_LOG);
db.execSQL(CREATE_TABLE_QUEUE);
@ -1701,7 +1614,6 @@ public class PodDBAdapter {
db.execSQL(CREATE_TABLE_FAVORITES);
db.execSQL(CREATE_INDEX_FEEDITEMS_FEED);
db.execSQL(CREATE_INDEX_FEEDITEMS_IMAGE);
db.execSQL(CREATE_INDEX_FEEDITEMS_PUBDATE);
db.execSQL(CREATE_INDEX_FEEDITEMS_READ);
db.execSQL(CREATE_INDEX_FEEDMEDIA_FEEDITEM);
@ -1716,263 +1628,7 @@ public class PodDBAdapter {
EventBus.getDefault().post(ProgressEvent.start(context.getString(R.string.progress_upgrading_database)));
Log.w("DBAdapter", "Upgrading from version " + oldVersion + " to "
+ newVersion + ".");
if (oldVersion <= 1) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + " ADD COLUMN "
+ KEY_TYPE + " TEXT");
}
if (oldVersion <= 2) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS
+ " ADD COLUMN " + KEY_LINK + " TEXT");
}
if (oldVersion <= 3) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ " ADD COLUMN " + KEY_ITEM_IDENTIFIER + " TEXT");
}
if (oldVersion <= 4) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + " ADD COLUMN "
+ KEY_FEED_IDENTIFIER + " TEXT");
}
if (oldVersion <= 5) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_DOWNLOAD_LOG
+ " ADD COLUMN " + KEY_REASON_DETAILED + " TEXT");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_DOWNLOAD_LOG
+ " ADD COLUMN " + KEY_DOWNLOADSTATUS_TITLE + " TEXT");
}
if (oldVersion <= 6) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS
+ " ADD COLUMN " + KEY_CHAPTER_TYPE + " INTEGER");
}
if (oldVersion <= 7) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ " ADD COLUMN " + KEY_PLAYBACK_COMPLETION_DATE
+ " INTEGER");
}
if (oldVersion <= 8) {
final int KEY_ID_POSITION = 0;
final int KEY_MEDIA_POSITION = 1;
// Add feeditem column to feedmedia table
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ " ADD COLUMN " + KEY_FEEDITEM
+ " INTEGER");
Cursor feeditemCursor = db.query(PodDBAdapter.TABLE_NAME_FEED_ITEMS,
new String[]{KEY_ID, KEY_MEDIA}, "? > 0",
new String[]{KEY_MEDIA}, null, null, null);
if (feeditemCursor.moveToFirst()) {
db.beginTransaction();
ContentValues contentValues = new ContentValues();
do {
long mediaId = feeditemCursor.getLong(KEY_MEDIA_POSITION);
contentValues.put(KEY_FEEDITEM, feeditemCursor.getLong(KEY_ID_POSITION));
db.update(PodDBAdapter.TABLE_NAME_FEED_MEDIA, contentValues, KEY_ID + "=?", new String[]{String.valueOf(mediaId)});
contentValues.clear();
} while (feeditemCursor.moveToNext());
db.setTransactionSuccessful();
db.endTransaction();
}
feeditemCursor.close();
}
if (oldVersion <= 9) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + KEY_AUTO_DOWNLOAD
+ " INTEGER DEFAULT 1");
}
if (oldVersion <= 10) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + KEY_FLATTR_STATUS
+ " INTEGER");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ " ADD COLUMN " + KEY_FLATTR_STATUS
+ " INTEGER");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ " ADD COLUMN " + KEY_PLAYED_DURATION
+ " INTEGER");
}
if (oldVersion <= 11) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + KEY_USERNAME
+ " TEXT");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + KEY_PASSWORD
+ " TEXT");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ " ADD COLUMN " + KEY_IMAGE
+ " INTEGER");
}
if (oldVersion <= 12) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + KEY_IS_PAGED + " INTEGER DEFAULT 0");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + KEY_NEXT_PAGE_LINK + " TEXT");
}
if (oldVersion <= 13) {
// remove duplicate rows in "Chapters" table that were created because of a bug.
db.execSQL(String.format("DELETE FROM %s WHERE %s NOT IN " +
"(SELECT MIN(%s) as %s FROM %s GROUP BY %s,%s,%s,%s,%s)",
PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS,
KEY_ID,
KEY_ID,
KEY_ID,
PodDBAdapter.TABLE_NAME_SIMPLECHAPTERS,
KEY_TITLE,
KEY_START,
KEY_FEEDITEM,
KEY_LINK,
KEY_CHAPTER_TYPE));
}
if (oldVersion <= 14) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ " ADD COLUMN " + KEY_AUTO_DOWNLOAD + " INTEGER");
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ " SET " + KEY_AUTO_DOWNLOAD + " = "
+ "(SELECT " + KEY_AUTO_DOWNLOAD
+ " FROM " + PodDBAdapter.TABLE_NAME_FEEDS
+ " WHERE " + PodDBAdapter.TABLE_NAME_FEEDS + "." + KEY_ID
+ " = " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + KEY_FEED + ")");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + KEY_HIDE + " TEXT");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + KEY_LAST_UPDATE_FAILED + " INTEGER DEFAULT 0");
// create indexes
db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_FEED);
db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_IMAGE);
db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDMEDIA_FEEDITEM);
db.execSQL(PodDBAdapter.CREATE_INDEX_QUEUE_FEEDITEM);
db.execSQL(PodDBAdapter.CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM);
}
if (oldVersion <= 15) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ " ADD COLUMN " + KEY_HAS_EMBEDDED_PICTURE + " INTEGER DEFAULT -1");
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ " SET " + KEY_HAS_EMBEDDED_PICTURE + "=0"
+ " WHERE " + KEY_DOWNLOADED + "=0");
Cursor c = db.rawQuery("SELECT " + KEY_FILE_URL
+ " FROM " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ " WHERE " + KEY_DOWNLOADED + "=1 "
+ " AND " + KEY_HAS_EMBEDDED_PICTURE + "=-1", null);
if (c.moveToFirst()) {
MediaMetadataRetriever mmr = new MediaMetadataRetriever();
do {
String fileUrl = c.getString(0);
try {
mmr.setDataSource(fileUrl);
byte[] image = mmr.getEmbeddedPicture();
if (image != null) {
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ " SET " + KEY_HAS_EMBEDDED_PICTURE + "=1"
+ " WHERE " + KEY_FILE_URL + "='" + fileUrl + "'");
} else {
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ " SET " + KEY_HAS_EMBEDDED_PICTURE + "=0"
+ " WHERE " + KEY_FILE_URL + "='" + fileUrl + "'");
}
} catch (Exception e) {
e.printStackTrace();
}
} while (c.moveToNext());
}
c.close();
}
if (oldVersion <= 16) {
String selectNew = "SELECT " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + KEY_ID
+ " FROM " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ " INNER JOIN " + PodDBAdapter.TABLE_NAME_FEED_MEDIA + " ON "
+ PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM
+ " LEFT OUTER JOIN " + PodDBAdapter.TABLE_NAME_QUEUE + " ON "
+ PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "="
+ PodDBAdapter.TABLE_NAME_QUEUE + "." + KEY_FEEDITEM
+ " WHERE "
+ PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + KEY_READ + " = 0 AND " // unplayed
+ PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " = 0 AND " // undownloaded
+ PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + " = 0 AND " // not partially played
+ PodDBAdapter.TABLE_NAME_QUEUE + "." + KEY_ID + " IS NULL"; // not in queue
String sql = "UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS
+ " SET " + KEY_READ + "=" + FeedItem.NEW
+ " WHERE " + KEY_ID + " IN (" + selectNew + ")";
Log.d("Migration", "SQL: " + sql);
db.execSQL(sql);
}
if (oldVersion <= 17) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DELETE_ACTION + " INTEGER DEFAULT 0");
}
if (oldVersion < 1030005) {
db.execSQL("UPDATE FeedItems SET auto_download=0 WHERE " +
"(read=1 OR id IN (SELECT feeditem FROM FeedMedia WHERE position>0 OR downloaded=1)) " +
"AND id NOT IN (SELECT feeditem FROM Queue)");
}
if (oldVersion < 1040001) {
db.execSQL(CREATE_TABLE_FAVORITES);
}
if (oldVersion < 1040002) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ " ADD COLUMN " + PodDBAdapter.KEY_LAST_PLAYED_TIME + " INTEGER DEFAULT 0");
}
if (oldVersion < 1040013) {
db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_PUBDATE);
db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_READ);
}
if (oldVersion < 1050003) {
// Migrates feed list filter data
db.beginTransaction();
// Change to intermediate values to avoid overwriting in the following find/replace
db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
"SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'unplayed', 'noplay')");
db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
"SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'not_queued', 'noqueue')");
db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
"SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'not_downloaded', 'nodl')");
// Replace played, queued, and downloaded with their opposites
db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
"SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'played', 'unplayed')");
db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
"SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'queued', 'not_queued')");
db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
"SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'downloaded', 'not_downloaded')");
// Now replace intermediates for unplayed, not queued, etc. with their opposites
db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
"SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'noplay', 'played')");
db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
"SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'noqueue', 'queued')");
db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
"SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'nodl', 'downloaded')");
// Paused doesn't have an opposite, so unplayed is the next best option
db.execSQL("UPDATE " + TABLE_NAME_FEEDS + "\n" +
"SET " + KEY_HIDE + " = replace(" + KEY_HIDE + ", 'paused', 'unplayed')");
db.setTransactionSuccessful();
db.endTransaction();
// and now get ready for autodownload filters
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_INCLUDE_FILTER + " TEXT DEFAULT ''");
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_EXCLUDE_FILTER + " TEXT DEFAULT ''");
// and now auto refresh
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_KEEP_UPDATED + " INTEGER DEFAULT 1");
}
if (oldVersion < 1050004) {
// prevent old timestamps to be misinterpreted as ETags
db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " SET " + PodDBAdapter.KEY_LASTUPDATE + "=NULL");
}
if (oldVersion < 1060200) {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS
+ " ADD COLUMN " + PodDBAdapter.KEY_CUSTOM_TITLE + " TEXT");
}
DBUpgrader.upgrade(db, oldVersion, newVersion);
EventBus.getDefault().post(ProgressEvent.end());
}
}

View File

@ -7,7 +7,6 @@ import org.xml.sax.Attributes;
import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
public class NSITunes extends Namespace {
@ -16,7 +15,6 @@ public class NSITunes extends Namespace {
public static final String NSURI = "http://www.itunes.com/dtds/podcast-1.0.dtd";
private static final String IMAGE = "image";
private static final String IMAGE_TITLE = "image";
private static final String IMAGE_HREF = "href";
private static final String AUTHOR = "author";
@ -29,21 +27,15 @@ public class NSITunes extends Namespace {
public SyndElement handleElementStart(String localName, HandlerState state,
Attributes attributes) {
if (IMAGE.equals(localName)) {
FeedImage image = new FeedImage();
image.setTitle(IMAGE_TITLE);
image.setDownload_url(attributes.getValue(IMAGE_HREF));
String url = attributes.getValue(IMAGE_HREF);
if (state.getCurrentItem() != null) {
// this is an items image
image.setTitle(state.getCurrentItem().getTitle() + IMAGE_TITLE);
image.setOwner(state.getCurrentItem());
state.getCurrentItem().setImage(image);
state.getCurrentItem().setImageUrl(url);
} else {
// this is the feed image
// prefer to all other images
if (!TextUtils.isEmpty(image.getDownload_url())) {
image.setOwner(state.getFeed());
state.getFeed().setImage(image);
if (!TextUtils.isEmpty(url)) {
state.getFeed().setImageUrl(url);
}
}
}

View File

@ -7,7 +7,6 @@ import org.xml.sax.Attributes;
import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
import de.danoeh.antennapod.core.syndication.namespace.atom.AtomText;
@ -94,25 +93,16 @@ public class NSMedia extends Namespace {
}
state.getCurrentItem().setMedia(media);
} else if (state.getCurrentItem() != null && url != null && validTypeImage) {
FeedImage image = new FeedImage();
image.setDownload_url(url);
image.setOwner(state.getCurrentItem());
state.getCurrentItem().setImage(image);
state.getCurrentItem().setImageUrl(url);
}
} else if (IMAGE.equals(localName)) {
String url = attributes.getValue(IMAGE_URL);
if (url != null) {
FeedImage image = new FeedImage();
image.setDownload_url(url);
if (state.getCurrentItem() != null) {
image.setOwner(state.getCurrentItem());
state.getCurrentItem().setImage(image);
state.getCurrentItem().setImageUrl(url);
} else {
if (state.getFeed().getImage() == null) {
image.setOwner(state.getFeed());
state.getFeed().setImage(image);
if (state.getFeed().getImageUrl() == null) {
state.getFeed().setImageUrl(url);
}
}
}

View File

@ -6,7 +6,6 @@ import android.util.Log;
import org.xml.sax.Attributes;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
@ -77,17 +76,6 @@ public class NSRSS20 extends Namespace {
state.getCurrentItem().setMedia(media);
}
} else if (IMAGE.equals(localName)) {
if (state.getTagstack().size() >= 1) {
String parent = state.getTagstack().peek().getName();
if (CHANNEL.equals(parent)) {
Feed feed = state.getFeed();
if(feed != null && feed.getImage() == null) {
feed.setImage(new FeedImage());
feed.getImage().setOwner(state.getFeed());
}
}
}
}
return new SyndElement(localName, this);
}
@ -134,11 +122,6 @@ public class NSRSS20 extends Namespace {
state.getCurrentItem().setTitle(title);
} else if (CHANNEL.equals(second) && state.getFeed() != null) {
state.getFeed().setTitle(title);
} else if (IMAGE.equals(second) && CHANNEL.equals(third)) {
if(state.getFeed() != null && state.getFeed().getImage() != null &&
state.getFeed().getImage().getTitle() == null) {
state.getFeed().getImage().setTitle(title);
}
}
} else if (LINK.equals(top)) {
if (CHANNEL.equals(second) && state.getFeed() != null) {
@ -150,9 +133,8 @@ public class NSRSS20 extends Namespace {
state.getCurrentItem().setPubDate(DateUtils.parse(content));
} else if (URL.equals(top) && IMAGE.equals(second) && CHANNEL.equals(third)) {
// prefer itunes:image
if(state.getFeed() != null && state.getFeed().getImage() != null &&
state.getFeed().getImage().getDownload_url() == null) {
state.getFeed().getImage().setDownload_url(content);
if (state.getFeed() != null) {
state.getFeed().setImageUrl(content);
}
} else if (DESCR.equals(localName)) {
if (CHANNEL.equals(second) && state.getFeed() != null) {

View File

@ -5,7 +5,6 @@ import android.util.Log;
import org.xml.sax.Attributes;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.syndication.handler.HandlerState;
@ -210,10 +209,10 @@ public class NSAtom extends Namespace {
state.getCurrentItem().setPubDate(DateUtils.parse(content));
} else if (PUBLISHED.equals(top) && ENTRY.equals(second) && state.getCurrentItem() != null) {
state.getCurrentItem().setPubDate(DateUtils.parse(content));
} else if (IMAGE_LOGO.equals(top) && state.getFeed() != null && state.getFeed().getImage() == null) {
state.getFeed().setImage(new FeedImage(state.getFeed(), content, null));
} else if (IMAGE_LOGO.equals(top) && state.getFeed() != null && state.getFeed().getImageUrl() == null) {
state.getFeed().setImageUrl(content);
} else if (IMAGE_ICON.equals(top) && state.getFeed() != null) {
state.getFeed().setImage(new FeedImage(state.getFeed(), content, null));
state.getFeed().setImageUrl(content);
} else if (AUTHOR_NAME.equals(top) && AUTHOR.equals(second) &&
state.getFeed() != null && state.getCurrentItem() == null) {
String currentName = state.getFeed().getAuthor();

View File

@ -75,4 +75,18 @@ public class FeedItemUtil {
return false;
}
/**
* Get the link for the feed item for the purpose of Share. It fallbacks to
* use the feed's link if the named feed item has no link.
*/
public static String getLinkWithFallback(FeedItem item) {
if (item == null) {
return null;
} else if (item.getLink() != null) {
return item.getLink();
} else if (item.getFeed() != null) {
return item.getFeed().getLink();
}
return null;
}
}

View File

@ -0,0 +1,21 @@
package de.danoeh.antennapod.core.util;
import android.content.Context;
import android.util.Log;
import de.danoeh.antennapod.core.storage.DBTasks;
public class FeedUpdateUtils {
private static final String TAG = "FeedUpdateUtils";
private FeedUpdateUtils() {
}
public static void startAutoUpdate(Context context, Runnable callback) {
if (NetworkUtils.networkAvailable() && NetworkUtils.isDownloadAllowed()) {
DBTasks.refreshAllFeeds(context, null, callback);
} else {
Log.d(TAG, "Blocking automatic update: no wifi available / no mobile updates allowed");
}
}
}

View File

@ -50,11 +50,15 @@ public class ShareUtils {
return item.getFeed().getTitle() + ": " + item.getTitle();
}
public static boolean hasLinkToShare(FeedItem item) {
return FeedItemUtil.getLinkWithFallback(item) != null;
}
public static void shareFeedItemLink(Context context, FeedItem item, boolean withPosition) {
String text = getItemShareText(item) + " " + item.getLink();
String text = getItemShareText(item) + " " + FeedItemUtil.getLinkWithFallback(item);
if(withPosition) {
int pos = item.getMedia().getPosition();
text = item.getLink() + " [" + Converter.getDurationStringLong(pos) + "]";
text += " [" + Converter.getDurationStringLong(pos) + "]";
}
shareLink(context, text);
}

View File

@ -1,7 +1,12 @@
package de.danoeh.antennapod.core.util;
import android.content.Context;
import android.support.annotation.AttrRes;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
import android.util.Log;
import android.util.TypedValue;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.preferences.UserPreferences;
@ -12,6 +17,8 @@ public class ThemeUtils {
int theme = UserPreferences.getTheme();
if (theme == R.style.Theme_AntennaPod_Dark) {
return R.color.selection_background_color_dark;
} else if (theme == R.style.Theme_AntennaPod_TrueBlack){
return R.color.selection_background_color_trueblack;
} else if (theme == R.style.Theme_AntennaPod_Light) {
return R.color.selection_background_color_light;
} else {
@ -20,4 +27,10 @@ public class ThemeUtils {
return R.color.selection_background_color_light;
}
}
public static @ColorInt int getColorFromAttr(Context context, @AttrRes int attr) {
TypedValue typedValue = new TypedValue();
context.getTheme().resolveAttribute(attr, typedValue, true);
return typedValue.data;
}
}

View File

@ -0,0 +1,155 @@
package de.danoeh.antennapod.core.util.download;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.SystemClock;
import android.support.annotation.RequiresApi;
import android.util.Log;
import de.danoeh.antennapod.core.receiver.FeedUpdateReceiver;
import de.danoeh.antennapod.core.service.FeedUpdateJobService;
import java.util.Calendar;
import java.util.concurrent.TimeUnit;
public class AutoUpdateManager {
private static final int JOB_ID_FEED_UPDATE = 42;
private static final String TAG = "AutoUpdateManager";
private AutoUpdateManager() {
}
/**
* Sets the interval in which the feeds are refreshed automatically
*/
public static void restartUpdateIntervalAlarm(Context context, long triggerAtMillis, long intervalMillis) {
Log.d(TAG, "Restarting update alarm.");
if (Build.VERSION.SDK_INT >= 24) {
restartJobServiceInterval(context, intervalMillis);
} else {
restartAlarmManagerInterval(context, triggerAtMillis, intervalMillis);
}
}
/**
* Sets time of day the feeds are refreshed automatically
*/
public static void restartUpdateTimeOfDayAlarm(Context context, int hoursOfDay, int minute) {
Log.d(TAG, "Restarting update alarm.");
Calendar now = Calendar.getInstance();
Calendar alarm = (Calendar)now.clone();
alarm.set(Calendar.HOUR_OF_DAY, hoursOfDay);
alarm.set(Calendar.MINUTE, minute);
if (alarm.before(now) || alarm.equals(now)) {
alarm.add(Calendar.DATE, 1);
}
if (Build.VERSION.SDK_INT >= 24) {
long triggerAtMillis = alarm.getTimeInMillis() - now.getTimeInMillis();
restartJobServiceTriggerAt(context, triggerAtMillis);
} else {
restartAlarmManagerTimeOfDay(context, alarm);
}
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private static JobInfo.Builder getFeedUpdateJobBuilder(Context context) {
ComponentName serviceComponent = new ComponentName(context, FeedUpdateJobService.class);
JobInfo.Builder builder = new JobInfo.Builder(JOB_ID_FEED_UPDATE, serviceComponent);
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
builder.setPersisted(true);
return builder;
}
@RequiresApi(api = Build.VERSION_CODES.N)
private static void restartJobServiceInterval(Context context, long intervalMillis) {
JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
if (jobScheduler == null) {
Log.d(TAG, "JobScheduler was null.");
return;
}
JobInfo oldJob = jobScheduler.getPendingJob(JOB_ID_FEED_UPDATE);
if (oldJob != null && oldJob.getIntervalMillis() == intervalMillis) {
Log.d(TAG, "JobScheduler was already set at interval " + intervalMillis + ", ignoring.");
return;
}
JobInfo.Builder builder = getFeedUpdateJobBuilder(context);
builder.setPeriodic(intervalMillis);
jobScheduler.cancel(JOB_ID_FEED_UPDATE);
if (intervalMillis <= 0) {
Log.d(TAG, "Automatic update was deactivated");
return;
}
jobScheduler.schedule(builder.build());
Log.d(TAG, "JobScheduler was set at interval " + intervalMillis);
}
private static void restartAlarmManagerInterval(Context context, long triggerAtMillis, long intervalMillis) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if (alarmManager == null) {
Log.d(TAG, "AlarmManager was null");
return;
}
PendingIntent updateIntent = PendingIntent.getBroadcast(context, 0,
new Intent(context, FeedUpdateReceiver.class), 0);
alarmManager.cancel(updateIntent);
if (intervalMillis <= 0) {
Log.d(TAG, "Automatic update was deactivated");
return;
}
alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + triggerAtMillis,
updateIntent);
Log.d(TAG, "Changed alarm to new interval " + TimeUnit.MILLISECONDS.toHours(intervalMillis) + " h");
}
@RequiresApi(api = Build.VERSION_CODES.N)
private static void restartJobServiceTriggerAt(Context context, long triggerAtMillis) {
JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
if (jobScheduler == null) {
Log.d(TAG, "JobScheduler was null.");
return;
}
JobInfo.Builder builder = getFeedUpdateJobBuilder(context);
builder.setMinimumLatency(triggerAtMillis);
jobScheduler.cancel(JOB_ID_FEED_UPDATE);
jobScheduler.schedule(builder.build());
Log.d(TAG, "JobScheduler was set for " + triggerAtMillis);
}
private static void restartAlarmManagerTimeOfDay(Context context, Calendar alarm) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if (alarmManager == null) {
Log.d(TAG, "AlarmManager was null");
return;
}
PendingIntent updateIntent = PendingIntent.getBroadcast(context, 0,
new Intent(context, FeedUpdateReceiver.class), 0);
alarmManager.cancel(updateIntent);
Log.d(TAG, "Alarm set for: " + alarm.toString() + " : " + alarm.getTimeInMillis());
alarmManager.set(AlarmManager.RTC_WAKEUP,
alarm.getTimeInMillis(),
updateIntent);
Log.d(TAG, "Changed alarm to new time of day " + alarm.get(Calendar.HOUR_OF_DAY) + ":" + alarm.get(Calendar.MINUTE));
}
}

View File

@ -0,0 +1,62 @@
package de.danoeh.antennapod.core.util.gui;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Build;
import android.support.annotation.RequiresApi;
import de.danoeh.antennapod.core.R;
public class NotificationUtils {
public static final String CHANNEL_ID_USER_ACTION = "user_action";
public static final String CHANNEL_ID_DOWNLOADING = "downloading";
public static final String CHANNEL_ID_PLAYING = "playing";
public static final String CHANNEL_ID_ERROR = "error";
public static void createChannels(Context context) {
if (android.os.Build.VERSION.SDK_INT < 26) {
return;
}
NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (mNotificationManager != null) {
mNotificationManager.createNotificationChannel(createChannelUserAction(context));
mNotificationManager.createNotificationChannel(createChannelDownloading(context));
mNotificationManager.createNotificationChannel(createChannelPlaying(context));
mNotificationManager.createNotificationChannel(createChannelError(context));
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
private static NotificationChannel createChannelUserAction(Context c) {
NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID_USER_ACTION,
c.getString(R.string.notification_channel_user_action), NotificationManager.IMPORTANCE_HIGH);
mChannel.setDescription(c.getString(R.string.notification_channel_user_action_description));
return mChannel;
}
@RequiresApi(api = Build.VERSION_CODES.O)
private static NotificationChannel createChannelDownloading(Context c) {
NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID_DOWNLOADING,
c.getString(R.string.notification_channel_downloading), NotificationManager.IMPORTANCE_LOW);
mChannel.setDescription(c.getString(R.string.notification_channel_downloading_description));
return mChannel;
}
@RequiresApi(api = Build.VERSION_CODES.O)
private static NotificationChannel createChannelPlaying(Context c) {
NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID_PLAYING,
c.getString(R.string.notification_channel_playing), NotificationManager.IMPORTANCE_LOW);
mChannel.setDescription(c.getString(R.string.notification_channel_playing_description));
return mChannel;
}
@RequiresApi(api = Build.VERSION_CODES.O)
private static NotificationChannel createChannelError(Context c) {
NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID_ERROR,
c.getString(R.string.notification_channel_error), NotificationManager.IMPORTANCE_HIGH);
mChannel.setDescription(c.getString(R.string.notification_channel_error_description));
return mChannel;
}
}

View File

@ -3,6 +3,8 @@ package de.danoeh.antennapod.core.util.playback;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.util.Log;
import java.util.List;
@ -11,6 +13,7 @@ import de.danoeh.antennapod.core.asynctask.ImageResource;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.ShownotesProvider;
@ -175,6 +178,23 @@ public interface Playable extends Parcelable,
class PlayableUtils {
private static final String TAG = "PlayableUtils";
/**
* Restores a playable object from a sharedPreferences file. This method might load data from the database,
* depending on the type of playable that was restored.
*
* @return The restored Playable object
*/
@Nullable
public static Playable createInstanceFromPreferences(Context context) {
long currentlyPlayingMedia = PlaybackPreferences.getCurrentlyPlayingMedia();
if (currentlyPlayingMedia != PlaybackPreferences.NO_MEDIA_PLAYING) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
return PlayableUtils.createInstanceFromPreferences(context,
(int) currentlyPlayingMedia, prefs);
}
return null;
}
/**
* Restores a playable object from a sharedPreferences file. This method might load data from the database,
* depending on the type of playable that was restored.

View File

@ -14,6 +14,8 @@ import android.os.Build;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
@ -59,7 +61,7 @@ public abstract class PlaybackController {
private PlaybackService playbackService;
private Playable media;
private PlayerStatus status;
private PlayerStatus status = PlayerStatus.STOPPED;
private final ScheduledThreadPoolExecutor schedExecutor;
private static final int SCHED_EX_POOLSIZE = 1;
@ -69,6 +71,7 @@ public abstract class PlaybackController {
private boolean mediaInfoLoaded = false;
private boolean released = false;
private boolean initialized = false;
private Subscription serviceBinder;
@ -92,10 +95,22 @@ public abstract class PlaybackController {
}
/**
* Creates a new connection to the playbackService. Should be called in the
* activity's onResume() method.
* Creates a new connection to the playbackService.
*/
public void init() {
if (PlaybackService.isRunning) {
initServiceRunning();
} else {
initServiceNotRunning();
}
}
private synchronized void initServiceRunning() {
if (initialized) {
return;
}
initialized = true;
activity.registerReceiver(statusUpdate, new IntentFilter(
PlaybackService.ACTION_PLAYER_STATUS_CHANGED));
@ -167,7 +182,7 @@ public abstract class PlaybackController {
*/
private void bindToService() {
Log.d(TAG, "Trying to connect to service");
if(serviceBinder != null) {
if (serviceBinder != null) {
serviceBinder.unsubscribe();
}
serviceBinder = Observable.fromCallable(this::getPlayLastPlayedMediaIntent)
@ -178,7 +193,7 @@ public abstract class PlaybackController {
if (!PlaybackService.started) {
if (intent != null) {
Log.d(TAG, "Calling start service");
activity.startService(intent);
ContextCompat.startForegroundService(activity, intent);
bound = activity.bindService(intent, mConnection, 0);
} else {
status = PlayerStatus.STOPPED;
@ -198,31 +213,24 @@ public abstract class PlaybackController {
* Returns an intent that starts the PlaybackService and plays the last
* played media or null if no last played media could be found.
*/
private Intent getPlayLastPlayedMediaIntent() {
@Nullable private Intent getPlayLastPlayedMediaIntent() {
Log.d(TAG, "Trying to restore last played media");
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
activity.getApplicationContext());
long currentlyPlayingMedia = PlaybackPreferences.getCurrentlyPlayingMedia();
if (currentlyPlayingMedia != PlaybackPreferences.NO_MEDIA_PLAYING) {
Playable media = PlayableUtils.createInstanceFromPreferences(activity,
(int) currentlyPlayingMedia, prefs);
if (media != null) {
Intent serviceIntent = new Intent(activity, PlaybackService.class);
serviceIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
serviceIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED, false);
serviceIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY, true);
boolean fileExists = media.localFileAvailable();
boolean lastIsStream = PlaybackPreferences.getCurrentEpisodeIsStream();
if (!fileExists && !lastIsStream && media instanceof FeedMedia) {
DBTasks.notifyMissingFeedMediaFile(activity, (FeedMedia) media);
}
serviceIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM,
lastIsStream || !fileExists);
return serviceIntent;
}
Playable media = PlayableUtils.createInstanceFromPreferences(activity);
if (media == null) {
Log.d(TAG, "No last played media found");
return null;
}
Log.d(TAG, "No last played media found");
return null;
boolean fileExists = media.localFileAvailable();
boolean lastIsStream = PlaybackPreferences.getCurrentEpisodeIsStream();
if (!fileExists && !lastIsStream && media instanceof FeedMedia) {
DBTasks.notifyMissingFeedMediaFile(activity, (FeedMedia) media);
}
return new PlaybackServiceStarter(activity, media)
.startWhenPrepared(false)
.shouldStream(lastIsStream || !fileExists)
.getIntent();
}
@ -511,7 +519,7 @@ public abstract class PlaybackController {
"PlaybackService has no media object. Trying to restore last played media.");
Intent serviceIntent = getPlayLastPlayedMediaIntent();
if (serviceIntent != null) {
activity.startService(serviceIntent);
ContextCompat.startForegroundService(activity, serviceIntent);
}
}
*/
@ -576,6 +584,10 @@ public abstract class PlaybackController {
public void playPause() {
if (playbackService == null) {
new PlaybackServiceStarter(activity, media)
.startWhenPrepared(true)
.streamIfLastWasStream()
.start();
Log.w(TAG, "Play/Pause button was pressed, but playbackservice was null!");
return;
}
@ -609,6 +621,8 @@ public abstract class PlaybackController {
public int getPosition() {
if (playbackService != null) {
return playbackService.getCurrentPosition();
} else if (media != null) {
return media.getPosition();
} else {
return PlaybackService.INVALID_TIME;
}
@ -617,12 +631,17 @@ public abstract class PlaybackController {
public int getDuration() {
if (playbackService != null) {
return playbackService.getDuration();
} else if (media != null) {
return media.getDuration();
} else {
return PlaybackService.INVALID_TIME;
}
}
public Playable getMedia() {
if (media == null) {
media = PlayableUtils.createInstanceFromPreferences(activity);
}
return media;
}
@ -714,8 +733,13 @@ public abstract class PlaybackController {
}
public boolean isPlayingVideoLocally() {
return playbackService != null && PlaybackService.getCurrentMediaType() == MediaType.VIDEO
&& !PlaybackService.isCasting();
if (PlaybackService.isCasting()) {
return false;
} else if (playbackService != null) {
return PlaybackService.getCurrentMediaType() == MediaType.VIDEO;
} else {
return getMedia() != null && getMedia().getMediaType() == MediaType.VIDEO;
}
}
public Pair<Integer, Integer> getVideoSize() {
@ -755,6 +779,21 @@ public abstract class PlaybackController {
}
}
private void initServiceNotRunning() {
if (getMedia() == null) {
return;
}
if (getMedia().getMediaType() == MediaType.AUDIO) {
TypedArray res = activity.obtainStyledAttributes(new int[]{
de.danoeh.antennapod.core.R.attr.av_play_big});
getPlayButton().setImageResource(
res.getResourceId(0, de.danoeh.antennapod.core.R.drawable.ic_play_arrow_grey600_36dp));
res.recycle();
} else {
getPlayButton().setImageResource(R.drawable.ic_av_play_circle_outline_80dp);
}
}
/**
* Refreshes the current position of the media file that is playing.
*/

View File

@ -0,0 +1,76 @@
package de.danoeh.antennapod.core.util.playback;
import android.content.Context;
import android.content.Intent;
import android.media.MediaPlayer;
import android.support.v4.content.ContextCompat;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
public class PlaybackServiceStarter {
private final Context context;
private final Playable media;
private boolean startWhenPrepared = false;
private boolean shouldStream = false;
private boolean callEvenIfRunning = false;
private boolean prepareImmediately = true;
public PlaybackServiceStarter(Context context, Playable media) {
this.context = context;
this.media = media;
}
/**
* Default value: false
*/
public PlaybackServiceStarter shouldStream(boolean shouldStream) {
this.shouldStream = shouldStream;
return this;
}
public PlaybackServiceStarter streamIfLastWasStream() {
boolean lastIsStream = PlaybackPreferences.getCurrentEpisodeIsStream();
return shouldStream(lastIsStream);
}
/**
* Default value: false
*/
public PlaybackServiceStarter startWhenPrepared(boolean startWhenPrepared) {
this.startWhenPrepared = startWhenPrepared;
return this;
}
/**
* Default value: false
*/
public PlaybackServiceStarter callEvenIfRunning(boolean callEvenIfRunning) {
this.callEvenIfRunning = callEvenIfRunning;
return this;
}
/**
* Default value: true
*/
public PlaybackServiceStarter prepareImmediately(boolean prepareImmediately) {
this.prepareImmediately = prepareImmediately;
return this;
}
public Intent getIntent() {
Intent launchIntent = new Intent(context, PlaybackService.class);
launchIntent.putExtra(PlaybackService.EXTRA_PLAYABLE, media);
launchIntent.putExtra(PlaybackService.EXTRA_START_WHEN_PREPARED, startWhenPrepared);
launchIntent.putExtra(PlaybackService.EXTRA_SHOULD_STREAM, shouldStream);
launchIntent.putExtra(PlaybackService.EXTRA_PREPARE_IMMEDIATELY, prepareImmediately);
return launchIntent;
}
public void start() {
if (PlaybackService.isRunning && !callEvenIfRunning) {
return;
}
ContextCompat.startForegroundService(context, getIntent());
}
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<solid android:color="#000000"/>
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<solid android:color="@color/holo_blue_dark"/>
</shape>
</clip>
</item>
</layer-list>

View File

@ -6,6 +6,19 @@
<style name="Theme.AntennaPod.Dark" parent="Theme.Base.AntennaPod.Dark">
<item name="android:windowContentTransitions">true</item>
</style>
<style name="Theme.AntennaPod.TrueBlack" parent="Theme.Base.AntennaPod.TrueBlack">
<item name="android:windowContentTransitions">true</item>
<item name="android:navigationBarColor">@color/black</item>
<item name="android:colorAccent">@color/white</item>
<item name="android:colorPrimary">@color/black</item>
<item name="android:colorPrimaryDark">@color/black</item>
</style>
<style name="Theme.AntennaPod.TrueBlack.NoTitle" parent="Theme.Base.AntennaPod.TrueBlack.NoTitle">
<item name="android:navigationBarColor">@color/black</item>
<item name="android:colorAccent">@color/white</item>
<item name="android:colorPrimary">@color/black</item>
<item name="android:colorPrimaryDark">@color/black</item>
</style>
<style name="Widget.AntennaPod.Button" parent="Widget.AppCompat.Button">
<item name="textAllCaps">true</item>

View File

@ -137,10 +137,12 @@
<string-array name="theme_options">
<item>@string/pref_theme_title_light</item>
<item>@string/pref_theme_title_dark</item>
<item>@string/pref_theme_title_trueblack</item>
</string-array>
<string-array name="theme_values">
<item>0</item>
<item>1</item>
<item>2</item>
</string-array>
<string-array name="nav_drawer_titles">

View File

@ -56,6 +56,7 @@
<attr name="ic_cast_disconnect" format="reference"/>
<attr name="ic_swap" format="reference"/>
<attr name="master_switch_background" format="color"/>
<attr name="currently_playing_background" format="color"/>
<!-- Used in itemdescription -->
<attr name="non_transparent_background" format="reference"/>

View File

@ -22,6 +22,7 @@
<color name="image_readability_tint">#80000000</color>
<color name="feed_image_bg">#50000000</color>
<color name="selection_background_color_trueblack">#286E8A</color>
<color name="selection_background_color_dark">#286E8A</color>
<color name="selection_background_color_light">#81CFEA</color>
@ -30,6 +31,7 @@
<color name="highlight_light">#DDDDDD</color>
<color name="highlight_dark">#414141</color>
<color name="highlight_trueblack">#000000</color>
<color name="antennapod_blue">#147BAF</color>

View File

@ -6,6 +6,7 @@
<!-- Activitiy and fragment titles -->
<string name="app_name" translate="false">AntennaPod</string>
<string name="provider_authority" translate="false">de.danoeh.antennapod.provider</string>
<string name="feed_update_receiver_name">Update Subscriptions</string>
<string name="feeds_label">Feeds</string>
<string name="statistics_label">Statistics</string>
<string name="add_feed_label">Add Podcast</string>
@ -29,6 +30,7 @@
<string name="free_space_label">%1$s free</string>
<string name="episode_cache_full_title">Episode cache full</string>
<string name="episode_cache_full_message">The episode cache limit has been reached. You can increase the cache size in the Settings.</string>
<string name="synchronizing">Synchronizing…</string>
<!-- Statistics fragment -->
<string name="total_time_listened_to_podcasts">Total time of podcasts played:</string>
@ -393,6 +395,7 @@
<string name="pref_episode_cache_title">Episode Cache</string>
<string name="pref_theme_title_light">Light</string>
<string name="pref_theme_title_dark">Dark</string>
<string name="pref_theme_title_trueblack">True Black</string>
<string name="pref_episode_cache_unlimited">Unlimited</string>
<string name="pref_update_interval_hours_plural">hours</string>
<string name="pref_update_interval_hours_singular">hour</string>
@ -709,4 +712,14 @@
<string name="cast_failed_seek">Failed to seek to the new position on the cast device</string>
<string name="cast_failed_receiver_player_error">Receiver player has encountered a severe error</string>
<string name="cast_failed_media_error_skipping">Error playing media. Skipping&#8230;</string>
<!-- Notification channels -->
<string name="notification_channel_user_action">Action required</string>
<string name="notification_channel_user_action_description">Shown if your action is required, for example if you need to enter a password.</string>
<string name="notification_channel_downloading">Downloading</string>
<string name="notification_channel_downloading_description">Shown while currently downloading.</string>
<string name="notification_channel_playing">Currently playing</string>
<string name="notification_channel_playing_description">Allows to control playback. This is the main notification you see while playing a podcast.</string>
<string name="notification_channel_error">Errors</string>
<string name="notification_channel_error_description">Shown if something went wrong, for example if download or gpodder sync fails.</string>
</resources>

View File

@ -67,6 +67,8 @@
<item type="attr" name="ic_create_new_folder">@drawable/ic_create_new_folder_grey600_24dp</item>
<item type="attr" name="ic_cast_disconnect">@drawable/ic_cast_disconnect_grey600_36dp</item>
<item type="attr" name="master_switch_background">@color/master_switch_background_light</item>
<item type="attr" name="currently_playing_background">@color/highlight_light</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
</style>
<style name="Theme.AntennaPod.Dark" parent="Theme.Base.AntennaPod.Dark">
@ -135,8 +137,31 @@
<item type="attr" name="ic_create_new_folder">@drawable/ic_create_new_folder_white_24dp</item>
<item type="attr" name="ic_cast_disconnect">@drawable/ic_cast_disconnect_white_36dp</item>
<item type="attr" name="master_switch_background">@color/master_switch_background_dark</item>
<item type="attr" name="currently_playing_background">@color/highlight_dark</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
</style>
<style name="Theme.AntennaPod.TrueBlack" parent="Theme.Base.AntennaPod.TrueBlack">
<!-- Room for API dependent attributes -->
</style>
<style name="Theme.Base.AntennaPod.TrueBlack" parent="Theme.Base.AntennaPod.Dark">
<item name="progressBarTheme">@style/ProgressBarTrueBlack</item>
<item type="attr" name="non_transparent_background">@color/black</item>
<item type="attr" name="overlay_background">@color/overlay_dark</item>
<item type="attr" name="overlay_drawable">@drawable/overlay_drawable_dark</item>
<item type="attr" name="dragview_background">@drawable/ic_drag_vertical_white_48dp</item>
<item type="attr" name="dragview_float_background">@color/black</item>
<item type="attr" name="nav_drawer_background">@color/black</item>
<item name="android:textColorPrimary">@color/white</item>
<item name="android:color">@color/white</item>
<item name="android:windowBackground">@color/black</item>
<item name="android:actionBarStyle">@color/black</item>
<item name="colorPrimary">@color/black</item>
<item name="colorPrimaryDark">@color/black</item>
</style>
<style name="Theme.AntennaPod.Light.NoTitle" parent="Theme.Base.AntennaPod.Light.NoTitle">
<!-- Room for API dependent attributes -->
</style>
@ -204,6 +229,8 @@
<item type="attr" name="ic_create_new_folder">@drawable/ic_create_new_folder_grey600_24dp</item>
<item type="attr" name="ic_cast_disconnect">@drawable/ic_cast_disconnect_grey600_36dp</item>
<item type="attr" name="master_switch_background">@color/master_switch_background_light</item>
<item type="attr" name="currently_playing_background">@color/highlight_light</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
</style>
<style name="Theme.AntennaPod.Dark.NoTitle" parent="Theme.Base.AntennaPod.Dark.NoTitle">
@ -273,8 +300,32 @@
<item type="attr" name="ic_create_new_folder">@drawable/ic_create_new_folder_white_24dp</item>
<item type="attr" name="ic_cast_disconnect">@drawable/ic_cast_disconnect_white_36dp</item>
<item type="attr" name="master_switch_background">@color/master_switch_background_dark</item>
<item type="attr" name="currently_playing_background">@color/highlight_dark</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
</style>
<style name="Theme.AntennaPod.TrueBlack.NoTitle" parent="Theme.Base.AntennaPod.TrueBlack.NoTitle">
<!-- Room for API dependent attributes -->
</style>
<style name="Theme.Base.AntennaPod.TrueBlack.NoTitle" parent="Theme.Base.AntennaPod.Dark.NoTitle">
<item name="progressBarTheme">@style/ProgressBarTrueBlack</item>
<item type="attr" name="non_transparent_background">@color/black</item>
<item type="attr" name="overlay_background">@color/black</item>
<item type="attr" name="overlay_drawable">@drawable/overlay_drawable_dark</item>
<item type="attr" name="dragview_background">@drawable/ic_drag_vertical_white_48dp</item>
<item type="attr" name="dragview_float_background">@color/black</item>
<item type="attr" name="nav_drawer_background">@color/black</item>
<item type="attr" name="currently_playing_background">@color/highlight_trueblack</item>
<item name="android:textColorPrimary">@color/white</item>
<item name="android:color">@color/white</item>
<item name="android:windowBackground">@color/black</item>
<item name="android:actionBarStyle">@color/black</item>
<item name="colorPrimary">@color/black</item>
<item name="colorPrimaryDark">@color/black</item>
</style>
<style name="Theme.AntennaPod.Dark.Splash" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">@drawable/bg_splash</item>
</style>
@ -362,4 +413,9 @@
<item name="android:progressDrawable">@drawable/progress_bar_horizontal_dark</item>
</style>
<style name="ProgressBarTrueBlack">
<item name="android:indeterminateOnly">false</item>
<item name="android:progressDrawable">@drawable/progress_bar_horizontal_trueblack</item>
</style>
</resources>

View File

@ -13,7 +13,6 @@ import java.util.Calendar;
import java.util.List;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.storage.DBReader;
@ -97,9 +96,9 @@ public class CastUtils {
if (subtitle != null) {
metadata.putString(MediaMetadata.KEY_SUBTITLE, subtitle);
}
FeedImage image = feedItem.getImage();
if (image != null && !TextUtils.isEmpty(image.getDownload_url())) {
metadata.addImage(new WebImage(Uri.parse(image.getDownload_url())));
if (!TextUtils.isEmpty(feedItem.getImageUrl())) {
metadata.addImage(new WebImage(Uri.parse(feedItem.getImageUrl())));
}
Calendar calendar = Calendar.getInstance();
calendar.setTime(media.getItem().getPubDate());

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