summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/asynctask/DownloadObserver.java191
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/dialog/DownloadRequestErrorDialogCreator.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/event/DownloadEvent.java28
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java53
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java5
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java9
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java20
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java123
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java193
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java177
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java65
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/LongList.java22
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/flattr/FlattrUtils.java2
-rw-r--r--core/src/main/res/values-nb/strings.xml502
-rw-r--r--core/src/main/res/values-no/strings.xml31
-rw-r--r--core/src/main/res/values-v16/styles.xml2
-rw-r--r--core/src/main/res/values/colors.xml8
-rw-r--r--core/src/main/res/values/strings.xml6
-rw-r--r--core/src/main/res/values/styles.xml68
25 files changed, 946 insertions, 579 deletions
diff --git a/core/src/main/java/de/danoeh/antennapod/core/asynctask/DownloadObserver.java b/core/src/main/java/de/danoeh/antennapod/core/asynctask/DownloadObserver.java
deleted file mode 100644
index 2ce506c22..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/asynctask/DownloadObserver.java
+++ /dev/null
@@ -1,191 +0,0 @@
-package de.danoeh.antennapod.core.asynctask;
-
-import android.app.Activity;
-import android.content.*;
-import android.os.Handler;
-import android.os.IBinder;
-import android.util.Log;
-
-import org.apache.commons.lang3.Validate;
-
-import de.danoeh.antennapod.core.BuildConfig;
-import de.danoeh.antennapod.core.service.download.DownloadService;
-import de.danoeh.antennapod.core.service.download.Downloader;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * Provides access to the DownloadService's list of items that are currently being downloaded.
- * The DownloadObserver object should be created in the activity's onCreate() method. resume() and pause()
- * should be called in the activity's onResume() and onPause() methods
- */
-public class DownloadObserver {
- private static final String TAG = "DownloadObserver";
-
- /**
- * Time period between update notifications.
- */
- public static final int WAITING_INTERVAL_MS = 3000;
-
- private volatile Activity activity;
- private final Handler handler;
- private final Callback callback;
-
- private DownloadService downloadService = null;
- private AtomicBoolean mIsBound = new AtomicBoolean(false);
-
- private Thread refresherThread;
- private AtomicBoolean refresherThreadRunning = new AtomicBoolean(false);
-
- private AtomicInteger users = new AtomicInteger(0);
-
-
- /**
- * Creates a new download observer.
- *
- * @param activity Used for registering receivers
- * @param handler All callback methods are executed on this handler. The handler MUST run on the GUI thread.
- * @param callback Callback methods for posting content updates
- * @throws java.lang.IllegalArgumentException if one of the arguments is null.
- */
- public DownloadObserver(Activity activity, Handler handler, Callback callback) {
- Validate.notNull(activity);
- Validate.notNull(handler);
- Validate.notNull(callback);
-
- this.activity = activity;
- this.handler = handler;
- this.callback = callback;
- }
-
- public void onResume() {
- Log.d(TAG, "DownloadObserver resumed");
- if(users.getAndIncrement() == 0) {
- activity.registerReceiver(contentChangedReceiver, new IntentFilter(DownloadService.ACTION_DOWNLOADS_CONTENT_CHANGED));
- connectToDownloadService();
- }
- }
-
- public void onPause() {
- Log.d(TAG, "DownloadObserver paused");
- if(users.decrementAndGet() > 0) {
- return;
- }
- try {
- activity.unregisterReceiver(contentChangedReceiver);
- } catch (IllegalArgumentException e) {
- e.printStackTrace();
- }
- try {
- activity.unbindService(mConnection);
- } catch (IllegalArgumentException e) {
- e.printStackTrace();
- }
- stopRefresher();
- }
-
- private BroadcastReceiver contentChangedReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- // reconnect to DownloadService if connection has been closed
- if (downloadService == null) {
- connectToDownloadService();
- }
- if (downloadService != null) {
- callback.onContentChanged(downloadService.getDownloads());
- } else {
- // the service is gone, there are no more downloads.
- callback.onContentChanged(new ArrayList<Downloader>());
- }
- startRefresher();
- }
- };
-
- public interface Callback {
- void onContentChanged(List<Downloader> downloaderList);
- }
-
- private void connectToDownloadService() {
- activity.bindService(new Intent(activity, DownloadService.class), mConnection, 0);
- }
-
- private ServiceConnection mConnection = new ServiceConnection() {
- public void onServiceDisconnected(ComponentName className) {
- downloadService = null;
- mIsBound.set(false);
- stopRefresher();
- Log.i(TAG, "Closed connection with DownloadService.");
- }
-
- public void onServiceConnected(ComponentName name, IBinder service) {
- downloadService = ((DownloadService.LocalBinder) service)
- .getService();
- mIsBound.set(true);
- if (BuildConfig.DEBUG)
- Log.d(TAG, "Connection to service established");
- List<Downloader> downloaderList = downloadService.getDownloads();
- if (downloaderList != null && !downloaderList.isEmpty()) {
- callback.onContentChanged(downloaderList);
- startRefresher();
- }
- }
- };
-
- private void stopRefresher() {
- if (refresherThread != null) {
- refresherThread.interrupt();
- }
- }
-
- private void startRefresher() {
- if (refresherThread == null || refresherThread.isInterrupted()) {
- refresherThread = new Thread(new RefresherThread());
- refresherThread.start();
- }
- }
-
- private class RefresherThread implements Runnable {
-
- public void run() {
- refresherThreadRunning.set(true);
- while (!Thread.interrupted()) {
- try {
- Thread.sleep(WAITING_INTERVAL_MS);
- } catch (InterruptedException e) {
- Log.d(TAG, "Refresher thread was interrupted");
- }
- if (mIsBound.get()) {
- postUpdate();
- }
- }
- refresherThreadRunning.set(false);
- }
-
- private void postUpdate() {
- handler.post(new Runnable() {
- @Override
- public void run() {
- if (downloadService != null) {
- List<Downloader> downloaderList = downloadService.getDownloads();
- callback.onContentChanged(downloaderList);
- if (downloaderList == null || downloaderList.isEmpty()) {
- Thread.currentThread().interrupt();
- }
- } else {
- callback.onContentChanged(new ArrayList<Downloader>());
- }
- }
- });
- }
- }
-
- public void setActivity(Activity activity) {
- Validate.notNull(activity);
- this.activity = activity;
- }
-
-}
-
diff --git a/core/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java b/core/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java
index fea2bbb2b..abb75e5e7 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/dialog/ConfirmationDialog.java
@@ -1,8 +1,8 @@
package de.danoeh.antennapod.core.dialog;
-import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
+import android.support.v7.app.AlertDialog;
import android.util.Log;
import de.danoeh.antennapod.core.R;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/dialog/DownloadRequestErrorDialogCreator.java b/core/src/main/java/de/danoeh/antennapod/core/dialog/DownloadRequestErrorDialogCreator.java
index 3d174bd8e..b7e79431d 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/dialog/DownloadRequestErrorDialogCreator.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/dialog/DownloadRequestErrorDialogCreator.java
@@ -1,8 +1,9 @@
package de.danoeh.antennapod.core.dialog;
-import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
+import android.support.v7.app.AlertDialog;
+
import de.danoeh.antennapod.core.R;
/** Creates Alert Dialogs if a DownloadRequestException has happened. */
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/DownloadEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/DownloadEvent.java
new file mode 100644
index 000000000..124fd3e64
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/event/DownloadEvent.java
@@ -0,0 +1,28 @@
+package de.danoeh.antennapod.core.event;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import de.danoeh.antennapod.core.service.download.Downloader;
+
+public class DownloadEvent {
+
+ public final DownloaderUpdate update;
+
+ private DownloadEvent(DownloaderUpdate downloader) {
+ this.update = downloader;
+ }
+
+ public static DownloadEvent refresh(List<Downloader> list) {
+ list = new ArrayList<>(list);
+ DownloaderUpdate update = new DownloaderUpdate(list);
+ return new DownloadEvent(update);
+ }
+
+ @Override
+ public String toString() {
+ return "DownloadEvent{" +
+ "update=" + update +
+ '}';
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java b/core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java
new file mode 100644
index 000000000..dcb033267
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java
@@ -0,0 +1,53 @@
+package de.danoeh.antennapod.core.event;
+
+import java.util.Arrays;
+import java.util.List;
+
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.service.download.Downloader;
+import de.danoeh.antennapod.core.util.LongList;
+
+public class DownloaderUpdate {
+
+ /* Downloaders that are currently running */
+ public final List<Downloader> downloaders;
+
+ /**
+ * IDs of feeds that are currently being downloaded
+ * Often used to show some progress wheel in the action bar
+ */
+ public final long[] feedIds;
+
+ /**
+ * IDs of feed media that are currently being downloaded
+ * Can be used to show and update download progress bars
+ */
+ public final long[] mediaIds;
+
+ public DownloaderUpdate(List<Downloader> downloaders) {
+ this.downloaders = downloaders;
+ LongList feedIds1 = new LongList(), mediaIds1 = new LongList();
+ for(Downloader d1 : downloaders) {
+ int type = d1.getDownloadRequest().getFeedfileType();
+ long id = d1.getDownloadRequest().getFeedfileId();
+ if(type == Feed.FEEDFILETYPE_FEED) {
+ feedIds1.add(id);
+ } else if(type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
+ mediaIds1.add(id);
+ }
+ }
+
+ this.feedIds = feedIds1.toArray();
+ this.mediaIds = mediaIds1.toArray();
+ }
+
+ @Override
+ public String toString() {
+ return "DownloaderUpdate{" +
+ "downloaders=" + downloaders +
+ ", feedIds=" + Arrays.toString(feedIds) +
+ ", mediaIds=" + Arrays.toString(mediaIds) +
+ '}';
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java
index a4a79187e..7ff241456 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java
@@ -6,6 +6,7 @@ import android.support.annotation.NonNull;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
+import java.util.Arrays;
import java.util.List;
import de.danoeh.antennapod.core.feed.FeedItem;
@@ -32,6 +33,10 @@ public class FeedItemEvent {
return new FeedItemEvent(Action.UPDATE, items);
}
+ public static FeedItemEvent updated(FeedItem... items) {
+ return new FeedItemEvent(Action.UPDATE, Arrays.asList(items));
+ }
+
@Override
public String toString() {
return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE)
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java b/core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java
index 2667a2e12..3425e8a8e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/EventDistributor.java
@@ -26,7 +26,6 @@ public class EventDistributor extends Observable {
public static final int UNREAD_ITEMS_UPDATE = 2;
public static final int DOWNLOADLOG_UPDATE = 8;
public static final int PLAYBACK_HISTORY_UPDATE = 16;
- public static final int DOWNLOAD_QUEUED = 32;
public static final int DOWNLOAD_HANDLED = 64;
public static final int PLAYER_STATUS_UPDATE = 128;
@@ -88,10 +87,6 @@ public class EventDistributor extends Observable {
Validate.isInstanceOf(EventListener.class, observer);
}
- public void sendDownloadQueuedBroadcast() {
- addEvent(DOWNLOAD_QUEUED);
- }
-
public void sendUnreadItemsUpdateBroadcast() {
addEvent(UNREAD_ITEMS_UPDATE);
}
@@ -108,10 +103,6 @@ public class EventDistributor extends Observable {
addEvent(DOWNLOADLOG_UPDATE);
}
- public void sendDownloadHandledBroadcast() {
- addEvent(DOWNLOAD_HANDLED);
- }
-
public void sendPlayerStatusUpdateBroadcast() { addEvent(PLAYER_STATUS_UPDATE); }
public static abstract class EventListener implements Observer {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java
index cd865731b..ee13bef25 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/gpoddernet/model/GpodnetTag.java
@@ -20,11 +20,10 @@ public class GpodnetTag implements Parcelable {
this.usage = usage;
}
- public static GpodnetTag createFromParcel(Parcel in) {
- final String title = in.readString();
- final String tag = in.readString();
- final int usage = in.readInt();
- return new GpodnetTag(title, tag, usage);
+ protected GpodnetTag(Parcel in) {
+ title = in.readString();
+ tag = in.readString();
+ usage = in.readInt();
}
@Override
@@ -56,5 +55,16 @@ public class GpodnetTag implements Parcelable {
dest.writeInt(usage);
}
+ public static final Creator<GpodnetTag> CREATOR = new Creator<GpodnetTag>() {
+ @Override
+ public GpodnetTag createFromParcel(Parcel in) {
+ return new GpodnetTag(in);
+ }
+
+ @Override
+ public GpodnetTag[] newArray(int size) {
+ return new GpodnetTag[size];
+ }
+ };
}
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 d3c4d3ffd..6de546d3b 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
@@ -565,7 +565,7 @@ public class UserPreferences {
}
public static void setDataFolder(String dir) {
- Log.d(TAG, "Result from DirectoryChooser: " + dir);
+ Log.d(TAG, "setDataFolder(dir: " + dir + ")");
prefs.edit()
.putString(PREF_DATA_FOLDER, dir)
.apply();
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java
index 9aff7489b..5deb5e501 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java
@@ -1,7 +1,7 @@
package de.danoeh.antennapod.core.service.download;
-import android.support.annotation.NonNull;
import android.os.Build;
+import android.support.annotation.NonNull;
import android.util.Log;
import com.squareup.okhttp.OkHttpClient;
@@ -18,8 +18,6 @@ import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
-import de.danoeh.antennapod.core.BuildConfig;
-
/**
* Provides access to a HttpClient singleton.
*/
@@ -151,7 +149,7 @@ public class AntennapodHttpClient {
}
private void configureSocket(SSLSocket s) {
- s.setEnabledProtocols(new String[] { "TLSv1.2", "TLSv1.1" } );
+ s.setEnabledProtocols(new String[] { "TLSv1.2", "TLSv1.1", "TLSv1" } );
}
}
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 0698107a7..677765e92 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
@@ -53,7 +53,8 @@ import javax.xml.parsers.ParserConfigurationException;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
-import de.danoeh.antennapod.core.feed.EventDistributor;
+import de.danoeh.antennapod.core.event.DownloadEvent;
+import de.danoeh.antennapod.core.event.FeedItemEvent;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedImage;
import de.danoeh.antennapod.core.feed.FeedItem;
@@ -74,6 +75,7 @@ import de.danoeh.antennapod.core.syndication.handler.UnsupportedFeedtypeExceptio
import de.danoeh.antennapod.core.util.ChapterUtils;
import de.danoeh.antennapod.core.util.DownloadError;
import de.danoeh.antennapod.core.util.InvalidFeedException;
+import de.greenrobot.event.EventBus;
/**
* Manages the download of feedfiles in the app. Downloads can be enqueued viathe startService intent.
@@ -102,12 +104,6 @@ public class DownloadService extends Service {
public static final String EXTRA_DOWNLOAD_URL = "downloadUrl";
/**
- * Sent by the DownloadService when the content of the downloads list
- * changes.
- */
- public static final String ACTION_DOWNLOADS_CONTENT_CHANGED = "action.de.danoeh.antennapod.core.service.downloadsContentChanged";
-
- /**
* Extra for ACTION_ENQUEUE_DOWNLOAD intent.
*/
public static final String EXTRA_REQUEST = "request";
@@ -155,6 +151,8 @@ public class DownloadService extends Service {
private static final int SCHED_EX_POOL_SIZE = 1;
private ScheduledThreadPoolExecutor schedExecutor;
+ private Handler postHandler = new Handler();
+
private final IBinder mBinder = new LocalBinder();
public class LocalBinder extends Binder {
@@ -180,10 +178,7 @@ public class DownloadService extends Service {
final int type = status.getFeedfileType();
if (successful) {
if (type == Feed.FEEDFILETYPE_FEED) {
- handleCompletedFeedDownload(downloader
- .getDownloadRequest());
- } else if (type == FeedImage.FEEDFILETYPE_FEEDIMAGE) {
- handleCompletedImageDownload(status, downloader.getDownloadRequest());
+ handleCompletedFeedDownload(downloader.getDownloadRequest());
} else if (type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
handleCompletedFeedMediaDownload(status, downloader.getDownloadRequest());
}
@@ -202,9 +197,22 @@ public class DownloadService extends Service {
Log.e(TAG, "Download failed");
saveDownloadStatus(status);
handleFailedDownload(status, downloader.getDownloadRequest());
+
+ // to make lists reload the failed item, we fake an item update
+ if(type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
+ long id = status.getFeedfileId();
+ FeedMedia media = DBReader.getFeedMedia(id);
+ EventBus.getDefault().post(FeedItemEvent.updated(media.getItem()));
+ }
+ }
+ } else {
+ // if FeedMedia download has been canceled, fake FeedItem update
+ // so that lists reload that it
+ if(status.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
+ FeedMedia media = DBReader.getFeedMedia(status.getFeedfileId());
+ EventBus.getDefault().post(FeedItemEvent.updated(media.getItem()));
}
}
- sendDownloadHandledIntent();
queryDownloadsAsync();
}
} catch (InterruptedException e) {
@@ -306,6 +314,9 @@ public class DownloadService extends Service {
updateReport();
}
+ postHandler.removeCallbacks(postDownloaderTask);
+ EventBus.getDefault().postSticky(DownloadEvent.refresh(Collections.emptyList()));
+
stopForeground(true);
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
nm.cancel(NOTIFICATION_ID);
@@ -407,15 +418,14 @@ public class DownloadService extends Service {
} else {
Log.e(TAG, "Could not cancel download with url " + url);
}
- sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
+ postDownloaders();
} else if (StringUtils.equals(intent.getAction(), ACTION_CANCEL_ALL_DOWNLOADS)) {
for (Downloader d : downloads) {
d.cancel();
Log.d(TAG, "Cancelled all downloads");
}
- sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
-
+ postDownloaders();
}
queryDownloads();
}
@@ -434,13 +444,14 @@ public class DownloadService extends Service {
if (downloader != null) {
numberOfDownloads.incrementAndGet();
// smaller rss feeds before bigger media files
- if(request.getFeedfileId() == Feed.FEEDFILETYPE_FEED) {
+ if(request.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
downloads.add(0, downloader);
} else {
downloads.add(downloader);
}
downloadExecutor.submit(downloader);
- sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
+
+ postDownloaders();
}
queryDownloads();
@@ -471,7 +482,7 @@ public class DownloadService extends Service {
boolean rc = downloads.remove(d);
Log.d(TAG, "Result of downloads.remove: " + rc);
DownloadRequester.getInstance().removeDownload(d.getDownloadRequest());
- sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
+ postDownloaders();
}
});
}
@@ -487,10 +498,6 @@ public class DownloadService extends Service {
DBWriter.addDownloadStatus(status);
}
- private void sendDownloadHandledIntent() {
- EventDistributor.getInstance().sendDownloadHandledBroadcast();
- }
-
/**
* 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
@@ -607,14 +614,6 @@ public class DownloadService extends Service {
}
/**
- * Is called whenever a Feed-Image is downloaded
- */
- private void handleCompletedImageDownload(DownloadStatus status, DownloadRequest request) {
- Log.d(TAG, "Handling completed Image Download");
- syncExecutor.execute(new ImageHandlerThread(status, request));
- }
-
- /**
* Is called whenever a FeedMedia is downloaded.
*/
private void handleCompletedFeedMediaDownload(DownloadStatus status, DownloadRequest request) {
@@ -767,8 +766,6 @@ public class DownloadService extends Service {
numberOfDownloads.decrementAndGet();
}
- sendDownloadHandledIntent();
-
queryDownloadsAsync();
}
});
@@ -1013,40 +1010,6 @@ public class DownloadService extends Service {
}
/**
- * Handles a completed image download.
- */
- class ImageHandlerThread implements Runnable {
-
- private DownloadRequest request;
- private DownloadStatus status;
-
- public ImageHandlerThread(DownloadStatus status, DownloadRequest request) {
- Validate.notNull(status);
- Validate.notNull(request);
-
- this.status = status;
- this.request = request;
- }
-
- @Override
- public void run() {
- FeedImage image = DBReader.getFeedImage(request.getFeedfileId());
- if (image == null) {
- throw new IllegalStateException("Could not find downloaded image in database");
- }
-
- image.setFile_url(request.getDestination());
- image.setDownloaded(true);
-
- saveDownloadStatus(status);
- sendDownloadHandledIntent();
- DBWriter.setFeedImage(image);
- numberOfDownloads.decrementAndGet();
- queryDownloadsAsync();
- }
- }
-
- /**
* Handles a completed media download.
*/
class MediaHandlerThread implements Runnable {
@@ -1064,8 +1027,7 @@ public class DownloadService extends Service {
@Override
public void run() {
- FeedMedia media = DBReader.getFeedMedia(
- request.getFeedfileId());
+ FeedMedia media = DBReader.getFeedMedia(request.getFeedfileId());
if (media == null) {
throw new IllegalStateException(
"Could not find downloaded media object in database");
@@ -1121,7 +1083,6 @@ public class DownloadService extends Service {
}
saveDownloadStatus(status);
- sendDownloadHandledIntent();
if(GpodnetPreferences.loggedIn()) {
FeedItem item = media.getItem();
@@ -1174,16 +1135,24 @@ public class DownloadService extends Service {
}
}
- public List<Downloader> getDownloads() {
- if (downloads == null) {
- // this is unusual, but it should be OK, we'll return
- // an empty list to make it easy for people
- return new ArrayList<Downloader>();
+
+ private long lastPost = 0;
+
+ final Runnable postDownloaderTask = new Runnable() {
+ @Override
+ public void run() {
+ List<Downloader> list = Collections.unmodifiableList(downloads);
+ EventBus.getDefault().postSticky(DownloadEvent.refresh(list));
+ postHandler.postDelayed(postDownloaderTask, 1500);
}
+ };
- // return a copy of downloads, but the copy doesn't need to be synchronized.
- synchronized (downloads) {
- return new ArrayList<Downloader>(downloads);
+ private void postDownloaders() {
+ long now = System.currentTimeMillis();
+ if(now - lastPost >= 250) {
+ postHandler.removeCallbacks(postDownloaderTask);
+ postDownloaderTask.run();
+ lastPost = now;
}
}
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 f6c71daa7..b136de47f 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
@@ -16,6 +16,7 @@ import android.support.v4.media.session.PlaybackStateCompat;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.Pair;
+import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.SurfaceHolder;
@@ -116,9 +117,19 @@ public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPre
PendingIntent buttonReceiverIntent = PendingIntent.getBroadcast(context, 0, mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT);
mediaSession = new MediaSessionCompat(context, TAG, eventReceiver, buttonReceiverIntent);
- mediaSession.setCallback(sessionCallback);
- mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
- mediaSession.setActive(true);
+
+ try {
+ mediaSession.setCallback(sessionCallback);
+ mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
+ mediaSession.setActive(true);
+ } catch (NullPointerException npe) {
+ // on some devices (Huawei) setting active can cause a NullPointerException
+ // even with correct use of the api.
+ // See http://stackoverflow.com/questions/31556679/android-huawei-mediassessioncompat
+ // and https://plus.google.com/+IanLake/posts/YgdTkKFxz7d
+ Log.e(TAG, "NullPointerException while setting up MediaSession");
+ npe.printStackTrace();
+ }
mediaPlayer = null;
statusBeforeSeeking = null;
@@ -1172,129 +1183,99 @@ public class PlaybackServiceMediaPlayer implements SharedPreferences.OnSharedPre
private final MediaSessionCompat.Callback sessionCallback = new MediaSessionCompat.Callback() {
- @Override
- public void onPlay() {
- if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PREPARED) {
- resume();
- } else if (playerStatus == PlayerStatus.INITIALIZED) {
- setStartWhenPrepared(true);
- prepare();
- }
- }
-
- @Override
- public void onPause() {
- super.onPause();
- if (playerStatus == PlayerStatus.PLAYING) {
- pause(false, true);
- }
- if (UserPreferences.isPersistNotify()) {
- pause(false, true);
- } else {
- pause(true, true);
- }
- }
-
- @Override
- public void onSkipToNext() {
- super.onSkipToNext();
- endPlayback(true);
- }
-
- @Override
- public void onFastForward() {
- super.onFastForward();
- seekDelta(UserPreferences.getFastFowardSecs() * 1000);
- }
-
- @Override
- public void onRewind() {
- super.onRewind();
- seekDelta(-UserPreferences.getRewindSecs() * 1000);
- }
-
- @Override
- public void onSeekTo(long pos) {
- super.onSeekTo(pos);
- seekTo((int) pos);
- }
+ private static final String TAG = "MediaSessionCompat";
@Override
public boolean onMediaButtonEvent(final Intent mediaButton) {
- Log.d(TAG, "GOT MediaButton EVENT");
+ Log.d(TAG, "onMediaButtonEvent(" + mediaButton + ")");
if (mediaButton != null) {
KeyEvent keyEvent = (KeyEvent) mediaButton.getExtras().get(Intent.EXTRA_KEY_EVENT);
handleMediaKey(keyEvent);
}
- return super.onMediaButtonEvent(mediaButton);
+ return false;
}
};
- public boolean handleMediaKey(KeyEvent event) {
- if (event != null
- && event.getAction() == KeyEvent.ACTION_DOWN
- && event.getRepeatCount() == 0) {
- switch (event.getKeyCode()) {
- case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
- case KeyEvent.KEYCODE_HEADSETHOOK:
- {
- Log.d(TAG, "Received Play/Pause event from RemoteControlClient");
- if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PREPARED) {
- resume();
- } else if (playerStatus == PlayerStatus.INITIALIZED) {
- setStartWhenPrepared(true);
- prepare();
- } else if (playerStatus == PlayerStatus.PLAYING) {
- pause(false, true);
- if (UserPreferences.isPersistNotify()) {
- pause(false, true);
- } else {
- pause(true, true);
- }
- }
- return true;
- }
- case KeyEvent.KEYCODE_MEDIA_PLAY:
- {
- Log.d(TAG, "Received Play event from RemoteControlClient");
- if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PREPARED) {
- resume();
- } else if (playerStatus == PlayerStatus.INITIALIZED) {
- setStartWhenPrepared(true);
- prepare();
- }
- return true;
- }
- case KeyEvent.KEYCODE_MEDIA_PAUSE:
- {
- Log.d(TAG, "Received Pause event from RemoteControlClient");
- if (playerStatus == PlayerStatus.PLAYING) {
- pause(false, true);
- }
+ public boolean handleMediaKey(KeyEvent event) {
+ Log.d(TAG, "handleMediaKey(" + event +")");
+ if (event != null
+ && event.getAction() == KeyEvent.ACTION_DOWN
+ && event.getRepeatCount() == 0) {
+ switch (event.getKeyCode()) {
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_HEADSETHOOK: {
+ Log.d(TAG, "Received Play/Pause event from RemoteControlClient");
+ if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PREPARED) {
+ resume();
+ } else if (playerStatus == PlayerStatus.INITIALIZED) {
+ setStartWhenPrepared(true);
+ prepare();
+ } else if (playerStatus == PlayerStatus.PLAYING) {
+ pause(false, true);
if (UserPreferences.isPersistNotify()) {
pause(false, true);
} else {
pause(true, true);
}
- return true;
}
- case KeyEvent.KEYCODE_MEDIA_STOP:
- {
- Log.d(TAG, "Received Stop event from RemoteControlClient");
- stop();
- return true;
+ return true;
+ }
+ case KeyEvent.KEYCODE_MEDIA_PLAY: {
+ Log.d(TAG, "Received Play event from RemoteControlClient");
+ if (playerStatus == PlayerStatus.PAUSED || playerStatus == PlayerStatus.PREPARED) {
+ resume();
+ } else if (playerStatus == PlayerStatus.INITIALIZED) {
+ setStartWhenPrepared(true);
+ prepare();
}
- case KeyEvent.KEYCODE_MEDIA_NEXT:
- {
- Log.d(TAG, "Received next event from RemoteControlClient");
+ return true;
+ }
+ case KeyEvent.KEYCODE_MEDIA_PAUSE: {
+ Log.d(TAG, "Received Pause event from RemoteControlClient");
+ if (playerStatus == PlayerStatus.PLAYING) {
+ pause(false, true);
+ }
+ if (UserPreferences.isPersistNotify()) {
+ pause(false, true);
+ } else {
+ pause(true, true);
+ }
+ return true;
+ }
+ case KeyEvent.KEYCODE_MEDIA_STOP: {
+ Log.d(TAG, "Received Stop event from RemoteControlClient");
+ stop();
+ return true;
+ }
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS: {
+ seekDelta(-UserPreferences.getRewindSecs() * 1000);
+ return true;
+ }
+ case KeyEvent.KEYCODE_MEDIA_REWIND: {
+ seekDelta(-UserPreferences.getRewindSecs() * 1000);
+ return true;
+ }
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
+ seekDelta(UserPreferences.getFastFowardSecs() * 1000);
+ return true;
+ }
+ case KeyEvent.KEYCODE_MEDIA_NEXT: {
+ if(event.getSource() == InputDevice.SOURCE_CLASS_NONE) {
+ // assume the skip command comes from a notification or the lockscreen
+ // a >| skip button should actually skip
endPlayback(true);
- return true;
+ } else {
+ // assume skip command comes from a (bluetooth) media button
+ // user actually wants to fast-forward
+ seekDelta(UserPreferences.getFastFowardSecs() * 1000);
}
- default:
- Log.d(TAG, "Unhandled key code: " + event.getKeyCode());
- break;
+ return true;
}
+ default:
+ Log.d(TAG, "Unhandled key code: " + event.getKeyCode());
+ break;
}
- return false;
}
+ return false;
+ }
}
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 9c3ff4413..961f923e9 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
@@ -31,7 +31,7 @@ import de.greenrobot.event.EventBus;
* to notify the PlaybackService about updates from the running tasks.
*/
public class PlaybackServiceTaskManager {
- private static final String TAG = "PlaybackServiceTaskManager";
+ private static final String TAG = "PlaybackServiceTaskMgr";
/**
* Update interval of position saver in milliseconds.
@@ -81,6 +81,7 @@ public class PlaybackServiceTaskManager {
}
public void onEvent(QueueEvent event) {
+ Log.d(TAG, "onEvent(QueueEvent " + event +")");
cancelQueueLoader();
loadQueue();
}
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 a422a3b0c..c0a30f740 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
@@ -3,13 +3,13 @@ package de.danoeh.antennapod.core.storage;
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.HashMap;
import java.util.List;
+import java.util.Map;
import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.feed.Feed;
@@ -160,7 +160,7 @@ public final class DBReader {
* The method does NOT change the items-attribute of the feed.
*/
public static List<FeedItem> getFeedItemList(final Feed feed) {
- Log.d(TAG, "Extracting Feeditems of feed " + feed.getTitle());
+ Log.d(TAG, "getFeedItemList() called with: " + "feed = [" + feed + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
@@ -169,11 +169,10 @@ public final class DBReader {
List<FeedItem> items = extractItemlistFromCursor(adapter,
itemlistCursor);
itemlistCursor.close();
+ adapter.close();
Collections.sort(items, new FeedItemPubdateComparator());
- adapter.close();
-
for (FeedItem item : items) {
item.setFeed(feed);
}
@@ -182,6 +181,7 @@ public final class DBReader {
}
public static List<FeedItem> extractItemlistFromCursor(Cursor itemlistCursor) {
+ Log.d(TAG, "extractItemlistFromCursor() called with: " + "itemlistCursor = [" + itemlistCursor + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
List<FeedItem> result = extractItemlistFromCursor(adapter, itemlistCursor);
@@ -189,53 +189,61 @@ public final class DBReader {
return result;
}
- private static List<FeedItem> extractItemlistFromCursor(
- PodDBAdapter adapter, Cursor itemlistCursor) {
- ArrayList<String> itemIds = new ArrayList<>();
- List<FeedItem> items = new ArrayList<>(itemlistCursor.getCount());
+ private static List<FeedItem> extractItemlistFromCursor(PodDBAdapter adapter,
+ Cursor cursor) {
+ List<FeedItem> result = new ArrayList<>(cursor.getCount());
- if (itemlistCursor.moveToFirst()) {
+ LongList imageIds = new LongList(cursor.getCount());
+ LongList itemIds = new LongList(cursor.getCount());
+ if (cursor.moveToFirst()) {
do {
- int indexImage = itemlistCursor.getColumnIndex(PodDBAdapter.KEY_IMAGE);
- long imageId = itemlistCursor.getLong(indexImage);
- FeedImage image = null;
- if (imageId != 0) {
- image = getFeedImage(adapter, imageId);
- }
+ int indexImage = cursor.getColumnIndex(PodDBAdapter.KEY_IMAGE);
+ long imageId = cursor.getLong(indexImage);
+ imageIds.add(imageId);
- FeedItem item = FeedItem.fromCursor(itemlistCursor);
+ FeedItem item = FeedItem.fromCursor(cursor);
+ result.add(item);
+ itemIds.add(item.getId());
+ } while (cursor.moveToNext());
+ Map<Long,FeedImage> images = getFeedImages(adapter, imageIds.toArray());
+ Map<Long,FeedMedia> medias = getFeedMedia(adapter, itemIds.toArray());
+ for(int i=0; i < result.size(); i++) {
+ FeedItem item = result.get(i);
+ long imageId = imageIds.get(i);
+ FeedImage image = images.get(imageId);
item.setImage(image);
-
- itemIds.add(String.valueOf(item.getId()));
-
- items.add(item);
- } while (itemlistCursor.moveToNext());
+ FeedMedia media = medias.get(item.getId());
+ item.setMedia(media);
+ if(media != null) {
+ media.setItem(item);
+ }
+ }
}
-
- extractMediafromItemlist(adapter, items, itemIds);
- return items;
+ return result;
}
- private static void extractMediafromItemlist(PodDBAdapter adapter,
- List<FeedItem> items, ArrayList<String> itemIds) {
+ private static Map<Long,FeedMedia> getFeedMedia(PodDBAdapter adapter,
+ long... itemIds) {
- List<FeedItem> itemsCopy = new ArrayList<>(items);
- Cursor cursor = adapter.getFeedMediaCursorByItemID(itemIds
- .toArray(new String[itemIds.size()]));
- if (cursor.moveToFirst()) {
- do {
- int index = cursor.getColumnIndex(PodDBAdapter.KEY_FEEDITEM);
- long itemId = cursor.getLong(index);
- // find matching feed item
- FeedItem item = getMatchingItemForMedia(itemId, itemsCopy);
- if (item != null) {
+ ArrayList<String> ids = new ArrayList<>(itemIds.length);
+ for(long itemId : itemIds) {
+ ids.add(String.valueOf(itemId));
+ }
+ Map<Long,FeedMedia> result = new HashMap<>(itemIds.length);
+ Cursor cursor = adapter.getFeedMediaCursor(ids.toArray(new String[0]));
+ try {
+ if (cursor.moveToFirst()) {
+ do {
+ int index = cursor.getColumnIndex(PodDBAdapter.KEY_FEEDITEM);
+ long itemId = cursor.getLong(index);
FeedMedia media = FeedMedia.fromCursor(cursor);
- item.setMedia(media);
- item.getMedia().setItem(item);
- }
- } while (cursor.moveToNext());
+ result.put(itemId, media);
+ } while (cursor.moveToNext());
+ }
+ } finally {
+ cursor.close();
}
- cursor.close();
+ return result;
}
private static Feed extractFeedFromCursorRow(PodDBAdapter adapter,
@@ -261,16 +269,6 @@ public final class DBReader {
return feed;
}
- private static FeedItem getMatchingItemForMedia(long itemId,
- List<FeedItem> items) {
- for (FeedItem item : items) {
- if (item.getId() == itemId) {
- return item;
- }
- }
- return null;
- }
-
static List<FeedItem> getQueue(PodDBAdapter adapter) {
Log.d(TAG, "getQueue()");
Cursor itemlistCursor = adapter.getQueueCursor();
@@ -288,6 +286,7 @@ public final class DBReader {
* list in a {@link de.danoeh.antennapod.core.util.QueueAccess} object for easier access to the queue's properties.
*/
public static LongList getQueueIDList() {
+ Log.d(TAG, "getQueueIDList() called");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
LongList result = getQueueIDList(adapter);
@@ -296,7 +295,6 @@ public final class DBReader {
}
static LongList getQueueIDList(PodDBAdapter adapter) {
- adapter.open();
Cursor queueCursor = adapter.getQueueIDCursor();
LongList queueIds = new LongList(queueCursor.getCount());
@@ -317,7 +315,7 @@ public final class DBReader {
* list in a {@link de.danoeh.antennapod.core.util.QueueAccess} object for easier access to the queue's properties.
*/
public static List<FeedItem> getQueue() {
- Log.d(TAG, "getQueue()");
+ Log.d(TAG, "getQueue() called with: " + "");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
@@ -332,7 +330,7 @@ public final class DBReader {
* @return A list of FeedItems whose episdoe has been downloaded.
*/
public static List<FeedItem> getDownloadedItems() {
- Log.d(TAG, "Extracting downloaded items");
+ Log.d(TAG, "getDownloadedItems() called with: " + "");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
@@ -342,9 +340,10 @@ public final class DBReader {
itemlistCursor);
itemlistCursor.close();
loadAdditionalFeedItemListData(items);
+ adapter.close();
+
Collections.sort(items, new FeedItemPubdateComparator());
- adapter.close();
return items;
}
@@ -355,7 +354,7 @@ public final class DBReader {
* @return A list of FeedItems whose 'read'-attribute it set to false.
*/
public static List<FeedItem> getUnreadItemsList() {
- Log.d(TAG, "Extracting unread items list");
+ Log.d(TAG, "getUnreadItemsList() called");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
@@ -376,7 +375,7 @@ public final class DBReader {
* @return A list of FeedItems that are considered new.
*/
public static List<FeedItem> getNewItemsList() {
- Log.d(TAG, "getNewItemsList()");
+ Log.d(TAG, "getNewItemsList() called");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
@@ -393,7 +392,7 @@ public final class DBReader {
}
public static List<FeedItem> getFavoriteItemsList() {
- Log.d(TAG, "getFavoriteItemsList()");
+ Log.d(TAG, "getFavoriteItemsList() called");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
@@ -410,7 +409,9 @@ public final class DBReader {
}
static LongList getFavoriteIDList() {
- PodDBAdapter adapter = PodDBAdapter.getInstance().open();
+ Log.d(TAG, "getFavoriteIDList() called");
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
Cursor favoritesCursor = adapter.getFavoritesCursor();
LongList favoriteIDs = new LongList(favoritesCursor.getCount());
@@ -420,6 +421,7 @@ public final class DBReader {
} while (favoritesCursor.moveToNext());
}
favoritesCursor.close();
+ adapter.close();
return favoriteIDs;
}
@@ -429,7 +431,7 @@ public final class DBReader {
* @param limit The maximum number of episodes that should be loaded.
*/
public static List<FeedItem> getRecentlyPublishedEpisodes(int limit) {
- Log.d(TAG, "Extracting recently published items list");
+ Log.d(TAG, "getRecentlyPublishedEpisodes() called with: " + "limit = [" + limit + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
@@ -453,7 +455,7 @@ public final class DBReader {
* The size of the returned list is limited by {@link #PLAYBACK_HISTORY_SIZE}.
*/
public static List<FeedItem> getPlaybackHistory() {
- Log.d(TAG, "Loading playback history");
+ Log.d(TAG, "getPlaybackHistory() called");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
@@ -482,7 +484,7 @@ public final class DBReader {
* The size of the returned list is limited by {@link #DOWNLOAD_LOG_SIZE}.
*/
public static List<DownloadStatus> getDownloadLog() {
- Log.d(TAG, "Extracting DownloadLog");
+ Log.d(TAG, "getDownloadLog() called");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
@@ -496,6 +498,7 @@ public final class DBReader {
} while (logCursor.moveToNext());
}
logCursor.close();
+ adapter.close();
Collections.sort(downloadLog, new DownloadStatusComparator());
return downloadLog;
}
@@ -508,7 +511,7 @@ public final class DBReader {
* newest events first.
*/
public static List<DownloadStatus> getFeedDownloadLog(Feed feed) {
- Log.d(TAG, "getFeedDownloadLog(" + feed.toString() + ")");
+ Log.d(TAG, "getFeedDownloadLog() called with: " + "feed = [" + feed + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
@@ -522,6 +525,7 @@ public final class DBReader {
} while (cursor.moveToNext());
}
cursor.close();
+ adapter.close();
Collections.sort(downloadLog, new DownloadStatusComparator());
return downloadLog;
}
@@ -534,6 +538,7 @@ public final class DBReader {
* @return A list of FeedItemStatistics objects sorted alphabetically by their Feed's title.
*/
public static List<FeedItemStatistics> getFeedStatisticsList() {
+ Log.d(TAG, "getFeedStatisticsList() called");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
List<FeedItemStatistics> result = new ArrayList<>();
@@ -558,6 +563,7 @@ public final class DBReader {
* database and the items-attribute will be set correctly.
*/
public static Feed getFeed(final long feedId) {
+ Log.d(TAG, "getFeed() called with: " + "feedId = [" + feedId + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Feed result = getFeed(feedId, adapter);
@@ -566,7 +572,6 @@ public final class DBReader {
}
static Feed getFeed(final long feedId, PodDBAdapter adapter) {
- Log.d(TAG, "Loading feed with id " + feedId);
Feed feed = null;
Cursor feedCursor = adapter.getFeedCursor(feedId);
@@ -635,7 +640,7 @@ public final class DBReader {
* as well as chapter marks of the FeedItem will also be loaded from the database.
*/
public static FeedItem getFeedItem(final long itemId) {
- Log.d(TAG, "Loading feeditem with id " + itemId);
+ Log.d(TAG, "getFeedItem() called with: " + "itemId = [" + itemId + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
@@ -671,8 +676,7 @@ public final class DBReader {
* as well as chapter marks of the FeedItems will also be loaded from the database.
*/
public static List<FeedItem> getFeedItems(final long... itemIds) {
- Log.d(TAG, "Loading feeditem with ids: " + StringUtils.join(itemIds, ","));
-
+ Log.d(TAG, "getFeedItems() called with: " + "itemIds = [" + itemIds + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
List<FeedItem> items = getFeedItems(adapter, itemIds);
@@ -688,7 +692,7 @@ public final class DBReader {
* @return Credentials in format "<Username>:<Password>", empty String if no authorization given
*/
public static String getImageAuthentication(final String imageUrl) {
- Log.d(TAG, "Loading credentials for image with URL " + imageUrl);
+ Log.d(TAG, "getImageAuthentication() called with: " + "imageUrl = [" + imageUrl + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
@@ -728,7 +732,7 @@ public final class DBReader {
* as well as chapter marks of the FeedItem will also be loaded from the database.
*/
public static FeedItem getFeedItem(final String podcastUrl, final String episodeUrl) {
- Log.d(TAG, "Loading feeditem with podcast url " + podcastUrl + " and episode url " + episodeUrl);
+ Log.d(TAG, "getFeedItem() called with: " + "podcastUrl = [" + podcastUrl + "], episodeUrl = [" + episodeUrl + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
@@ -743,6 +747,7 @@ public final class DBReader {
* @param item The FeedItem
*/
public static void loadExtraInformationOfFeedItem(final FeedItem item) {
+ Log.d(TAG, "loadExtraInformationOfFeedItem() called with: " + "item = [" + item + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
Cursor extraCursor = adapter.getExtraInformationOfItem(item);
@@ -766,6 +771,7 @@ public final class DBReader {
* @param item The FeedItem
*/
public static void loadChaptersOfFeedItem(final FeedItem item) {
+ Log.d(TAG, "loadChaptersOfFeedItem() called with: " + "item = [" + item + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
loadChaptersOfFeedItem(adapter, item);
@@ -820,6 +826,7 @@ public final class DBReader {
* @return The number of downloaded episodes.
*/
public static int getNumberOfDownloadedEpisodes() {
+ Log.d(TAG, "getNumberOfDownloadedEpisodes() called with: " + "");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
final int result = adapter.getNumberOfDownloadedEpisodes();
@@ -834,6 +841,7 @@ public final class DBReader {
* @return The found object
*/
public static FeedImage getFeedImage(final long imageId) {
+ Log.d(TAG, "getFeedImage() called with: " + "imageId = [" + imageId + "]");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
FeedImage result = getFeedImage(adapter, imageId);
@@ -844,21 +852,34 @@ public final class DBReader {
/**
* Searches the DB for a FeedImage of the given id.
*
- * @param id The id of the object
+ * @param imageId The id of the object
* @return The found object
*/
- static FeedImage getFeedImage(PodDBAdapter adapter, final long id) {
- Cursor cursor = adapter.getImageCursor(id);
+ private static FeedImage getFeedImage(PodDBAdapter adapter, final long imageId) {
+ return getFeedImages(adapter, imageId).get(imageId);
+ }
+
+ /**
+ * Searches the DB for a FeedImage of the given id.
+ *
+ * @param ids The id of the object
+ * @return The found object
+ */
+ private static Map<Long,FeedImage> getFeedImages(PodDBAdapter adapter, final long... ids) {
+ Cursor cursor = adapter.getImageCursor(ids);
+ Map<Long, FeedImage> result = new HashMap<>(cursor.getCount());
try {
if ((cursor.getCount() == 0) || !cursor.moveToFirst()) {
- return null;
+ return Collections.emptyMap();
}
- FeedImage image = FeedImage.fromCursor(cursor);
- image.setId(id);
- return image;
+ do {
+ FeedImage image = FeedImage.fromCursor(cursor);
+ result.put(image.getId(), image);
+ } while(cursor.moveToNext());
} finally {
cursor.close();
}
+ return result;
}
/**
@@ -897,6 +918,7 @@ public final class DBReader {
* @return The flattr queue as a List.
*/
public static List<FlattrThing> getFlattrQueue() {
+ Log.d(TAG, "getFlattrQueue() called with: " + "");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
List<FlattrThing> result = new ArrayList<>();
@@ -927,6 +949,7 @@ public final class DBReader {
*
*/
public static NavDrawerData getNavDrawerData() {
+ Log.d(TAG, "getNavDrawerData() called with: " + "");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
List<Feed> feeds = getFeedList(adapter);
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 14683506e..49a62da8c 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
@@ -766,6 +766,7 @@ public class DBWriter {
adapter.open();
adapter.setSingleFeedItem(item);
adapter.close();
+ EventBus.getDefault().post(FeedItemEvent.updated(item));
});
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java
index b13f7a0cb..318060abc 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java
@@ -15,7 +15,6 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import de.danoeh.antennapod.core.BuildConfig;
-import de.danoeh.antennapod.core.feed.EventDistributor;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedFile;
import de.danoeh.antennapod.core.feed.FeedMedia;
@@ -85,7 +84,7 @@ public class DownloadRequester {
Intent launchIntent = new Intent(context, DownloadService.class);
launchIntent.putExtra(DownloadService.EXTRA_REQUEST, request);
context.startService(launchIntent);
- EventDistributor.getInstance().sendDownloadQueuedBroadcast();
+
return true;
}
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 6ccb1d226..c67fb956a 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
@@ -10,14 +10,15 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.media.MediaMetadataRetriever;
+import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
+import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import java.util.Arrays;
import java.util.List;
-import java.util.concurrent.atomic.AtomicInteger;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.event.ProgressEvent;
@@ -188,6 +189,15 @@ public class PodDBAdapter {
+ TABLE_NAME_FEED_ITEMS + "_" + KEY_IMAGE + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ KEY_IMAGE + ")";
+ public static final String CREATE_INDEX_FEEDITEMS_PUBDATE = "CREATE INDEX IF NOT EXISTS "
+ + TABLE_NAME_FEED_ITEMS + "_" + KEY_PUBDATE + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ + KEY_PUBDATE + ")";
+
+ public static final String CREATE_INDEX_FEEDITEMS_READ = "CREATE INDEX IF NOT EXISTS "
+ + TABLE_NAME_FEED_ITEMS + "_" + KEY_READ + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ + KEY_READ + ")";
+
+
public static final String CREATE_INDEX_QUEUE_FEEDITEM = "CREATE INDEX "
+ TABLE_NAME_QUEUE + "_" + KEY_FEEDITEM + " ON " + TABLE_NAME_QUEUE + " ("
+ KEY_FEEDITEM + ")";
@@ -270,10 +280,10 @@ public class PodDBAdapter {
KEY_CONTENT_ENCODED, KEY_FEED};
- private SQLiteDatabase db;
+ private static SQLiteDatabase db;
private static Context context;
private static PodDBHelper dbHelper;
- private static AtomicInteger counter = new AtomicInteger(0);
+ private static int counter = 0;
public static void init(Context context) {
PodDBAdapter.context = context.getApplicationContext();
@@ -288,12 +298,15 @@ public class PodDBAdapter {
private PodDBAdapter() {}
- public PodDBAdapter open() {
- counter.incrementAndGet();
+ public synchronized PodDBAdapter open() {
+ counter++;
if (db == null || !db.isOpen() || db.isReadOnly()) {
Log.v(TAG, "Opening DB");
try {
db = dbHelper.getWritableDatabase();
+ if(Build.VERSION.SDK_INT >= 11) {
+ db.enableWriteAheadLogging();
+ }
} catch (SQLException ex) {
Log.e(TAG, Log.getStackTraceString(ex));
db = dbHelper.getReadableDatabase();
@@ -302,12 +315,13 @@ public class PodDBAdapter {
return this;
}
- public void close() {
- if(counter.decrementAndGet() == 0) {
+ public synchronized void close() {
+ counter--;
+ if(counter == 0) {
Log.v(TAG, "Closing DB");
db.close();
+ db = null;
}
- db = null;
}
public static boolean deleteDatabase() {
@@ -990,14 +1004,16 @@ public class PodDBAdapter {
}
/**
- * Returns a cursor for a DB query in the FeedImages table for a given ID.
+ * Returns a cursor for a DB query in the FeedImages table for given IDs.
*
- * @param id ID of the FeedImage
+ * @param ids IDs of the FeedImages
* @return The cursor of the query
*/
- public final Cursor getImageCursor(final long id) {
- Cursor c = db.query(TABLE_NAME_FEED_IMAGES, null, KEY_ID + "=?",
- new String[]{String.valueOf(id)}, null, null, null);
+ public final Cursor getImageCursor(long... ids) {
+ String sql = "SELECT * FROM " + TABLE_NAME_FEED_IMAGES +
+ " WHERE " + KEY_ID + " IN (" + StringUtils.join(ids, ',') + ")";
+ Cursor c = db.rawQuery(sql, null);
+
return c;
}
@@ -1138,26 +1154,26 @@ public class PodDBAdapter {
return db.query(TABLE_NAME_FEED_MEDIA, null, KEY_ID + "=?", new String[]{String.valueOf(id)}, null, null, null);
}
- public final Cursor getFeedMediaCursorByItemID(String... mediaIds) {
- int length = mediaIds.length;
+ public final Cursor getFeedMediaCursor(String... itemIds) {
+ int length = itemIds.length;
if (length > IN_OPERATOR_MAXIMUM) {
Log.w(TAG, "Length of id array is larger than "
+ IN_OPERATOR_MAXIMUM + ". Creating multiple cursors");
int numCursors = (int) (((double) length) / (IN_OPERATOR_MAXIMUM)) + 1;
Cursor[] cursors = new Cursor[numCursors];
for (int i = 0; i < numCursors; i++) {
- int neededLength = 0;
- String[] parts = null;
+ int neededLength;
+ String[] parts;
final int elementsLeft = length - i * IN_OPERATOR_MAXIMUM;
if (elementsLeft >= IN_OPERATOR_MAXIMUM) {
neededLength = IN_OPERATOR_MAXIMUM;
- parts = Arrays.copyOfRange(mediaIds, i
+ parts = Arrays.copyOfRange(itemIds, i
* IN_OPERATOR_MAXIMUM, (i + 1)
* IN_OPERATOR_MAXIMUM);
} else {
neededLength = elementsLeft;
- parts = Arrays.copyOfRange(mediaIds, i
+ parts = Arrays.copyOfRange(itemIds, i
* IN_OPERATOR_MAXIMUM, (i * IN_OPERATOR_MAXIMUM)
+ neededLength);
}
@@ -1169,7 +1185,7 @@ public class PodDBAdapter {
return new MergeCursor(cursors);
} else {
return db.query(TABLE_NAME_FEED_MEDIA, null, KEY_FEEDITEM + " IN "
- + buildInOperator(length), mediaIds, null, null, null);
+ + buildInOperator(length), itemIds, null, null, null);
}
}
@@ -1442,7 +1458,7 @@ public class PodDBAdapter {
*/
private static class PodDBHelper extends SQLiteOpenHelper {
- private final static int VERSION = 1040002;
+ private final static int VERSION = 1040013;
private Context context;
@@ -1472,6 +1488,8 @@ public class PodDBAdapter {
db.execSQL(CREATE_INDEX_FEEDITEMS_FEED);
db.execSQL(CREATE_INDEX_FEEDITEMS_IMAGE);
+ db.execSQL(CREATE_INDEX_FEEDITEMS_PUBDATE);
+ db.execSQL(CREATE_INDEX_FEEDITEMS_READ);
db.execSQL(CREATE_INDEX_FEEDMEDIA_FEEDITEM);
db.execSQL(CREATE_INDEX_QUEUE_FEEDITEM);
db.execSQL(CREATE_INDEX_SIMPLECHAPTERS_FEEDITEM);
@@ -1680,6 +1698,11 @@ public class PodDBAdapter {
db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_MEDIA
+ " ADD COLUMN " + PodDBAdapter.KEY_LAST_PLAYED_TIME + " INTEGER DEFAULT 0");
}
+ if(oldVersion < 1040013) {
+ db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_PUBDATE);
+ db.execSQL(PodDBAdapter.CREATE_INDEX_FEEDITEMS_READ);
+ }
+
EventBus.getDefault().post(ProgressEvent.end());
}
}
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 10ffd4bec..6ed8b820e 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
@@ -177,6 +177,28 @@ public final class LongList {
}
/**
+ * Removes values from this list.
+ *
+ * @param values values to remove
+ */
+ public void removeAll(long[] values) {
+ for(long value : values) {
+ remove(value);
+ }
+ }
+
+ /**
+ * Removes values from this list.
+ *
+ * @param list List with values to remove
+ */
+ public void removeAll(LongList list) {
+ for(long value : list.values) {
+ remove(value);
+ }
+ }
+
+ /**
* Removes an element at a given index, shifting elements at greater
* indicies down one.
*
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 f37933876..318839e1d 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
@@ -1,6 +1,5 @@
package de.danoeh.antennapod.core.util.flattr;
-import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
@@ -8,6 +7,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.preference.PreferenceManager;
+import android.support.v7.app.AlertDialog;
import android.util.Log;
import org.apache.commons.lang3.StringUtils;
diff --git a/core/src/main/res/values-nb/strings.xml b/core/src/main/res/values-nb/strings.xml
new file mode 100644
index 000000000..59c04afb0
--- /dev/null
+++ b/core/src/main/res/values-nb/strings.xml
@@ -0,0 +1,502 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<resources xmlns:tools="http://schemas.android.com/tools">
+ <!--Activitiy and fragment titles-->
+ <string name="app_name">AntennaPod</string>
+ <string name="feeds_label">Strømmer</string>
+ <string name="add_feed_label">Legg til podcast</string>
+ <string name="podcasts_label">PODCASTER</string>
+ <string name="episodes_label">Episoder</string>
+ <string name="new_episodes_label">Nye episoder</string>
+ <string name="all_episodes_label">Alle episoder</string>
+ <string name="all_episodes_short_label">Alle</string>
+ <string name="favorite_episodes_label">Favoritter</string>
+ <string name="new_label">Nye</string>
+ <string name="waiting_list_label">Venteliste</string>
+ <string name="settings_label">Innstillinger</string>
+ <string name="add_new_feed_label">Legg til podcast</string>
+ <string name="downloads_label">Nedlastninger</string>
+ <string name="downloads_running_label">Kjører</string>
+ <string name="downloads_completed_label">Fullført</string>
+ <string name="downloads_log_label">Logg</string>
+ <string name="cancel_download_label">Avbryt\nLast ned</string>
+ <string name="playback_history_label">Avspillingshistorikk</string>
+ <string name="gpodnet_main_label">gpodder.net</string>
+ <string name="gpodnet_auth_label">gpodder.net-innlogging</string>
+ <!--New episodes fragment-->
+ <string name="recently_published_episodes_label">Nylig publisert</string>
+ <string name="episode_filter_label">Vis kun nye episoder</string>
+ <!--Main activity-->
+ <string name="drawer_open">Åpne menyen</string>
+ <string name="drawer_close">Lukk menyen</string>
+ <string name="drawer_preferences">Skuff-innstillinger</string>
+ <string name="drawer_feed_order_unplayed_episodes">Sorter på teller</string>
+ <string name="drawer_feed_order_alphabetical">Sorter alfabetisk</string>
+ <string name="drawer_feed_order_last_update">Sorter på utgivelsesdato</string>
+ <string name="drawer_feed_counter_new_unplayed">Antall nye og uavspilte episoder</string>
+ <string name="drawer_feed_counter_new">Antall nye episoder</string>
+ <string name="drawer_feed_counter_unplayed">Antall uavspilte episoder</string>
+ <string name="drawer_feed_counter_none">Ingen</string>
+ <!--Webview actions-->
+ <string name="open_in_browser_label">Åpne i nettleser</string>
+ <string name="copy_url_label">Kopier URL</string>
+ <string name="share_url_label">Del URL</string>
+ <string name="copied_url_msg">URL er kopiert til utklippstavlen</string>
+ <string name="go_to_position_label">Gå til denne posisjonen</string>
+ <!--Playback history-->
+ <string name="clear_history_label">Tøm historikk</string>
+ <!--Other-->
+ <string name="confirm_label">Bekreft</string>
+ <string name="cancel_label">Avbryt</string>
+ <string name="yes">Ja</string>
+ <string name="no">Nei</string>
+ <string name="author_label">Opphavsperson</string>
+ <string name="language_label">Språk</string>
+ <string name="url_label">URL</string>
+ <string name="podcast_settings_label">Innstillinger</string>
+ <string name="cover_label">Bilde</string>
+ <string name="error_label">Feil</string>
+ <string name="error_msg_prefix">Det oppsto en feil:</string>
+ <string name="refresh_label">Oppdater</string>
+ <string name="external_storage_error_msg">Finner ikke ekstern lagring. Sjekk at det eksterne lagringsmediet er montert.</string>
+ <string name="chapters_label">Kapittel</string>
+ <string name="shownotes_label">Shownotater</string>
+ <string name="description_label">Beskrivelse</string>
+ <string name="most_recent_prefix">Nyeste episode:\u0020</string>
+ <string name="episodes_suffix">\u0020episoder</string>
+ <string name="length_prefix">Lengde:\u0020</string>
+ <string name="size_prefix">Størrelse:\u0020</string>
+ <string name="processing_label">Behandler</string>
+ <string name="loading_label">Laster opp...</string>
+ <string name="save_username_password_label">Lagre brukernavn og passord</string>
+ <string name="close_label">Lukk</string>
+ <string name="retry_label">Prøv igjen</string>
+ <string name="auto_download_label">Inkluder i automatiske nedlastninger</string>
+ <string name="auto_download_apply_to_items_title">Angi for tidligere episoder</string>
+ <string name="auto_download_apply_to_items_message">Den nye <i>Automatisk nedlasting</i>-innstillingen vil automatisk aktiveres for nye episoder.\nØnsker du å aktivere den for tidligere utgitte episoder også?</string>
+ <string name="auto_delete_label">Automatisk sletting av episode\n(overstyr den globale standarden)</string>
+ <string name="parallel_downloads_suffix">\u0020samtidige nedlastinger</string>
+ <string name="feed_auto_download_global">Global</string>
+ <string name="feed_auto_download_always">Alltid</string>
+ <string name="feed_auto_download_never">Aldri</string>
+ <string name="send_label">Send ...</string>
+ <string name="episode_cleanup_never">AldriAldri</string>
+ <string name="episode_cleanup_queue_removal">Når ikke i kø</string>
+ <string name="episode_cleanup_after_listening">Etter den er ferdig</string>
+ <plurals name="episode_cleanup_days_after_listening">
+ <item quantity="one">1 dag etter fullført avspilling</item>
+ <item quantity="other">%d dager etter fullført avspilling</item>
+ </plurals>
+ <!--'Add Feed' Activity labels-->
+ <string name="feedurl_label">Strømmens URL</string>
+ <string name="etxtFeedurlHint">www.example.com/feed</string>
+ <string name="txtvfeedurl_label">Legg til en podcast via URL</string>
+ <string name="podcastdirectories_label">Finn podcast i katalog</string>
+ <string name="podcastdirectories_descr">Du kan søke etter nye podcaster på navn, kategori eller popularitet i gpodder.nets katalog eller på iTunes.</string>
+ <string name="browse_gpoddernet_label">Bla gjennom gpodder.net</string>
+ <!--Actions on feeds-->
+ <string name="mark_all_read_label">Marker alle som avspilt</string>
+ <string name="mark_all_read_msg">Marker alle episoder som avspilt</string>
+ <string name="mark_all_read_confirmation_msg">Vennligst bekreft at du ønsker å markere alle episoder som avspilt.</string>
+ <string name="mark_all_read_feed_confirmation_msg">Vennligst bekreft at du ønsker å markere alle episoder i denne strømmen som avspilt.</string>
+ <string name="mark_all_seen_label">Marker alle som sett</string>
+ <string name="show_info_label">Vis informasjon</string>
+ <string name="remove_feed_label">Fjern podcast</string>
+ <string name="share_label">Del ...</string>
+ <string name="share_link_label">Del lenke</string>
+ <string name="share_link_with_position_label">Del lenke med plassering</string>
+ <string name="share_feed_url_label">Del strømmens URL</string>
+ <string name="share_item_url_label">Del episodens URL</string>
+ <string name="share_item_url_with_position_label">Del episodens URL med posisjon</string>
+ <string name="feed_delete_confirmation_msg">Vil du virkelig slette denne strømmen og alle episodene av denne strømmen du har lastet ned?</string>
+ <string name="feed_remover_msg">Fjerner strøm</string>
+ <string name="load_complete_feed">Oppdater hele strømmen</string>
+ <string name="hide_episodes_title">Skjul episoder</string>
+ <string name="episode_actions">Lagre handlinger</string>
+ <string name="hide_unplayed_episodes_label">Ikke avspilt</string>
+ <string name="hide_paused_episodes_label">Pauset</string>
+ <string name="hide_played_episodes_label">Avspilt</string>
+ <string name="hide_queued_episodes_label">I kø</string>
+ <string name="hide_not_queued_episodes_label">Ikke i kø</string>
+ <string name="hide_downloaded_episodes_label">Nedlastet</string>
+ <string name="hide_not_downloaded_episodes_label">Ikke nedlastet</string>
+ <string name="filtered_label">Filtrert</string>
+ <string name="refresh_failed_msg">{fa-exclamation-circle} Siste oppdatering mislyktes</string>
+ <!--actions on feeditems-->
+ <string name="download_label">Last ned</string>
+ <string name="play_label">Spill</string>
+ <string name="pause_label">Pause</string>
+ <string name="stop_label">Stopp</string>
+ <string name="stream_label">Stream</string>
+ <string name="remove_label">Fjern</string>
+ <string name="remove_episode_lable">Fjern episode</string>
+ <string name="mark_read_label">Marker som avspilt</string>
+ <string name="marked_as_read_label">Marker som avspilt</string>
+ <string name="mark_unread_label">Marker som ikke avspilt</string>
+ <string name="add_to_queue_label">Legg til queue</string>
+ <string name="added_to_queue_label">Lagt til i kø</string>
+ <string name="remove_from_queue_label">Fjern fra queue</string>
+ <string name="add_to_favorite_label">Legg til i favoritter</string>
+ <string name="remove_from_favorite_label">Fjern fra favoritter</string>
+ <string name="visit_website_label">Besøk nettside</string>
+ <string name="support_label">Flattr\'e dette</string>
+ <string name="enqueue_all_new">Legg alle til queue</string>
+ <string name="download_all">Last ned alle</string>
+ <string name="skip_episode_label">Skip episode</string>
+ <string name="activate_auto_download">Aktiver automatisk nedlasting</string>
+ <string name="deactivate_auto_download">Deaktiver automatisk nedlasting</string>
+ <string name="reset_position">Tilbakestill avspillingsposisjon</string>
+ <string name="removed_item">Element fjernet</string>
+ <!--Download messages and labels-->
+ <string name="download_successful">nedlastning lyktes</string>
+ <string name="download_failed">mislyktes</string>
+ <string name="download_pending">Nedlastning venter</string>
+ <string name="download_running">Nedlasting pågår</string>
+ <string name="download_error_device_not_found">Lagringsenhet ikke funnet</string>
+ <string name="download_error_insufficient_space">Ikke nok plass</string>
+ <string name="download_error_file_error">Fil-feil</string>
+ <string name="download_error_http_data_error">HTTP-datafeil</string>
+ <string name="download_error_error_unknown">Ukjent feil</string>
+ <string name="download_error_parser_exception">Parser-unntak</string>
+ <string name="download_error_unsupported_type">Strøm-typen er ikke støttet</string>
+ <string name="download_error_connection_error">Tilkoblingsfeil</string>
+ <string name="download_error_unknown_host">Ukjent vert</string>
+ <string name="download_error_unauthorized">Autentiseringsfeil</string>
+ <string name="cancel_all_downloads_label">Avbryt alle nedlastninger</string>
+ <string name="download_canceled_msg">Nedlasting avbrutt</string>
+ <string name="download_canceled_autodownload_enabled_msg">Nedlasting avbrutt\n<i>Automatisk nedlasting</i> for dette elementet er deaktivert</string>
+ <string name="download_report_title">Nedlasting fullført med feilmeldinger</string>
+ <string name="download_report_content_title">Nedlastingsrapport</string>
+ <string name="download_error_malformed_url">Misformet URL</string>
+ <string name="download_error_io_error">IO feil</string>
+ <string name="download_error_request_error">Forespørselfeil</string>
+ <string name="download_error_db_access">Tilgangsfeil for database</string>
+ <string name="downloads_left">\u0020Nedlastninger igjen</string>
+ <string name="downloads_processing">Behandler nedlastninger</string>
+ <string name="download_notification_title">Laster ned data til podcast</string>
+ <string name="download_report_content">%1$d nedlastninger lyktes, %2$d mislyktes</string>
+ <string name="download_log_title_unknown">Ukjent tittel</string>
+ <string name="download_type_feed">Strøm</string>
+ <string name="download_type_media">Mediafil</string>
+ <string name="download_type_image">Bilde</string>
+ <string name="download_request_error_dialog_message_prefix">En feil oppsto under nedlastningen av filen:\u0020</string>
+ <string name="authentication_notification_title">Autentisering påkreves</string>
+ <string name="authentication_notification_msg">Ressursen du forespør krever brukernavn og passord</string>
+ <string name="confirm_mobile_download_dialog_title">Bekreft nedlasting over mobildata</string>
+ <string name="confirm_mobile_download_dialog_message_not_in_queue">Nedlasting over mobildata er deaktivert i innstillingene.\n\nDu kan velge å legge episoden til i køen eller tillate midlertidig nedlasting over mobildata.\n\n<small>Valget ditt vil gjelde i 10 minutter.</small></string>
+ <string name="confirm_mobile_download_dialog_message">Nedlasting over mobildata er deaktivert i innstillingene.\n\nVil du tillate nedlasting over mobildata midlertidig?\n\n<small>Valget ditt vil gjelde i 10 minutter.</small></string>
+ <string name="confirm_mobile_download_dialog_only_add_to_queue">Legg til i kø</string>
+ <string name="confirm_mobile_download_dialog_enable_temporarily">Tillat midlertidig</string>
+ <!--Mediaplayer messages-->
+ <string name="player_error_msg">Error!</string>
+ <string name="player_stopped_msg">Ingen media spillende for øyeblikket.</string>
+ <string name="player_preparing_msg">Forbereder</string>
+ <string name="player_ready_msg">Klar</string>
+ <string name="player_seeking_msg">Oppsøker</string>
+ <string name="playback_error_server_died">Serveren døde</string>
+ <string name="playback_error_unknown">Ukjent feil</string>
+ <string name="no_media_playing_label">Ingen media spillende for øyeblikket.</string>
+ <string name="position_default_label">00:00:00</string>
+ <string name="player_buffering_msg">Bufrer</string>
+ <string name="playbackservice_notification_title">Spiller podcast</string>
+ <string name="unknown_media_key">AntennaPod - Ukjent medienøkkel: %1$d</string>
+ <!--Queue operations-->
+ <string name="lock_queue">Lås køen</string>
+ <string name="unlock_queue">Lås opp køen</string>
+ <string name="clear_queue_label">Slett køen</string>
+ <string name="undo">Angre</string>
+ <string name="removed_from_queue">Objekt er fjernet</string>
+ <string name="move_to_top_label">Gå til toppen</string>
+ <string name="move_to_bottom_label">Gå til bunnen</string>
+ <string name="sort">Sortér</string>
+ <string name="alpha">Alfabetisk</string>
+ <string name="date">På dato</string>
+ <string name="duration">På varighet</string>
+ <string name="ascending">Økende</string>
+ <string name="descending">Synkende</string>
+ <string name="clear_queue_confirmation_msg">Vennligst bekreft at du ønsker å slette ALLE elementer i køen</string>
+ <!--Flattr-->
+ <string name="flattr_auth_label">Flattr innlogging</string>
+ <string name="flattr_auth_explanation">Trykk knappen nedenfor for å starte autentiseringsprosessen. Du vil videresendes til flattr sin innloggsinsskjerm i din nettleser og spurt om å gi AntennaPod tillatelse til å flattr\'e ting. Etter at du har gitt tillatelse blir du returnert hit automatisk.</string>
+ <string name="authenticate_label">Autentiser</string>
+ <string name="return_home_label">Returner hjem</string>
+ <string name="flattr_auth_success">Autentisering fullført! Nå kan du flattr tingene i denne appen.</string>
+ <string name="no_flattr_token_title">Flattr-token ikke funnet</string>
+ <string name="no_flattr_token_notification_msg">Det virker som at Flattr-kontoen din ikke er sammenkoblet med AntennaPod. Trykk her for å autentisere.</string>
+ <string name="no_flattr_token_msg">Det virker ikke som din flattr konto er koblet til AntennaPod. Du kan enten koble kontoen din til AntennaPod for å flattr\'e ting i appen eller du kan besøke nettsiden til tingen for å flattr\'e det der.</string>
+ <string name="authenticate_now_label">Autentiser</string>
+ <string name="action_forbidden_title">Handling forbudt</string>
+ <string name="action_forbidden_msg">AntennaPod har ikke tilgang til denne handlingen. Grunnen kan være at tilgangstokenet til kontoen din er blitt inndratt. Du kan enten re-autentisere eller besøke tjenestens nettsted.</string>
+ <string name="access_revoked_title">Tilgang opphevet</string>
+ <string name="access_revoked_info">Du har fjernet AntennaPods tilgang til kontoen din. For å fullføre prossessen må du fjerne denne appen fra listen over tillatte apper på flattr-nettsiden.</string>
+ <!--Flattr-->
+ <string name="flattr_click_success">Flattr\'erte en ting!</string>
+ <string name="flattr_click_success_count">Flattret %d ting!</string>
+ <string name="flattr_click_success_queue">Flattret: %s.</string>
+ <string name="flattr_click_failure_count">Klarte ikke flattre %d ting!</string>
+ <string name="flattr_click_failure">Ikke flattret: %s.</string>
+ <string name="flattr_click_enqueued">Tingen vil bli flattret senere</string>
+ <string name="flattring_thing">Flattrer %s</string>
+ <string name="flattring_label">AntennaPod flattrer</string>
+ <string name="flattrd_label">AntennaPod har flattret</string>
+ <string name="flattrd_failed_label">AntennaPods flattring feilet</string>
+ <string name="flattr_retrieving_status">Henter flattrede ting</string>
+ <!--Variable Speed-->
+ <string name="download_plugin_label">Last ned programtillegg</string>
+ <string name="no_playback_plugin_title">Programtillegg er ikke installert</string>
+ <string name="no_playback_plugin_or_sonic_msg">For at variabel avspillingshastighet skal fungere må du installere et tredjepartsbibliotek eller aktivere den eksperimentelle Sonic-avspilleren [Android 4.1+].\n\nTrykk på «Last ned programtillegg» for å laste ned en gratis tilleggsmodul fra Google Play.\n\nEventuelle problemer som måtte oppstå på grunn av denne modulen er ikke AntennaPods ansvar og skal rapporteres til eieren av modulen.</string>
+ <string name="set_playback_speed_label">Avspillingshastigheter</string>
+ <string name="enable_sonic">Skru på Sonic</string>
+ <!--Empty list labels-->
+ <string name="no_items_label">Det er ingen objekter på denne listen.</string>
+ <string name="no_feeds_label">Du abonnerer ikke på noen strømmer enda.</string>
+ <!--Preferences-->
+ <string name="other_pref">Annet</string>
+ <string name="about_pref">Om</string>
+ <string name="queue_label">Queue</string>
+ <string name="services_label">Tjenester</string>
+ <string name="flattr_label">Flattr</string>
+ <string name="pref_episode_cleanup_title">Episodeopprydding</string>
+ <string name="pref_episode_cleanup_summary">Episoder som ikke er i køen og ikke er merket som favoritt vil være markert for sletting dersom lagringsplass trengs</string>
+ <string name="pref_unpauseOnHeadsetReconnect_sum">Gjenoppta avspilling når hodetelefoner gjeninnkoples</string>
+ <string name="pref_followQueue_sum">Hopp til neste element i køen når avspillingen er ferdig</string>
+ <string name="pref_auto_delete_sum">Slett episode når avspillingen er ferdig</string>
+ <string name="pref_auto_delete_title">Automatisk sletting</string>
+ <string name="pref_smart_mark_as_played_sum">Marker episoder som avspilt selv om det er X antall sekunder igjen av avspillingen</string>
+ <string name="pref_smart_mark_as_played_title">Smart markering av avspilt</string>
+ <string name="pref_skip_keeps_episodes_sum">Behold episoder når de hoppes over</string>
+ <string name="pref_skip_keeps_episodes_title">Behold episoder som er hoppet over</string>
+ <string name="playback_pref">Avspilling</string>
+ <string name="network_pref">Nettverk</string>
+ <string name="pref_autoUpdateIntervallOrTime_title">Oppdateringsintervall eler tidspunkt</string>
+ <string name="pref_autoUpdateIntervallOrTime_sum">Spesifiser en intervall eller et spesifikt tidspunkt når strømmer skal oppdateres automatisk</string>
+ <string name="pref_autoUpdateIntervallOrTime_message">Du kan sette en <i>intervall</i> som «hver andre time», et <i>spesifikt tidspunkt</i> som «07:00» eller <i>skru av</i> automatiske oppdateringer helt.\n\n<small>Merk: Oppdateringstider er ikke eksakte; du kan oppleve små forsinkelser.</small></string>
+ <string name="pref_autoUpdateIntervallOrTime_Disable">Skru av</string>
+ <string name="pref_autoUpdateIntervallOrTime_Interval">Sett intervall</string>
+ <string name="pref_autoUpdateIntervallOrTime_TimeOfDay">Angi klokkeslett</string>
+ <string name="pref_downloadMediaOnWifiOnly_sum">Last ned mediafiler eksklusivt med WiFi</string>
+ <string name="pref_followQueue_title">Kontinuerlig avspilling</string>
+ <string name="pref_downloadMediaOnWifiOnly_title">WiFi media nedlastning</string>
+ <string name="pref_pauseOnHeadsetDisconnect_title">Frakobling av hodetelefoner</string>
+ <string name="pref_unpauseOnHeadsetReconnect_title">Gjeninnkopling av hodetelefoner</string>
+ <string name="pref_mobileUpdate_title">Mobiloppdateringer</string>
+ <string name="pref_mobileUpdate_sum">Tillat oppdateringer over kobling via mobildata</string>
+ <string name="refreshing_label">Oppdaterer</string>
+ <string name="flattr_settings_label">Flattr innstillinger</string>
+ <string name="pref_flattr_auth_title">Flattr logg in</string>
+ <string name="pref_flattr_auth_sum">Logg på din flattr konto for å flattr ting direkte fra appen.</string>
+ <string name="pref_flattr_this_app_title">Flattr denne appen</string>
+ <string name="pref_flattr_this_app_sum">Støtt utviklingen av AntennaPod ved å flattr\'e det. Tusen takk!</string>
+ <string name="pref_revokeAccess_title">Opphev tilgang</string>
+ <string name="pref_revokeAccess_sum">Opphev tilgangstillatelsen til din flattr konto for denne appen.</string>
+ <string name="pref_auto_flattr_title">Automatisk Flattr</string>
+ <string name="pref_auto_flattr_sum">Konfigurer automatisk flattring</string>
+ <string name="user_interface_label">Brukergrensesnitt</string>
+ <string name="pref_set_theme_title">Velg tema</string>
+ <string name="pref_nav_drawer_title">Skreddersy navigasjonsskuff</string>
+ <string name="pref_nav_drawer_sum">Velg utseendet til navigasjonsskuffen.</string>
+ <string name="pref_nav_drawer_items_title">Velg elementer i navigasjonsskuffen</string>
+ <string name="pref_nav_drawer_items_sum">Endre hvilke elementer som vises i navigeringsfanen.</string>
+ <string name="pref_nav_drawer_feed_order_title">Velg rekkefølge på abbonement</string>
+ <string name="pref_nav_drawer_feed_order_sum">Endre rekkefølgen på abbonementene dine</string>
+ <string name="pref_nav_drawer_feed_counter_title">Velg abbonementsteller</string>
+ <string name="pref_nav_drawer_feed_counter_sum">Endre informasjonen vist av abonnementstelleren</string>
+ <string name="pref_set_theme_sum">Endre utseendet til AntennaPod</string>
+ <string name="pref_automatic_download_title">Automatisk nedlasting</string>
+ <string name="pref_automatic_download_sum">Konfigurer automatisk nedlasting av episoder.</string>
+ <string name="pref_autodl_wifi_filter_title">Skru på Wi-Fi-filter</string>
+ <string name="pref_autodl_wifi_filter_sum">Tillat automatisk nedlasting kun for valgte Wi-Fi-nettverk.</string>
+ <string name="pref_automatic_download_on_battery_title">Last ned når enheten ikke lader</string>
+ <string name="pref_automatic_download_on_battery_sum">Tillat automatisk nedlasting når enheten ikke står til lading</string>
+ <string name="pref_parallel_downloads_title">Parallelle nedlastinger</string>
+ <string name="pref_episode_cache_title">Mellomlager for episoder</string>
+ <string name="pref_theme_title_light">Lyst</string>
+ <string name="pref_theme_title_dark">Mørkt</string>
+ <string name="pref_episode_cache_unlimited">Ulimitert</string>
+ <string name="pref_update_interval_hours_plural">Timer</string>
+ <string name="pref_update_interval_hours_singular">Time</string>
+ <string name="pref_update_interval_hours_manual">Manuelt</string>
+ <string name="pref_gpodnet_authenticate_title">Logg inn</string>
+ <string name="pref_gpodnet_authenticate_sum">Logg inn med din gpodder.net konto for å synkronisere dine abonnementer.</string>
+ <string name="pref_gpodnet_logout_title">Logg ut</string>
+ <string name="pref_gpodnet_logout_toast">Utloggelse lyktes</string>
+ <string name="pref_gpodnet_setlogin_information_title">Endre innloggingsinformasjon</string>
+ <string name="pref_gpodnet_setlogin_information_sum">Endre innlogginsinformasjonen til din gpodder.net konto</string>
+ <string name="pref_playback_speed_title">Avspillingshastigheter</string>
+ <string name="pref_playback_speed_sum">Egendefiner hastighetene tilgjengelig for variabel avspillingshastighet</string>
+ <string name="pref_fast_forward">Spoling fremover</string>
+ <string name="pref_rewind">Spoling bakover</string>
+ <string name="pref_gpodnet_sethostname_title">Sett vertsnavn</string>
+ <string name="pref_gpodnet_sethostname_use_default_host">Bruk standard vert</string>
+ <string name="pref_expandNotify_title">Utvid varsel</string>
+ <string name="pref_expandNotify_sum">Utvider alltid varselet for å inkludere avspillingsknapper.</string>
+ <string name="pref_persistNotify_title">Vedvarende avspillingskontroller</string>
+ <string name="pref_persistNotify_sum">Behold varsel- og låseskjermkontroller når avspilling er satt på pause.</string>
+ <string name="pref_lockscreen_background_title">Angi som bakgrunn på låseskjermen</string>
+ <string name="pref_lockscreen_background_sum">Angir låseskjermbakgrunnsbildet til å være den nåværende episodens bilde. Som en sideeffekt vil dette også vise bildet i tredjepartsapper.</string>
+ <string name="pref_showDownloadReport_title">Vis nedlastingsrapport</string>
+ <string name="pref_showDownloadReport_sum">Generer en rapport som viser detaljer dersom nedlastinger feiler.</string>
+ <string name="pref_expand_notify_unsupport_toast">Android-versjoner tidligere enn 4.1 støtter ikke utvidede varsler.</string>
+ <string name="pref_queueAddToFront_sum">Legg til nye episoder i begynnelsen av køen.</string>
+ <string name="pref_queueAddToFront_title">Legg til foran i køen</string>
+ <string name="pref_smart_mark_as_played_disabled">Deaktivert</string>
+ <string name="pref_image_cache_size_title">Størrelse for bildemellomlager</string>
+ <string name="pref_image_cache_size_sum">Størrelsen på mellomlageret for bilder.</string>
+ <string name="experimental_pref">Eksperimentell</string>
+ <string name="pref_sonic_title">Sonic medieavspiller</string>
+ <string name="pref_sonic_message">Bruk den innebygde Sonic medieavspilleren som en erstatning for Prestissimo</string>
+ <!--Auto-Flattr dialog-->
+ <string name="auto_flattr_enable">Aktiver automatisk flattring</string>
+ <string name="auto_flattr_after_percent">Flattre episode så snart %d prosent er avspilt</string>
+ <string name="auto_flattr_ater_beginning">Flattre episode når avspillingen starter</string>
+ <string name="auto_flattr_ater_end">Flattre episode når avspillingen tar slutt</string>
+ <!--Search-->
+ <string name="search_hint">Søk etter strømmer eller episoder</string>
+ <string name="found_in_shownotes_label">Funnet i shownotater</string>
+ <string name="found_in_chapters_label">Funnet i kapitler</string>
+ <string name="search_status_no_results">Ingen resultater ble funnet</string>
+ <string name="search_label">Søk</string>
+ <string name="found_in_title_label">Funnet i tittel</string>
+ <!--OPML import and export-->
+ <string name="opml_import_txtv_button_lable">OPML-filer lar deg flytte podcastene dine fra en podcatcher til en annen.</string>
+ <string name="opml_import_option">Valg %1$d</string>
+ <string name="opml_import_explanation_1">Velg en spesifikk filbane fra det lokale filsystemet.</string>
+ <string name="opml_import_explanation_2">Bruk en ektern applikasjon som Dropbox, Google Drive eller favoritt-filbehandleren din for å åpne en OPML-fil.</string>
+ <string name="opml_import_explanation_3">Mange applikasjoner som Gmail, Dropbox, Google Drive og de fleste filbehandlere kan <i>åpne</i> OPML-filer <i>med</i> AntennaPod.</string>
+ <string name="start_import_label">Start importering</string>
+ <string name="opml_import_label">OPML-import</string>
+ <string name="opml_directory_error">ERROR!</string>
+ <string name="reading_opml_label">Leser OPML-fil</string>
+ <string name="opml_reader_error">En feil oppsto under lesingen av opml dokumentet:</string>
+ <string name="opml_import_error_dir_empty">Importkatalogen er tom.</string>
+ <string name="select_all_label">Velg alle</string>
+ <string name="deselect_all_label">Opphev alle markeringene</string>
+ <string name="select_options_label">Velg ...</string>
+ <string name="choose_file_from_filesystem">Fra lokalt filsystem</string>
+ <string name="choose_file_from_external_application">Bruk ekstern applikasjon</string>
+ <string name="opml_export_label">OPML-eksportering</string>
+ <string name="exporting_label">Eksporterer...</string>
+ <string name="export_error_label">Eksporteringserror</string>
+ <string name="opml_export_success_title">OPML-import vellykket.</string>
+ <string name="opml_export_success_sum">.opml-filen ble skrevet til:\u0020</string>
+ <!--Sleep timer-->
+ <string name="set_sleeptimer_label">Sett opp sovetimer</string>
+ <string name="disable_sleeptimer_label">Deaktiver sovetimer</string>
+ <string name="enter_time_here_label">Legg til tid</string>
+ <string name="sleep_timer_label">Sovetimer</string>
+ <string name="time_left_label">Tid igjen:\u0020</string>
+ <string name="time_dialog_invalid_input">Ugyldig innspill, tid må være et heltall</string>
+ <string name="timer_about_to_expire_label"><b>Når nedtellingen er i ferd med å utløpe:</b></string>
+ <string name="shake_to_reset_label">Rist for å tilbakestille nedtellingen</string>
+ <string name="timer_vibration_label">Vibrer</string>
+ <string name="time_seconds">sekunder</string>
+ <string name="time_minutes">minutter</string>
+ <string name="time_hours">timer</string>
+ <plurals name="time_seconds_quantified">
+ <item quantity="one">1 sekund</item>
+ <item quantity="other">%d sekunder</item>
+ </plurals>
+ <plurals name="time_minutes_quantified">
+ <item quantity="one">1 minutt</item>
+ <item quantity="other">%d minutter</item>
+ </plurals>
+ <plurals name="time_hours_quantified">
+ <item quantity="one">1 time</item>
+ <item quantity="other">%d timer</item>
+ </plurals>
+ <!--gpodder.net-->
+ <string name="gpodnet_taglist_header">KATEGORIER</string>
+ <string name="gpodnet_toplist_header">TOPP-PODCASTER</string>
+ <string name="gpodnet_suggestions_header">FORSLAG</string>
+ <string name="gpodnet_search_hint">Søk på gpodder.net</string>
+ <string name="gpodnetauth_login_title">Logg inn</string>
+ <string name="gpodnetauth_login_descr">Velkommen til gpodder.net innlogginsprosess. Først begynner vi med å skrive inn innlogginsinformasjon.</string>
+ <string name="gpodnetauth_login_butLabel">Logg inn</string>
+ <string name="gpodnetauth_login_register">Dersom du ikke har en konto enda kan du opprette en her:\nhttps://gpodder.net/register/</string>
+ <string name="username_label">Brukernavn</string>
+ <string name="password_label">Passord</string>
+ <string name="gpodnetauth_device_title">Enhetsvalg</string>
+ <string name="gpodnetauth_device_descr">Lag en ny enhet til å bruke for din gpodder.net konto eller velg en som allerede eksisterer.</string>
+ <string name="gpodnetauth_device_deviceID">EnhetsID:\u0020</string>
+ <string name="gpodnetauth_device_caption">Tekst</string>
+ <string name="gpodnetauth_device_butCreateNewDevice">Lag en ny enhet</string>
+ <string name="gpodnetauth_device_chooseExistingDevice">Velg eksisterende enhet:</string>
+ <string name="gpodnetauth_device_errorEmpty">Device ID kan ikke være tom</string>
+ <string name="gpodnetauth_device_errorAlreadyUsed">EnhetsID er allerede i bruk</string>
+ <string name="gpodnetauth_device_butChoose">Velg</string>
+ <string name="gpodnetauth_finish_title">Innlogging lyktes!</string>
+ <string name="gpodnetauth_finish_descr">Gratulerer! Din gpodder.net konto er nå linket opp med din enhet. AntennaPod vil nå automatisk synkronisere abonnementer på din enhet med din gpodder.net konto.</string>
+ <string name="gpodnetauth_finish_butsyncnow">Start synkronisering nå.</string>
+ <string name="gpodnetauth_finish_butgomainscreen">Gå til hovedskjermen</string>
+ <string name="gpodnetsync_auth_error_title">gpodder.net-autentiseringsfeil</string>
+ <string name="gpodnetsync_auth_error_descr">Feil brukernavn eller passord.</string>
+ <string name="gpodnetsync_error_title">gpodder.net synkroniseringserror</string>
+ <string name="gpodnetsync_error_descr">En feil oppsto under synkronisering av:\u0020</string>
+ <!--Directory chooser-->
+ <string name="selected_folder_label">Valgt mappe</string>
+ <string name="create_folder_label">Lag mappe</string>
+ <string name="choose_data_directory">Velg datamappe</string>
+ <string name="create_folder_msg">Lag en ny mappe med navn \"%1$s\"?</string>
+ <string name="create_folder_success">Lagde en ny mappe</string>
+ <string name="create_folder_error_no_write_access">Kan ikke skrive til denne mappen</string>
+ <string name="create_folder_error_already_exists">Mappe eksisterer allerede</string>
+ <string name="create_folder_error">Kunne ikke lage mappe</string>
+ <string name="folder_not_empty_dialog_title">Mappen er ikke tom</string>
+ <string name="folder_not_empty_dialog_msg">Mappen du har valgt er ikke tom. Nedlastet media og andre filer vil bli plassert direkte i denne mappen? Vil du fortsette?</string>
+ <string name="set_to_default_folder">Velg standardmappe</string>
+ <string name="pref_pausePlaybackForFocusLoss_sum">Sett pause på playback istedenfor å skru ned volumet når en annen app har lyst å spille av lyder</string>
+ <string name="pref_pausePlaybackForFocusLoss_title">Pause for avbrytelser</string>
+ <string name="pref_resumeAfterCall_sum">Gjenoppta avspilling etter at telefonsamtaler avsluttes</string>
+ <string name="pref_resumeAfterCall_title">Gjenoppta etter samtale</string>
+ <string name="pref_restart_required">AntennaPod må startes om for at denne endringen skal lagres.</string>
+ <!--Online feed view-->
+ <string name="subscribe_label">Abonner</string>
+ <string name="subscribed_label">Abonnert</string>
+ <string name="downloading_label">Laster ned...</string>
+ <!--Content descriptions for image buttons-->
+ <string name="show_chapters_label">Vis kapitler</string>
+ <string name="show_shownotes_label">Vis notater</string>
+ <string name="show_cover_label">Vis bilde</string>
+ <string name="rewind_label">Spol tilbake</string>
+ <string name="fast_forward_label">Spol fremover</string>
+ <string name="media_type_audio_label">Lyd</string>
+ <string name="media_type_video_label">Video</string>
+ <string name="navigate_upwards_label">Naviger oppover</string>
+ <string name="butAction_label">Flere handlinger</string>
+ <string name="status_playing_label">Episode spilles nå</string>
+ <string name="status_downloading_label">Episode lastes ned nå</string>
+ <string name="status_downloaded_label">Episode har blitt lastet ned</string>
+ <string name="status_unread_label">Objekt er nytt</string>
+ <string name="in_queue_label">Episoden er i queue</string>
+ <string name="new_episodes_count_label">Antall nye episoder</string>
+ <string name="in_progress_episodes_count_label">Antall episoder du har begynt å høre på</string>
+ <string name="drag_handle_content_description">Dra for å endre posisjonen til dette objektet</string>
+ <string name="load_next_page_label">Last inn neste side</string>
+ <!--Feed information screen-->
+ <string name="authentication_label">Autentisering</string>
+ <string name="authentication_descr">Endre brukernavnet og passordet for denne podcasten og dens episoder.</string>
+ <!--Progress information-->
+ <string name="progress_upgrading_database">Oppgraderer databasen</string>
+ <!--AntennaPodSP-->
+ <string name="sp_apps_importing_feeds_msg">Importerer abbonementer fra enkeltstående applikasjoner ...</string>
+ <string name="search_itunes_label">Søk på iTunes</string>
+ <string name="select_label"><b>Velg ...</b></string>
+ <string name="all_label">Alle</string>
+ <string name="selected_all_label">Valgte alle episoder</string>
+ <string name="none_label">Ingen</string>
+ <string name="deselected_all_label">Fjernet valg av alle episoder</string>
+ <string name="played_label">Avspilt</string>
+ <string name="selected_played_label">Valgte avspilte episoder</string>
+ <string name="unplayed_label">Uavspilt</string>
+ <string name="selected_unplayed_label">Valgte uavspilte episoder</string>
+ <string name="downloaded_label">Nedlastede</string>
+ <string name="selected_downloaded_label">Valgte nedlastede episoder</string>
+ <string name="not_downloaded_label">Ikke nedlastet</string>
+ <string name="selected_not_downloaded_label">Valgte ikke-nedlastede episoder</string>
+ <string name="sort_title"><b>Sorter på ...</b></string>
+ <string name="sort_title_a_z">Tittel (A \u2192 Z)</string>
+ <string name="sort_title_z_a">Tittel (Z \u2192 A)</string>
+ <string name="sort_date_new_old">Dato (Ny \u2192 Gammel)</string>
+ <string name="sort_date_old_new">Dato (Gammel \u2192 Ny)</string>
+ <string name="sort_duration_short_long">Lengde (Kort \u2192 Lang)</string>
+ <string name="sort_duration_long_short">Lengde (Lang \u2192 Kort)</string>
+</resources>
diff --git a/core/src/main/res/values-no/strings.xml b/core/src/main/res/values-no/strings.xml
deleted file mode 100644
index 645d576a4..000000000
--- a/core/src/main/res/values-no/strings.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version='1.0' encoding='UTF-8'?>
-<resources xmlns:tools="http://schemas.android.com/tools">
- <!--Activitiy and fragment titles-->
- <!--New episodes fragment-->
- <!--Main activity-->
- <!--Webview actions-->
- <!--Playback history-->
- <!--Other-->
- <!--'Add Feed' Activity labels-->
- <!--Actions on feeds-->
- <!--actions on feeditems-->
- <!--Download messages and labels-->
- <!--Mediaplayer messages-->
- <!--Queue operations-->
- <!--Flattr-->
- <!--Flattr-->
- <!--Variable Speed-->
- <!--Empty list labels-->
- <!--Preferences-->
- <!--Auto-Flattr dialog-->
- <!--Search-->
- <!--OPML import and export-->
- <!--Sleep timer-->
- <!--gpodder.net-->
- <!--Directory chooser-->
- <!--Online feed view-->
- <!--Content descriptions for image buttons-->
- <!--Feed information screen-->
- <!--Progress information-->
- <!--AntennaPodSP-->
-</resources>
diff --git a/core/src/main/res/values-v16/styles.xml b/core/src/main/res/values-v16/styles.xml
index e7c56b5f5..a92790152 100644
--- a/core/src/main/res/values-v16/styles.xml
+++ b/core/src/main/res/values-v16/styles.xml
@@ -9,7 +9,7 @@
<style name="AntennaPod.Dialog.Title" parent="@android:style/TextAppearance.Medium">
<item name="android:textSize">@dimen/text_size_medium</item>
- <item name="android:textColor">@color/bright_blue</item>
+ <item name="android:textColor">@color/holo_blue_light</item>
<item name="android:maxLines">2</item>
<item name="android:ellipsize">end</item>
<item name="android:fontFamily">sans-serif-light</item>
diff --git a/core/src/main/res/values/colors.xml b/core/src/main/res/values/colors.xml
index 566032da8..ff061be4c 100644
--- a/core/src/main/res/values/colors.xml
+++ b/core/src/main/res/values/colors.xml
@@ -6,7 +6,8 @@
<color name="grey600">#757575</color>
<color name="light_gray">#bfbfbf</color>
<color name="black">#000000</color>
- <color name="bright_blue">#33B5E5</color>
+ <color name="holo_blue_light">#33B5E5</color>
+ <color name="holo_blue_dark">#0099CC</color>
<color name="ics_gray">#858585</color>
<color name="actionbar_gray">#DDDDDD</color>
<color name="download_success_green">#669900</color>
@@ -26,7 +27,8 @@
<!-- Theme colors -->
<color name="primary_light">#FFFFFF</color>
- <color name="color_accent">#009EC8</color>
+ <color name="highlight_light">#DDDDDD</color>
+ <color name="highlight_dark">#414141</color>
-</resources> \ No newline at end of file
+</resources>
diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml
index e0a7a6a7a..151c0b915 100644
--- a/core/src/main/res/values/strings.xml
+++ b/core/src/main/res/values/strings.xml
@@ -25,6 +25,7 @@
<string name="playback_history_label">Playback History</string>
<string name="gpodnet_main_label">gpodder.net</string>
<string name="gpodnet_auth_label">gpodder.net Login</string>
+ <string name="free_space_label">"%1$s free</string>
<!-- New episodes fragment -->
<string name="recently_published_episodes_label">Recently published</string>
@@ -464,11 +465,15 @@
<string name="selected_folder_label">Selected folder:</string>
<string name="create_folder_label">Create folder</string>
<string name="choose_data_directory">Choose Data Folder</string>
+ <string name="choose_data_directory_message">Please choose the base of your data folder. AntennaPod will create the appropriate sub-directories.</string>
<string name="create_folder_msg">Create new folder with name "%1$s"?</string>
<string name="create_folder_success">Created new folder</string>
<string name="create_folder_error_no_write_access">Cannot write to this folder</string>
<string name="create_folder_error_already_exists">Folder already exists</string>
<string name="create_folder_error">Could not create folder</string>
+ <string name="folder_does_not_exist_error">"%1$s" does not exist</string>
+ <string name="folder_not_readable_error">"%1$s" is not readable</string>
+ <string name="folder_not_writable_error">"%1$s" is not writable</string>
<string name="folder_not_empty_dialog_title">Folder is not empty</string>
<string name="folder_not_empty_dialog_msg">The folder you have selected is not empty. Media downloads and other files will be placed directly in this folder. Continue anyway?</string>
<string name="set_to_default_folder">Choose default folder</string>
@@ -478,6 +483,7 @@
<string name="pref_resumeAfterCall_title">Resume after Call</string>
<string name="pref_restart_required">AntennaPod has to be restarted for this change to take effect.</string>
+
<!-- Online feed view -->
<string name="subscribe_label">Subscribe</string>
<string name="subscribed_label">Subscribed</string>
diff --git a/core/src/main/res/values/styles.xml b/core/src/main/res/values/styles.xml
index 5d1eac12d..b9ee70dbf 100644
--- a/core/src/main/res/values/styles.xml
+++ b/core/src/main/res/values/styles.xml
@@ -1,10 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <style name="Theme.AntennaPod.Light" parent="@style/Theme.AppCompat.Light">
+ <style name="Theme.AntennaPod.Light" parent="Theme.AppCompat.Light">
<item name="colorPrimary">@color/primary_light</item>
- <item name="colorAccent">@color/color_accent</item>
+ <item name="colorAccent">@color/holo_blue_light</item>
<item name="buttonStyle">@style/Widget.AntennaPod.Button</item>
+ <item name="alertDialogTheme">@style/AntennaPod.Dialog.Light</item>
<item name="attr/action_bar_icon_color">@color/grey600</item>
<item name="attr/action_about">@drawable/ic_info_grey600_24dp</item>
<item name="attr/action_search">@drawable/ic_search_grey600_24dp</item>
@@ -49,11 +50,12 @@
<item name="attr/ic_filter">@drawable/ic_filter_grey600_24dp</item>
</style>
- <style name="Theme.AntennaPod.Dark" parent="@style/Theme.AppCompat">
- <item name="colorAccent">@color/color_accent</item>
+ <style name="Theme.AntennaPod.Dark" parent="Theme.AppCompat">
+ <item name="colorAccent">@color/holo_blue_dark</item>
<item name="buttonStyle">@style/Widget.AntennaPod.Button</item>
+ <item name="alertDialogTheme">@style/AntennaPod.Dialog.Dark</item>
<item name="attr/action_bar_icon_color">@color/white</item>
- <item name="attr/action_about">@drawable/ic_info_white_24dp</item>
+ <item name="attr/action_about">@drawable/ic_info_white_24dp</item>g
<item name="attr/action_search">@drawable/ic_search_white_24dp</item>
<item name="attr/action_stream">@drawable/ic_settings_input_antenna_white_24dp</item>
<item name="attr/av_download">@drawable/ic_file_download_white_24dp</item>
@@ -96,12 +98,13 @@
<item name="attr/ic_filter">@drawable/ic_filter_white_24dp</item>
</style>
- <style name="Theme.AntennaPod.Light.NoTitle" parent="@style/Theme.AppCompat.Light.NoActionBar">
+ <style name="Theme.AntennaPod.Light.NoTitle" parent="Theme.AppCompat.Light.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowActionModeOverlay">true</item>
<item name="colorPrimary">@color/primary_light</item>
- <item name="colorAccent">@color/color_accent</item>
+ <item name="colorAccent">@color/holo_blue_light</item>
<item name="buttonStyle">@style/Widget.AntennaPod.Button</item>
+ <item name="alertDialogTheme">@style/AntennaPod.Dialog.Light</item>
<item name="attr/action_about">@drawable/ic_info_grey600_24dp</item>
<item name="attr/action_search">@drawable/ic_search_grey600_24dp</item>
<item name="attr/action_stream">@drawable/ic_settings_input_antenna_grey600_24dp</item>
@@ -145,11 +148,12 @@
<item name="attr/ic_filter">@drawable/ic_filter_grey600_24dp</item>
</style>
- <style name="Theme.AntennaPod.Dark.NoTitle" parent="@style/Theme.AppCompat.NoActionBar">
+ <style name="Theme.AntennaPod.Dark.NoTitle" parent="Theme.AppCompat.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowActionModeOverlay">true</item>
- <item name="colorAccent">@color/color_accent</item>
+ <item name="colorAccent">@color/holo_blue_dark</item>
<item name="buttonStyle">@style/Widget.AntennaPod.Button</item>
+ <item name="alertDialogTheme">@style/AntennaPod.Dialog.Dark</item>
<item name="attr/action_about">@drawable/ic_info_white_24dp</item>
<item name="attr/action_search">@drawable/ic_search_white_24dp</item>
<item name="attr/action_stream">@drawable/ic_settings_input_antenna_white_24dp</item>
@@ -197,44 +201,6 @@
<item name="windowActionBarOverlay">true</item>
</style>
- <style name="UndoBar">
- <item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">48dp</item>
- <item name="android:layout_gravity">bottom</item>
- <item name="android:layout_marginLeft">8dp</item>
- <item name="android:layout_marginRight">8dp</item>
- <item name="android:layout_marginBottom">16dp</item>
- <item name="android:orientation">horizontal</item>
- <item name="android:background">@drawable/undobar</item>
- <item name="android:clickable">true</item>
- <item name="android:divider">@drawable/undobar_divider</item>
- </style>
-
- <style name="UndoBarMessage">
- <item name="android:layout_width">0dp</item>
- <item name="android:layout_weight">1</item>
- <item name="android:layout_height">wrap_content</item>
- <item name="android:layout_marginLeft">16dp</item>
- <item name="android:layout_gravity">center_vertical</item>
- <item name="android:layout_marginRight">16dp</item>
- <item name="android:textAppearance">?android:textAppearanceMedium</item>
- <item name="android:textColor">#fff</item>
- </style>
-
- <style name="UndoBarButton">
- <item name="android:layout_width">wrap_content</item>
- <item name="android:layout_height">match_parent</item>
- <item name="android:paddingLeft">16dp</item>
- <item name="android:paddingRight">16dp</item>
- <item name="android:background">@drawable/undobar_button</item>
- <item name="android:drawableLeft">@drawable/ic_undobar_undo</item>
- <item name="android:drawablePadding">12dp</item>
- <item name="android:textAppearance">?android:textAppearanceSmall</item>
- <item name="android:textStyle">bold</item>
- <item name="android:textColor">#fff</item>
- <item name="android:text">@string/undo</item>
- </style>
-
<style name="AntennaPod.TextView.Heading" parent="@android:style/TextAppearance.Medium">
<item name="android:textSize">@dimen/text_size_large</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
@@ -271,6 +237,14 @@
<item name="textAllCaps">false</item>
</style>
+ <style name="AntennaPod.Dialog.Light" parent="Theme.AppCompat.Light.Dialog">
+ <item name="colorAccent">@color/holo_blue_light</item>
+ </style>
+
+ <style name="AntennaPod.Dialog.Dark" parent="Theme.AppCompat.Dialog">
+ <item name="colorAccent">@color/holo_blue_dark</item>
+ </style>
+
<style name="BigBlurryBackground">
<item name="android:scaleType">centerCrop</item>
<!-- <item name="android:tint">@color/image_readability_tint</item> -->