/**
     * 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 (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());
    }
  }
  @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();
  }