diff options
Diffstat (limited to 'core/src/main/java')
33 files changed, 1926 insertions, 854 deletions
diff --git a/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java index b6ece6dc8..4f2d5b204 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java +++ b/core/src/main/java/de/danoeh/antennapod/core/asynctask/PicassoProvider.java @@ -6,8 +6,12 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.media.MediaMetadataRetriever; import android.net.Uri; +import android.text.TextUtils; import android.util.Log; +import com.squareup.okhttp.Interceptor; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Response; import com.squareup.picasso.Cache; import com.squareup.picasso.LruCache; import com.squareup.picasso.OkHttpDownloader; @@ -22,13 +26,18 @@ import org.apache.commons.lang3.StringUtils; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.HttpURLConnection; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import de.danoeh.antennapod.core.service.download.HttpDownloader; +import de.danoeh.antennapod.core.storage.DBReader; + /** * Provides access to Picasso instances. */ public class PicassoProvider { + private static final String TAG = "PicassoProvider"; private static final boolean DEBUG = false; @@ -56,10 +65,12 @@ public class PicassoProvider { if (picassoSetup) { return; } + OkHttpClient client = new OkHttpClient(); + client.interceptors().add(new BasicAuthenticationInterceptor(appContext)); Picasso picasso = new Picasso.Builder(appContext) .indicatorsEnabled(DEBUG) .loggingEnabled(DEBUG) - .downloader(new OkHttpDownloader(appContext)) + .downloader(new OkHttpDownloader(client)) .addRequestHandler(new MediaRequestHandler(appContext)) .executor(getExecutorService()) .memoryCache(getMemoryCache(appContext)) @@ -75,6 +86,48 @@ public class PicassoProvider { picassoSetup = true; } + private static class BasicAuthenticationInterceptor implements Interceptor { + + private final Context context; + + public BasicAuthenticationInterceptor(Context context) { + this.context = context; + } + + @Override + public Response intercept(Chain chain) throws IOException { + com.squareup.okhttp.Request request = chain.request(); + String url = request.urlString(); + String authentication = DBReader.getImageAuthentication(context, url); + + if(TextUtils.isEmpty(authentication)) { + Log.d(TAG, "no credentials for '" + url + "'"); + return chain.proceed(request); + } + + // add authentication + String[] auth = authentication.split(":"); + String credentials = HttpDownloader.encodeCredentials(auth[0], auth[1], "ISO-8859-1"); + com.squareup.okhttp.Request newRequest = request + .newBuilder() + .addHeader("Authorization", credentials) + .build(); + Log.d(TAG, "Basic authentication with ISO-8859-1 encoding"); + Response response = chain.proceed(newRequest); + if (!response.isSuccessful() && response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) { + credentials = HttpDownloader.encodeCredentials(auth[0], auth[1], "UTF-8"); + newRequest = request + .newBuilder() + .addHeader("Authorization", credentials) + .build(); + Log.d(TAG, "Basic authentication with UTF-8 encoding"); + return chain.proceed(newRequest); + } else { + return response; + } + } + } + private static class MediaRequestHandler extends RequestHandler { final Context context; @@ -90,7 +143,7 @@ public class PicassoProvider { } @Override - public Result load(Request data) throws IOException { + public Result load(Request data, int networkPolicy) throws IOException { Bitmap bitmap = null; MediaMetadataRetriever mmr = null; try { @@ -109,13 +162,7 @@ public class PicassoProvider { } if (bitmap == null) { - // check for fallback Uri - String fallbackParam = data.uri.getQueryParameter(PicassoImageResource.PARAM_FALLBACK); - - if (fallbackParam != null) { - Uri fallback = Uri.parse(fallbackParam); - bitmap = decodeStreamFromFile(data, fallback); - } + Log.wtf(TAG, "THIS SHOULD NEVER EVER HAPPEN!!"); } return new Result(bitmap, Picasso.LoadedFrom.DISK); diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java index c0f71ed55..29ba721fe 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java @@ -2,6 +2,7 @@ package de.danoeh.antennapod.core.feed; import android.content.Context; import android.net.Uri; +import android.support.annotation.Nullable; import org.apache.commons.lang3.StringUtils; @@ -10,9 +11,7 @@ import java.util.Date; import java.util.List; import de.danoeh.antennapod.core.asynctask.PicassoImageResource; -import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBWriter; -import de.danoeh.antennapod.core.util.EpisodeFilter; import de.danoeh.antennapod.core.util.flattr.FlattrStatus; import de.danoeh.antennapod.core.util.flattr.FlattrThing; @@ -81,12 +80,20 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource */ private String nextPageLink; + private boolean lastUpdateFailed; + + /** + * Contains property strings. If such a property applies to a feed item, it is not shown in the feed list + */ + private FeedItemFilter itemfilter; + /** * This constructor is used for restoring a feed from the database. */ public Feed(long id, Date lastUpdate, String title, String link, String description, String paymentLink, String author, String language, String type, String feedIdentifier, FeedImage image, String fileUrl, - String downloadUrl, boolean downloaded, FlattrStatus status, boolean paged, String nextPageLink) { + String downloadUrl, boolean downloaded, FlattrStatus status, boolean paged, String nextPageLink, + String filter, boolean lastUpdateFailed) { super(fileUrl, downloadUrl, downloaded); this.id = id; this.title = title; @@ -106,8 +113,13 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource this.flattrStatus = status; this.paged = paged; this.nextPageLink = nextPageLink; - - items = new ArrayList<FeedItem>(); + this.items = new ArrayList<FeedItem>(); + if(filter != null) { + this.itemfilter = new FeedItemFilter(filter); + } else { + this.itemfilter = new FeedItemFilter(new String[0]); + } + this.lastUpdateFailed = lastUpdateFailed; } /** @@ -117,7 +129,7 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource String author, String language, String type, String feedIdentifier, FeedImage image, String fileUrl, String downloadUrl, boolean downloaded) { this(id, lastUpdate, title, link, description, paymentLink, author, language, type, feedIdentifier, image, - fileUrl, downloadUrl, downloaded, new FlattrStatus(), false, null); + fileUrl, downloadUrl, downloaded, new FlattrStatus(), false, null, null, false); } /** @@ -125,7 +137,6 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource */ public Feed() { super(); - items = new ArrayList<FeedItem>(); lastUpdate = new Date(); this.flattrStatus = new FlattrStatus(); } @@ -159,53 +170,15 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource preferences = new FeedPreferences(0, true, username, password); } - /** - * Returns the number of FeedItems where 'read' is false. If the 'display - * only episodes' - preference is set to true, this method will only count - * items with episodes. - */ - public int getNumOfNewItems() { - int count = 0; - for (FeedItem item : items) { - if (item.getState() == FeedItem.State.NEW) { - if (!UserPreferences.isDisplayOnlyEpisodes() - || item.getMedia() != null) { - count++; - } - } - } - return count; - } - - /** - * Returns the number of FeedItems where the media started to play but - * wasn't finished yet. - */ - public int getNumOfStartedItems() { - int count = 0; - - for (FeedItem item : items) { - FeedItem.State state = item.getState(); - if (state == FeedItem.State.IN_PROGRESS - || state == FeedItem.State.PLAYING) { - count++; - } - } - return count; - } /** * Returns true if at least one item in the itemlist is unread. * - * @param enableEpisodeFilter true if this method should only count items with episodes if - * the 'display only episodes' - preference is set to true by the - * user. */ - public boolean hasNewItems(boolean enableEpisodeFilter) { + public boolean hasNewItems() { for (FeedItem item : items) { - if (item.getState() == FeedItem.State.NEW) { - if (!(enableEpisodeFilter && UserPreferences - .isDisplayOnlyEpisodes()) || item.getMedia() != null) { + if (item.getState() == FeedItem.State.UNREAD) { + if (item.getMedia() != null) { return true; } } @@ -216,30 +189,17 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource /** * Returns the number of FeedItems. * - * @param enableEpisodeFilter true if this method should only count items with episodes if - * the 'display only episodes' - preference is set to true by the - * user. */ - public int getNumOfItems(boolean enableEpisodeFilter) { - if (enableEpisodeFilter && UserPreferences.isDisplayOnlyEpisodes()) { - return EpisodeFilter.countItemsWithEpisodes(items); - } else { - return items.size(); - } + public int getNumOfItems() { + return items.size(); } /** * Returns the item at the specified index. * - * @param enableEpisodeFilter true if this method should ignore items without episdodes if - * the episodes filter has been enabled by the user. */ - public FeedItem getItemAtIndex(boolean enableEpisodeFilter, int position) { - if (enableEpisodeFilter && UserPreferences.isDisplayOnlyEpisodes()) { - return EpisodeFilter.accessEpisodeByIndex(items, position); - } else { - return items.get(position); - } + public FeedItem getItemAtIndex(int position) { + return items.get(position); } /** @@ -350,6 +310,19 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource return false; } + public FeedItem getMostRecentItem() { + // we could sort, but we don't need to, a simple search is fine... + Date mostRecentDate = new Date(0); + FeedItem mostRecentItem = null; + for (FeedItem item : items) { + if (item.getPubDate().after(mostRecentDate)) { + mostRecentDate = item.getPubDate(); + mostRecentItem = item; + } + } + return mostRecentItem; + } + @Override public int getTypeAsInt() { return FEEDFILETYPE_FEED; @@ -503,4 +476,24 @@ public class Feed extends FeedFile implements FlattrThing, PicassoImageResource public void setNextPageLink(String nextPageLink) { this.nextPageLink = nextPageLink; } + + @Nullable + public FeedItemFilter getItemFilter() { + return itemfilter; + } + + public void setHiddenItemProperties(String[] properties) { + if (properties != null) { + this.itemfilter = new FeedItemFilter(properties); + } + } + + public boolean hasLastUpdateFailed() { + return this.lastUpdateFailed; + } + + public void setLastUpdateFailed(boolean lastUpdateFailed) { + this.lastUpdateFailed = lastUpdateFailed; + } + } diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedEvent.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedEvent.java new file mode 100644 index 000000000..d04d236e4 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedEvent.java @@ -0,0 +1,28 @@ +package de.danoeh.antennapod.core.feed; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +public class FeedEvent { + + public enum Action { + FILTER_CHANGED + } + + public final Action action; + public final long feedId; + + public FeedEvent(Action action, long feedId) { + this.action = action; + this.feedId = feedId; + } + + @Override + public String toString() { + return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) + .append("action", action) + .append("feedId", feedId) + .toString(); + } + +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java index 4fd7a184c..11348953e 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItem.java @@ -63,6 +63,8 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr private List<Chapter> chapters; private FeedImage image; + private boolean autoDownload = true; + public FeedItem() { this.read = true; this.flattrStatus = new FlattrStatus(); @@ -74,7 +76,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr * */ public FeedItem(long id, String title, String link, Date pubDate, String paymentLink, long feedId, FlattrStatus flattrStatus, boolean hasChapters, FeedImage image, boolean read, - String itemIdentifier) { + String itemIdentifier, boolean autoDownload) { this.id = id; this.title = title; this.link = link; @@ -86,6 +88,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr this.image = image; this.read = read; this.itemIdentifier = itemIdentifier; + this.autoDownload = autoDownload; } /** @@ -236,7 +239,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr } public boolean isRead() { - return read || isInProgress(); + return read; } public void setRead(boolean read) { @@ -315,10 +318,10 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr @Override public Uri getImageUri() { - if (hasItemImageDownloaded()) { - return image.getImageUri(); - } else if (hasMedia()) { + if(media != null && media.hasEmbeddedPicture()) { return media.getImageUri(); + } else if (hasItemImageDownloaded()) { + return image.getImageUri(); } else if (feed != null) { return feed.getImageUri(); } else { @@ -327,7 +330,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr } public enum State { - NEW, IN_PROGRESS, READ, PLAYING + UNREAD, IN_PROGRESS, READ, PLAYING } public State getState() { @@ -339,7 +342,7 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr return State.IN_PROGRESS; } } - return (isRead() ? State.READ : State.NEW); + return (isRead() ? State.READ : State.UNREAD); } public long getFeedId() { @@ -388,6 +391,22 @@ public class FeedItem extends FeedComponent implements ShownotesProvider, Flattr return hasChapters; } + public void setAutoDownload(boolean autoDownload) { + this.autoDownload = autoDownload; + } + + public boolean getAutoDownload() { + return this.autoDownload; + } + + public boolean isAutoDownloadable() { + return this.hasMedia() && + false == this.getMedia().isPlaying() && + false == this.getMedia().isDownloaded() && + false == this.isRead() && + this.getAutoDownload(); + } + @Override public String toString() { return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE); diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java new file mode 100644 index 000000000..4ad084b39 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java @@ -0,0 +1,82 @@ +package de.danoeh.antennapod.core.feed; + +import android.content.Context; + +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +import de.danoeh.antennapod.core.storage.DBReader; + +public class FeedItemFilter { + + private final String[] properties; + + private boolean hideUnplayed = false; + private boolean hidePaused = false; + private boolean hidePlayed = false; + private boolean hideQueued = false; + private boolean hideNotQueued = false; + private boolean hideDownloaded = false; + private boolean hideNotDownloaded = false; + + public FeedItemFilter(String properties) { + this(StringUtils.split(properties, ',')); + } + + public FeedItemFilter(String[] properties) { + this.properties = properties; + for(String property : properties) { + // see R.arrays.feed_filter_values + switch(property) { + case "unplayed": + hideUnplayed = true; + break; + case "paused": + hidePaused = true; + break; + case "played": + hidePlayed = true; + break; + case "queued": + hideQueued = true; + break; + case "not_queued": + hideNotQueued = true; + break; + case "downloaded": + hideDownloaded = true; + break; + case "not_downloaded": + hideNotDownloaded = true; + break; + } + } + } + + public List<FeedItem> filter(Context context, List<FeedItem> items) { + if(properties.length == 0) { + return items; + } + List<FeedItem> result = new ArrayList<FeedItem>(); + for(FeedItem item : items) { + if(hideUnplayed && false == item.isRead()) continue; + if(hidePaused && item.getState() == FeedItem.State.IN_PROGRESS) continue; + if(hidePlayed && item.isRead()) continue; + boolean isQueued = DBReader.getQueueIDList(context).contains(item.getId()); + if(hideQueued && isQueued) continue; + if(hideNotQueued && false == isQueued) continue; + boolean isDownloaded = item.getMedia() != null && item.getMedia().isDownloaded(); + if(hideDownloaded && isDownloaded) continue; + if(hideNotDownloaded && false == isDownloaded) continue; + result.add(item); + } + return result; + } + + public String[] getValues() { + return properties.clone(); + } + +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java index 93f826894..f875eb812 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java @@ -2,6 +2,7 @@ package de.danoeh.antennapod.core.feed; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; +import android.media.MediaMetadataRetriever; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; @@ -34,6 +35,7 @@ public class FeedMedia extends FeedFile implements Playable { private String mime_type; private volatile FeedItem item; private Date playbackCompletionDate; + private boolean hasEmbeddedPicture; /* Used for loading item when restoring from parcel. */ private long itemID; @@ -50,6 +52,7 @@ public class FeedMedia extends FeedFile implements Playable { long size, String mime_type, String file_url, String download_url, boolean downloaded, Date playbackCompletionDate, int played_duration) { super(file_url, download_url, downloaded); + checkEmbeddedPicture(); this.id = id; this.item = item; this.duration = duration; @@ -61,12 +64,6 @@ public class FeedMedia extends FeedFile implements Playable { ? null : (Date) playbackCompletionDate.clone(); } - public FeedMedia(long id, FeedItem item) { - super(); - this.id = id; - this.item = item; - } - @Override public String getHumanReadableIdentifier() { if (item != null && item.getTitle() != null) { @@ -227,18 +224,15 @@ public class FeedMedia extends FeedFile implements Playable { return (this.position > 0); } - public FeedImage getImage() { - if (item != null) { - return (item.hasItemImageDownloaded()) ? item.getImage() : item.getFeed().getImage(); - } - return null; - } - @Override public int describeContents() { return 0; } + public boolean hasEmbeddedPicture() { + return this.hasEmbeddedPicture; + } + @Override public void writeToParcel(Parcel dest, int flags) { dest.writeLong(id); @@ -415,28 +409,45 @@ public class FeedMedia extends FeedFile implements Playable { @Override public Uri getImageUri() { - final Uri feedImgUri = getFeedImageUri(); - - if (localFileAvailable()) { + if (hasEmbeddedPicture) { Uri.Builder builder = new Uri.Builder(); - builder.scheme(SCHEME_MEDIA) - .encodedPath(getLocalMediaUrl()); - if (feedImgUri != null) { - builder.appendQueryParameter(PARAM_FALLBACK, feedImgUri.toString()); - } + builder.scheme(SCHEME_MEDIA).encodedPath(getLocalMediaUrl()); return builder.build(); - } else if (item.hasItemImageDownloaded()) { - return item.getImage().getImageUri(); } else { - return feedImgUri; + return item.getImageUri(); } } - private Uri getFeedImageUri() { - if (item != null && item.getFeed() != null) { - return item.getFeed().getImageUri(); - } else { - return null; + @Override + public void setDownloaded(boolean downloaded) { + super.setDownloaded(downloaded); + checkEmbeddedPicture(); + } + + @Override + public void setFile_url(String file_url) { + super.setFile_url(file_url); + checkEmbeddedPicture(); + } + + private void checkEmbeddedPicture() { + if (!localFileAvailable()) { + hasEmbeddedPicture = false; + return; + } + MediaMetadataRetriever mmr = new MediaMetadataRetriever(); + try { + mmr.setDataSource(getLocalMediaUrl()); + byte[] image = mmr.getEmbeddedPicture(); + if(image != null) { + hasEmbeddedPicture = true; + } + else { + hasEmbeddedPicture = false; + } + } catch (Exception e) { + e.printStackTrace(); + hasEmbeddedPicture = false; } } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/QueueEvent.java b/core/src/main/java/de/danoeh/antennapod/core/feed/QueueEvent.java index 9f1eec754..c8497f509 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/QueueEvent.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/QueueEvent.java @@ -8,7 +8,7 @@ import java.util.List; public class QueueEvent { public enum Action { - ADDED, ADDED_ITEMS, REMOVED, CLEARED, DELETED_MEDIA, SORTED + ADDED, ADDED_ITEMS, REMOVED, CLEARED, DELETED_MEDIA, SORTED, MOVED } public final Action action; diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java index db242c3bc..1a40120e2 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/GpodnetService.java @@ -75,84 +75,9 @@ public class GpodnetService { public GpodnetService() { httpClient = AntennapodHttpClient.getHttpClient(); - if (Build.VERSION.SDK_INT <= 10) { - Log.d(TAG, "Use custom SSL factory"); - SSLSocketFactory factory = getCustomSslSocketFactory(); - httpClient.setSslSocketFactory(factory); - } BASE_HOST = GpodnetPreferences.getHostname(); } - private synchronized static SSLSocketFactory getCustomSslSocketFactory() { - try { - TrustManagerFactory defaultTrustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - defaultTrustManagerFactory.init((KeyStore) null); // use system keystore - final X509TrustManager defaultTrustManager = (X509TrustManager) defaultTrustManagerFactory.getTrustManagers()[0]; - TrustManager[] customTrustManagers = new TrustManager[]{new X509TrustManager() { - @Override - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return null; - } - @Override - public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException { - } - @Override - public void checkServerTrusted(X509Certificate[] chain, String authType) - throws CertificateException { - // chain may out of order - construct data structures to walk from server certificate to root certificate - Map<Principal, X509Certificate> certificates = new HashMap<Principal, X509Certificate>(chain.length - 1); - X509Certificate subject = null; - for (X509Certificate cert : chain) { - cert.checkValidity(); - if (cert.getSubjectDN().toString().startsWith("CN=" + DEFAULT_BASE_HOST)) { - subject = cert; - } else { - certificates.put(cert.getSubjectDN(), cert); - } - } - if (subject == null) { - throw new CertificateException("Chain does not contain a certificate for " + DEFAULT_BASE_HOST); - } - // follow chain to root CA - while (certificates.get(subject.getIssuerDN()) != null) { - subject.checkValidity(); - X509Certificate issuer = certificates.get(subject.getIssuerDN()); - try { - subject.verify(issuer.getPublicKey()); - } catch (Exception e) { - Log.d(TAG, "failed: " + issuer.getSubjectDN() + " -> " + subject.getSubjectDN()); - throw new CertificateException("Could not verify certificate"); - } - subject = issuer; - } - X500Principal rootAuthority = subject.getIssuerX500Principal(); - boolean accepted = false; - for (X509Certificate cert : - defaultTrustManager.getAcceptedIssuers()) { - if (cert.getSubjectX500Principal().equals(rootAuthority)) { - try { - subject.verify(cert.getPublicKey()); - accepted = true; - } catch (Exception e) { - Log.d(TAG, "failed: " + cert.getSubjectDN() + " -> " + subject.getSubjectDN()); - throw new CertificateException("Could not verify root certificate"); - } - } - } - if (accepted == false) { - throw new CertificateException("Could not verify root certificate"); - } - } - }}; - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, customTrustManagers, null); - return sslContext.getSocketFactory(); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - /** * Returns the [count] most used tags. */ @@ -726,7 +651,12 @@ public class GpodnetService { Validate.notNull(body); ByteArrayOutputStream outputStream; - int contentLength = (int) body.contentLength(); + int contentLength = 0; + try { + contentLength = (int) body.contentLength(); + } catch (IOException ignore) { + // ignore + } if (contentLength > 0) { outputStream = new ByteArrayOutputStream(contentLength); } else { diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java index 0c431d60b..bd6210d13 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java +++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetEpisodeAction.java @@ -258,7 +258,7 @@ public class GpodnetEpisodeAction { private int total = -1; public Builder(FeedItem item, Action action) { - this(item.getFeed().getDownload_url(), item.getItemIdentifier(), action); + this(item.getFeed().getDownload_url(), item.getMedia().getDownload_url(), action); } public Builder(String podcast, String episode, Action action) { diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java index cfdd0c5d6..c3c6ce8c5 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java +++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/GpodnetPreferences.java @@ -173,7 +173,7 @@ public class GpodnetPreferences { writePreference(PREF_SYNC_REMOVED, removedFeeds); } feedListLock.unlock(); - GpodnetSyncService.sendSyncIntent(ClientConfig.applicationCallbacks.getApplicationInstance()); + GpodnetSyncService.sendSyncSubscriptionsIntent(ClientConfig.applicationCallbacks.getApplicationInstance()); } public static void addRemovedFeed(String feed) { @@ -186,7 +186,7 @@ public class GpodnetPreferences { writePreference(PREF_SYNC_ADDED, addedFeeds); } feedListLock.unlock(); - GpodnetSyncService.sendSyncIntent(ClientConfig.applicationCallbacks.getApplicationInstance()); + GpodnetSyncService.sendSyncSubscriptionsIntent(ClientConfig.applicationCallbacks.getApplicationInstance()); } public static Set<String> getAddedFeedsCopy() { @@ -225,6 +225,7 @@ public class GpodnetPreferences { ensurePreferencesLoaded(); queuedEpisodeActions.add(action); writePreference(PREF_SYNC_EPISODE_ACTIONS, writeEpisodeActionsToString(queuedEpisodeActions)); + GpodnetSyncService.sendSyncActionsIntent(ClientConfig.applicationCallbacks.getApplicationInstance()); } public static List<GpodnetEpisodeAction> getQueuedEpisodeActions() { diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java index 022c03ca7..594241573 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java +++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java @@ -16,11 +16,12 @@ import org.json.JSONException; import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.concurrent.TimeUnit; -import de.danoeh.antennapod.core.BuildConfig; import de.danoeh.antennapod.core.ClientConfig; import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.receiver.FeedUpdateReceiver; @@ -33,35 +34,52 @@ import de.danoeh.antennapod.core.receiver.FeedUpdateReceiver; */ public class UserPreferences implements SharedPreferences.OnSharedPreferenceChangeListener { + public static final String IMPORT_DIR = "import/"; + private static final String TAG = "UserPreferences"; + // User Infercasce + public static final String PREF_THEME = "prefTheme"; + public static final String PREF_HIDDEN_DRAWER_ITEMS = "prefHiddenDrawerItems"; + public static final String PREF_EXPANDED_NOTIFICATION = "prefExpandNotify"; + public static final String PREF_PERSISTENT_NOTIFICATION = "prefPersistNotify"; + + // Queue + public static final String PREF_QUEUE_ADD_TO_FRONT = "prefQueueAddToFront"; + + // Playback public static final String PREF_PAUSE_ON_HEADSET_DISCONNECT = "prefPauseOnHeadsetDisconnect"; public static final String PREF_UNPAUSE_ON_HEADSET_RECONNECT = "prefUnpauseOnHeadsetReconnect"; public static final String PREF_FOLLOW_QUEUE = "prefFollowQueue"; - public static final String PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY = "prefDownloadMediaOnWifiOnly"; - public static final String PREF_UPDATE_INTERVAL = "prefAutoUpdateIntervall"; - public static final String PREF_PARALLEL_DOWNLOADS = "prefParallelDownloads"; - public static final String PREF_MOBILE_UPDATE = "prefMobileUpdate"; - public static final String PREF_DISPLAY_ONLY_EPISODES = "prefDisplayOnlyEpisodes"; public static final String PREF_AUTO_DELETE = "prefAutoDelete"; public static final String PREF_SMART_MARK_AS_PLAYED_SECS = "prefSmartMarkAsPlayedSecs"; + private static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray"; + public static final String PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS = "prefPauseForFocusLoss"; + public static final String PREF_RESUME_AFTER_CALL = "prefResumeAfterCall"; + + // Network + public static final String PREF_UPDATE_INTERVAL = "prefAutoUpdateIntervall"; + public static final String PREF_MOBILE_UPDATE = "prefMobileUpdate"; + public static final String PREF_PARALLEL_DOWNLOADS = "prefParallelDownloads"; + public static final String PREF_EPISODE_CACHE_SIZE = "prefEpisodeCacheSize"; + public static final String PREF_ENABLE_AUTODL = "prefEnableAutoDl"; + public static final String PREF_ENABLE_AUTODL_ON_BATTERY = "prefEnableAutoDownloadOnBattery"; + public static final String PREF_ENABLE_AUTODL_WIFI_FILTER = "prefEnableAutoDownloadWifiFilter"; + public static final String PREF_AUTODL_SELECTED_NETWORKS = "prefAutodownloadSelectedNetworks"; + + // Services public static final String PREF_AUTO_FLATTR = "pref_auto_flattr"; public static final String PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD = "prefAutoFlattrPlayedDurationThreshold"; - public static final String PREF_THEME = "prefTheme"; + + // Other public static final String PREF_DATA_FOLDER = "prefDataFolder"; - public static final String PREF_ENABLE_AUTODL = "prefEnableAutoDl"; - public static final String PREF_ENABLE_AUTODL_WIFI_FILTER = "prefEnableAutoDownloadWifiFilter"; - public static final String PREF_ENABLE_AUTODL_ON_BATTERY = "prefEnableAutoDownloadOnBattery"; - private static final String PREF_AUTODL_SELECTED_NETWORKS = "prefAutodownloadSelectedNetworks"; - public static final String PREF_EPISODE_CACHE_SIZE = "prefEpisodeCacheSize"; - private static final String PREF_PLAYBACK_SPEED = "prefPlaybackSpeed"; - private static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray"; - public static final String PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS = "prefPauseForFocusLoss"; - private static final String PREF_SEEK_DELTA_SECS = "prefSeekDeltaSecs"; - private static final String PREF_EXPANDED_NOTIFICATION = "prefExpandNotify"; - private static final String PREF_PERSISTENT_NOTIFICATION = "prefPersistNotify"; - public static final String PREF_QUEUE_ADD_TO_FRONT = "prefQueueAddToFront"; + + // Mediaplayer + public static final String PREF_PLAYBACK_SPEED = "prefPlaybackSpeed"; + private static final String PREF_FAST_FORWARD_SECS = "prefFastForwardSecs"; + private static final String PREF_REWIND_SECS = "prefRewindSecs"; + public static final String PREF_QUEUE_LOCKED = "prefQueueLocked"; // TODO: Make this value configurable private static final float PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT = 0.8f; @@ -71,32 +89,45 @@ public class UserPreferences implements private static UserPreferences instance; private final Context context; - // Preferences + // User Interface + private int theme; + private List<String> hiddenDrawerItems; + private int notifyPriority; + private boolean persistNotify; + + // Queue + private boolean enqueueAtFront; + + // Playback private boolean pauseOnHeadsetDisconnect; private boolean unpauseOnHeadsetReconnect; private boolean followQueue; - private boolean downloadMediaOnWifiOnly; - private long updateInterval; - private boolean allowMobileUpdate; - private boolean displayOnlyEpisodes; private boolean autoDelete; private int smartMarkAsPlayedSecs; - private boolean autoFlattr; - private float autoFlattrPlayedDurationThreshold; - private int theme; + private String[] playbackSpeedArray; + private boolean pauseForFocusLoss; + private boolean resumeAfterCall; + + // Network + private long updateInterval; + private boolean allowMobileUpdate; + private int parallelDownloads; + private int episodeCacheSize; private boolean enableAutodownload; - private boolean enableAutodownloadWifiFilter; private boolean enableAutodownloadOnBattery; + private boolean enableAutodownloadWifiFilter; private String[] autodownloadSelectedNetworks; - private int parallelDownloads; - private int episodeCacheSize; + + // Services + private boolean autoFlattr; + private float autoFlattrPlayedDurationThreshold; + + // Settings somewhere in the GUI private String playbackSpeed; - private String[] playbackSpeedArray; - private boolean pauseForFocusLoss; - private int seekDeltaSecs; - private boolean isFreshInstall; - private int notifyPriority; - private boolean persistNotify; + private int fastForwardSecs; + private int rewindSecs; + private boolean queueLocked; + private UserPreferences(Context context) { this.context = context; @@ -109,8 +140,7 @@ public class UserPreferences implements * @throws IllegalArgumentException if context is null */ public static void createInstance(Context context) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Creating new instance of UserPreferences"); + Log.d(TAG, "Creating new instance of UserPreferences"); Validate.notNull(context); instance = new UserPreferences(context); @@ -123,48 +153,54 @@ public class UserPreferences implements } private void loadPreferences() { - SharedPreferences sp = PreferenceManager - .getDefaultSharedPreferences(context); - EPISODE_CACHE_SIZE_UNLIMITED = context.getResources().getInteger( - R.integer.episode_cache_size_unlimited); - pauseOnHeadsetDisconnect = sp.getBoolean( - PREF_PAUSE_ON_HEADSET_DISCONNECT, true); - unpauseOnHeadsetReconnect = sp.getBoolean( - PREF_UNPAUSE_ON_HEADSET_RECONNECT, true); + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); + + // User Interface + theme = readThemeValue(sp.getString(PREF_THEME, "0")); + if (sp.getBoolean(PREF_EXPANDED_NOTIFICATION, false)) { + notifyPriority = NotificationCompat.PRIORITY_MAX; + } else { + notifyPriority = NotificationCompat.PRIORITY_DEFAULT; + } + hiddenDrawerItems = Arrays.asList(StringUtils.split(sp.getString(PREF_HIDDEN_DRAWER_ITEMS, ""), ',')); + persistNotify = sp.getBoolean(PREF_PERSISTENT_NOTIFICATION, false); + + // Queue + enqueueAtFront = sp.getBoolean(PREF_QUEUE_ADD_TO_FRONT, false); + + // Playback + pauseOnHeadsetDisconnect = sp.getBoolean(PREF_PAUSE_ON_HEADSET_DISCONNECT, true); + unpauseOnHeadsetReconnect = sp.getBoolean(PREF_UNPAUSE_ON_HEADSET_RECONNECT, true); followQueue = sp.getBoolean(PREF_FOLLOW_QUEUE, false); - downloadMediaOnWifiOnly = sp.getBoolean( - PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY, true); - updateInterval = readUpdateInterval(sp.getString(PREF_UPDATE_INTERVAL, - "0")); - allowMobileUpdate = sp.getBoolean(PREF_MOBILE_UPDATE, false); - displayOnlyEpisodes = sp.getBoolean(PREF_DISPLAY_ONLY_EPISODES, false); autoDelete = sp.getBoolean(PREF_AUTO_DELETE, false); smartMarkAsPlayedSecs = Integer.valueOf(sp.getString(PREF_SMART_MARK_AS_PLAYED_SECS, "30")); - autoFlattr = sp.getBoolean(PREF_AUTO_FLATTR, false); - autoFlattrPlayedDurationThreshold = sp.getFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD, - PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT); - theme = readThemeValue(sp.getString(PREF_THEME, "0")); - enableAutodownloadWifiFilter = sp.getBoolean( - PREF_ENABLE_AUTODL_WIFI_FILTER, false); - autodownloadSelectedNetworks = StringUtils.split( - sp.getString(PREF_AUTODL_SELECTED_NETWORKS, ""), ','); + playbackSpeedArray = readPlaybackSpeedArray(sp.getString( + PREF_PLAYBACK_SPEED_ARRAY, null)); + pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false); + + // Network + updateInterval = readUpdateInterval(sp.getString(PREF_UPDATE_INTERVAL, "0")); + allowMobileUpdate = sp.getBoolean(PREF_MOBILE_UPDATE, false); parallelDownloads = Integer.valueOf(sp.getString(PREF_PARALLEL_DOWNLOADS, "6")); - episodeCacheSize = readEpisodeCacheSizeInternal(sp.getString( - PREF_EPISODE_CACHE_SIZE, "20")); + EPISODE_CACHE_SIZE_UNLIMITED = context.getResources().getInteger( + R.integer.episode_cache_size_unlimited); + episodeCacheSize = readEpisodeCacheSizeInternal(sp.getString(PREF_EPISODE_CACHE_SIZE, "20")); enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false); enableAutodownloadOnBattery = sp.getBoolean(PREF_ENABLE_AUTODL_ON_BATTERY, true); + enableAutodownloadWifiFilter = sp.getBoolean(PREF_ENABLE_AUTODL_WIFI_FILTER, false); + autodownloadSelectedNetworks = StringUtils.split( + sp.getString(PREF_AUTODL_SELECTED_NETWORKS, ""), ','); + + // Services + autoFlattr = sp.getBoolean(PREF_AUTO_FLATTR, false); + autoFlattrPlayedDurationThreshold = sp.getFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD, + PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT); + + // MediaPlayer playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0"); - playbackSpeedArray = readPlaybackSpeedArray(sp.getString( - PREF_PLAYBACK_SPEED_ARRAY, null)); - pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false); - seekDeltaSecs = Integer.valueOf(sp.getString(PREF_SEEK_DELTA_SECS, "30")); - if (sp.getBoolean(PREF_EXPANDED_NOTIFICATION, false)) { - notifyPriority = NotificationCompat.PRIORITY_MAX; - } - else { - notifyPriority = NotificationCompat.PRIORITY_DEFAULT; - } - persistNotify = sp.getBoolean(PREF_PERSISTENT_NOTIFICATION, false); + fastForwardSecs = sp.getInt(PREF_FAST_FORWARD_SECS, 30); + rewindSecs = sp.getInt(PREF_REWIND_SECS, 30); + queueLocked = sp.getBoolean(PREF_QUEUE_LOCKED, false); } private int readThemeValue(String valueFromPrefs) { @@ -214,8 +250,7 @@ public class UserPreferences implements selectedSpeeds[i] = jsonArray.getString(i); } } catch (JSONException e) { - Log.e(TAG, - "Got JSON error when trying to get speeds from JSONArray"); + Log.e(TAG, "Got JSON error when trying to get speeds from JSONArray"); e.printStackTrace(); } } @@ -224,45 +259,78 @@ public class UserPreferences implements private static void instanceAvailable() { if (instance == null) { - throw new IllegalStateException( - "UserPreferences was used before being set up"); + throw new IllegalStateException("UserPreferences was used before being set up"); } } - public static boolean isPauseOnHeadsetDisconnect() { + /** + * Returns theme as R.style value + * + * @return R.style.Theme_AntennaPod_Light or R.style.Theme_AntennaPod_Dark + */ + public static int getTheme() { instanceAvailable(); - return instance.pauseOnHeadsetDisconnect; + return instance.theme; } - public static boolean isUnpauseOnHeadsetReconnect() { + public static int getNoTitleTheme() { + int theme = getTheme(); + if (theme == R.style.Theme_AntennaPod_Dark) { + return R.style.Theme_AntennaPod_Dark_NoTitle; + } else { + return R.style.Theme_AntennaPod_Light_NoTitle; + } + } + + public static List<String> getHiddenDrawerItems() { instanceAvailable(); - return instance.unpauseOnHeadsetReconnect; + return new ArrayList<String>(instance.hiddenDrawerItems); } - public static boolean isFollowQueue() { + /** + * Returns notification priority. + * + * @return NotificationCompat.PRIORITY_MAX or NotificationCompat.PRIORITY_DEFAULT + */ + public static int getNotifyPriority() { instanceAvailable(); - return instance.followQueue; + return instance.notifyPriority; + } + + /** + * Returns true if notifications are persistent + * + * @return {@code true} if notifications are persistent, {@code false} otherwise + */ + public static boolean isPersistNotify() { + instanceAvailable(); + return instance.persistNotify; } - public static boolean isDownloadMediaOnWifiOnly() { + /** + * Returns {@code true} if new queue elements are added to the front + * + * @return {@code true} if new queue elements are added to the front; {@code false} otherwise + */ + public static boolean enqueueAtFront() { instanceAvailable(); - return instance.downloadMediaOnWifiOnly; + return instance.enqueueAtFront; } - public static long getUpdateInterval() { + public static boolean isPauseOnHeadsetDisconnect() { instanceAvailable(); - return instance.updateInterval; + return instance.pauseOnHeadsetDisconnect; } - public static boolean isAllowMobileUpdate() { + public static boolean isUnpauseOnHeadsetReconnect() { instanceAvailable(); - return instance.allowMobileUpdate; + return instance.unpauseOnHeadsetReconnect; } - public static boolean isDisplayOnlyEpisodes() { + + public static boolean isFollowQueue() { instanceAvailable(); - //return instance.displayOnlyEpisodes; - return false; + return instance.followQueue; } public static boolean isAutoDelete() { @@ -271,7 +339,7 @@ public class UserPreferences implements } public static int getSmartMarkAsPlayedSecs() { - instanceAvailable();; + instanceAvailable(); return instance.smartMarkAsPlayedSecs; } @@ -280,48 +348,29 @@ public class UserPreferences implements return instance.autoFlattr; } - public static int getNotifyPriority() { - instanceAvailable(); - return instance.notifyPriority; - } - - public static boolean isPersistNotify() { + public static String getPlaybackSpeed() { instanceAvailable(); - return instance.persistNotify; + return instance.playbackSpeed; } - - /** - * Returns the time after which an episode should be auto-flattr'd in percent of the episode's - * duration. - */ - public static float getAutoFlattrPlayedDurationThreshold() { + public static String[] getPlaybackSpeedArray() { instanceAvailable(); - return instance.autoFlattrPlayedDurationThreshold; + return instance.playbackSpeedArray; } - public static int getTheme() { + public static boolean shouldPauseForFocusLoss() { instanceAvailable(); - return instance.theme; - } - - public static int getNoTitleTheme() { - int theme = getTheme(); - if (theme == R.style.Theme_AntennaPod_Dark) { - return R.style.Theme_AntennaPod_Dark_NoTitle; - } else { - return R.style.Theme_AntennaPod_Light_NoTitle; - } + return instance.pauseForFocusLoss; } - public static boolean isEnableAutodownloadWifiFilter() { + public static long getUpdateInterval() { instanceAvailable(); - return instance.enableAutodownloadWifiFilter; + return instance.updateInterval; } - public static String[] getAutodownloadSelectedNetworks() { + public static boolean isAllowMobileUpdate() { instanceAvailable(); - return instance.autodownloadSelectedNetworks; + return instance.allowMobileUpdate; } public static int getParallelDownloads() { @@ -333,21 +382,6 @@ public class UserPreferences implements return EPISODE_CACHE_SIZE_UNLIMITED; } - public static String getPlaybackSpeed() { - instanceAvailable(); - return instance.playbackSpeed; - } - - public static String[] getPlaybackSpeedArray() { - instanceAvailable(); - return instance.playbackSpeedArray; - } - - public static int getSeekDeltaMs() { - instanceAvailable(); - return 1000 * instance.seekDeltaSecs; - } - /** * Returns the capacity of the episode cache. This method will return the * negative integer EPISODE_CACHE_SIZE_UNLIMITED if the cache size is set to @@ -368,88 +402,163 @@ public class UserPreferences implements return instance.enableAutodownloadOnBattery; } - public static boolean shouldPauseForFocusLoss() { + public static boolean isEnableAutodownloadWifiFilter() { instanceAvailable(); - return instance.pauseForFocusLoss; + return instance.enableAutodownloadWifiFilter; + } + + public static int getFastFowardSecs() { + instanceAvailable(); + return instance.fastForwardSecs; + } + + public static int getRewindSecs() { + instanceAvailable(); + return instance.rewindSecs; + } + + + /** + * Returns the time after which an episode should be auto-flattr'd in percent of the episode's + * duration. + */ + public static float getAutoFlattrPlayedDurationThreshold() { + instanceAvailable(); + return instance.autoFlattrPlayedDurationThreshold; + } + + public static String[] getAutodownloadSelectedNetworks() { + instanceAvailable(); + return instance.autodownloadSelectedNetworks; } - public static boolean isFreshInstall() { + public static boolean shouldResumeAfterCall() { instanceAvailable(); - return instance.isFreshInstall; + return instance.resumeAfterCall; + } + + public static boolean isQueueLocked() { + instanceAvailable(); + return instance.queueLocked; } @Override public void onSharedPreferenceChanged(SharedPreferences sp, String key) { Log.d(TAG, "Registered change of user preferences. Key: " + key); - - if (key.equals(PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY)) { - downloadMediaOnWifiOnly = sp.getBoolean( - PREF_DOWNLOAD_MEDIA_ON_WIFI_ONLY, true); - - } else if (key.equals(PREF_MOBILE_UPDATE)) { - allowMobileUpdate = sp.getBoolean(PREF_MOBILE_UPDATE, false); - - } else if (key.equals(PREF_FOLLOW_QUEUE)) { - followQueue = sp.getBoolean(PREF_FOLLOW_QUEUE, false); - - } else if (key.equals(PREF_UPDATE_INTERVAL)) { - updateInterval = readUpdateInterval(sp.getString( - PREF_UPDATE_INTERVAL, "0")); - ClientConfig.applicationCallbacks.setUpdateInterval(updateInterval); - } else if (key.equals(PREF_AUTO_DELETE)) { - autoDelete = sp.getBoolean(PREF_AUTO_DELETE, false); - } else if (key.equals(PREF_SMART_MARK_AS_PLAYED_SECS)) { - smartMarkAsPlayedSecs = Integer.valueOf(sp.getString(PREF_SMART_MARK_AS_PLAYED_SECS, "30")); - } else if (key.equals(PREF_AUTO_FLATTR)) { - autoFlattr = sp.getBoolean(PREF_AUTO_FLATTR, false); - } else if (key.equals(PREF_DISPLAY_ONLY_EPISODES)) { - displayOnlyEpisodes = sp.getBoolean(PREF_DISPLAY_ONLY_EPISODES, - false); - } else if (key.equals(PREF_THEME)) { - theme = readThemeValue(sp.getString(PREF_THEME, "")); - } else if (key.equals(PREF_ENABLE_AUTODL_WIFI_FILTER)) { - enableAutodownloadWifiFilter = sp.getBoolean( - PREF_ENABLE_AUTODL_WIFI_FILTER, false); - } else if (key.equals(PREF_AUTODL_SELECTED_NETWORKS)) { - autodownloadSelectedNetworks = StringUtils.split( - sp.getString(PREF_AUTODL_SELECTED_NETWORKS, ""), ','); - } else if(key.equals(PREF_PARALLEL_DOWNLOADS)) { - parallelDownloads = Integer.valueOf(sp.getString(PREF_PARALLEL_DOWNLOADS, "6")); - } else if (key.equals(PREF_EPISODE_CACHE_SIZE)) { - episodeCacheSize = readEpisodeCacheSizeInternal(sp.getString( - PREF_EPISODE_CACHE_SIZE, "20")); - } else if (key.equals(PREF_ENABLE_AUTODL)) { - enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false); - } else if (key.equals(PREF_ENABLE_AUTODL_ON_BATTERY)) { - enableAutodownloadOnBattery = sp.getBoolean(PREF_ENABLE_AUTODL_ON_BATTERY, true); - } else if (key.equals(PREF_PLAYBACK_SPEED)) { - playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0"); - } else if (key.equals(PREF_PLAYBACK_SPEED_ARRAY)) { - playbackSpeedArray = readPlaybackSpeedArray(sp.getString( - PREF_PLAYBACK_SPEED_ARRAY, null)); - } else if (key.equals(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS)) { - pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false); - } else if (key.equals(PREF_SEEK_DELTA_SECS)) { - seekDeltaSecs = Integer.valueOf(sp.getString(PREF_SEEK_DELTA_SECS, "30")); - } else if (key.equals(PREF_PAUSE_ON_HEADSET_DISCONNECT)) { - pauseOnHeadsetDisconnect = sp.getBoolean(PREF_PAUSE_ON_HEADSET_DISCONNECT, true); - } else if (key.equals(PREF_UNPAUSE_ON_HEADSET_RECONNECT)) { - unpauseOnHeadsetReconnect = sp.getBoolean(PREF_UNPAUSE_ON_HEADSET_RECONNECT, true); - } else if (key.equals(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD)) { - autoFlattrPlayedDurationThreshold = sp.getFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD, - PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT); - } else if (key.equals(PREF_EXPANDED_NOTIFICATION)) { - if (sp.getBoolean(PREF_EXPANDED_NOTIFICATION, false)) { - notifyPriority = NotificationCompat.PRIORITY_MAX; - } - else { - notifyPriority = NotificationCompat.PRIORITY_DEFAULT; - } - } else if (key.equals(PREF_PERSISTENT_NOTIFICATION)) { - persistNotify = sp.getBoolean(PREF_PERSISTENT_NOTIFICATION, false); + switch(key) { + // User Interface + case PREF_THEME: + theme = readThemeValue(sp.getString(PREF_THEME, "")); + break; + case PREF_HIDDEN_DRAWER_ITEMS: + hiddenDrawerItems = Arrays.asList(StringUtils.split(sp.getString(PREF_HIDDEN_DRAWER_ITEMS, ""), ',')); + break; + case PREF_EXPANDED_NOTIFICATION: + if (sp.getBoolean(PREF_EXPANDED_NOTIFICATION, false)) { + notifyPriority = NotificationCompat.PRIORITY_MAX; + } else { + notifyPriority = NotificationCompat.PRIORITY_DEFAULT; + } + break; + case PREF_PERSISTENT_NOTIFICATION: + persistNotify = sp.getBoolean(PREF_PERSISTENT_NOTIFICATION, false); + break; + // Queue + case PREF_QUEUE_ADD_TO_FRONT: + enqueueAtFront = sp.getBoolean(PREF_QUEUE_ADD_TO_FRONT, false); + break; + // Playback + case PREF_PAUSE_ON_HEADSET_DISCONNECT: + pauseOnHeadsetDisconnect = sp.getBoolean(PREF_PAUSE_ON_HEADSET_DISCONNECT, true); + break; + case PREF_UNPAUSE_ON_HEADSET_RECONNECT: + unpauseOnHeadsetReconnect = sp.getBoolean(PREF_UNPAUSE_ON_HEADSET_RECONNECT, true); + break; + case PREF_FOLLOW_QUEUE: + followQueue = sp.getBoolean(PREF_FOLLOW_QUEUE, false); + break; + case PREF_AUTO_DELETE: + autoDelete = sp.getBoolean(PREF_AUTO_DELETE, false); + break; + case PREF_SMART_MARK_AS_PLAYED_SECS: + smartMarkAsPlayedSecs = Integer.valueOf(sp.getString(PREF_SMART_MARK_AS_PLAYED_SECS, "30")); + break; + case PREF_PLAYBACK_SPEED_ARRAY: + playbackSpeedArray = readPlaybackSpeedArray(sp.getString(PREF_PLAYBACK_SPEED_ARRAY, null)); + break; + case PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS: + pauseForFocusLoss = sp.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false); + break; + case PREF_RESUME_AFTER_CALL: + resumeAfterCall = sp.getBoolean(PREF_RESUME_AFTER_CALL, true); + break; + // Network + case PREF_UPDATE_INTERVAL: + updateInterval = readUpdateInterval(sp.getString(PREF_UPDATE_INTERVAL, "0")); + ClientConfig.applicationCallbacks.setUpdateInterval(updateInterval); + break; + case PREF_MOBILE_UPDATE: + allowMobileUpdate = sp.getBoolean(PREF_MOBILE_UPDATE, false); + break; + case PREF_PARALLEL_DOWNLOADS: + parallelDownloads = Integer.valueOf(sp.getString(PREF_PARALLEL_DOWNLOADS, "6")); + break; + case PREF_EPISODE_CACHE_SIZE: + episodeCacheSize = readEpisodeCacheSizeInternal(sp.getString(PREF_EPISODE_CACHE_SIZE, "20")); + break; + case PREF_ENABLE_AUTODL: + enableAutodownload = sp.getBoolean(PREF_ENABLE_AUTODL, false); + break; + case PREF_ENABLE_AUTODL_ON_BATTERY: + enableAutodownloadOnBattery = sp.getBoolean(PREF_ENABLE_AUTODL_ON_BATTERY, true); + break; + case PREF_ENABLE_AUTODL_WIFI_FILTER: + enableAutodownloadWifiFilter = sp.getBoolean(PREF_ENABLE_AUTODL_WIFI_FILTER, false); + break; + case PREF_AUTODL_SELECTED_NETWORKS: + autodownloadSelectedNetworks = StringUtils.split( + sp.getString(PREF_AUTODL_SELECTED_NETWORKS, ""), ','); + break; + // Services + case PREF_AUTO_FLATTR: + autoFlattr = sp.getBoolean(PREF_AUTO_FLATTR, false); + break; + case PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD: + autoFlattrPlayedDurationThreshold = sp.getFloat(PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD, + PREF_AUTO_FLATTR_PLAYED_DURATION_THRESHOLD_DEFAULT); + break; + // Mediaplayer + case PREF_PLAYBACK_SPEED: + playbackSpeed = sp.getString(PREF_PLAYBACK_SPEED, "1.0"); + break; + case PREF_FAST_FORWARD_SECS: + fastForwardSecs = sp.getInt(PREF_FAST_FORWARD_SECS, 30); + break; + case PREF_REWIND_SECS: + rewindSecs = sp.getInt(PREF_REWIND_SECS, 30); + break; + case PREF_QUEUE_LOCKED: + queueLocked = sp.getBoolean(PREF_QUEUE_LOCKED, false); + break; + default: + Log.w(TAG, "Unhandled key: " + key); } } + public static void setPrefFastForwardSecs(int secs) { + Log.d(TAG, "setPrefFastForwardSecs(" + secs +")"); + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(instance.context).edit(); + editor.putInt(PREF_FAST_FORWARD_SECS, secs); + editor.commit(); + } + + public static void setPrefRewindSecs(int secs) { + Log.d(TAG, "setPrefRewindSecs(" + secs +")"); + SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(instance.context).edit(); + editor.putInt(PREF_REWIND_SECS, secs); + editor.commit(); + } + public static void setPlaybackSpeed(String speed) { PreferenceManager.getDefaultSharedPreferences(instance.context).edit() .putString(PREF_PLAYBACK_SPEED, speed).apply(); @@ -509,6 +618,26 @@ public class UserPreferences implements instance.autoFlattrPlayedDurationThreshold = autoFlattrThreshold; } + public static void setHiddenDrawerItems(Context context, List<String> items) { + instanceAvailable(); + instance.hiddenDrawerItems = items; + String str = StringUtils.join(items, ','); + PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()) + .edit() + .putString(PREF_HIDDEN_DRAWER_ITEMS, str) + .commit(); + } + + public static void setQueueLocked(boolean locked) { + instanceAvailable(); + instance.queueLocked = locked; + PreferenceManager.getDefaultSharedPreferences(instance.context) + .edit() + .putBoolean(PREF_QUEUE_LOCKED, locked) + .commit(); + } + + /** * Return the folder where the app stores all of its data. This method will * return the standard data folder if none has been set by the user. @@ -524,8 +653,7 @@ public class UserPreferences implements .getDefaultSharedPreferences(context.getApplicationContext()); String strDir = prefs.getString(PREF_DATA_FOLDER, null); if (strDir == null) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Using default data folder"); + Log.d(TAG, "Using default data folder"); return context.getExternalFilesDir(type); } else { File dataDir = new File(strDir); @@ -556,21 +684,18 @@ public class UserPreferences implements if (!typeDir.exists()) { if (dataDir.canWrite()) { if (!typeDir.mkdir()) { - Log.e(TAG, "Could not create data folder named " - + type); + Log.e(TAG, "Could not create data folder named " + type); return null; } } } return typeDir; } - } } public static void setDataFolder(String dir) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Result from DirectoryChooser: " + dir); + Log.d(TAG, "Result from DirectoryChooser: " + dir); instanceAvailable(); SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(instance.context); @@ -593,8 +718,7 @@ public class UserPreferences implements Log.e(TAG, "Could not create .nomedia file"); e.printStackTrace(); } - if (BuildConfig.DEBUG) - Log.d(TAG, ".nomedia file created"); + Log.d(TAG, ".nomedia file created"); } } @@ -607,16 +731,13 @@ public class UserPreferences implements IMPORT_DIR); if (importDir != null) { if (importDir.exists()) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Import directory already exists"); + Log.d(TAG, "Import directory already exists"); } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Creating import directory"); + Log.d(TAG, "Creating import directory"); importDir.mkdir(); } } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Could not access external storage."); + Log.d(TAG, "Could not access external storage."); } } @@ -625,8 +746,7 @@ public class UserPreferences implements */ public static void restartUpdateAlarm(long triggerAtMillis, long intervalMillis) { instanceAvailable(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Restarting update alarm."); + Log.d(TAG, "Restarting update alarm."); AlarmManager alarmManager = (AlarmManager) instance.context .getSystemService(Context.ALARM_SERVICE); PendingIntent updateIntent = PendingIntent.getBroadcast( @@ -635,11 +755,9 @@ public class UserPreferences implements if (intervalMillis != 0) { alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, triggerAtMillis, intervalMillis, updateIntent); - if (BuildConfig.DEBUG) - Log.d(TAG, "Changed alarm to new interval"); + Log.d(TAG, "Changed alarm to new interval"); } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Automatic update was deactivated"); + Log.d(TAG, "Automatic update was deactivated"); } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java b/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java index 95dc4fb07..d37f97a5f 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java +++ b/core/src/main/java/de/danoeh/antennapod/core/receiver/FeedUpdateReceiver.java @@ -3,41 +3,26 @@ package de.danoeh.antennapod.core.receiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; import android.util.Log; -import de.danoeh.antennapod.core.BuildConfig; -import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBTasks; +import de.danoeh.antennapod.core.util.NetworkUtils; /** * Refreshes all feeds when it receives an intent */ public class FeedUpdateReceiver extends BroadcastReceiver { + private static final String TAG = "FeedUpdateReceiver"; @Override public void onReceive(Context context, Intent intent) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Received intent"); - boolean mobileUpdate = UserPreferences.isAllowMobileUpdate(); - if (mobileUpdate || connectedToWifi(context)) { + Log.d(TAG, "Received intent"); + if (NetworkUtils.isDownloadAllowed(context)) { DBTasks.refreshExpiredFeeds(context); } else { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Blocking automatic update: no wifi available / no mobile updates allowed"); + Log.d(TAG, "Blocking automatic update: no wifi available / no mobile updates allowed"); } } - private boolean connectedToWifi(Context context) { - ConnectivityManager connManager = (ConnectivityManager) context - .getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo mWifi = connManager - .getNetworkInfo(ConnectivityManager.TYPE_WIFI); - - return mWifi.isConnected(); - } - } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java b/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java index e39197387..3f2222f42 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/GpodnetSyncService.java @@ -12,7 +12,6 @@ import android.util.Log; import android.util.Pair; import java.util.Collection; -import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -22,6 +21,7 @@ import de.danoeh.antennapod.core.ClientConfig; import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; +import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.gpoddernet.GpodnetService; import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceAuthenticationException; import de.danoeh.antennapod.core.gpoddernet.GpodnetServiceException; @@ -50,17 +50,38 @@ public class GpodnetSyncService extends Service { public static final String ARG_ACTION = "action"; public static final String ACTION_SYNC = "de.danoeh.antennapod.intent.action.sync"; + public static final String ACTION_SYNC_SUBSCRIPTIONS = "de.danoeh.antennapod.intent.action.sync_subscriptions"; + public static final String ACTION_SYNC_ACTIONS = "de.danoeh.antennapod.intent.action.sync_ACTIONS"; private GpodnetService service; + private boolean syncSubscriptions = false; + private boolean syncActions = false; + @Override public int onStartCommand(Intent intent, int flags, int startId) { final String action = (intent != null) ? intent.getStringExtra(ARG_ACTION) : null; - if (action != null && action.equals(ACTION_SYNC)) { - Log.d(TAG, String.format("Waiting %d milliseconds before uploading changes", WAIT_INTERVAL)); - syncWaiterThread.restart(); + if (action != null) { + switch(action) { + case ACTION_SYNC: + syncSubscriptions = true; + syncActions = true; + break; + case ACTION_SYNC_SUBSCRIPTIONS: + syncSubscriptions = true; + break; + case ACTION_SYNC_ACTIONS: + syncActions = true; + break; + default: + Log.e(TAG, "Received invalid intent: action argument is invalid"); + } + if(syncSubscriptions || syncActions) { + Log.d(TAG, String.format("Waiting %d milliseconds before uploading changes", WAIT_INTERVAL)); + syncWaiterThread.restart(); + } } else { - Log.e(TAG, "Received invalid intent: action argument is null or invalid"); + Log.e(TAG, "Received invalid intent: action argument is null"); } return START_FLAG_REDELIVERY; } @@ -91,8 +112,14 @@ public class GpodnetSyncService extends Service { stopSelf(); return; } - syncSubscriptionChanges(); - syncEpisodeActions(); + if(syncSubscriptions) { + syncSubscriptionChanges(); + syncSubscriptions = false; + } + if(syncActions) { + syncEpisodeActions(); + syncActions = false; + } stopSelf(); } @@ -100,37 +127,36 @@ public class GpodnetSyncService extends Service { final long timestamp = GpodnetPreferences.getLastSubscriptionSyncTimestamp(); try { final List<String> localSubscriptions = DBReader.getFeedListDownloadUrls(this); + Collection<String> localAdded = GpodnetPreferences.getAddedFeedsCopy(); + Collection<String> localRemoved = GpodnetPreferences.getRemovedFeedsCopy(); GpodnetService service = tryLogin(); // first sync: download all subscriptions... GpodnetSubscriptionChange subscriptionChanges = service.getSubscriptionChanges(GpodnetPreferences.getUsername(), GpodnetPreferences.getDeviceID(), timestamp); - long lastUpdate = subscriptionChanges.getTimestamp(); + long newTimeStamp = subscriptionChanges.getTimestamp(); Log.d(TAG, "Downloaded subscription changes: " + subscriptionChanges); - processSubscriptionChanges(localSubscriptions, subscriptionChanges); - - Collection<String> added; - Collection<String> removed; - if (timestamp == 0) { - added = localSubscriptions; - GpodnetPreferences.removeRemovedFeeds(GpodnetPreferences.getRemovedFeedsCopy()); - removed = Collections.emptyList(); - } else { - added = GpodnetPreferences.getAddedFeedsCopy(); - removed = GpodnetPreferences.getRemovedFeedsCopy(); + processSubscriptionChanges(localSubscriptions, localAdded, localRemoved, subscriptionChanges); + + if(timestamp == 0) { + // this is this apps first sync with gpodder: + // only submit changes gpodder has not just sent us + localAdded = localSubscriptions; + localAdded.removeAll(subscriptionChanges.getAdded()); + localRemoved.removeAll(subscriptionChanges.getRemoved()); } - if(added.size() > 0 || removed.size() > 0) { + if(localAdded.size() > 0 || localRemoved.size() > 0) { Log.d(TAG, String.format("Uploading subscriptions, Added: %s\nRemoved: %s", - added, removed)); + localAdded, localRemoved)); GpodnetUploadChangesResponse uploadResponse = service.uploadChanges(GpodnetPreferences.getUsername(), - GpodnetPreferences.getDeviceID(), added, removed); - lastUpdate = uploadResponse.timestamp; + GpodnetPreferences.getDeviceID(), localAdded, localRemoved); + newTimeStamp = uploadResponse.timestamp; Log.d(TAG, "Upload changes response: " + uploadResponse); - GpodnetPreferences.removeAddedFeeds(added); - GpodnetPreferences.removeRemovedFeeds(removed); + GpodnetPreferences.removeAddedFeeds(localAdded); + GpodnetPreferences.removeRemovedFeeds(localRemoved); } - GpodnetPreferences.setLastSubscriptionSyncTimestamp(lastUpdate); + GpodnetPreferences.setLastSubscriptionSyncTimestamp(newTimeStamp); clearErrorNotifications(); } catch (GpodnetServiceException e) { e.printStackTrace(); @@ -140,6 +166,27 @@ public class GpodnetSyncService extends Service { } } + private synchronized void processSubscriptionChanges(List<String> localSubscriptions, + Collection<String> localAdded, + Collection<String> localRemoved, + GpodnetSubscriptionChange changes) throws DownloadRequestException { + // local changes are always superior to remote changes! + // add subscription if (1) not already subscribed and (2) not just unsubscribed + for (String downloadUrl : changes.getAdded()) { + if (false == localSubscriptions.contains(downloadUrl) && + false == localRemoved.contains(downloadUrl)) { + Feed feed = new Feed(downloadUrl, new Date(0)); + DownloadRequester.getInstance().downloadFeed(this, feed); + } + } + // remove subscription if not just subscribed (again) + for (String downloadUrl : changes.getRemoved()) { + if(false == localAdded.contains(downloadUrl)) { + DBTasks.removeFeedWithDownloadUrl(GpodnetSyncService.this, downloadUrl); + } + } + } + private synchronized void syncEpisodeActions() { final long timestamp = GpodnetPreferences.getLastEpisodeActionsSyncTimestamp(); Log.d(TAG, "last episode actions sync timestamp: " + timestamp); @@ -173,25 +220,13 @@ public class GpodnetSyncService extends Service { } } - private synchronized void processSubscriptionChanges(List<String> localSubscriptions, GpodnetSubscriptionChange changes) throws DownloadRequestException { - for (String downloadUrl : changes.getAdded()) { - if (!localSubscriptions.contains(downloadUrl)) { - Feed feed = new Feed(downloadUrl, new Date()); - DownloadRequester.getInstance().downloadFeed(this, feed); - } - } - for (String downloadUrl : changes.getRemoved()) { - DBTasks.removeFeedWithDownloadUrl(GpodnetSyncService.this, downloadUrl); - } - } - private synchronized void processEpisodeActions(List<GpodnetEpisodeAction> localActions, List<GpodnetEpisodeAction> remoteActions) throws DownloadRequestException { + private synchronized void processEpisodeActions(List<GpodnetEpisodeAction> localActions, + List<GpodnetEpisodeAction> remoteActions) throws DownloadRequestException { if(remoteActions.size() == 0) { return; } Map<Pair<String, String>, GpodnetEpisodeAction> localMostRecentPlayAction = new HashMap<Pair<String, String>, GpodnetEpisodeAction>(); - Map<Pair<String, String>, GpodnetEpisodeAction> remoteMostRecentPlayAction = new HashMap<Pair<String, String>, GpodnetEpisodeAction>(); - // make sure more recent local actions are not overwritten by older remote actions for(GpodnetEpisodeAction action : localActions) { Pair key = new Pair(action.getPodcast(), action.getEpisode()); GpodnetEpisodeAction mostRecent = localMostRecentPlayAction.get(key); @@ -201,6 +236,9 @@ public class GpodnetSyncService extends Service { localMostRecentPlayAction.put(key, action); } } + + // make sure more recent local actions are not overwritten by older remote actions + Map<Pair<String, String>, GpodnetEpisodeAction> mostRecentPlayAction = new HashMap<Pair<String, String>, GpodnetEpisodeAction>(); for (GpodnetEpisodeAction action : remoteActions) { switch (action.getAction()) { case NEW: @@ -218,11 +256,11 @@ public class GpodnetSyncService extends Service { GpodnetEpisodeAction localMostRecent = localMostRecentPlayAction.get(key); if(localMostRecent == null || localMostRecent.getTimestamp().before(action.getTimestamp())) { - GpodnetEpisodeAction mostRecent = remoteMostRecentPlayAction.get(key); + GpodnetEpisodeAction mostRecent = mostRecentPlayAction.get(key); if (mostRecent == null) { - remoteMostRecentPlayAction.put(key, action); + mostRecentPlayAction.put(key, action); } else if (mostRecent.getTimestamp().before(action.getTimestamp())) { - remoteMostRecentPlayAction.put(key, action); + mostRecentPlayAction.put(key, action); } } break; @@ -231,10 +269,12 @@ public class GpodnetSyncService extends Service { break; } } - for (GpodnetEpisodeAction action : remoteMostRecentPlayAction.values()) { + for (GpodnetEpisodeAction action : mostRecentPlayAction.values()) { FeedItem playItem = DBReader.getFeedItem(this, action.getPodcast(), action.getEpisode()); if (playItem != null) { - playItem.getMedia().setPosition(action.getPosition() * 1000); + FeedMedia media = playItem.getMedia(); + media.setPosition(action.getPosition() * 1000); + DBWriter.setFeedMedia(this, media); if(playItem.getMedia().hasAlmostEnded()) { DBWriter.markItemRead(this, playItem, true, true); DBWriter.addItemToPlaybackHistory(this, playItem.getMedia()); @@ -342,4 +382,20 @@ public class GpodnetSyncService extends Service { context.startService(intent); } } + + public static void sendSyncSubscriptionsIntent(Context context) { + if (GpodnetPreferences.loggedIn()) { + Intent intent = new Intent(context, GpodnetSyncService.class); + intent.putExtra(ARG_ACTION, ACTION_SYNC_SUBSCRIPTIONS); + context.startService(intent); + } + } + + public static void sendSyncActionsIntent(Context context) { + if (GpodnetPreferences.loggedIn()) { + Intent intent = new Intent(context, GpodnetSyncService.class); + intent.putExtra(ARG_ACTION, ACTION_SYNC_ACTIONS); + context.startService(intent); + } + } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java index d5f17c099..e7b226eca 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java @@ -520,9 +520,7 @@ public class DownloadService extends Service { /** * Creates a notification at the end of the service lifecycle to notify the * user about the number of completed downloads. A report will only be - * created if the number of successfully downloaded feeds is bigger than 1 - * or if there is at least one failed download which is not an image or if - * there is at least one downloaded media file. + * created if there is at least one failed download excluding images */ private void updateReport() { // check if report should be created @@ -550,16 +548,16 @@ public class DownloadService extends Service { .setTicker( getString(R.string.download_report_title)) .setContentTitle( - getString(R.string.download_report_title)) + getString(R.string.download_report_content_title)) .setContentText( String.format( getString(R.string.download_report_content), successfulDownloads, failedDownloads) ) - .setSmallIcon(R.drawable.stat_notify_sync) + .setSmallIcon(R.drawable.stat_notify_sync_error) .setLargeIcon( BitmapFactory.decodeResource(getResources(), - R.drawable.stat_notify_sync) + R.drawable.stat_notify_sync_error) ) .setContentIntent( ClientConfig.downloadServiceCallbacks.getReportNotificationContentIntent(this) @@ -939,6 +937,13 @@ public class DownloadService extends Service { if (successful) { + // we create a 'successful' download log if the feed's last refresh failed + List<DownloadStatus> log = DBReader.getFeedDownloadLog(DownloadService.this, feed); + if(log.size() > 0 && log.get(0).isSuccessful() == false) { + saveDownloadStatus(new DownloadStatus(feed, + feed.getHumanReadableIdentifier(), DownloadError.SUCCESS, successful, + reasonDetailed)); + } return Pair.create(request, result); } else { numberOfDownloads.decrementAndGet(); @@ -1055,9 +1060,11 @@ public class DownloadService extends Service { @Override public void run() { - if (request.isDeleteOnFailure()) { + if(request.getFeedfileType() == Feed.FEEDFILETYPE_FEED) { + DBWriter.setFeedLastUpdateFailed(DownloadService.this, request.getFeedfileId(), true); + } else if (request.isDeleteOnFailure()) { Log.d(TAG, "Ignoring failed download, deleteOnFailure=true"); - } else { + } else { File dest = new File(request.getDestination()); if (dest.exists() && request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { Log.d(TAG, "File has been partially downloaded. Writing file url"); diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java index 7abb6df5e..ac0fe8036 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java @@ -2,7 +2,6 @@ package de.danoeh.antennapod.core.service.download; import android.util.Log; -import com.squareup.okhttp.Credentials; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; import com.squareup.okhttp.Response; @@ -18,19 +17,20 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; +import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.SocketTimeoutException; import java.net.URI; import java.net.UnknownHostException; import java.util.Date; -import de.danoeh.antennapod.core.BuildConfig; import de.danoeh.antennapod.core.ClientConfig; import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.feed.FeedImage; import de.danoeh.antennapod.core.util.DownloadError; import de.danoeh.antennapod.core.util.StorageUtils; import de.danoeh.antennapod.core.util.URIUtil; +import okio.ByteString; public class HttpDownloader extends Downloader { private static final String TAG = "HttpDownloader"; @@ -81,11 +81,12 @@ public class HttpDownloader extends Downloader { if (userInfo != null) { String[] parts = userInfo.split(":"); if (parts.length == 2) { - String credentials = Credentials.basic(parts[0], parts[1]); + String credentials = encodeCredentials(parts[0], parts[1], "ISO-8859-1"); httpReq.header("Authorization", credentials); } } else if (!StringUtils.isEmpty(request.getUsername()) && request.getPassword() != null) { - String credentials = Credentials.basic(request.getUsername(), request.getPassword()); + String credentials = encodeCredentials(request.getUsername(), request.getPassword(), + "ISO-8859-1"); httpReq.header("Authorization", credentials); } @@ -99,13 +100,29 @@ public class HttpDownloader extends Downloader { Response response = httpClient.newCall(httpReq.build()).execute(); responseBody = response.body(); - String contentEncodingHeader = response.header("Content-Encoding"); - - final boolean isGzip = StringUtils.equalsIgnoreCase(contentEncodingHeader, "gzip"); - - if (BuildConfig.DEBUG) - Log.d(TAG, "Response code is " + response.code()); + boolean isGzip = StringUtils.equalsIgnoreCase(contentEncodingHeader, "gzip"); + + Log.d(TAG, "Response code is " + response.code()); + + if(!response.isSuccessful() && response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) { + Log.d(TAG, "Authorization failed, re-trying with UTF-8 encoding"); + if (userInfo != null) { + String[] parts = userInfo.split(":"); + if (parts.length == 2) { + String credentials = encodeCredentials(parts[0], parts[1], "UTF-8"); + httpReq.header("Authorization", credentials); + } + } else if (!StringUtils.isEmpty(request.getUsername()) && request.getPassword() != null) { + String credentials = encodeCredentials(request.getUsername(), request.getPassword(), + "UTF-8"); + httpReq.header("Authorization", credentials); + } + response = httpClient.newCall(httpReq.build()).execute(); + responseBody = response.body(); + contentEncodingHeader = response.header("Content-Encoding"); + isGzip = StringUtils.equalsIgnoreCase(contentEncodingHeader, "gzip"); + } if(!response.isSuccessful() && response.code() == HttpURLConnection.HTTP_NOT_MODIFIED) { Log.d(TAG, "Feed '" + request.getSource() + "' not modified since last update, Download canceled"); @@ -151,22 +168,18 @@ public class HttpDownloader extends Downloader { out = new RandomAccessFile(destination, "rw"); } - byte[] buffer = new byte[BUFFER_SIZE]; int count = 0; request.setStatusMsg(R.string.download_running); - if (BuildConfig.DEBUG) - Log.d(TAG, "Getting size of download"); + Log.d(TAG, "Getting size of download"); request.setSize(responseBody.contentLength() + request.getSoFar()); - if (BuildConfig.DEBUG) - Log.d(TAG, "Size is " + request.getSize()); + Log.d(TAG, "Size is " + request.getSize()); if (request.getSize() < 0) { request.setSize(DownloadStatus.SIZE_UNKNOWN); } long freeSpace = StorageUtils.getFreeSpaceAvailable(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Free space is " + freeSpace); + Log.d(TAG, "Free space is " + freeSpace); if (request.getSize() != DownloadStatus.SIZE_UNKNOWN && request.getSize() > freeSpace) { @@ -174,8 +187,7 @@ public class HttpDownloader extends Downloader { return; } - if (BuildConfig.DEBUG) - Log.d(TAG, "Starting download"); + Log.d(TAG, "Starting download"); while (!cancelled && (count = connection.read(buffer)) != -1) { out.write(buffer, 0, count); @@ -226,15 +238,12 @@ public class HttpDownloader extends Downloader { } private void onSuccess() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Download was successful"); + Log.d(TAG, "Download was successful"); result.setSuccessful(); } private void onFail(DownloadError reason, String reasonDetailed) { - if (BuildConfig.DEBUG) { - Log.d(TAG, "Download failed"); - } + Log.d(TAG, "Download failed"); result.setFailed(reason, reasonDetailed); if (request.isDeleteOnFailure()) { cleanup(); @@ -242,8 +251,7 @@ public class HttpDownloader extends Downloader { } private void onCancelled() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Download was cancelled"); + Log.d(TAG, "Download was cancelled"); result.setCancelled(); cleanup(); } @@ -256,14 +264,23 @@ public class HttpDownloader extends Downloader { File dest = new File(request.getDestination()); if (dest.exists()) { boolean rc = dest.delete(); - if (BuildConfig.DEBUG) - Log.d(TAG, "Deleted file " + dest.getName() + "; Result: " + Log.d(TAG, "Deleted file " + dest.getName() + "; Result: " + rc); } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "cleanup() didn't delete file: does not exist."); + Log.d(TAG, "cleanup() didn't delete file: does not exist."); } } } + public static String encodeCredentials(String username, String password, String charset) { + try { + String credentials = username + ":" + password; + byte[] bytes = credentials.getBytes(charset); + String encoded = ByteString.of(bytes).base64(); + return "Basic " + encoded; + } catch (UnsupportedEncodingException e) { + throw new AssertionError(); + } + } + } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java index 43c345fec..3f6769ee4 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java @@ -254,6 +254,8 @@ public class PlaybackService extends Service { unregisterReceiver(bluetoothStateUpdated); unregisterReceiver(audioBecomingNoisy); unregisterReceiver(skipCurrentEpisodeReceiver); + unregisterReceiver(pausePlayCurrentEpisodeReceiver); + unregisterReceiver(pauseResumeCurrentEpisodeReceiver); mediaPlayer.shutdown(); taskManager.shutdown(); } @@ -344,11 +346,11 @@ public class PlaybackService extends Service { break; case KeyEvent.KEYCODE_MEDIA_NEXT: case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: - mediaPlayer.seekDelta(UserPreferences.getSeekDeltaMs()); + mediaPlayer.seekDelta(UserPreferences.getFastFowardSecs() * 1000); break; case KeyEvent.KEYCODE_MEDIA_PREVIOUS: case KeyEvent.KEYCODE_MEDIA_REWIND: - mediaPlayer.seekDelta(-UserPreferences.getSeekDeltaMs()); + mediaPlayer.seekDelta(-UserPreferences.getRewindSecs() * 1000); break; case KeyEvent.KEYCODE_MEDIA_STOP: if (status == PlayerStatus.PLAYING) { @@ -481,9 +483,8 @@ public class PlaybackService extends Service { } Intent statusUpdate = new Intent(ACTION_PLAYER_STATUS_CHANGED); - statusUpdate.putExtra(EXTRA_NEW_PLAYER_STATUS, newInfo.playerStatus.ordinal()); + // statusUpdate.putExtra(EXTRA_NEW_PLAYER_STATUS, newInfo.playerStatus.ordinal()); sendBroadcast(statusUpdate); - sendBroadcast(new Intent(ACTION_PLAYER_STATUS_CHANGED)); updateWidget(); refreshRemoteControlClientState(newInfo); bluetoothNotifyChange(newInfo, AVRCP_ACTION_PLAYER_STATUS_CHANGED); @@ -626,7 +627,6 @@ public class PlaybackService extends Service { prepareImmediately = startWhenPrepared = true; } else { Log.d(TAG, "No more episodes available to play"); - prepareImmediately = startWhenPrepared = false; stopForeground(true); stopWidgetUpdater(); @@ -933,7 +933,6 @@ public class PlaybackService extends Service { // Auto flattr if (isAutoFlattrable(media) && (media.getPlayedDuration() > UserPreferences.getAutoFlattrPlayedDurationThreshold() * duration)) { - Log.d(TAG, "saveCurrentPosition: performing auto flattr since played duration " + Integer.toString(media.getPlayedDuration()) + " is " + UserPreferences.getAutoFlattrPlayedDurationThreshold() * 100 + "% of file duration " + Integer.toString(duration)); DBTasks.flattrItemIfLoggedIn(this, item); diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java index f0acc3531..243ee78e4 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java @@ -40,7 +40,7 @@ import de.danoeh.antennapod.core.util.playback.VideoPlayer; * Manages the MediaPlayer object of the PlaybackService. */ public class PlaybackServiceMediaPlayer { - public static final String TAG = "PlaybackServiceMediaPlayer"; + public static final String TAG = "PlaybackSvcMediaPlayer"; /** * Return value of some PSMP methods if the method call failed. @@ -449,15 +449,15 @@ public class PlaybackServiceMediaPlayer { if (playerStatus == PlayerStatus.PLAYING || playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PREPARED) { - if (stream) { - // statusBeforeSeeking = playerStatus; - // setPlayerStatus(PlayerStatus.SEEKING, media); + if (!stream) { + statusBeforeSeeking = playerStatus; + setPlayerStatus(PlayerStatus.SEEKING, media); } mediaPlayer.seekTo(t); } else if (playerStatus == PlayerStatus.INITIALIZED) { media.setPosition(t); - startWhenPrepared.set(true); + startWhenPrepared.set(false); prepare(); } playerLock.unlock(); @@ -534,20 +534,20 @@ public class PlaybackServiceMediaPlayer { * Returns the position of the current media object or INVALID_TIME if the position could not be retrieved. */ public int getPosition() { - if (!playerLock.tryLock()) { - return INVALID_TIME; - } + playerLock.lock(); int retVal = INVALID_TIME; if (playerStatus == PlayerStatus.PLAYING || playerStatus == PlayerStatus.PAUSED - || playerStatus == PlayerStatus.PREPARED) { + || playerStatus == PlayerStatus.PREPARED + || playerStatus == PlayerStatus.SEEKING) { retVal = mediaPlayer.getCurrentPosition(); } else if (media != null && media.getPosition() > 0) { retVal = media.getPosition(); } playerLock.unlock(); + Log.d(TAG, "getPosition() -> " + retVal); return retVal; } @@ -735,6 +735,7 @@ public class PlaybackServiceMediaPlayer { int state; if (playerStatus != null) { + Log.d(TAG, "playerStatus: " + playerStatus.toString()); switch (playerStatus) { case PLAYING: state = PlaybackStateCompat.STATE_PLAYING; @@ -798,10 +799,10 @@ public class PlaybackServiceMediaPlayer { // If there is an incoming call, playback should be paused permanently TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); final int callState = (tm != null) ? tm.getCallState() : 0; - Log.d(TAG, "Call state: " + callState); Log.i(TAG, "Call state:" + callState); - if (focusChange == AudioManager.AUDIOFOCUS_LOSS || callState != TelephonyManager.CALL_STATE_IDLE) { + if (focusChange == AudioManager.AUDIOFOCUS_LOSS || + (!UserPreferences.shouldResumeAfterCall() && callState != TelephonyManager.CALL_STATE_IDLE)) { Log.d(TAG, "Lost audio focus"); pause(true, false); callback.shouldStop(); @@ -1095,13 +1096,13 @@ public class PlaybackServiceMediaPlayer { @Override public void onFastForward() { super.onFastForward(); - seekDelta(UserPreferences.getSeekDeltaMs()); + seekDelta(UserPreferences.getFastFowardSecs() * 1000); } @Override public void onRewind() { super.onRewind(); - seekDelta(-UserPreferences.getSeekDeltaMs()); + seekDelta(-UserPreferences.getRewindSecs() * 1000); } @Override diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java index cde03adea..fc73c9446 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java @@ -19,8 +19,10 @@ import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.QueueEvent; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.util.playback.Playable; + import de.greenrobot.event.EventBus; + /** * Manages the background tasks of PlaybackSerivce, i.e. * the sleep timer, the position saver, the widget updater and @@ -147,9 +149,9 @@ public class PlaybackServiceTaskManager { positionSaverFuture = schedExecutor.scheduleWithFixedDelay(positionSaver, POSITION_SAVER_WAITING_INTERVAL, POSITION_SAVER_WAITING_INTERVAL, TimeUnit.MILLISECONDS); - if (BuildConfig.DEBUG) Log.d(TAG, "Started PositionSaver"); + Log.d(TAG, "Started PositionSaver"); } else { - if (BuildConfig.DEBUG) Log.d(TAG, "Call to startPositionSaver was ignored."); + Log.d(TAG, "Call to startPositionSaver was ignored."); } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java index de6c02de7..f647fd537 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java @@ -41,10 +41,10 @@ public class APCleanupAlgorithm implements EpisodeCleanupAlgorithm<Integer> { Date r = rhs.getMedia().getPlaybackCompletionDate(); if (l == null) { - l = new Date(0); + l = new Date(); } if (r == null) { - r = new Date(0); + r = new Date(); } return l.compareTo(r); } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java index c5f871f48..92de1eee7 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java @@ -4,10 +4,9 @@ import android.content.Context; import android.util.Log; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Iterator; import java.util.List; -import de.danoeh.antennapod.core.BuildConfig; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.util.NetworkUtils; @@ -53,75 +52,53 @@ public class APDownloadAlgorithm implements AutomaticDownloadAlgorithm { Log.d(TAG, "Performing auto-dl of undownloaded episodes"); - final List<FeedItem> queue = DBReader.getQueue(context); - final List<FeedItem> unreadItems = DBReader - .getUnreadItemsList(context); + List<FeedItem> candidates; + if(mediaIds.length > 0) { + candidates = DBReader.getFeedItems(context, mediaIds); + } else { + final List<FeedItem> queue = DBReader.getQueue(context); + final List<FeedItem> unreadItems = DBReader.getUnreadItemsList(context); + candidates = new ArrayList<FeedItem>(queue.size() + unreadItems.size()); + candidates.addAll(queue); + for(FeedItem unreadItem : unreadItems) { + if(candidates.contains(unreadItem) == false) { + candidates.add(unreadItem); + } + } + } - int undownloadedEpisodes = DBTasks.getNumberOfUndownloadedEpisodes(queue, - unreadItems); - int downloadedEpisodes = DBReader - .getNumberOfDownloadedEpisodes(context); + // filter items that are not auto downloadable + Iterator<FeedItem> it = candidates.iterator(); + while(it.hasNext()) { + FeedItem item = it.next(); + if(item.isAutoDownloadable() == false) { + it.remove(); + } + } + + int autoDownloadableEpisodes = candidates.size(); + int downloadedEpisodes = DBReader.getNumberOfDownloadedEpisodes(context); int deletedEpisodes = cleanupAlgorithm.performCleanup(context, - APCleanupAlgorithm.getPerformAutoCleanupArgs(context, undownloadedEpisodes)); - int episodeSpaceLeft = undownloadedEpisodes; + APCleanupAlgorithm.getPerformAutoCleanupArgs(context, autoDownloadableEpisodes)); boolean cacheIsUnlimited = UserPreferences.getEpisodeCacheSize() == UserPreferences .getEpisodeCacheSizeUnlimited(); - - if (!cacheIsUnlimited - && UserPreferences.getEpisodeCacheSize() < downloadedEpisodes - + undownloadedEpisodes) { - episodeSpaceLeft = UserPreferences.getEpisodeCacheSize() - - (downloadedEpisodes - deletedEpisodes); + int episodeCacheSize = UserPreferences.getEpisodeCacheSize(); + + int episodeSpaceLeft; + if (cacheIsUnlimited || + episodeCacheSize >= downloadedEpisodes + autoDownloadableEpisodes) { + episodeSpaceLeft = autoDownloadableEpisodes; + } else { + episodeSpaceLeft = episodeCacheSize - (downloadedEpisodes - deletedEpisodes); } - Arrays.sort(mediaIds); // sort for binary search - final boolean ignoreMediaIds = mediaIds.length == 0; - List<FeedItem> itemsToDownload = new ArrayList<FeedItem>(); - - if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) { - for (int i = 0; i < queue.size(); i++) { // ignore playing item - FeedItem item = queue.get(i); - long mediaId = (item.hasMedia()) ? item.getMedia().getId() : -1; - if ((ignoreMediaIds || Arrays.binarySearch(mediaIds, mediaId) >= 0) - && item.hasMedia() - && !item.getMedia().isDownloaded() - && !item.getMedia().isPlaying() - && item.getFeed().getPreferences().getAutoDownload()) { - itemsToDownload.add(item); - episodeSpaceLeft--; - undownloadedEpisodes--; - if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) { - break; - } - } - } - } + FeedItem[] itemsToDownload = candidates.subList(0, episodeSpaceLeft) + .toArray(new FeedItem[episodeSpaceLeft]); - if (episodeSpaceLeft > 0 && undownloadedEpisodes > 0) { - for (FeedItem item : unreadItems) { - long mediaId = (item.hasMedia()) ? item.getMedia().getId() : -1; - if ((ignoreMediaIds || Arrays.binarySearch(mediaIds, mediaId) >= 0) - && item.hasMedia() - && !item.getMedia().isDownloaded() - && item.getFeed().getPreferences().getAutoDownload()) { - itemsToDownload.add(item); - episodeSpaceLeft--; - undownloadedEpisodes--; - if (episodeSpaceLeft == 0 || undownloadedEpisodes == 0) { - break; - } - } - } - } - if (BuildConfig.DEBUG) - Log.d(TAG, "Enqueueing " + itemsToDownload.size() - + " items for download"); + Log.d(TAG, "Enqueueing " + itemsToDownload.length + " items for download"); try { - DBTasks.downloadFeedItems(false, context, - itemsToDownload.toArray(new FeedItem[itemsToDownload - .size()]) - ); + DBTasks.downloadFeedItems(false, context, itemsToDownload); } catch (DownloadRequestException e) { e.printStackTrace(); } @@ -130,4 +107,5 @@ public class APDownloadAlgorithm implements AutomaticDownloadAlgorithm { } }; } + } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java index a7c98c7c6..cc20b3d37 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java @@ -4,8 +4,11 @@ import android.content.Context; import android.database.Cursor; import android.util.Log; +import org.apache.commons.lang3.StringUtils; + import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.List; @@ -21,6 +24,7 @@ import de.danoeh.antennapod.core.feed.SimpleChapter; import de.danoeh.antennapod.core.feed.VorbisCommentChapter; import de.danoeh.antennapod.core.service.download.DownloadStatus; import de.danoeh.antennapod.core.util.DownloadError; +import de.danoeh.antennapod.core.util.LongIntMap; import de.danoeh.antennapod.core.util.LongList; import de.danoeh.antennapod.core.util.comparator.DownloadStatusComparator; import de.danoeh.antennapod.core.util.comparator.FeedItemPubdateComparator; @@ -174,8 +178,7 @@ public final class DBReader { */ public static List<FeedItem> getFeedItemList(Context context, final Feed feed) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Extracting Feeditems of feed " + feed.getTitle()); + Log.d(TAG, "Extracting Feeditems of feed " + feed.getTitle()); PodDBAdapter adapter = new PodDBAdapter(context); adapter.open(); @@ -228,7 +231,9 @@ public final class DBReader { itemlistCursor.getInt(PodDBAdapter.IDX_FI_SMALL_HAS_CHAPTERS) > 0, image, (itemlistCursor.getInt(PodDBAdapter.IDX_FI_SMALL_READ) > 0), - itemlistCursor.getString(PodDBAdapter.IDX_FI_SMALL_ITEM_IDENTIFIER)); + itemlistCursor.getString(PodDBAdapter.IDX_FI_SMALL_ITEM_IDENTIFIER), + itemlistCursor.getInt(itemlistCursor.getColumnIndex(PodDBAdapter.KEY_AUTO_DOWNLOAD)) > 0 + ); itemIds.add(String.valueOf(item.getId())); @@ -312,7 +317,10 @@ public final class DBReader { cursor.getInt(PodDBAdapter.IDX_FEED_SEL_STD_DOWNLOADED) > 0, new FlattrStatus(cursor.getLong(PodDBAdapter.IDX_FEED_SEL_STD_FLATTR_STATUS)), cursor.getInt(PodDBAdapter.IDX_FEED_SEL_STD_IS_PAGED) > 0, - cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_NEXT_PAGE_LINK)); + cursor.getString(PodDBAdapter.IDX_FEED_SEL_STD_NEXT_PAGE_LINK), + cursor.getString(cursor.getColumnIndex(PodDBAdapter.KEY_HIDE)), + cursor.getInt(cursor.getColumnIndex(PodDBAdapter.KEY_LAST_UPDATE_FAILED)) > 0 + ); if (image != null) { image.setOwner(feed); @@ -327,6 +335,21 @@ public final class DBReader { return feed; } + private static DownloadStatus extractDownloadStatusFromCursorRow(final Cursor cursor) { + long id = cursor.getLong(PodDBAdapter.KEY_ID_INDEX); + long feedfileId = cursor.getLong(PodDBAdapter.KEY_FEEDFILE_INDEX); + int feedfileType = cursor.getInt(PodDBAdapter.KEY_FEEDFILETYPE_INDEX); + boolean successful = cursor.getInt(PodDBAdapter.KEY_SUCCESSFUL_INDEX) > 0; + int reason = cursor.getInt(PodDBAdapter.KEY_REASON_INDEX); + String reasonDetailed = cursor.getString(PodDBAdapter.KEY_REASON_DETAILED_INDEX); + String title = cursor.getString(PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE_INDEX); + Date completionDate = new Date(cursor.getLong(PodDBAdapter.KEY_COMPLETION_DATE_INDEX)); + + return new DownloadStatus(id, title, feedfileId, + feedfileType, successful, DownloadError.fromCode(reason), completionDate, + reasonDetailed); + } + private static FeedItem getMatchingItemForMedia(long itemId, List<FeedItem> items) { @@ -468,21 +491,45 @@ public final class DBReader { } /** + * Loads a list of FeedItems that are considered new. + * + * @param context A context that is used for opening a database connection. + * @return A list of FeedItems that are considered new. + */ + public static List<FeedItem> getNewItemsList(Context context) { + Log.d(TAG, "getNewItemsList()"); + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + + Cursor itemlistCursor = adapter.getNewItemsCursor(); + List<FeedItem> items = extractItemlistFromCursor(adapter, itemlistCursor); + itemlistCursor.close(); + + loadFeedDataOfFeedItemlist(context, items); + + adapter.close(); + + return items; + } + + /** * Loads the IDs of the FeedItems whose 'read'-attribute is set to false. * * @param context A context that is used for opening a database connection. * @return A list of IDs of the FeedItems whose 'read'-attribute is set to false. This method should be preferred * over {@link #getUnreadItemsList(android.content.Context)} if the FeedItems in the UnreadItems list are not used. */ - public static long[] getUnreadItemIds(Context context) { + public static LongList getNewItemIds(Context context) { PodDBAdapter adapter = new PodDBAdapter(context); adapter.open(); - Cursor cursor = adapter.getUnreadItemIdsCursor(); - long[] itemIds = new long[cursor.getCount()]; + Cursor cursor = adapter.getNewItemIdsCursor(); + LongList itemIds = new LongList(cursor.getCount()); int i = 0; if (cursor.moveToFirst()) { do { - itemIds[i] = cursor.getLong(PodDBAdapter.KEY_ID_INDEX); + long id = cursor.getLong(PodDBAdapter.KEY_ID_INDEX); + itemIds.add(id); i++; } while (cursor.moveToNext()); } @@ -565,27 +612,7 @@ public final class DBReader { if (logCursor.moveToFirst()) { do { - long id = logCursor.getLong(PodDBAdapter.KEY_ID_INDEX); - - long feedfileId = logCursor - .getLong(PodDBAdapter.KEY_FEEDFILE_INDEX); - int feedfileType = logCursor - .getInt(PodDBAdapter.KEY_FEEDFILETYPE_INDEX); - boolean successful = logCursor - .getInt(PodDBAdapter.KEY_SUCCESSFUL_INDEX) > 0; - int reason = logCursor.getInt(PodDBAdapter.KEY_REASON_INDEX); - String reasonDetailed = logCursor - .getString(PodDBAdapter.KEY_REASON_DETAILED_INDEX); - String title = logCursor - .getString(PodDBAdapter.KEY_DOWNLOADSTATUS_TITLE_INDEX); - Date completionDate = new Date( - logCursor - .getLong(PodDBAdapter.KEY_COMPLETION_DATE_INDEX) - ); - downloadLog.add(new DownloadStatus(id, title, feedfileId, - feedfileType, successful, DownloadError.fromCode(reason), completionDate, - reasonDetailed)); - + downloadLog.add(extractDownloadStatusFromCursorRow(logCursor)); } while (logCursor.moveToNext()); } logCursor.close(); @@ -594,6 +621,60 @@ public final class DBReader { } /** + * Loads the download log for a particular feed from the database. + * + * @param context A context that is used for opening a database connection. + * @param feed Feed for which the download log is loaded + * @return A list with DownloadStatus objects that represent the feed's download log, + * newest events first. + */ + public static List<DownloadStatus> getFeedDownloadLog(Context context, Feed feed) { + Log.d(TAG, "getFeedDownloadLog(CONTEXT, " + feed.toString() + ")"); + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + Cursor cursor = adapter.getDownloadLog(Feed.FEEDFILETYPE_FEED, feed.getId()); + List<DownloadStatus> downloadLog = new ArrayList<DownloadStatus>( + cursor.getCount()); + + if (cursor.moveToFirst()) { + do { + downloadLog.add(extractDownloadStatusFromCursorRow(cursor)); + } while (cursor.moveToNext()); + } + cursor.close(); + Collections.sort(downloadLog, new DownloadStatusComparator()); + return downloadLog; + } + + /** + * Loads the download log for a particular feed media from the database. + * + * @param context A context that is used for opening a database connection. + * @param media Feed media for which the download log is loaded + * @return A list with DownloadStatus objects that represent the feed media's download log, + * newest events first. + */ + public static List<DownloadStatus> getFeedMediaDownloadLog(Context context, FeedMedia media) { + Log.d(TAG, "getFeedDownloadLog(CONTEXT, " + media.toString() + ")"); + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + Cursor cursor = adapter.getDownloadLog(FeedMedia.FEEDFILETYPE_FEEDMEDIA, media.getId()); + List<DownloadStatus> downloadLog = new ArrayList<DownloadStatus>( + cursor.getCount()); + + if (cursor.moveToFirst()) { + do { + downloadLog.add(extractDownloadStatusFromCursorRow(cursor)); + } while (cursor.moveToNext()); + } + cursor.close(); + Collections.sort(downloadLog, new DownloadStatusComparator()); + return downloadLog; + } + + /** * Loads the FeedItemStatistics objects of all Feeds in the database. This method should be preferred over * {@link #getFeedItemList(android.content.Context, de.danoeh.antennapod.core.feed.Feed)} if only metadata about * the FeedItems is needed. @@ -670,6 +751,31 @@ public final class DBReader { } } return item; + } + + static List<FeedItem> getFeedItems(final Context context, PodDBAdapter adapter, final long... itemIds) { + + String[] ids = new String[itemIds.length]; + for(int i = 0; i < itemIds.length; i++) { + long itemId = itemIds[i]; + ids[i] = Long.toString(itemId); + } + + List<FeedItem> result; + + Cursor itemCursor = adapter.getFeedItemCursor(ids); + if (itemCursor.moveToFirst()) { + result = extractItemlistFromCursor(adapter, itemCursor); + loadFeedDataOfFeedItemlist(context, result); + for(FeedItem item : result) { + if (item.hasChapters()) { + loadChaptersOfFeedItem(adapter, item); + } + } + } else { + result = Collections.emptyList(); + } + return result; } @@ -691,7 +797,6 @@ public final class DBReader { FeedItem item = getFeedItem(context, itemId, adapter); adapter.close(); return item; - } static FeedItem getFeedItem(final Context context, final String podcastUrl, final String episodeUrl, PodDBAdapter adapter) { @@ -712,6 +817,55 @@ public final class DBReader { } /** + * Loads specific FeedItems from the database. This method canbe used for loading more + * than one FeedItem + * + * @param context A context that is used for opening a database connection. + * @param itemIds The IDs of the FeedItems + * @return The FeedItems or an empty list if none of the FeedItems could be found. All FeedComponent-attributes + * as well as chapter marks of the FeedItems will also be loaded from the database. + */ + public static List<FeedItem> getFeedItems(final Context context, final long... itemIds) { + Log.d(TAG, "Loading feeditem with ids: " + StringUtils.join(itemIds, ",")); + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + List<FeedItem> items = getFeedItems(context, adapter, itemIds); + adapter.close(); + return items; + } + + + /** + * Returns credentials based on image URL + * + * @param context A context that is used for opening a database connection. + * @param imageUrl The URL of the image + * @return Credentials in format "<Username>:<Password>", empty String if no authorization given + */ + public static String getImageAuthentication(final Context context, final String imageUrl) { + Log.d(TAG, "Loading credentials for image with URL " + imageUrl); + + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + String credentials = getImageAuthentication(context, imageUrl, adapter); + adapter.close(); + return credentials; + + } + + static String getImageAuthentication(final Context context, final String imageUrl, PodDBAdapter adapter) { + String credentials = null; + Cursor cursor = adapter.getImageAuthenticationCursor(imageUrl); + if (cursor.moveToFirst()) { + String username = cursor.getString(0); + String password = cursor.getString(1); + return username + ":" + password; + } + return ""; + } + + /** * Loads a specific FeedItem from the database. * * @param context A context that is used for opening a database connection. @@ -828,10 +982,24 @@ public final class DBReader { * @param context A context that is used for opening a database connection. * @return The number of unread items. */ - public static int getNumberOfUnreadItems(final Context context) { + public static int getNumberOfNewItems(final Context context) { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + final int result = adapter.getNumberOfNewItems(); + adapter.close(); + return result; + } + + /** + * Returns a map containing the number of unread items per feed + * + * @param context A context that is used for opening a database connection. + * @return The number of unread items per feed. + */ + public static LongIntMap getNumberOfUnreadFeedItems(final Context context, long... feedIds) { PodDBAdapter adapter = new PodDBAdapter(context); adapter.open(); - final int result = adapter.getNumberOfUnreadItems(); + final LongIntMap result = adapter.getNumberOfUnreadFeedItems(feedIds); adapter.close(); return result; } @@ -960,9 +1128,31 @@ public final class DBReader { PodDBAdapter adapter = new PodDBAdapter(context); adapter.open(); List<Feed> feeds = getFeedList(adapter); + long[] feedIds = new long[feeds.size()]; + for(int i=0; i < feeds.size(); i++) { + feedIds[i] = feeds.get(i).getId(); + } + final LongIntMap numUnreadFeedItems = adapter.getNumberOfUnreadFeedItems(feedIds); + Collections.sort(feeds, new Comparator<Feed>() { + @Override + public int compare(Feed lhs, Feed rhs) { + long numUnreadLhs = numUnreadFeedItems.get(lhs.getId()); + Log.d(TAG, "feed with id " + lhs.getId() + " has " + numUnreadLhs + " unread items"); + long numUnreadRhs = numUnreadFeedItems.get(rhs.getId()); + Log.d(TAG, "feed with id " + rhs.getId() + " has " + numUnreadRhs + " unread items"); + if(numUnreadLhs > numUnreadRhs) { + // reverse natural order: podcast with most unplayed episodes first + return -1; + } else if(numUnreadLhs == numUnreadRhs) { + return lhs.getTitle().compareTo(rhs.getTitle()); + } else { + return 1; + } + } + }); int queueSize = adapter.getQueueSize(); - int numUnreadItems = adapter.getNumberOfUnreadItems(); - NavDrawerData result = new NavDrawerData(feeds, queueSize, numUnreadItems); + int numNewItems = adapter.getNumberOfNewItems(); + NavDrawerData result = new NavDrawerData(feeds, queueSize, numNewItems, numUnreadFeedItems); adapter.close(); return result; } @@ -970,12 +1160,15 @@ public final class DBReader { public static class NavDrawerData { public List<Feed> feeds; public int queueSize; - public int numUnreadItems; + public int numNewItems; + public LongIntMap numUnreadFeedItems; - public NavDrawerData(List<Feed> feeds, int queueSize, int numUnreadItems) { + public NavDrawerData(List<Feed> feeds, int queueSize, int numNewItems, + LongIntMap numUnreadFeedItems) { this.feeds = feeds; this.queueSize = queueSize; - this.numUnreadItems = numUnreadItems; + this.numNewItems = numNewItems; + this.numUnreadFeedItems = numUnreadFeedItems; } } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java index 9fa17bf72..e570ee709 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java @@ -20,7 +20,6 @@ import java.util.concurrent.FutureTask; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicBoolean; -import de.danoeh.antennapod.core.BuildConfig; import de.danoeh.antennapod.core.ClientConfig; import de.danoeh.antennapod.core.asynctask.FlattrClickWorker; import de.danoeh.antennapod.core.asynctask.FlattrStatusFetcher; @@ -171,10 +170,10 @@ public final class DBTasks { isRefreshing.set(false); if (FlattrUtils.hasToken()) { - if (BuildConfig.DEBUG) Log.d(TAG, "Flattring all pending things."); + Log.d(TAG, "Flattring all pending things."); new FlattrClickWorker(context).executeAsync(); // flattr pending things - if (BuildConfig.DEBUG) Log.d(TAG, "Fetching flattr status."); + Log.d(TAG, "Fetching flattr status."); new FlattrStatusFetcher(context).start(); } @@ -185,9 +184,7 @@ public final class DBTasks { } }.start(); } else { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Ignoring request to refresh all feeds: Refresh lock is locked"); + Log.d(TAG, "Ignoring request to refresh all feeds: Refresh lock is locked"); } } @@ -223,8 +220,7 @@ public final class DBTasks { * @param context Used for DB access. */ public static void refreshExpiredFeeds(final Context context) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Refreshing expired feeds"); + Log.d(TAG, "Refreshing expired feeds"); new Thread() { public void run() { @@ -306,6 +302,7 @@ public final class DBTasks { */ public static void refreshFeed(Context context, Feed feed) throws DownloadRequestException { + Log.d(TAG, "id " + feed.getId()); refreshFeed(context, feed, false); } @@ -428,25 +425,6 @@ public final class DBTasks { } } - static int getNumberOfUndownloadedEpisodes( - final List<FeedItem> queue, final List<FeedItem> unreadItems) { - int counter = 0; - for (FeedItem item : queue) { - if (item.hasMedia() && !item.getMedia().isDownloaded() - && !item.getMedia().isPlaying() - && item.getFeed().getPreferences().getAutoDownload()) { - counter++; - } - } - for (FeedItem item : unreadItems) { - if (item.hasMedia() && !item.getMedia().isDownloaded() - && item.getFeed().getPreferences().getAutoDownload()) { - counter++; - } - } - return counter; - } - /** * Looks for undownloaded episodes in the queue or list of unread items and request a download if * 1. Network is available @@ -479,14 +457,6 @@ public final class DBTasks { } /** - * Adds all FeedItem objects whose 'read'-attribute is false to the queue in a separate thread. - */ - public static void enqueueAllNewItems(final Context context) { - long[] unreadItems = DBReader.getUnreadItemIds(context); - DBWriter.addQueueItem(context, unreadItems); - } - - /** * Returns the successor of a FeedItem in the queue. * * @param context Used for accessing the DB. @@ -560,7 +530,7 @@ public final class DBTasks { /** * Adds new Feeds to the database or updates the old versions if they already exists. If another Feed with the same * identifying value already exists, this method will add new FeedItems from the new Feed to the existing Feed. - * These FeedItems will be marked as unread. + * These FeedItems will be marked as unread with the exception of the most recent FeedItem. * <p/> * This method can update multiple feeds at once. Submitting a feed twice in the same method call can result in undefined behavior. * <p/> @@ -586,17 +556,20 @@ public final class DBTasks { final Feed savedFeed = searchFeedByIdentifyingValueOrID(context, adapter, newFeed); if (savedFeed == null) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Found no existing Feed with title " - + newFeed.getTitle() + ". Adding as new one." - ); + Log.d(TAG, "Found no existing Feed with title " + + newFeed.getTitle() + ". Adding as new one."); + // Add a new Feed + // all new feeds will have the most recent item marked as unplayed + FeedItem mostRecent = newFeed.getMostRecentItem(); + if (mostRecent != null) { + mostRecent.setRead(false); + } + newFeedsList.add(newFeed); resultFeeds[feedIdx] = newFeed; } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Feed with title " + newFeed.getTitle() + Log.d(TAG, "Feed with title " + newFeed.getTitle() + " already exists. Syncing new with existing one."); Collections.sort(newFeed.getItems(), new FeedItemPubdateComparator()); @@ -604,21 +577,17 @@ public final class DBTasks { final boolean markNewItemsAsUnread; if (newFeed.getPageNr() == savedFeed.getPageNr()) { if (savedFeed.compareWithOther(newFeed)) { - if (BuildConfig.DEBUG) - Log.d(TAG, - "Feed has updated attribute values. Updating old feed's attributes"); + Log.d(TAG, "Feed has updated attribute values. Updating old feed's attributes"); savedFeed.updateFromOther(newFeed); } markNewItemsAsUnread = true; } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "New feed has a higher page number. Merging without marking as unread"); + Log.d(TAG, "New feed has a higher page number. Merging without marking as unread"); markNewItemsAsUnread = false; savedFeed.setNextPageLink(newFeed.getNextPageLink()); } if (savedFeed.getPreferences().compareWithOther(newFeed.getPreferences())) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Feed has updated preferences. Updating old feed's preferences"); + Log.d(TAG, "Feed has updated preferences. Updating old feed's preferences"); savedFeed.getPreferences().updateFromOther(newFeed.getPreferences()); } // Look for new or updated Items @@ -630,6 +599,7 @@ public final class DBTasks { // item is new final int i = idx; item.setFeed(savedFeed); + item.setAutoDownload(savedFeed.getPreferences().getAutoDownload()); savedFeed.getItems().add(i, item); if (markNewItemsAsUnread) { item.setRead(false); @@ -641,6 +611,7 @@ public final class DBTasks { // update attributes savedFeed.setLastUpdate(newFeed.getLastUpdate()); savedFeed.setType(newFeed.getType()); + savedFeed.setLastUpdateFailed(false); updatedFeedsList.add(savedFeed); resultFeeds[feedIdx] = savedFeed; diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java index bd0cfee5b..fe5d0dfd3 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java @@ -29,6 +29,7 @@ import de.danoeh.antennapod.core.ClientConfig; import de.danoeh.antennapod.core.asynctask.FlattrClickWorker; 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; @@ -353,30 +354,16 @@ public class DBWriter { FeedItem item = null; if (queue != null) { - boolean queueModified = false; - boolean unreadItemsModified = false; - if (!itemListContains(queue, itemId)) { item = DBReader.getFeedItem(context, itemId); if (item != null) { queue.add(index, item); - queueModified = true; - if (!item.isRead()) { - item.setRead(true); - unreadItemsModified = true; - } + adapter.setQueue(queue); + EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.ADDED, item, index)); } } - if (queueModified) { - adapter.setQueue(queue); - EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.ADDED, item, index)); - } - if (unreadItemsModified && item != null) { - adapter.setSingleFeedItem(item); - EventDistributor.getInstance() - .sendUnreadItemsUpdateBroadcast(); - } } + adapter.close(); if (performAutoDownload) { DBTasks.autodownloadUndownloadedItems(context); @@ -417,21 +404,15 @@ public class DBWriter { if (item != null) { // add item to either front ot back of queue - boolean addToFront = PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean(UserPreferences.PREF_QUEUE_ADD_TO_FRONT, false); + boolean addToFront = UserPreferences.enqueueAtFront(); if(addToFront){ queue.add(0, item); - }else{ + } else { queue.add(item); } queueModified = true; - if (!item.isRead()) { - item.setRead(true); - itemsToSave.add(item); - unreadItemsModified = true; - } } } } @@ -439,11 +420,6 @@ public class DBWriter { adapter.setQueue(queue); EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.ADDED_ITEMS, queue)); } - if (unreadItemsModified) { - adapter.setFeedItemlist(itemsToSave); - EventDistributor.getInstance() - .sendUnreadItemsUpdateBroadcast(); - } } adapter.close(); DBTasks.autodownloadUndownloadedItems(context); @@ -608,8 +584,7 @@ public class DBWriter { adapter.setQueue(queue); if (broadcastUpdate) { - EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.REMOVED, item)); - EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.ADDED, item, to)); + EventBus.getDefault().post(new QueueEvent(QueueEvent.Action.MOVED, item, to)); } } @@ -937,6 +912,26 @@ public class DBWriter { } /** + * Saves if a feed's last update failed + * + * @param lastUpdateFailed true if last update failed + */ + public static Future<?> setFeedLastUpdateFailed(final Context context, + final long feedId, + final boolean lastUpdateFailed) { + return dbExec.submit(new Runnable() { + + @Override + public void run() { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.setFeedLastUpdateFailed(feedId, lastUpdateFailed); + adapter.close(); + } + }); + } + + /** * format an url for querying the database * (postfix a / and apply percent-encoding) */ @@ -1040,4 +1035,52 @@ public class DBWriter { } }); } + + /** + * Sets the 'auto_download'-attribute of specific FeedItem. + * + * @param context A context that is used for opening a database connection. + * @param feedItem FeedItem. + */ + public static Future<?> setFeedItemAutoDownload(final Context context, final FeedItem feedItem, + final boolean autoDownload) { + Log.d(TAG, "FeedItem[id=" + feedItem.getId() + "] SET auto_download " + autoDownload); + return dbExec.submit(new Runnable() { + + @Override + public void run() { + final PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.setFeedItemAutoDownload(feedItem, autoDownload); + adapter.close(); + + EventDistributor.getInstance().sendUnreadItemsUpdateBroadcast(); + } + }); + + } + + /** + * Set filter of the feed + * + * @param context Used for opening a database connection. + * @param feedId The feed's ID + * @param filterValues Values that represent properties to filter by + */ + public static Future<?> setFeedItemsFilter(final Context context, final long feedId, + final List<String> filterValues) { + Log.d(TAG, "setFeedFilter"); + + return dbExec.submit(new Runnable() { + @Override + public void run() { + PodDBAdapter adapter = new PodDBAdapter(context); + adapter.open(); + adapter.setFeedItemFilter(feedId, filterValues); + adapter.close(); + EventBus.getDefault().post(new FeedEvent(FeedEvent.Action.FILTER_CHANGED, feedId)); + } + }); + } + } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java index f518a4f5f..4780098e0 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java @@ -11,6 +11,7 @@ import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import java.util.Arrays; @@ -26,8 +27,11 @@ import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.FeedPreferences; import de.danoeh.antennapod.core.service.download.DownloadStatus; +import de.danoeh.antennapod.core.util.LongIntMap; import de.danoeh.antennapod.core.util.flattr.FlattrStatus; +; + // TODO Remove media column from feeditem table /** @@ -149,6 +153,8 @@ public class PodDBAdapter { public static final String KEY_PASSWORD = "password"; public static final String KEY_IS_PAGED = "is_paged"; public static final String KEY_NEXT_PAGE_LINK = "next_page_link"; + public static final String KEY_HIDE = "hide"; + public static final String KEY_LAST_UPDATE_FAILED = "last_update_failed"; // Table names public static final String TABLE_NAME_FEEDS = "Feeds"; @@ -175,8 +181,9 @@ public class PodDBAdapter { + KEY_USERNAME + " TEXT," + KEY_PASSWORD + " TEXT," + KEY_IS_PAGED + " INTEGER DEFAULT 0," - + KEY_NEXT_PAGE_LINK + " TEXT)"; - + + KEY_NEXT_PAGE_LINK + " TEXT," + + KEY_HIDE + " TEXT," + + KEY_LAST_UPDATE_FAILED + " INTEGER DEFAULT 0)"; public static final String CREATE_TABLE_FEED_ITEMS = "CREATE TABLE " + TABLE_NAME_FEED_ITEMS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE @@ -186,7 +193,8 @@ 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 + " INTEGER," + + KEY_AUTO_DOWNLOAD + " INTEGER)"; public static final String CREATE_TABLE_FEED_IMAGES = "CREATE TABLE " + TABLE_NAME_FEED_IMAGES + " (" + TABLE_PRIMARY_KEY + KEY_TITLE @@ -200,7 +208,8 @@ public class PodDBAdapter { + " INTEGER," + KEY_SIZE + " INTEGER," + KEY_MIME_TYPE + " TEXT," + KEY_PLAYBACK_COMPLETION_DATE + " INTEGER," + KEY_FEEDITEM + " INTEGER," - + KEY_PLAYED_DURATION + " INTEGER)"; + + KEY_PLAYED_DURATION + " INTEGER," + + KEY_AUTO_DOWNLOAD + " INTEGER)"; public static final String CREATE_TABLE_DOWNLOAD_LOG = "CREATE TABLE " + TABLE_NAME_DOWNLOAD_LOG + " (" + TABLE_PRIMARY_KEY + KEY_FEEDFILE @@ -218,6 +227,28 @@ public class PodDBAdapter { + " TEXT," + KEY_START + " INTEGER," + KEY_FEEDITEM + " INTEGER," + KEY_LINK + " TEXT," + KEY_CHAPTER_TYPE + " INTEGER)"; + // SQL Statements for creating indexes + public static final String CREATE_INDEX_FEEDITEMS_FEED = "CREATE INDEX " + + TABLE_NAME_FEED_ITEMS + "_" + KEY_FEED + " ON " + TABLE_NAME_FEED_ITEMS + " (" + + KEY_FEED + ")"; + + public static final String CREATE_INDEX_FEEDITEMS_IMAGE = "CREATE INDEX " + + TABLE_NAME_FEED_ITEMS + "_" + KEY_IMAGE + " ON " + TABLE_NAME_FEED_ITEMS + " (" + + KEY_IMAGE + ")"; + + public static final String CREATE_INDEX_QUEUE_FEEDITEM = "CREATE INDEX " + + TABLE_NAME_QUEUE + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_QUEUE + " (" + + KEY_FEEDITEM + ")"; + + public static final String CREATE_INDEX_FEEDMEDIA_FEEDITEM = "CREATE INDEX " + + TABLE_NAME_FEED_MEDIA + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_FEED_MEDIA + " (" + + KEY_FEEDITEM + ")"; + + public static final String CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM = "CREATE INDEX " + + TABLE_NAME_SIMPLECHAPTERS + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_SIMPLECHAPTERS + " (" + + KEY_FEEDITEM + ")"; + + private SQLiteDatabase db; private final Context context; private PodDBHelper helper; @@ -246,6 +277,8 @@ public class PodDBAdapter { TABLE_NAME_FEEDS + "." + KEY_NEXT_PAGE_LINK, TABLE_NAME_FEEDS + "." + KEY_USERNAME, TABLE_NAME_FEEDS + "." + KEY_PASSWORD, + TABLE_NAME_FEEDS + "." + KEY_HIDE, + TABLE_NAME_FEEDS + "." + KEY_LAST_UPDATE_FAILED, }; // column indices for FEED_SEL_STD @@ -270,7 +303,6 @@ public class PodDBAdapter { public static final int IDX_FEED_SEL_PREFERENCES_USERNAME = 18; public static final int IDX_FEED_SEL_PREFERENCES_PASSWORD = 19; - /** * Select all columns from the feeditems-table except description and * content-encoded. @@ -286,7 +318,9 @@ 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, + TABLE_NAME_FEED_ITEMS + "." + KEY_AUTO_DOWNLOAD + }; /** * Contains FEEDITEM_SEL_FI_SMALL as comma-separated list. Useful for raw queries. @@ -400,17 +434,20 @@ public class PodDBAdapter { values.put(KEY_FLATTR_STATUS, feed.getFlattrStatus().toLong()); values.put(KEY_IS_PAGED, feed.isPaged()); values.put(KEY_NEXT_PAGE_LINK, feed.getNextPageLink()); + if(feed.getItemFilter() != null && feed.getItemFilter().getValues().length > 0) { + values.put(KEY_HIDE, StringUtils.join(feed.getItemFilter().getValues(), ",")); + } else { + values.put(KEY_HIDE, ""); + } + values.put(KEY_LAST_UPDATE_FAILED, feed.hasLastUpdateFailed()); if (feed.getId() == 0) { // Create new entry - if (BuildConfig.DEBUG) - Log.d(this.toString(), "Inserting new Feed into db"); + Log.d(this.toString(), "Inserting new Feed into db"); feed.setId(db.insert(TABLE_NAME_FEEDS, null, values)); } else { - if (BuildConfig.DEBUG) - Log.d(this.toString(), "Updating existing Feed in db"); + Log.d(this.toString(), "Updating existing Feed in db"); db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(feed.getId())}); - } return feed.getId(); } @@ -426,6 +463,13 @@ public class PodDBAdapter { db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(prefs.getFeedID())}); } + public void setFeedItemFilter(long feedId, List<String> filterValues) { + ContentValues values = new ContentValues(); + values.put(KEY_HIDE, StringUtils.join(filterValues, ",")); + Log.d(TAG, StringUtils.join(filterValues, ",")); + db.update(TABLE_NAME_FEEDS, values, KEY_ID + "=?", new String[]{String.valueOf(feedId)}); + } + /** * Inserts or updates an image entry * @@ -696,6 +740,7 @@ public class PodDBAdapter { values.put(KEY_HAS_CHAPTERS, item.getChapters() != null || item.hasChapters()); 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()); @@ -766,6 +811,13 @@ public class PodDBAdapter { } } + public void setFeedLastUpdateFailed(long feedId, boolean failed) { + final String sql = "UPDATE " + TABLE_NAME_FEEDS + + " SET " + KEY_LAST_UPDATE_FAILED+ "=" + (failed ? "1" : "0") + + " WHERE " + KEY_ID + "="+ feedId; + db.execSQL(sql); + } + /** * Inserts or updates a download status. */ @@ -787,6 +839,13 @@ public class PodDBAdapter { return status.getId(); } + public void setFeedItemAutoDownload(FeedItem feedItem, boolean autoDownload) { + ContentValues values = new ContentValues(); + values.put(KEY_AUTO_DOWNLOAD, autoDownload); + db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", + new String[] { String.valueOf(feedItem.getId()) } ); + } + public long getDownloadLogSize() { final String query = String.format("SELECT COUNT(%s) FROM %s", KEY_ID, TABLE_NAME_DOWNLOAD_LOG); Cursor result = db.rawQuery(query, null); @@ -977,6 +1036,14 @@ public class PodDBAdapter { return c; } + public final Cursor getDownloadLog(final int feedFileType, final long feedFileId) { + final String query = "SELECT * FROM " + TABLE_NAME_DOWNLOAD_LOG + + " WHERE " + KEY_FEEDFILE + "=" + feedFileId + " AND " + KEY_FEEDFILETYPE + "=" + feedFileType + + " ORDER BY " + KEY_ID + " DESC"; + Cursor c = db.rawQuery(query, null); + return c; + } + public final Cursor getDownloadLogCursor(final int limit) { Cursor c = db.query(TABLE_NAME_DOWNLOAD_LOG, null, null, null, null, null, KEY_COMPLETION_DATE + " DESC LIMIT " + limit); @@ -1021,11 +1088,43 @@ public class PodDBAdapter { return c; } - public final Cursor getUnreadItemIdsCursor() { - Cursor c = db.query(TABLE_NAME_FEED_ITEMS, new String[]{KEY_ID}, - KEY_READ + "=0", null, null, null, KEY_PUBDATE + " DESC"); - return c; + public final Cursor getNewItemIdsCursor() { + final String query = "SELECT " + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + + " FROM " + TABLE_NAME_FEED_ITEMS + + " INNER JOIN " + TABLE_NAME_FEED_MEDIA + " ON " + + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "=" + + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM + + " LEFT OUTER JOIN " + TABLE_NAME_QUEUE + " ON " + + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "=" + + TABLE_NAME_QUEUE + "." + KEY_FEEDITEM + + " WHERE " + + TABLE_NAME_FEED_ITEMS + "." + KEY_READ + " = 0 AND " // unplayed + + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " = 0 AND " // undownloaded + + TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + " = 0 AND " // not partially played + + TABLE_NAME_QUEUE + "." + KEY_ID + " IS NULL"; // not in queue + return db.rawQuery(query, null); + } + /** + * Returns a cursor which contains all feed items that are considered new. + * The returned cursor uses the FEEDITEM_SEL_FI_SMALL selection. + */ + public final Cursor getNewItemsCursor() { + final String query = "SELECT " + SEL_FI_SMALL_STR + " FROM " + TABLE_NAME_FEED_ITEMS + + " INNER JOIN " + TABLE_NAME_FEED_MEDIA + " ON " + + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "=" + + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM + + " LEFT OUTER JOIN " + TABLE_NAME_QUEUE + " ON " + + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "=" + + TABLE_NAME_QUEUE + "." + KEY_FEEDITEM + + " WHERE " + + TABLE_NAME_FEED_ITEMS + "." + KEY_READ + " = 0 AND " // unplayed + + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " = 0 AND " // undownloaded + + TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + " = 0 AND " // not partially played + + TABLE_NAME_QUEUE + "." + KEY_ID + " IS NULL" // not in queue + + " ORDER BY " + KEY_PUBDATE + " DESC"; + Cursor c = db.rawQuery(query, null); + return c; } public final Cursor getRecentlyPublishedItemsCursor(int limit) { @@ -1121,7 +1220,7 @@ public class PodDBAdapter { } public final Cursor getFeedItemCursor(final String id) { - return getFeedItemCursor(new String[] { id }); + return getFeedItemCursor(new String[]{id}); } public final Cursor getFeedItemCursor(final String[] ids) { @@ -1146,6 +1245,20 @@ public class PodDBAdapter { return db.rawQuery(query, null); } + public Cursor getImageAuthenticationCursor(final String imageUrl) { + final String query = "SELECT " + KEY_USERNAME + "," + KEY_PASSWORD + " FROM " + + TABLE_NAME_FEED_IMAGES + " 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 + "='" + imageUrl + "' 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 + "='" + imageUrl + "'"; + Log.d(TAG, "Query: " + query); + return db.rawQuery(query, null); + } + public int getQueueSize() { final String query = String.format("SELECT COUNT(%s) FROM %s", KEY_ID, TABLE_NAME_QUEUE); Cursor c = db.rawQuery(query, null); @@ -1157,9 +1270,20 @@ public class PodDBAdapter { return result; } - public final int getNumberOfUnreadItems() { - final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_ITEMS + - " WHERE " + KEY_READ + " = 0"; + public final int getNumberOfNewItems() { + final String query = "SELECT COUNT(" + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + ")" + +" FROM " + TABLE_NAME_FEED_ITEMS + + " LEFT JOIN " + TABLE_NAME_FEED_MEDIA + " ON " + + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "=" + + TABLE_NAME_FEED_MEDIA + "." + KEY_FEEDITEM + + " LEFT JOIN " + TABLE_NAME_QUEUE + " ON " + + TABLE_NAME_FEED_ITEMS + "." + KEY_ID + "=" + + TABLE_NAME_QUEUE + "." + KEY_FEEDITEM + + " WHERE " + + TABLE_NAME_FEED_ITEMS + "." + KEY_READ + " = 0 AND " // unplayed + + TABLE_NAME_FEED_MEDIA + "." + KEY_DOWNLOADED + " = 0 AND " // undownloaded + + TABLE_NAME_FEED_MEDIA + "." + KEY_POSITION + " = 0 AND " // not partially played + + TABLE_NAME_QUEUE + "." + KEY_ID + " IS NULL"; // not in queue Cursor c = db.rawQuery(query, null); int result = 0; if (c.moveToFirst()) { @@ -1169,6 +1293,25 @@ public class PodDBAdapter { return result; } + public final LongIntMap getNumberOfUnreadFeedItems(long... feedIds) { + final String query = "SELECT " + KEY_FEED + ", COUNT(" + KEY_ID + ") AS count " + + " FROM " + TABLE_NAME_FEED_ITEMS + + " WHERE " + KEY_FEED + " IN (" + StringUtils.join(feedIds, ',') + ") " + + " AND " + KEY_READ + " = 0" + + " GROUP BY " + KEY_FEED; + Cursor c = db.rawQuery(query, null); + LongIntMap result = new LongIntMap(c.getCount()); + if (c.moveToFirst()) { + do { + long feedId = c.getLong(0); + int count = c.getInt(1); + result.put(feedId, count); + } while(c.moveToNext()); + } + c.close(); + return result; + } + public final int getNumberOfDownloadedEpisodes() { final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_MEDIA + " WHERE " + KEY_DOWNLOADED + " > 0"; @@ -1330,6 +1473,13 @@ public class PodDBAdapter { db.execSQL(CREATE_TABLE_DOWNLOAD_LOG); db.execSQL(CREATE_TABLE_QUEUE); db.execSQL(CREATE_TABLE_SIMPLECHAPTERS); + + db.execSQL(CREATE_INDEX_FEEDITEMS_FEED); + db.execSQL(CREATE_INDEX_FEEDITEMS_IMAGE); + db.execSQL(CREATE_INDEX_FEEDMEDIA_FEEDITEM); + db.execSQL(CREATE_INDEX_QUEUE_FEEDITEM); + db.execSQL(CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM); + } @Override diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java index 6622eab73..b6df2dc85 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/DateUtils.java @@ -1,5 +1,7 @@ package de.danoeh.antennapod.core.util; +import android.util.Log; + import org.apache.commons.lang3.StringUtils; import java.text.ParsePosition; @@ -11,45 +13,14 @@ import java.util.Locale; * Parses several date formats. */ public class DateUtils { - private static final String TAG = "DateUtils"; - - private static final String[] RFC822DATES = {"dd MMM yy HH:mm:ss Z", - "dd MMM yy HH:mm Z"}; - - /** - * RFC 3339 date format for UTC dates. - */ - public static final String RFC3339UTC = "yyyy-MM-dd'T'HH:mm:ss'Z'"; - - /** - * RFC 3339 date format for localtime dates with offset. - */ - public static final String RFC3339LOCAL = "yyyy-MM-dd'T'HH:mm:ssZ"; + + private static final String TAG = "DateUtils"; - public static final String ISO8601_SHORT = "yyyy-MM-dd"; - - private static ThreadLocal<SimpleDateFormat> RFC822Formatter = new ThreadLocal<SimpleDateFormat>() { - @Override - protected SimpleDateFormat initialValue() { - return new SimpleDateFormat("dd MMM yy HH:mm:ss Z", Locale.US); - } - - }; - - private static ThreadLocal<SimpleDateFormat> RFC3339Formatter = new ThreadLocal<SimpleDateFormat>() { - @Override - protected SimpleDateFormat initialValue() { - return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); - } - - }; - - public static Date parse(String date) { - if(date == null) { + public static Date parse(final String input) { + if(input == null) { throw new IllegalArgumentException("Date most not be null"); } - date = date.replace('/', ' '); - date = date.replace('-', ' '); + String date = input.replace('/', '-'); if(date.contains(".")) { int start = date.indexOf('.'); int current = start+1; @@ -75,15 +46,16 @@ public class DateUtils { "dd MMM yy HH:mm:ss Z", "dd MMM yy HH:mm Z", "EEE, dd MMM yyyy HH:mm:ss Z", + "EEE, dd MMMM yyyy HH:mm:ss Z", "EEEE, dd MMM yy HH:mm:ss Z", "EEE MMM d HH:mm:ss yyyy", - "yyyy MM dd'T'HH:mm:ss", - "yyyy MM dd'T'HH:mm:ss.SSS", - "yyyy MM dd'T'HH:mm:ss.SSS Z", - "yyyy MM dd'T'HH:mm:ssZ", - "yyyy MM dd'T'HH:mm:ss'Z'", - "yyyy MM ddZ", - "yyyy MM dd" + "yyyy-MM-dd'T'HH:mm:ss", + "yyyy-MM-dd'T'HH:mm:ss.SSS", + "yyyy-MM-dd'T'HH:mm:ss.SSS Z", + "yyyy-MM-dd'T'HH:mm:ssZ", + "yyyy-MM-dd'T'HH:mm:ss'Z'", + "yyyy-MM-ddZ", + "yyyy-MM-dd" }; SimpleDateFormat parser = new SimpleDateFormat("", Locale.US); parser.setLenient(false); @@ -96,6 +68,8 @@ public class DateUtils { return result; } } + + Log.d(TAG, "Could not parse date string \"" + input + "\""); return null; } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java b/core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java index 4c23b161b..029e7fe84 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/EpisodeFilter.java @@ -1,11 +1,12 @@ package de.danoeh.antennapod.core.util; -import de.danoeh.antennapod.core.feed.FeedItem; - import java.util.ArrayList; import java.util.List; +import de.danoeh.antennapod.core.feed.FeedItem; + public class EpisodeFilter { + private EpisodeFilter() { } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java b/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java new file mode 100644 index 000000000..673c81235 --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/util/IntList.java @@ -0,0 +1,240 @@ +package de.danoeh.antennapod.core.util; + +import java.util.Arrays; + +/** + * Fast and memory efficient int list + */ +public final class IntList { + + private int[] values; + protected int size; + + /** + * Constructs an empty instance with a default initial capacity. + */ + public IntList() { + this(4); + } + + /** + * Constructs an empty instance. + * + * @param initialCapacity {@code >= 0;} initial capacity of the list + */ + public IntList(int initialCapacity) { + if(initialCapacity < 0) { + throw new IllegalArgumentException("initial capacity must be 0 or higher"); + } + values = new int[initialCapacity]; + size = 0; + } + + @Override + public int hashCode() { + int hashCode = 1; + for (int i = 0; i < size; i++) { + int value = values[i]; + hashCode = 31 * hashCode + (int)(value ^ (value >>> 32)); + } + return hashCode; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (! (other instanceof IntList)) { + return false; + } + IntList otherList = (IntList) other; + if (size != otherList.size) { + return false; + } + for (int i = 0; i < size; i++) { + if (values[i] != otherList.values[i]) { + return false; + } + } + return true; + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(size * 5 + 10); + sb.append("IntList{"); + for (int i = 0; i < size; i++) { + if (i != 0) { + sb.append(", "); + } + sb.append(values[i]); + } + sb.append("}"); + return sb.toString(); + } + + /** + * Gets the number of elements in this list. + */ + public int size() { + return size; + } + + /** + * Gets the indicated value. + * + * @param n {@code >= 0, < size();} which element + * @return the indicated element's value + */ + public int get(int n) { + if (n >= size) { + throw new IndexOutOfBoundsException("n >= size()"); + } else if(n < 0) { + throw new IndexOutOfBoundsException("n < 0"); + } + return values[n]; + } + + /** + * Sets the value at the given index. + * + * @param index the index at which to put the specified object. + * @param value the object to add. + * @return the previous element at the index. + */ + public int set(int index, int value) { + if (index >= size) { + throw new IndexOutOfBoundsException("n >= size()"); + } else if(index < 0) { + throw new IndexOutOfBoundsException("n < 0"); + } + int result = values[index]; + values[index] = value; + return result; + } + + /** + * Adds an element to the end of the list. This will increase the + * list's capacity if necessary. + * + * @param value the value to add + */ + public void add(int value) { + growIfNeeded(); + values[size++] = value; + } + + /** + * Inserts element into specified index, moving elements at and above + * that index up one. May not be used to insert at an index beyond the + * current size (that is, insertion as a last element is legal but + * no further). + * + * @param n {@code >= 0, <=size();} index of where to insert + * @param value value to insert + */ + public void insert(int n, int value) { + if (n > size) { + throw new IndexOutOfBoundsException("n > size()"); + } else if(n < 0) { + throw new IndexOutOfBoundsException("n < 0"); + } + + growIfNeeded(); + + System.arraycopy (values, n, values, n+1, size - n); + values[n] = value; + size++; + } + + /** + * Removes value from this list. + * + * @param value value to remove + * return {@code true} if the value was removed, {@code false} otherwise + */ + public boolean remove(int value) { + for (int i = 0; i < size; i++) { + if (values[i] == value) { + size--; + System.arraycopy(values, i+1, values, i, size-i); + return true; + } + } + return false; + } + + /** + * Removes an element at a given index, shifting elements at greater + * indicies down one. + * + * @param index index of element to remove + */ + public void removeIndex(int index) { + if (index >= size) { + throw new IndexOutOfBoundsException("n >= size()"); + } else if(index < 0) { + throw new IndexOutOfBoundsException("n < 0"); + } + size--; + System.arraycopy (values, index + 1, values, index, size - index); + } + + /** + * Increases size of array if needed + */ + private void growIfNeeded() { + if (size == values.length) { + // Resize. + int[] newArray = new int[size * 3 / 2 + 10]; + System.arraycopy(values, 0, newArray, 0, size); + values = newArray; + } + } + + /** + * Returns the index of the given value, or -1 if the value does not + * appear in the list. + * + * @param value value to find + * @return index of value or -1 + */ + public int indexOf(int value) { + for (int i = 0; i < size; i++) { + if (values[i] == value) { + return i; + } + } + return -1; + } + + /** + * Removes all values from this list. + */ + public void clear() { + values = new int[4]; + size = 0; + } + + + /** + * Returns true if the given value is contained in the list + * + * @param value value to look for + * @return {@code true} if this list contains {@code value}, {@code false} otherwise + */ + public boolean contains(int value) { + return indexOf(value) >= 0; + } + + /** + * Returns an array with a copy of this list's values + * + * @return array with a copy of this list's values + */ + public int[] toArray() { + return Arrays.copyOf(values, size); + + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java b/core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java new file mode 100644 index 000000000..33fd252eb --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/util/LongIntMap.java @@ -0,0 +1,252 @@ +package de.danoeh.antennapod.core.util; + + +/** + * Fast and memory efficient long to long map + */ +public class LongIntMap { + + private long[] keys; + private int[] values; + private int size; + + /** + * Creates a new LongLongMap containing no mappings. + */ + public LongIntMap() { + this(10); + } + + /** + * Creates a new SparseLongArray containing no mappings that will not + * require any additional memory allocation to store the specified + * number of mappings. If you supply an initial capacity of 0, the + * sparse array will be initialized with a light-weight representation + * not requiring any additional array allocations. + */ + public LongIntMap(int initialCapacity) { + if(initialCapacity < 0) { + throw new IllegalArgumentException("initial capacity must be 0 or higher"); + } + keys = new long[initialCapacity]; + values = new int[initialCapacity]; + size = 0; + } + + /** + * Increases size of array if needed + */ + private void growIfNeeded() { + if (size == keys.length) { + // Resize. + long[] newKeysArray = new long[size * 3 / 2 + 10]; + int[] newValuesArray = new int[size * 3 / 2 + 10]; + System.arraycopy(keys, 0, newKeysArray, 0, size); + System.arraycopy(values, 0, newValuesArray, 0, size); + keys = newKeysArray; + values = newValuesArray; + } + } + + /** + * Gets the long mapped from the specified key, or <code>0</code> + * if no such mapping has been made. + */ + public int get(long key) { + return get(key, 0); + } + + /** + * Gets the long mapped from the specified key, or the specified value + * if no such mapping has been made. + */ + public int get(long key, int valueIfKeyNotFound) { + int index = indexOfKey(key); + if(index >= 0) { + return values[index]; + } else { + return valueIfKeyNotFound; + } + } + + /** + * Removes the mapping from the specified key, if there was any. + */ + public boolean delete(long key) { + int index = indexOfKey(key); + + if (index >= 0) { + removeAt(index); + return true; + } else { + return false; + } + } + + /** + * Removes the mapping at the given index. + */ + public void removeAt(int index) { + System.arraycopy(keys, index + 1, keys, index, size - (index + 1)); + System.arraycopy(values, index + 1, values, index, size - (index + 1)); + size--; + } + + /** + * Adds a mapping from the specified key to the specified value, + * replacing the previous mapping from the specified key if there + * was one. + */ + public void put(long key, int value) { + int index = indexOfKey(key); + + if (index >= 0) { + values[index] = value; + } else { + growIfNeeded(); + keys[size] = key; + values[size] = value; + size++; + } + } + + /** + * Returns the number of key-value mappings that this SparseIntArray + * currently stores. + */ + public int size() { + return size; + } + + /** + * Given an index in the range <code>0...size()-1</code>, returns + * the key from the <code>index</code>th key-value mapping that this + * SparseLongArray stores. + * + * <p>The keys corresponding to indices in ascending order are guaranteed to + * be in ascending order, e.g., <code>keyAt(0)</code> will return the + * smallest key and <code>keyAt(size()-1)</code> will return the largest + * key.</p> + */ + public long keyAt(int index) { + if (index >= size) { + throw new IndexOutOfBoundsException("n >= size()"); + } else if(index < 0) { + throw new IndexOutOfBoundsException("n < 0"); + } + return keys[index]; + } + + /** + * Given an index in the range <code>0...size()-1</code>, returns + * the value from the <code>index</code>th key-value mapping that this + * SparseLongArray stores. + * + * <p>The values corresponding to indices in ascending order are guaranteed + * to be associated with keys in ascending order, e.g., + * <code>valueAt(0)</code> will return the value associated with the + * smallest key and <code>valueAt(size()-1)</code> will return the value + * associated with the largest key.</p> + */ + public int valueAt(int index) { + if (index >= size) { + throw new IndexOutOfBoundsException("n >= size()"); + } else if(index < 0) { + throw new IndexOutOfBoundsException("n < 0"); + } + return values[index]; + } + + /** + * Returns the index for which {@link #keyAt} would return the + * specified key, or a negative number if the specified + * key is not mapped. + */ + public int indexOfKey(long key) { + for(int i=0; i < size; i++) { + if(keys[i] == key) { + return i; + } + } + return -1; + } + + /** + * Returns an index for which {@link #valueAt} would return the + * specified key, or a negative number if no keys map to the + * specified value. + * Beware that this is a linear search, unlike lookups by key, + * and that multiple keys can map to the same value and this will + * find only one of them. + */ + public int indexOfValue(long value) { + for (int i = 0; i < size; i++) { + if (values[i] == value) { + return i; + } + } + return -1; + } + + /** + * Removes all key-value mappings from this SparseIntArray. + */ + public void clear() { + keys = new long[10]; + values = new int[10]; + size = 0; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (! (other instanceof LongIntMap)) { + return false; + } + LongIntMap otherMap = (LongIntMap) other; + if (size != otherMap.size) { + return false; + } + for (int i = 0; i < size; i++) { + if (keys[i] != otherMap.keys[i] || + values[i] != otherMap.values[i]) { + return false; + } + } + return true; + } + + @Override + public int hashCode() { + int hashCode = 1; + for (int i = 0; i < size; i++) { + long value = values[i]; + hashCode = 31 * hashCode + (int)(value ^ (value >>> 32)); + } + return hashCode; + } + + @Override + public String toString() { + if (size() <= 0) { + return "LongLongMap{}"; + } + + StringBuilder buffer = new StringBuilder(size * 28); + buffer.append("LongLongMap{"); + for (int i=0; i < size; i++) { + if (i > 0) { + buffer.append(", "); + } + long key = keyAt(i); + buffer.append(key); + buffer.append('='); + long value = valueAt(i); + buffer.append(value); + } + buffer.append('}'); + return buffer.toString(); + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java b/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java index f5d0cab0c..8934f3272 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/LongList.java @@ -32,7 +32,6 @@ public final class LongList { @Override public int hashCode() { - Arrays.hashCode(values); int hashCode = 1; for (int i = 0; i < size; i++) { long value = values[i]; diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java index b321536a3..3a349e221 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java @@ -6,12 +6,13 @@ import android.net.NetworkInfo; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.util.Log; -import de.danoeh.antennapod.core.BuildConfig; -import de.danoeh.antennapod.core.preferences.UserPreferences; import java.util.Arrays; import java.util.List; +import de.danoeh.antennapod.core.BuildConfig; +import de.danoeh.antennapod.core.preferences.UserPreferences; + public class NetworkUtils { private static final String TAG = "NetworkUtils"; @@ -66,4 +67,18 @@ public class NetworkUtils { NetworkInfo info = cm.getActiveNetworkInfo(); return info != null && info.isConnected(); } + + public static boolean isDownloadAllowed(Context context) { + return UserPreferences.isAllowMobileUpdate() || NetworkUtils.connectedToWifi(context); + } + + public static boolean connectedToWifi(Context context) { + ConnectivityManager connManager = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo mWifi = connManager + .getNetworkInfo(ConnectivityManager.TYPE_WIFI); + + return mWifi.isConnected(); + } + } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java index 90caad946..50792ae26 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java @@ -26,7 +26,6 @@ import java.util.EnumSet; import java.util.List; import java.util.TimeZone; -import de.danoeh.antennapod.core.BuildConfig; import de.danoeh.antennapod.core.ClientConfig; import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.asynctask.FlattrTokenFetcher; @@ -65,18 +64,15 @@ public class FlattrUtils { private static AccessToken retrieveToken() { if (cachedToken == null) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Retrieving access token"); + Log.d(TAG, "Retrieving access token"); String token = PreferenceManager.getDefaultSharedPreferences( ClientConfig.applicationCallbacks.getApplicationInstance()) .getString(PREF_ACCESS_TOKEN, null); if (token != null) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Found access token. Caching."); + Log.d(TAG, "Found access token. Caching."); cachedToken = new AccessToken(token); } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "No access token found"); + Log.d(TAG, "No access token found"); return null; } } @@ -97,8 +93,7 @@ public class FlattrUtils { } public static void storeToken(AccessToken token) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Storing token"); + Log.d(TAG, "Storing token"); SharedPreferences.Editor editor = PreferenceManager .getDefaultSharedPreferences(ClientConfig.applicationCallbacks.getApplicationInstance()).edit(); if (token != null) { @@ -111,8 +106,7 @@ public class FlattrUtils { } public static void deleteToken() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Deleting flattr token"); + Log.d(TAG, "Deleting flattr token"); storeToken(null); } @@ -169,15 +163,11 @@ public class FlattrUtils { } } - if (BuildConfig.DEBUG) { - Log.d(TAG, "Got my flattrs list of length " + Integer.toString(myFlattrs.size()) + " comparison date" + firstOfMonthDate); - - for (Flattr fl : myFlattrs) { - Thing thing = fl.getThing(); - Log.d(TAG, "Flattr thing: " + fl.getThingId() + " name: " + thing.getTitle() + " url: " + thing.getUrl() + " on: " + fl.getCreated()); - } + Log.d(TAG, "Got my flattrs list of length " + Integer.toString(myFlattrs.size()) + " comparison date" + firstOfMonthDate); + for (Flattr fl : myFlattrs) { + Thing thing = fl.getThing(); + Log.d(TAG, "Flattr thing: " + fl.getThingId() + " name: " + thing.getTitle() + " url: " + thing.getUrl() + " on: " + fl.getCreated()); } - } else { Log.e(TAG, "retrieveFlattrdThings was called with null access token"); } @@ -191,8 +181,7 @@ public class FlattrUtils { } public static void revokeAccessToken(Context context) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Revoking access token"); + Log.d(TAG, "Revoking access token"); deleteToken(); FlattrServiceCreator.deleteFlattrService(); showRevokeDialog(context); diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/gui/UndoBarController.java b/core/src/main/java/de/danoeh/antennapod/core/util/gui/UndoBarController.java index 23d8cf7c7..26c712af3 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/gui/UndoBarController.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/gui/UndoBarController.java @@ -88,7 +88,8 @@ public class UndoBarController<T> { } public void close() { - mHideHandler.post(mHideRunnable); + hideUndoBar(true); + mUndoListener.onHide(mUndoToken); } public void hideUndoBar(boolean immediate) { diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java index 17c752bb6..a0d12d3e7 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java @@ -32,13 +32,11 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import de.danoeh.antennapod.core.BuildConfig; import de.danoeh.antennapod.core.R; 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.preferences.UserPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer; import de.danoeh.antennapod.core.service.playback.PlayerStatus; @@ -174,8 +172,7 @@ public abstract class PlaybackController { * as the arguments of the launch intent. */ private void bindToService() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Trying to connect to service"); + Log.d(TAG, "Trying to connect to service"); AsyncTask<Void, Void, Intent> intentLoader = new AsyncTask<Void, Void, Intent>() { @Override protected Intent doInBackground(Void... voids) { @@ -211,8 +208,7 @@ public abstract class PlaybackController { * played media or null if no last played media could be found. */ private Intent getPlayLastPlayedMediaIntent() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Trying to restore last played media"); + Log.d(TAG, "Trying to restore last played media"); SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(activity.getApplicationContext()); long currentlyPlayingMedia = PlaybackPreferences @@ -240,8 +236,7 @@ public abstract class PlaybackController { return serviceIntent; } } - if (BuildConfig.DEBUG) - Log.d(TAG, "No last played media found"); + Log.d(TAG, "No last played media found"); return null; } @@ -253,8 +248,7 @@ public abstract class PlaybackController { || (positionObserverFuture != null && positionObserverFuture .isDone()) || positionObserverFuture == null) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Setting up position observer"); + Log.d(TAG, "Setting up position observer"); positionObserver = new MediaPositionObserver(); positionObserverFuture = schedExecutor.scheduleWithFixedDelay( positionObserver, MediaPositionObserver.WAITING_INTERVALL, @@ -266,8 +260,7 @@ public abstract class PlaybackController { private void cancelPositionObserver() { if (positionObserverFuture != null) { boolean result = positionObserverFuture.cancel(true); - if (BuildConfig.DEBUG) - Log.d(TAG, "PositionObserver cancelled. Result: " + result); + Log.d(TAG, "PositionObserver cancelled. Result: " + result); } } @@ -295,8 +288,7 @@ public abstract class PlaybackController { protected BroadcastReceiver statusUpdate = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (BuildConfig.DEBUG) - Log.d(TAG, "Received statusUpdate Intent."); + Log.d(TAG, "Received statusUpdate Intent."); if (isConnectedToPlaybackService()) { PlaybackServiceMediaPlayer.PSMPInfo info = playbackService.getPSMPInfo(); status = info.playerStatus; @@ -353,8 +345,7 @@ public abstract class PlaybackController { } } else { - if (BuildConfig.DEBUG) - Log.d(TAG, "Bad arguments. Won't handle intent"); + Log.d(TAG, "Bad arguments. Won't handle intent"); } } else { bindToService(); @@ -425,6 +416,7 @@ public abstract class PlaybackController { pauseResource = R.drawable.ic_av_pause_circle_outline_80dp; } + Log.d(TAG, "status: " + status.toString()); switch (status) { case ERROR: @@ -470,6 +462,7 @@ public abstract class PlaybackController { updatePlayButtonAppearance(playResource, playText); break; case SEEKING: + onPositionObserverUpdate(); postStatusMsg(R.string.player_seeking_msg); break; case INITIALIZED: @@ -505,8 +498,7 @@ public abstract class PlaybackController { * information has to be refreshed */ void queryService() { - if (BuildConfig.DEBUG) - Log.d(TAG, "Querying service info"); + Log.d(TAG, "Querying service info"); if (playbackService != null) { status = playbackService.getStatus(); media = playbackService.getPlayable(); @@ -614,28 +606,6 @@ public abstract class PlaybackController { }; } - public OnClickListener newOnRevButtonClickListener() { - return new OnClickListener() { - @Override - public void onClick(View v) { - if (status == PlayerStatus.PLAYING) { - playbackService.seekDelta(-UserPreferences.getSeekDeltaMs()); - } - } - }; - } - - public OnClickListener newOnFFButtonClickListener() { - return new OnClickListener() { - @Override - public void onClick(View v) { - if (status == PlayerStatus.PLAYING) { - playbackService.seekDelta(UserPreferences.getSeekDeltaMs()); - } - } - }; - } - public boolean serviceAvailable() { return playbackService != null; } |