/**
     * 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;
  }
  /**
   * 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 created if the number of successfully downloaded
   * feeds is bigger than 1 or if there is at least one failed download which is not an image or if
   * there is at least one downloaded media file.
   */
  private void updateReport() {
    // check if report should be created
    boolean createReport = false;
    int successfulDownloads = 0;
    int failedDownloads = 0;

    // a download report is created if at least one download has failed
    // (excluding failed image downloads)
    for (DownloadStatus status : completedDownloads) {
      if (status.isSuccessful()) {
        successfulDownloads++;
      } else if (!status.isCancelled()) {
        if (status.getFeedfileType() != FeedImage.FEEDFILETYPE_FEEDIMAGE) {
          createReport = true;
        }
        failedDownloads++;
      }
    }

    if (createReport) {
      if (BuildConfig.DEBUG) Log.d(TAG, "Creating report");
      Intent intent = new Intent(this, MainActivity.class);
      intent.putExtra(MainActivity.EXTRA_NAV_TYPE, NavListAdapter.VIEW_TYPE_NAV);
      intent.putExtra(MainActivity.EXTRA_NAV_INDEX, MainActivity.POS_DOWNLOADS);
      Bundle args = new Bundle();
      args.putInt(DownloadsFragment.ARG_SELECTED_TAB, DownloadsFragment.POS_LOG);
      intent.putExtra(MainActivity.EXTRA_FRAGMENT_ARGS, args);

      // create notification object
      Notification notification =
          new NotificationCompat.Builder(this)
              .setTicker(getString(de.danoeh.antennapod.R.string.download_report_title))
              .setContentTitle(getString(de.danoeh.antennapod.R.string.download_report_title))
              .setContentText(
                  String.format(
                      getString(R.string.download_report_content),
                      successfulDownloads,
                      failedDownloads))
              .setSmallIcon(R.drawable.stat_notify_sync)
              .setLargeIcon(
                  BitmapFactory.decodeResource(getResources(), R.drawable.stat_notify_sync))
              .setContentIntent(
                  PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT))
              .setAutoCancel(true)
              .build();
      NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
      nm.notify(REPORT_ID, notification);
    } else {
      if (BuildConfig.DEBUG) Log.d(TAG, "No report is created");
    }
    completedDownloads.clear();
  }
  private void onDownloadQueued(Intent intent) {
    if (BuildConfig.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();
  }
    @Override
    public void run() {
      while (isActive) {
        final List<Feed> feeds = collectCompletedRequests();

        if (feeds == null) {
          continue;
        }

        if (BuildConfig.DEBUG) Log.d(TAG, "Bundling " + feeds.size() + " feeds");

        for (Feed feed : feeds) {
          removeDuplicateImages(
              feed); // duplicate images have to removed because the DownloadRequester does not
                     // accept two downloads with the same download URL yet.
        }

        // Save information of feed in DB
        if (dbUpdateFuture != null) {
          try {
            dbUpdateFuture.get();
          } catch (InterruptedException e) {
            e.printStackTrace();
          } catch (ExecutionException e) {
            e.printStackTrace();
          }
        }

        dbUpdateFuture =
            dbService.submit(
                new Runnable() {
                  @Override
                  public void run() {
                    Feed[] savedFeeds =
                        DBTasks.updateFeed(
                            DownloadService.this, feeds.toArray(new Feed[feeds.size()]));

                    for (Feed savedFeed : savedFeeds) {
                      // Download Feed Image if provided and not downloaded
                      if (savedFeed.getImage() != null
                          && savedFeed.getImage().isDownloaded() == false) {
                        if (BuildConfig.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()));
                        }
                      }
                      numberOfDownloads.decrementAndGet();
                    }

                    sendDownloadHandledIntent();

                    queryDownloadsAsync();
                  }
                });
      }

      if (dbUpdateFuture != null) {
        try {
          dbUpdateFuture.get();
        } catch (InterruptedException e) {
        } catch (ExecutionException e) {
          e.printStackTrace();
        }
      }

      if (BuildConfig.DEBUG) Log.d(TAG, "Shutting down");
    }
 /**
  * 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);
 }