Moved background tasks of PlaybackService into PlaybackServiceTaskManager

This commit is contained in:
daniel oeh 2013-12-15 02:33:23 +01:00
parent eee1f68a0d
commit 513183bc84

View File

@ -0,0 +1,364 @@
package de.danoeh.antennapod.service.playback;
import android.content.Context;
import android.util.Log;
import de.danoeh.antennapod.AppConfig;
import de.danoeh.antennapod.feed.EventDistributor;
import de.danoeh.antennapod.feed.FeedItem;
import de.danoeh.antennapod.storage.DBReader;
import de.danoeh.antennapod.util.playback.Playable;
import java.util.List;
import java.util.concurrent.*;
/**
* Manages the background tasks of PlaybackSerivce, i.e.
* the sleep timer, the position saver, the widget updater and
* the queue loader.
* <p/>
* The PlaybackServiceTaskManager(PSTM) uses a callback object (PSTMCallback)
* to notify the PlaybackService about updates from the running tasks.
*/
public class PlaybackServiceTaskManager {
private static final String TAG = "PlaybackServiceTaskManager";
/**
* Update interval of position saver in milliseconds.
*/
public static final int POSITION_SAVER_WAITING_INTERVAL = 5000;
/**
* Notification interval of widget updater in milliseconds.
*/
public static final int WIDGET_UPDATER_NOTIFICATION_INTERVAL = 1500;
private static final int SCHED_EX_POOL_SIZE = 2;
private final ScheduledThreadPoolExecutor schedExecutor;
private ScheduledFuture positionSaverFuture;
private ScheduledFuture widgetUpdaterFuture;
private Future sleepTimerFuture;
private volatile Future<List<FeedItem>> queueFuture;
private volatile Future chapterLoaderFuture;
private SleepTimer sleepTimer;
private final Context context;
private final PSTMCallback callback;
/**
* Sets up a new PSTM. This method will also start the queue loader task.
*
* @param context
* @param callback A PSTMCallback object for notifying the user about updates. Must not be null.
*/
public PlaybackServiceTaskManager(Context context, PSTMCallback callback) {
if (context == null)
throw new IllegalArgumentException("context must not be null");
if (callback == null)
throw new IllegalArgumentException("callback must not be null");
this.context = context;
this.callback = callback;
schedExecutor = new ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE, new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setPriority(Thread.MIN_PRIORITY);
return t;
}
});
loadQueue();
EventDistributor.getInstance().register(eventDistributorListener);
}
private final EventDistributor.EventListener eventDistributorListener = new EventDistributor.EventListener() {
@Override
public void update(EventDistributor eventDistributor, Integer arg) {
if ((EventDistributor.QUEUE_UPDATE & arg) != 0) {
loadQueue();
}
}
};
private synchronized boolean isQueueLoaderActive() {
return queueFuture != null && !queueFuture.isDone();
}
private synchronized void cancelQueueLoader() {
if (isQueueLoaderActive()) {
queueFuture.cancel(true);
}
}
private synchronized void loadQueue() {
if (!isQueueLoaderActive()) {
queueFuture = schedExecutor.submit(new Callable<List<FeedItem>>() {
@Override
public List<FeedItem> call() throws Exception {
return DBReader.getQueue(context);
}
});
}
}
/**
* Returns the queue or waits until the PSTM hasloaded the queue from the database.
*/
public synchronized List<FeedItem> getQueue() throws InterruptedException {
try {
return queueFuture.get();
} catch (ExecutionException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Starts the position saver task. If the position saver is already active, nothing will happen.
*/
public synchronized void startPositionSaver() {
if (!isPositionSaverActive()) {
Runnable positionSaver = new Runnable() {
@Override
public void run() {
callback.positionSaverTick();
}
};
positionSaverFuture = schedExecutor.scheduleWithFixedDelay(positionSaver, POSITION_SAVER_WAITING_INTERVAL,
POSITION_SAVER_WAITING_INTERVAL, TimeUnit.MILLISECONDS);
if (AppConfig.DEBUG) Log.d(TAG, "Started PositionSaver");
} else {
if (AppConfig.DEBUG) Log.d(TAG, "Call to startPositionSaver was ignored.");
}
}
/**
* Returns true if the position saver is currently running.
*/
public synchronized boolean isPositionSaverActive() {
return positionSaverFuture != null && !positionSaverFuture.isCancelled() && !positionSaverFuture.isDone();
}
/**
* Cancels the position saver. If the position saver is not running, nothing will happen.
*/
public synchronized void cancelPositionSaver() {
if (isPositionSaverActive()) {
positionSaverFuture.cancel(false);
if (AppConfig.DEBUG) Log.d(TAG, "Cancelled PositionSaver");
}
}
/**
* Starts the widget updater task. If the widget updater is already active, nothing will happen.
*/
public synchronized void startWidgetUpdater() {
if (!isWidgetUpdaterActive()) {
Runnable widgetUpdater = new Runnable() {
@Override
public void run() {
callback.onWidgetUpdaterTick();
}
};
widgetUpdaterFuture = schedExecutor.scheduleWithFixedDelay(widgetUpdater, WIDGET_UPDATER_NOTIFICATION_INTERVAL,
WIDGET_UPDATER_NOTIFICATION_INTERVAL, TimeUnit.MILLISECONDS);
if (AppConfig.DEBUG) Log.d(TAG, "Started WidgetUpdater");
} else {
if (AppConfig.DEBUG) Log.d(TAG, "Call to startWidgetUpdater was ignored.");
}
}
/**
* Starts a new sleep timer with the given waiting time. If another sleep timer is already active, it will be
* cancelled first.
* After waitingTime has elapsed, onSleepTimerExpired() will be called.
*
* @throws java.lang.IllegalArgumentException if waitingTime <= 0
*/
public synchronized void setSleepTimer(long waitingTime) {
if (waitingTime <= 0)
throw new IllegalArgumentException("waitingTime <= 0");
if (AppConfig.DEBUG)
Log.d(TAG, "Setting sleep timer to " + Long.toString(waitingTime)
+ " milliseconds");
if (isSleepTimerActive()) {
sleepTimerFuture.cancel(true);
}
sleepTimer = new SleepTimer(waitingTime);
sleepTimerFuture = schedExecutor.submit(sleepTimer);
}
/**
* Returns true if the sleep timer is currently active.
*/
public synchronized boolean isSleepTimerActive() {
return sleepTimer != null && sleepTimer.isWaiting() && sleepTimerFuture != null;
}
/**
* Disables the sleep timer. If the sleep timer is not active, nothing will happen.
*/
public synchronized void disableSleepTimer() {
if (isSleepTimerActive()) {
if (AppConfig.DEBUG)
Log.d(TAG, "Disabling sleep timer");
sleepTimerFuture.cancel(true);
}
}
/**
* Returns the current sleep timer time or 0 if the sleep timer is not active.
*/
public synchronized long getSleepTimerTimeLeft() {
if (isSleepTimerActive()) {
return sleepTimer.getWaitingTime();
} else {
return 0;
}
}
/**
* Returns true if the widget updater is currently running.
*/
public synchronized boolean isWidgetUpdaterActive() {
return widgetUpdaterFuture != null && !widgetUpdaterFuture.isCancelled() && !widgetUpdaterFuture.isDone();
}
/**
* Cancels the widget updater. If the widget updater is not running, nothing will happen.
*/
public synchronized void cancelWidgetUpdater() {
if (isWidgetUpdaterActive()) {
widgetUpdaterFuture.cancel(false);
if (AppConfig.DEBUG) Log.d(TAG, "Cancelled WidgetUpdater");
}
}
private synchronized void cancelChapterLoader() {
if (isChapterLoaderActive()) {
chapterLoaderFuture.cancel(true);
}
}
private synchronized boolean isChapterLoaderActive() {
return chapterLoaderFuture != null && !chapterLoaderFuture.isDone();
}
/**
* Starts a new thread that loads the chapter marks from a playable object. If another chapter loader is already active,
* it will be cancelled first.
* On completion, the callback's onChapterLoaded method will be called.
*/
public synchronized void startChapterLoader(final Playable media) {
if (media == null)
throw new IllegalArgumentException("media = null");
if (isChapterLoaderActive()) {
cancelChapterLoader();
}
Runnable chapterLoader = new Runnable() {
@Override
public void run() {
if (AppConfig.DEBUG)
Log.d(TAG, "Chapter loader started");
if (media.getChapters() == null) {
media.loadChapterMarks();
if (!Thread.currentThread().isInterrupted() && media.getChapters() != null) {
callback.onChapterLoaded(media);
}
}
if (AppConfig.DEBUG)
Log.d(TAG, "Chapter loader stopped");
}
};
chapterLoaderFuture = schedExecutor.submit(chapterLoader);
}
/**
* Cancels all tasks. The PSTM will be in the initial state after execution of this method.
*/
public synchronized void cancelAllTasks() {
cancelPositionSaver();
cancelWidgetUpdater();
disableSleepTimer();
cancelQueueLoader();
cancelChapterLoader();
}
/**
* Cancels all tasks and shuts down the internal executor service of the PSTM. The object should not be used after
* execution of this method.
*/
public synchronized void shutdown() {
EventDistributor.getInstance().unregister(eventDistributorListener);
cancelAllTasks();
schedExecutor.shutdown();
}
/**
* Sleeps for a given time and then pauses playback.
*/
private class SleepTimer implements Runnable {
private static final String TAG = "SleepTimer";
private static final long UPDATE_INTERVALL = 1000L;
private volatile long waitingTime;
private boolean isWaiting;
public SleepTimer(long waitingTime) {
super();
this.waitingTime = waitingTime;
}
@Override
public void run() {
isWaiting = true;
if (AppConfig.DEBUG)
Log.d(TAG, "Starting");
while (waitingTime > 0) {
try {
Thread.sleep(UPDATE_INTERVALL);
waitingTime -= UPDATE_INTERVALL;
if (waitingTime <= 0) {
if (AppConfig.DEBUG)
Log.d(TAG, "Waiting completed");
callback.onSleepTimerExpired();
postExecute();
}
} catch (InterruptedException e) {
Log.d(TAG, "Thread was interrupted while waiting");
break;
}
}
postExecute();
}
protected void postExecute() {
isWaiting = false;
}
public long getWaitingTime() {
return waitingTime;
}
public boolean isWaiting() {
return isWaiting;
}
}
public static interface PSTMCallback {
void positionSaverTick();
void onSleepTimerExpired();
void onWidgetUpdaterTick();
void onChapterLoaded(Playable media);
}
}