/** * Waits for completed requests. Once the first request has been taken, the method will wait * WAIT_TIMEOUT ms longer to collect more completed requests. * * @return Collected feeds or null if the method has been interrupted during the first waiting * period. */ private List<Feed> collectCompletedRequests() { List<Feed> results = new LinkedList<Feed>(); DownloadRequester requester = DownloadRequester.getInstance(); int tasks = 0; try { DownloadRequest request = completedRequests.take(); parserService.submit(new FeedParserTask(request)); tasks++; } catch (InterruptedException e) { return null; } tasks += pollCompletedDownloads(); isCollectingRequests = true; if (requester.isDownloadingFeeds()) { // wait for completion of more downloads long startTime = System.currentTimeMillis(); long currentTime = startTime; while (requester.isDownloadingFeeds() && (currentTime - startTime) < WAIT_TIMEOUT) { try { if (BuildConfig.DEBUG) Log.d(TAG, "Waiting for " + (startTime + WAIT_TIMEOUT - currentTime) + " ms"); sleep(startTime + WAIT_TIMEOUT - currentTime); } catch (InterruptedException e) { if (BuildConfig.DEBUG) Log.d(TAG, "interrupted while waiting for more downloads"); tasks += pollCompletedDownloads(); } finally { currentTime = System.currentTimeMillis(); } } tasks += pollCompletedDownloads(); } isCollectingRequests = false; for (int i = 0; i < tasks; i++) { try { Feed f = parserService.take().get(); if (f != null) { results.add(f); } } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } return results; }
/** * 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); int numDownloads = requester.getNumberOfDownloads(); String downloadsLeft; if (numDownloads > 0) { downloadsLeft = requester.getNumberOfDownloads() + getString(R.string.downloads_left); } else { downloadsLeft = getString(R.string.downloads_processing); } 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.build(); } } return null; }
@Override public void run() { if (BuildConfig.DEBUG) Log.d(TAG, "downloadCompletionThread was started"); while (!isInterrupted()) { try { Downloader downloader = downloadExecutor.take().get(); if (BuildConfig.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 (!status.isCancelled()) { if (status.getReason() == DownloadError.ERROR_UNAUTHORIZED) { postAuthenticationNotification(downloader.getDownloadRequest()); } else if (status.getReason() == DownloadError.ERROR_HTTP_DATA_ERROR && Integer.valueOf(status.getReasonDetailed()) == HttpStatus.SC_REQUESTED_RANGE_NOT_SATISFIABLE) { Log.d(TAG, "Requested invalid range, restarting download from the beginning"); FileUtils.deleteQuietly( new File(downloader.getDownloadRequest().getDestination())); DownloadRequester.getInstance() .download(DownloadService.this, downloader.getDownloadRequest()); } else { Log.e(TAG, "Download failed"); saveDownloadStatus(status); handleFailedDownload(status, downloader.getDownloadRequest()); } } sendDownloadHandledIntent(); queryDownloadsAsync(); } } catch (InterruptedException e) { if (BuildConfig.DEBUG) Log.d(TAG, "DownloadCompletionThread was interrupted"); } catch (ExecutionException e) { e.printStackTrace(); numberOfDownloads.decrementAndGet(); } } if (BuildConfig.DEBUG) Log.d(TAG, "End of downloadCompletionThread"); }
/** Check if there's something else to download, otherwise stop */ void queryDownloads() { if (BuildConfig.DEBUG) { Log.d(TAG, numberOfDownloads.get() + " downloads left"); } if (numberOfDownloads.get() <= 0 && DownloadRequester.getInstance().hasNoDownloads()) { if (BuildConfig.DEBUG) Log.d(TAG, "Number of downloads is " + numberOfDownloads.get() + ", attempting shutdown"); stopSelf(); } else { setupNotificationUpdater(); startForeground(NOTIFICATION_ID, updateNotifications()); } }
@SuppressLint("NewApi") @Override public void onCreate() { if (BuildConfig.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(); feedSyncThread = new FeedSyncThread(); feedSyncThread.start(); setupNotificationBuilders(); requester = DownloadRequester.getInstance(); }