package de.danoeh.antennapodsp.service.download;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaMetadataRetriever;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.webkit.URLUtil;
import de.danoeh.antennapodsp.AppConfig;
import de.danoeh.antennapodsp.R;
import de.danoeh.antennapodsp.activity.MainActivity;
import de.danoeh.antennapodsp.feed.*;
import de.danoeh.antennapodsp.storage.*;
import de.danoeh.antennapodsp.syndication.handler.FeedHandler;
import de.danoeh.antennapodsp.syndication.handler.UnsupportedFeedtypeException;
import de.danoeh.antennapodsp.util.ChapterUtils;
import de.danoeh.antennapodsp.util.DownloadError;
import de.danoeh.antennapodsp.util.InvalidFeedException;
import org.apache.commons.lang3.StringUtils;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Manages the download of feedfiles in the app. Downloads can be enqueued viathe startService intent.
* The argument of the intent is an instance of DownloadRequest in the EXTRA_REQUEST field of
* the intent.
* After the downloads have finished, the downloaded object will be passed on to a specific handler, depending on the
* type of the feedfile.
*/
public class DownloadService extends Service {
private static final String TAG = "DownloadService";
/**
* Cancels one download. The intent MUST have an EXTRA_DOWNLOAD_URL extra that contains the download URL of the
* object whose download should be cancelled.
*/
public static final String ACTION_CANCEL_DOWNLOAD = "action.de.danoeh.antennapod.service.cancelDownload";
/**
* Cancels all running downloads.
*/
public static final String ACTION_CANCEL_ALL_DOWNLOADS = "action.de.danoeh.antennapod.service.cancelAllDownloads";
/**
* Extra for ACTION_CANCEL_DOWNLOAD
*/
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.service.downloadsContentChanged";
/**
* Extra for ACTION_ENQUEUE_DOWNLOAD intent.
*/
public static final String EXTRA_REQUEST = "request";
/**
* Stores DownloadStatus objects of completed downloads for creating a report at the end of the lifecylce.
*/
private List<DownloadStatus> completedDownloads;
private ExecutorService syncExecutor;
private CompletionService<Downloader> downloadExecutor;
/**
* Number of threads of downloadExecutor.
*/
private static final int NUM_PARALLEL_DOWNLOADS = 4;
private DownloadRequester requester;
private NotificationCompat.Builder notificationCompatBuilder;
private Notification.BigTextStyle notificationBuilder;
private int NOTIFICATION_ID = 2;
private int REPORT_ID = 3;
/**
* Currently running downloads.
*/
private List<Downloader> downloads;
/**
* Number of running downloads.
*/
private AtomicInteger numberOfDownloads;
/**
* True if service is running.
*/
public static boolean isRunning = false;
private Handler handler;
private NotificationUpdater notificationUpdater;
private ScheduledFuture notificationUpdaterFuture;
private static final int SCHED_EX_POOL_SIZE = 1;
private ScheduledThreadPoolExecutor schedExecutor;
private final IBinder mBinder = new LocalBinder();
public class LocalBinder extends Binder {
public DownloadService getService() {
return DownloadService.this;
}
}
private Thread downloadCompletionThread = new Thread() {
private static final String TAG = "downloadCompletionThread";
@Override
public void run() {
if (AppConfig.DEBUG) Log.d(TAG, "downloadCompletionThread was started");
while (!isInterrupted()) {
try {
Downloader downloader = downloadExecutor.take().get();
if (AppConfig.DEBUG)
Log.d(TAG, "Received 'Download Complete' - message.");
removeDownload(downloader);
DownloadStatus status = downloader.getResult();
boolean successful = status.isSuccessful();
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());
} else if (type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
handleCompletedFeedMediaDownload(status, downloader.getDownloadRequest());
}
} else {
numberOfDownloads.decrementAndGet();
if (!successful && !status.isCancelled()) {
Log.e(TAG, "Download failed");
saveDownloadStatus(status);
}
sendDownloadHandledIntent();
queryDownloadsAsync();
}
} catch (InterruptedException e) {
if (AppConfig.DEBUG) Log.d(TAG, "DownloadCompletionThread was interrupted");
} catch (ExecutionException e) {
e.printStackTrace();
numberOfDownloads.decrementAndGet();
}
}
if (AppConfig.DEBUG) Log.d(TAG, "End of downloadCompletionThread");
}
};
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent.getParcelableExtra(EXTRA_REQUEST) != null) {
onDownloadQueued(intent);
} else if (numberOfDownloads.get() == 0) {
stopSelf();
}
return Service.START_NOT_STICKY;
}
@SuppressLint("NewApi")
@Override
public void onCreate() {
if (AppConfig.DEBUG)
Log.d(TAG, "Service started");
isRunning = true;
handler = new Handler();
completedDownloads = Collections.synchronizedList(new ArrayList<DownloadStatus>());
downloads = new ArrayList<Downloader>();
numberOfDownloads = new AtomicInteger(0);
IntentFilter cancelDownloadReceiverFilter = new IntentFilter();
cancelDownloadReceiverFilter.addAction(ACTION_CANCEL_ALL_DOWNLOADS);
cancelDownloadReceiverFilter.addAction(ACTION_CANCEL_DOWNLOAD);
registerReceiver(cancelDownloadReceiver, cancelDownloadReceiverFilter);
syncExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setPriority(Thread.MIN_PRIORITY);
return t;
}
});
downloadExecutor = new ExecutorCompletionService<Downloader>(
Executors.newFixedThreadPool(NUM_PARALLEL_DOWNLOADS,
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setPriority(Thread.MIN_PRIORITY);
return t;
}
}
)
);
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;
}
}, new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r,
ThreadPoolExecutor executor) {
Log.w(TAG, "SchedEx rejected submission of new task");
}
}
);
downloadCompletionThread.start();
setupNotificationBuilders();
requester = DownloadRequester.getInstance();
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public void onDestroy() {
if (AppConfig.DEBUG)
Log.d(TAG, "Service shutting down");
isRunning = false;
stopForeground(true);
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
nm.cancel(NOTIFICATION_ID);
downloadCompletionThread.interrupt();
syncExecutor.shutdown();
schedExecutor.shutdown();
cancelNotificationUpdater();
unregisterReceiver(cancelDownloadReceiver);
}
@SuppressLint("NewApi")
private void setupNotificationBuilders() {
PendingIntent pIntent = PendingIntent.getActivity(this, 0, new Intent(
this, MainActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT
);
Bitmap icon = BitmapFactory.decodeResource(getResources(),
R.drawable.stat_notify_sync);
if (android.os.Build.VERSION.SDK_INT >= 16) {
notificationBuilder = new Notification.BigTextStyle(
new Notification.Builder(this).setOngoing(true)
.setContentIntent(pIntent).setLargeIcon(icon)
.setSmallIcon(R.drawable.stat_notify_sync)
);
} else {
notificationCompatBuilder = new NotificationCompat.Builder(this)
.setOngoing(true).setContentIntent(pIntent)
.setLargeIcon(icon)
.setSmallIcon(R.drawable.stat_notify_sync);
}
if (AppConfig.DEBUG)
Log.d(TAG, "Notification set up");
}
/**
* Updates the contents of the service's notifications. Should be called
* before setupNotificationBuilders.
*/
@SuppressLint("NewApi")
private Notification updateNotifications() {
String contentTitle = getString(R.string.download_notification_title);
String downloadsLeft = requester.getNumberOfDownloads()
+ getString(R.string.downloads_left);
if (android.os.Build.VERSION.SDK_INT >= 16) {
if (notificationBuilder != null) {
StringBuilder bigText = new StringBuilder("");
for (int i = 0; i < downloads.size(); i++) {
Downloader downloader = downloads.get(i);
final DownloadRequest request = downloader
.getDownloadRequest();
if (request.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
if (request.getTitle() != null) {
if (i > 0) {
bigText.append("\n");
}
bigText.append("\u2022 " + request.getTitle());
}
} else if (request.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
if (request.getTitle() != null) {
if (i > 0) {
bigText.append("\n");
}
bigText.append("\u2022 " + request.getTitle()
+ " (" + request.getProgressPercent()
+ "%)");
}
}
}
notificationBuilder.setSummaryText(downloadsLeft);
notificationBuilder.setBigContentTitle(contentTitle);
if (bigText != null) {
notificationBuilder.bigText(bigText.toString());
}
return notificationBuilder.build();
}
} else {
if (notificationCompatBuilder != null) {
notificationCompatBuilder.setContentTitle(contentTitle);
notificationCompatBuilder.setContentText(downloadsLeft);
return notificationCompatBuilder.getNotification();
}
}
return null;
}
private Downloader getDownloader(String downloadUrl) {
for (Downloader downloader : downloads) {
if (downloader.getDownloadRequest().getSource().equals(downloadUrl)) {
return downloader;
}
}
return null;
}
private BroadcastReceiver cancelDownloadReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ACTION_CANCEL_DOWNLOAD)) {
String url = intent.getStringExtra(EXTRA_DOWNLOAD_URL);
if (url == null) {
throw new IllegalArgumentException(
"ACTION_CANCEL_DOWNLOAD intent needs download url extra");
}
if (AppConfig.DEBUG)
Log.d(TAG, "Cancelling download with url " + url);
Downloader d = getDownloader(url);
if (d != null) {
d.cancel();
} else {
Log.e(TAG, "Could not cancel download with url " + url);
}
} else if (intent.getAction().equals(ACTION_CANCEL_ALL_DOWNLOADS)) {
for (Downloader d : downloads) {
d.cancel();
if (AppConfig.DEBUG)
Log.d(TAG, "Cancelled all downloads");
}
sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
}
queryDownloads();
}
};
private void onDownloadQueued(Intent intent) {
if (AppConfig.DEBUG)
Log.d(TAG, "Received enqueue request");
DownloadRequest request = intent.getParcelableExtra(EXTRA_REQUEST);
if (request == null) {
throw new IllegalArgumentException(
"ACTION_ENQUEUE_DOWNLOAD intent needs request extra");
}
Downloader downloader = getDownloader(request);
if (downloader != null) {
numberOfDownloads.incrementAndGet();
downloads.add(downloader);
downloadExecutor.submit(downloader);
sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
}
queryDownloads();
}
private Downloader getDownloader(DownloadRequest request) {
if (URLUtil.isHttpUrl(request.getSource())
|| URLUtil.isHttpsUrl(request.getSource())) {
return new HttpDownloader(request);
}
Log.e(TAG,
"Could not find appropriate downloader for "
+ request.getSource()
);
return null;
}
/**
* Remove download from the DownloadRequester list and from the
* DownloadService list.
*/
private void removeDownload(final Downloader d) {
handler.post(new Runnable() {
@Override
public void run() {
if (AppConfig.DEBUG)
Log.d(TAG, "Removing downloader: "
+ d.getDownloadRequest().getSource());
boolean rc = downloads.remove(d);
if (AppConfig.DEBUG)
Log.d(TAG, "Result of downloads.remove: " + rc);
DownloadRequester.getInstance().removeDownload(d.getDownloadRequest());
sendBroadcast(new Intent(ACTION_DOWNLOADS_CONTENT_CHANGED));
}
});
}
/**
* Adds a new DownloadStatus object to the list of completed downloads and
* saves it in the database
*
* @param status the download that is going to be saved
*/
private void saveDownloadStatus(DownloadStatus status) {
completedDownloads.add(status);
DBWriter.addDownloadStatus(this, status);
}
private void sendDownloadHandledIntent() {
EventDistributor.getInstance().sendDownloadHandledBroadcast();
}
/**
* Calls query downloads on the services main thread. This method should be used instead of queryDownloads if it is
* used from a thread other than the main thread.
*/
void queryDownloadsAsync() {
handler.post(new Runnable() {
public void run() {
queryDownloads();
;
}
});
}
/**
* Check if there's something else to download, otherwise stop
*/
void queryDownloads() {
if (AppConfig.DEBUG) {
Log.d(TAG, numberOfDownloads.get() + " downloads left");
}
if (numberOfDownloads.get() <= 0 && DownloadRequester.getInstance().hasNoDownloads()) {
if (AppConfig.DEBUG)
Log.d(TAG, "Number of downloads is " + numberOfDownloads.get() + ", attempting shutdown");
stopSelf();
} else {
setupNotificationUpdater();
startForeground(NOTIFICATION_ID, updateNotifications());
}
}
/**
* Is called whenever a Feed is downloaded
*/
private void handleCompletedFeedDownload(DownloadRequest request) {
if (AppConfig.DEBUG)
Log.d(TAG, "Handling completed Feed Download");
syncExecutor.execute(new FeedSyncThread(request));
}
/**
* Is called whenever a Feed-Image is downloaded
*/
private void handleCompletedImageDownload(DownloadStatus status, DownloadRequest request) {
if (AppConfig.DEBUG)
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) {
if (AppConfig.DEBUG)
Log.d(TAG, "Handling completed FeedMedia Download");
syncExecutor.execute(new MediaHandlerThread(status, request));
}
/**
* Takes a single Feed, parses the corresponding file and refreshes
* information in the manager
*/
class FeedSyncThread implements Runnable {
private static final String TAG = "FeedSyncThread";
private DownloadRequest request;
private DownloadError reason;
private boolean successful;
public FeedSyncThread(DownloadRequest request) {
if (request == null) {
throw new IllegalArgumentException("Request must not be null");
}
this.request = request;
}
public void run() {
Feed savedFeed = null;
Feed feed = new Feed(request.getSource(), new Date());
feed.setFile_url(request.getDestination());
feed.setDownloaded(true);
reason = null;
String reasonDetailed = null;
successful = true;
FeedHandler feedHandler = new FeedHandler();
try {
feed = feedHandler.parseFeed(feed);
if (AppConfig.DEBUG)
Log.d(TAG, feed.getTitle() + " parsed");
if (checkFeedData(feed) == false) {
throw new InvalidFeedException();
}
// Save information of feed in DB
savedFeed = DBTasks.updateFeed(DownloadService.this, feed);
// Download Feed Image if provided and not downloaded
if (savedFeed.getImage() != null
&& savedFeed.getImage().isDownloaded() == false) {
if (AppConfig.DEBUG)
Log.d(TAG, "Feed has image; Downloading....");
savedFeed.getImage().setOwner(savedFeed);
final Feed savedFeedRef = savedFeed;
try {
requester.downloadImage(DownloadService.this,
savedFeedRef.getImage());
} catch (DownloadRequestException e) {
e.printStackTrace();
DBWriter.addDownloadStatus(
DownloadService.this,
new DownloadStatus(
savedFeedRef.getImage(),
savedFeedRef
.getImage()
.getHumanReadableIdentifier(),
DownloadError.ERROR_REQUEST_ERROR,
false, e.getMessage()
)
);
}
}
if (!hasDuplicateImages(savedFeed)) {
for (FeedItem item : savedFeed.getItems()) {
if (item.isItemImage() && (!item.getImage().isDownloaded())) {
if (AppConfig.DEBUG)
Log.d(TAG, "Item has image; Downloading....");
try {
requester.downloadImage(DownloadService.this,
item.getImage());
} catch (DownloadRequestException e) {
e.printStackTrace();
DBWriter.addDownloadStatus(
DownloadService.this,
new DownloadStatus(
item.getImage(),
item
.getImage()
.getHumanReadableIdentifier(),
DownloadError.ERROR_REQUEST_ERROR,
false, e.getMessage()
)
);
}
}
}
}
} catch (SAXException e) {
successful = false;
e.printStackTrace();
reason = DownloadError.ERROR_PARSER_EXCEPTION;
reasonDetailed = e.getMessage();
} catch (IOException e) {
successful = false;
e.printStackTrace();
reason = DownloadError.ERROR_PARSER_EXCEPTION;
reasonDetailed = e.getMessage();
} catch (ParserConfigurationException e) {
successful = false;
e.printStackTrace();
reason = DownloadError.ERROR_PARSER_EXCEPTION;
reasonDetailed = e.getMessage();
} catch (UnsupportedFeedtypeException e) {
e.printStackTrace();
successful = false;
reason = DownloadError.ERROR_UNSUPPORTED_TYPE;
reasonDetailed = e.getMessage();
} catch (InvalidFeedException e) {
e.printStackTrace();
successful = false;
reason = DownloadError.ERROR_PARSER_EXCEPTION;
reasonDetailed = e.getMessage();
}
// cleanup();
if (savedFeed == null) {
savedFeed = feed;
}
saveDownloadStatus(new DownloadStatus(savedFeed,
savedFeed.getHumanReadableIdentifier(), reason, successful,
reasonDetailed));
sendDownloadHandledIntent();
numberOfDownloads.decrementAndGet();
queryDownloadsAsync();
}
/**
* Checks if the feed was parsed correctly.
*/
private boolean checkFeedData(Feed feed) {
if (feed.getTitle() == null) {
Log.e(TAG, "Feed has no title.");
return false;
}
if (!hasValidFeedItems(feed)) {
Log.e(TAG, "Feed has invalid items");
return false;
}
if (AppConfig.DEBUG)
Log.d(TAG, "Feed appears to be valid.");
return true;
}
/**
* Checks if the FeedItems of this feed have images that point
* to the same URL.
*/
private boolean hasDuplicateImages(Feed feed) {
for (int x = 0; x < feed.getItems().size(); x++) {
for (int y = x + 1; y < feed.getItems().size(); y++) {
FeedItem item1 = feed.getItems().get(x);
FeedItem item2 = feed.getItems().get(y);
if (item1.isItemImage() && item2.isItemImage()) {
if (StringUtils.equals(item1.getImage().getDownload_url(), item2.getImage().getDownload_url())) {
return true;
}
}
}
}
return false;
}
private boolean hasValidFeedItems(Feed feed) {
for (FeedItem item : feed.getItems()) {
if (item.getTitle() == null) {
Log.e(TAG, "Item has no title");
return false;
}
if (item.getPubDate() == null) {
Log.e(TAG,
"Item has no pubDate. Using current time as pubDate");
if (item.getTitle() != null) {
Log.e(TAG, "Title of invalid item: " + item.getTitle());
}
item.setPubDate(new Date());
}
}
return true;
}
/**
* Delete files that aren't needed anymore
*/
private void cleanup(Feed feed) {
if (feed.getFile_url() != null) {
if (new File(feed.getFile_url()).delete())
if (AppConfig.DEBUG)
Log.d(TAG, "Successfully deleted cache file.");
else
Log.e(TAG, "Failed to delete cache file.");
feed.setFile_url(null);
} else if (AppConfig.DEBUG) {
Log.d(TAG, "Didn't delete cache file: File url is not set.");
}
}
}
/**
* Handles a completed image download.
*/
class ImageHandlerThread implements Runnable {
private DownloadRequest request;
private DownloadStatus status;
public ImageHandlerThread(DownloadStatus status, DownloadRequest request) {
if (status == null) {
throw new IllegalArgumentException("Status must not be null");
}
if (request == null) {
throw new IllegalArgumentException("Request must not be null");
}
this.status = status;
this.request = request;
}
@Override
public void run() {
FeedImage image = DBReader.getFeedImage(DownloadService.this, 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(DownloadService.this, image);
numberOfDownloads.decrementAndGet();
queryDownloadsAsync();
}
}
/**
* Handles a completed media download.
*/
class MediaHandlerThread implements Runnable {
private DownloadRequest request;
private DownloadStatus status;
public MediaHandlerThread(DownloadStatus status, DownloadRequest request) {
if (status == null) {
throw new IllegalArgumentException("Status must not be null");
}
if (request == null) {
throw new IllegalArgumentException("Request must not be null");
}
this.status = status;
this.request = request;
}
@Override
public void run() {
FeedMedia media = DBReader.getFeedMedia(DownloadService.this,
request.getFeedfileId());
if (media == null) {
throw new IllegalStateException(
"Could not find downloaded media object in database");
}
boolean chaptersRead = false;
media.setDownloaded(true);
media.setFile_url(request.getDestination());
// Get duration
MediaMetadataRetriever mmr = null;
try {
mmr = new MediaMetadataRetriever();
mmr.setDataSource(media.getFile_url());
String durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
media.setDuration(Integer.parseInt(durationStr));
if (AppConfig.DEBUG)
Log.d(TAG, "Duration of file is " + media.getDuration());
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (RuntimeException e) {
e.printStackTrace();
} finally {
if (mmr != null) {
mmr.release();
}
}
if (media.getItem().getChapters() == null) {
ChapterUtils.loadChaptersFromFileUrl(media);
if (media.getItem().getChapters() != null) {
chaptersRead = true;
}
}
try {
if (chaptersRead) {
DBWriter.setFeedItem(DownloadService.this, media.getItem()).get();
}
DBWriter.setFeedMedia(DownloadService.this, media).get();
if (!DBTasks.isInQueue(DownloadService.this, media.getItem().getId())) {
DBWriter.addQueueItem(DownloadService.this, media.getItem().getId()).get();
}
} catch (ExecutionException e) {
e.printStackTrace();
status = new DownloadStatus(media, media.getEpisodeTitle(), DownloadError.ERROR_DB_ACCESS_ERROR, false, e.getMessage());
} catch (InterruptedException e) {
e.printStackTrace();
status = new DownloadStatus(media, media.getEpisodeTitle(), DownloadError.ERROR_DB_ACCESS_ERROR, false, e.getMessage());
}
saveDownloadStatus(status);
sendDownloadHandledIntent();
numberOfDownloads.decrementAndGet();
queryDownloadsAsync();
}
}
/**
* Schedules the notification updater task if it hasn't been scheduled yet.
*/
private void setupNotificationUpdater() {
if (AppConfig.DEBUG)
Log.d(TAG, "Setting up notification updater");
if (notificationUpdater == null) {
notificationUpdater = new NotificationUpdater();
notificationUpdaterFuture = schedExecutor.scheduleAtFixedRate(
notificationUpdater, 5L, 5L, TimeUnit.SECONDS);
}
}
private void cancelNotificationUpdater() {
boolean result = false;
if (notificationUpdaterFuture != null) {
result = notificationUpdaterFuture.cancel(true);
}
notificationUpdater = null;
notificationUpdaterFuture = null;
Log.d(TAG, "NotificationUpdater cancelled. Result: " + result);
}
private class NotificationUpdater implements Runnable {
public void run() {
handler.post(new Runnable() {
@Override
public void run() {
Notification n = updateNotifications();
if (n != null) {
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(NOTIFICATION_ID, n);
}
}
});
}
}
public List<Downloader> getDownloads() {
return downloads;
}
}