title: how to track percentage of progressing download of each downloading file in exoplayer library?
body: <p>I am working on a media player app. I'm using ExoPlayer library. I have a playlist of videos, and I want to download videos simultaneously. I done it by using available demo app of exoplayer library on GitHub. I want to show progress of each downloading in the UI. For this job I get help from <code>DownloadNotificationUtil.buildProgressNotification</code> method.</p>
  
  <pre><code>  @Override
  protected Notification getForegroundNotification(TaskState[] taskStates) {
  
      float totalPercentage = 0;
      int downloadTaskCount = 0;
      boolean allDownloadPercentagesUnknown = true;
      boolean haveDownloadedBytes = false;
      boolean haveDownloadTasks = false;
      boolean haveRemoveTasks = false;
      Log.e(TAG,"size task state: "+taskStates.length);
      for (TaskState taskState : taskStates) {
          Log.e(TAG,"taskId= "+taskState.taskId);
          if (taskState.state != TaskState.STATE_STARTED
                  &amp;&amp; taskState.state != TaskState.STATE_COMPLETED) {
              continue;
          }
          if (taskState.action.isRemoveAction) {
              haveRemoveTasks = true;
              continue;
          }
          haveDownloadTasks = true;
          if (taskState.downloadPercentage != C.PERCENTAGE_UNSET) {
              allDownloadPercentagesUnknown = false;
              totalPercentage += taskState.downloadPercentage;
          }
          haveDownloadedBytes |= taskState.downloadedBytes &gt; 0;
          downloadTaskCount++;
      }
  
      int progress = 0;
      boolean indeterminate = true;
      if (haveDownloadTasks) {
          progress = (int) (totalPercentage / downloadTaskCount);
          indeterminate = allDownloadPercentagesUnknown &amp;&amp; haveDownloadedBytes;
  
          Log.e(TAG,"notifi "+progress);
      }
      return DownloadNotificationUtil.buildProgressNotification(
              this,
              R.drawable.exo_icon_play,
              DOWNLOAD_CHANNEL_ID,
              null,
              null,
              taskStates);
  }
  </code></pre>
  
  <p>Now,I can track the progress downloading. But I still have a problem. I can't understand which item is downloading to update it's progress bar in the UI. Is there a Identical id of each download to recognize it? For example Android Download Manager has a download ID for each downloading file. But I don't know , how to handle this problem.
  This is <code>MediaDownloadService</code>:</p>
  
  <pre><code>public class MediaDownloadService extends DownloadService {
  
  public static String TAG="MediaDownloadService";
  
  private static final int FOREGROUND_NOTIFICATION_ID = 1;
  public MediaDownloadService() {
      super(
              DOWNLOAD_NOTIFICATION_ID,
              DEFAULT_FOREGROUND_NOTIFICATION_UPDATE_INTERVAL,
              DOWNLOAD_CHANNEL_ID,
              R.string.download_channel_name);
  }
  
  
  
  @Override
  protected DownloadManager getDownloadManager() {
      return ((MyApplication) getApplication()).getDownloadManager();
  }
  
  @Nullable
  @Override
  protected Scheduler getScheduler() {
      return null;
  }
  
  @Override
  protected Notification getForegroundNotification(TaskState[] taskStates) {
  
      float totalPercentage = 0;
      int downloadTaskCount = 0;
      boolean allDownloadPercentagesUnknown = true;
      boolean haveDownloadedBytes = false;
      boolean haveDownloadTasks = false;
      boolean haveRemoveTasks = false;
      for (TaskState taskState : taskStates) {
          if (taskState.state != TaskState.STATE_STARTED
                  &amp;&amp; taskState.state != TaskState.STATE_COMPLETED) {
              continue;
          }
          if (taskState.action.isRemoveAction) {
              haveRemoveTasks = true;
              continue;
          }
          haveDownloadTasks = true;
          if (taskState.downloadPercentage != C.PERCENTAGE_UNSET) {
              allDownloadPercentagesUnknown = false;
              totalPercentage += taskState.downloadPercentage;
          }
          haveDownloadedBytes |= taskState.downloadedBytes &gt; 0;
          downloadTaskCount++;
      }
  
      int progress = 0;
      boolean indeterminate = true;
      if (haveDownloadTasks) {
          progress = (int) (totalPercentage / downloadTaskCount);
          indeterminate = allDownloadPercentagesUnknown &amp;&amp; haveDownloadedBytes;
  
          Log.e(TAG,"notifi "+progress);
          sendIntent(progress);
      }
      return DownloadNotificationUtil.buildProgressNotification(
              this,
              R.drawable.exo_icon_play,
              DOWNLOAD_CHANNEL_ID,
              null,
              null,
              taskStates);
  }
  
  private void sendIntent(int progress){
      Intent intent = new Intent(ConstantUtil.MESSAGE_PROGRESS);
      intent.putExtra("progress",progress);
      LocalBroadcastManager.getInstance(MediaDownloadService.this).sendBroadcast(intent);
  }
  
  @Override
  protected void onTaskStateChanged(TaskState taskState) {
      if (taskState.action.isRemoveAction) {
          return;
      }
  
      Notification notification = null;
      if (taskState.state == TaskState.STATE_COMPLETED) {
          Log.e(TAG,"STATE_COMPLETED");
          notification =
                  DownloadNotificationUtil.buildDownloadCompletedNotification(
                          /* context= */ this,
                          R.drawable.exo_controls_play,
                          DOWNLOAD_CHANNEL_ID,
                          /* contentIntent= */ null,
                          Util.fromUtf8Bytes(taskState.action.data));
      } else if (taskState.state == TaskState.STATE_FAILED) {
          Log.e(TAG,"STATE_FAILED");
          notification =
                  DownloadNotificationUtil.buildDownloadFailedNotification(
                          /* context= */ this,
                          R.drawable.exo_controls_play,
                          DOWNLOAD_CHANNEL_ID,
                          /* contentIntent= */ null,
                          Util.fromUtf8Bytes(taskState.action.data));
      }
      int notificationId = FOREGROUND_NOTIFICATION_ID + 1 + taskState.taskId;
      NotificationUtil.setNotification(this, notificationId, notification);
  }
  </code></pre>
  
  <p>}</p>
  
  <p>This is <code>DownloadTracker</code> class:</p>
  
  <pre><code>    public class DownloadTracker implements DownloadManager.Listener {
  
      /** Listens for changes in the tracked downloads. */
      public interface Listener {
  
          /** Called when the tracked downloads changed. */
          void onDownloadsChanged();
      }
  
      private static final String TAG = "DownloadTracker";
  
      private final Context context;
      private final DataSource.Factory dataSourceFactory;
      private final TrackNameProvider trackNameProvider;
      private final CopyOnWriteArraySet&lt;Listener&gt; listeners;
      private  Listener onDownloadsChanged;
      private final HashMap&lt;Uri, DownloadAction&gt; trackedDownloadStates;
      private final ActionFile actionFile;
      private final Handler actionFileWriteHandler;
  
      public DownloadTracker(
              Context context,
              DataSource.Factory dataSourceFactory,
              File actionFile,
              DownloadAction.Deserializer... deserializers) {
          this.context = context.getApplicationContext();
          this.dataSourceFactory = dataSourceFactory;
          this.actionFile = new ActionFile(actionFile);
          trackNameProvider = new DefaultTrackNameProvider(context.getResources());
          listeners = new CopyOnWriteArraySet&lt;&gt;();
          trackedDownloadStates = new HashMap&lt;&gt;();
          HandlerThread actionFileWriteThread = new HandlerThread("DownloadTracker");
          actionFileWriteThread.start();
          actionFileWriteHandler = new Handler(actionFileWriteThread.getLooper());
          loadTrackedActions(
                  deserializers.length &gt; 0 ? deserializers : DownloadAction.getDefaultDeserializers());
      }
  
  
  
      public void addListener(Listener listener) {
          listeners.add(listener);
      }
  
      public void removeListener(Listener listener) {
          listeners.remove(listener);
      }
  
      public boolean isDownloaded(Uri uri) {
          return trackedDownloadStates.containsKey(uri);
      }
  
      @SuppressWarnings("unchecked")
      public List&lt;StreamKey&gt; getOfflineStreamKeys(Uri uri) {
          if (!trackedDownloadStates.containsKey(uri)) {
              return Collections.emptyList();
          }
          return trackedDownloadStates.get(uri).getKeys();
      }
  
      public int toggleDownload(Activity activity, String name, Uri uri, String extension) {
          if (isDownloaded(uri)) {
              Log.e(TAG,"isDownloaded");
              DownloadAction removeAction =
                      getDownloadHelper(uri, extension).getRemoveAction(Util.getUtf8Bytes(name));
              startServiceWithAction(removeAction);
              return -1;
          } else {
              StartDownloadDialogHelper helper =
                      new StartDownloadDialogHelper(activity, getDownloadHelper(uri, extension), name);
              helper.prepare();
              return helper.getTaskId();
          }
      }
  
      @Override
      public void onInitialized(DownloadManager downloadManager) {
          // Do nothing.
      }
  
      @Override
      public void onTaskStateChanged(DownloadManager downloadManager, TaskState taskState) {
  
          DownloadAction action = taskState.action;
          Uri uri = action.uri;
          if ((action.isRemoveAction &amp;&amp; taskState.state == TaskState.STATE_COMPLETED)
                  || (!action.isRemoveAction &amp;&amp; taskState.state == TaskState.STATE_FAILED)) {
              // A download has been removed, or has failed. Stop tracking it.
              if (trackedDownloadStates.remove(uri) != null) {
  
  
                  handleTrackedDownloadStatesChanged();
              }
          }   
  
      }
  
      @Override
      public void onIdle(DownloadManager downloadManager) {
          // Do nothing.
      }
  
      // Internal methods
  
      private void loadTrackedActions(DownloadAction.Deserializer[] deserializers) {
          try {
              DownloadAction[] allActions = actionFile.load(deserializers);
              for (DownloadAction action : allActions) {
                  trackedDownloadStates.put(action.uri, action);
              }
          } catch (IOException e) {
              Log.e(TAG, "Failed to load tracked actions", e);
          }
      }
  
      private void handleTrackedDownloadStatesChanged() {
          for (Listener listener : listeners) {
              listener.onDownloadsChanged();
          }
          final DownloadAction[] actions = trackedDownloadStates.values().toArray(new DownloadAction[0]);
          Log.e(TAG,"actions: "+actions.toString());
          actionFileWriteHandler.post(
                  () -&gt; {
                      try {
                          actionFile.store(actions);
                      } catch (IOException e) {
                          Log.e(TAG, "Failed to store tracked actions", e);
                      }
                  });
      }
  
      private void startDownload(DownloadAction action) {
          if (trackedDownloadStates.containsKey(action.uri)) {
              // This content is already being downloaded. Do nothing.
              Log.e(TAG,"download already exsit");
              return;
          }
          trackedDownloadStates.put(action.uri, action);
          handleTrackedDownloadStatesChanged();
          startServiceWithAction(action);
      }
  
      private void startServiceWithAction(DownloadAction action) {
  
          DownloadService.startWithAction(context, MediaDownloadService.class, action, false);
      }
  
      private DownloadHelper getDownloadHelper(Uri uri, String extension) {
          int type = Util.inferContentType(uri, extension);
          switch (type) {
              case C.TYPE_DASH:
                  return new DashDownloadHelper(uri, dataSourceFactory);
              case C.TYPE_SS:
                  return new SsDownloadHelper(uri, dataSourceFactory);
              case C.TYPE_HLS:
                  return new HlsDownloadHelper(uri, dataSourceFactory);
              case C.TYPE_OTHER:
                  return new ProgressiveDownloadHelper(uri);
              default:
                  throw new IllegalStateException("Unsupported type: " + type);
          }
      }
  
      private final class StartDownloadDialogHelper
              implements DownloadHelper.Callback, DialogInterface.OnClickListener {
  
          private final DownloadHelper downloadHelper;
          private final String name;
  
          private final AlertDialog.Builder builder;
          private final View dialogView;
          private final List&lt;TrackKey&gt; trackKeys;
          private final ArrayAdapter&lt;String&gt; trackTitles;
          private final ListView representationList;
          private int taskId;
  
          public StartDownloadDialogHelper(
                  Activity activity, DownloadHelper downloadHelper, String name) {
              this.downloadHelper = downloadHelper;
              this.name = name;
              builder =
                      new AlertDialog.Builder(activity)
                              .setTitle(R.string.exo_download_description)
                              .setPositiveButton(android.R.string.ok, this)
                              .setNegativeButton(android.R.string.cancel, null);
  
              // Inflate with the builder's context to ensure the correct style is used.
              LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
              dialogView = dialogInflater.inflate(R.layout.start_download_dialog, null);
  
              trackKeys = new ArrayList&lt;&gt;();
              trackTitles =
                      new ArrayAdapter&lt;&gt;(
                              builder.getContext(), android.R.layout.simple_list_item_multiple_choice);
              representationList = dialogView.findViewById(R.id.representation_list);
              representationList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
              representationList.setAdapter(trackTitles);
          }
  
          public void prepare() {
              downloadHelper.prepare(this);
  
          }
  
          @Override
          public void onPrepared(DownloadHelper helper) {
  
              for (int i = 0; i &lt; downloadHelper.getPeriodCount(); i++) {
                  TrackGroupArray trackGroups = downloadHelper.getTrackGroups(i);
                  for (int j = 0; j &lt; trackGroups.length; j++) {
                      TrackGroup trackGroup = trackGroups.get(j);
                      for (int k = 0; k &lt; trackGroup.length; k++) {
                          trackKeys.add(new TrackKey(i, j, k));
                          trackTitles.add(trackNameProvider.getTrackName(trackGroup.getFormat(k)));
                      }
                  }
              }
              if (!trackKeys.isEmpty()) {
                  builder.setView(dialogView);
              }
              builder.create().show();
          }
  
          @Override
          public void onPrepareError(DownloadHelper helper, IOException e) {
              Toast.makeText(
                      context.getApplicationContext(), R.string.download_start_error, Toast.LENGTH_LONG)
                      .show();
              Log.e(TAG, "Failed to start download", e);
          }
  
          @Override
          public void onClick(DialogInterface dialog, int which) {
              ArrayList&lt;TrackKey&gt; selectedTrackKeys = new ArrayList&lt;&gt;();
              for (int i = 0; i &lt; representationList.getChildCount(); i++) {
                  if (representationList.isItemChecked(i)) {
                      selectedTrackKeys.add(trackKeys.get(i));
                  }
              }
              if (!selectedTrackKeys.isEmpty() || trackKeys.isEmpty()) {
                  // We have selected keys, or we're dealing with single stream content.
                  DownloadAction downloadAction =
                          downloadHelper.getDownloadAction(Util.getUtf8Bytes(name), selectedTrackKeys);
                  taskId=MyApplication.getInstance().getDownloadManager().handleAction(downloadAction);
  
                  startDownload(downloadAction);
              }
          }
  
      }
  }
  </code></pre>
  
  <p>In my Fragment/Activity: </p>
  
  <pre><code> /* this method will be called when user click on download button of each item */
      @Override
              public void onDownloadClick(LectureList lecture) {
                  Log.e(TAG,"onClickDownload");
  downloadTracker.toggleDownload(this,lecture.getTitle_lecture(),
                          Uri.parse(lecture.getUrlPath()),lecture.getExtension());
              }
  </code></pre>
  
  <p>And here is my broadcast receiver:</p>
  
  <pre><code>    private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
      @Override
      public void onReceive(Context context, Intent intent) {
          Log.e(TAG,"onRecive download");
  
          if(intent.getAction().equals(MESSAGE_PROGRESS)){
              int progress=intent.getLongExtra("progress",0);
          }
      }
  };
  </code></pre>