mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Support generating notifications for paused downloads
- Android 12 will not allow our download service to be restarted from the background when conditions that allow downloads to continue are met. As an interim (and possibly permanent) solution, we'll keep the service in the foreground if there are unfinished downloads that would continue if conditions were met. - Keeping the service in the foreground requires a foreground notification. Hence we need to be able to generate a meaningful notification for this state. PiperOrigin-RevId: 391969986
This commit is contained in:
parent
03d0b34ab9
commit
3f16730763
@ -25,6 +25,7 @@ import com.google.android.exoplayer2.offline.Download;
|
||||
import com.google.android.exoplayer2.offline.DownloadManager;
|
||||
import com.google.android.exoplayer2.offline.DownloadService;
|
||||
import com.google.android.exoplayer2.scheduler.PlatformScheduler;
|
||||
import com.google.android.exoplayer2.scheduler.Requirements;
|
||||
import com.google.android.exoplayer2.ui.DownloadNotificationHelper;
|
||||
import com.google.android.exoplayer2.util.NotificationUtil;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
@ -66,14 +67,16 @@ public class DemoDownloadService extends DownloadService {
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
protected Notification getForegroundNotification(@NonNull List<Download> downloads) {
|
||||
protected Notification getForegroundNotification(
|
||||
@NonNull List<Download> downloads, @Requirements.RequirementFlags int notMetRequirements) {
|
||||
return DemoUtil.getDownloadNotificationHelper(/* context= */ this)
|
||||
.buildProgressNotification(
|
||||
/* context= */ this,
|
||||
R.drawable.ic_download,
|
||||
/* contentIntent= */ null,
|
||||
/* message= */ null,
|
||||
downloads);
|
||||
downloads,
|
||||
notMetRequirements);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -747,13 +747,15 @@ public abstract class DownloadService extends Service {
|
||||
* be implemented to throw {@link UnsupportedOperationException}.
|
||||
*
|
||||
* @param downloads The current downloads.
|
||||
* @param notMetRequirements Any requirements for downloads that are not currently met.
|
||||
* @return The foreground notification to display.
|
||||
*/
|
||||
protected abstract Notification getForegroundNotification(List<Download> downloads);
|
||||
protected abstract Notification getForegroundNotification(
|
||||
List<Download> downloads, @Requirements.RequirementFlags int notMetRequirements);
|
||||
|
||||
/**
|
||||
* Invalidates the current foreground notification and causes {@link
|
||||
* #getForegroundNotification(List)} to be invoked again if the service isn't stopped.
|
||||
* #getForegroundNotification(List, int)} to be invoked again if the service isn't stopped.
|
||||
*/
|
||||
protected final void invalidateForegroundNotification() {
|
||||
if (foregroundNotificationUpdater != null && !isDestroyed) {
|
||||
@ -908,7 +910,9 @@ public abstract class DownloadService extends Service {
|
||||
|
||||
private void update() {
|
||||
List<Download> downloads = Assertions.checkNotNull(downloadManager).getCurrentDownloads();
|
||||
startForeground(notificationId, getForegroundNotification(downloads));
|
||||
@Requirements.RequirementFlags
|
||||
int notMetRequirements = downloadManager.getNotMetRequirements();
|
||||
startForeground(notificationId, getForegroundNotification(downloads, notMetRequirements));
|
||||
notificationDisplayed = true;
|
||||
if (periodicUpdatesStarted) {
|
||||
handler.removeCallbacksAndMessages(null);
|
||||
|
@ -35,6 +35,7 @@ import com.google.android.exoplayer2.offline.DownloadRequest;
|
||||
import com.google.android.exoplayer2.offline.DownloadService;
|
||||
import com.google.android.exoplayer2.offline.StreamKey;
|
||||
import com.google.android.exoplayer2.robolectric.TestDownloadManagerListener;
|
||||
import com.google.android.exoplayer2.scheduler.Requirements;
|
||||
import com.google.android.exoplayer2.scheduler.Scheduler;
|
||||
import com.google.android.exoplayer2.testutil.DummyMainThread;
|
||||
import com.google.android.exoplayer2.testutil.FakeDataSet;
|
||||
@ -139,7 +140,9 @@ public class DownloadServiceDashTest {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Notification getForegroundNotification(List<Download> downloads) {
|
||||
protected Notification getForegroundNotification(
|
||||
List<Download> downloads,
|
||||
@Requirements.RequirementFlags int notMetRequirements) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
|
@ -24,6 +24,7 @@ import androidx.annotation.StringRes;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.offline.Download;
|
||||
import com.google.android.exoplayer2.scheduler.Requirements;
|
||||
import java.util.List;
|
||||
|
||||
/** Helper for creating download notifications. */
|
||||
@ -42,6 +43,21 @@ public final class DownloadNotificationHelper {
|
||||
new NotificationCompat.Builder(context.getApplicationContext(), channelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #buildProgressNotification(Context, int, PendingIntent, String, List,
|
||||
* int)}.
|
||||
*/
|
||||
@Deprecated
|
||||
public Notification buildProgressNotification(
|
||||
Context context,
|
||||
@DrawableRes int smallIcon,
|
||||
@Nullable PendingIntent contentIntent,
|
||||
@Nullable String message,
|
||||
List<Download> downloads) {
|
||||
return buildProgressNotification(
|
||||
context, smallIcon, contentIntent, message, downloads, /* notMetRequirements= */ 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a progress notification for the given downloads.
|
||||
*
|
||||
@ -50,6 +66,7 @@ public final class DownloadNotificationHelper {
|
||||
* @param contentIntent An optional content intent to send when the notification is clicked.
|
||||
* @param message An optional message to display on the notification.
|
||||
* @param downloads The downloads.
|
||||
* @param notMetRequirements Any requirements for downloads that are not currently met.
|
||||
* @return The notification.
|
||||
*/
|
||||
public Notification buildProgressNotification(
|
||||
@ -57,52 +74,88 @@ public final class DownloadNotificationHelper {
|
||||
@DrawableRes int smallIcon,
|
||||
@Nullable PendingIntent contentIntent,
|
||||
@Nullable String message,
|
||||
List<Download> downloads) {
|
||||
List<Download> downloads,
|
||||
@Requirements.RequirementFlags int notMetRequirements) {
|
||||
float totalPercentage = 0;
|
||||
int downloadTaskCount = 0;
|
||||
boolean allDownloadPercentagesUnknown = true;
|
||||
boolean haveDownloadedBytes = false;
|
||||
boolean haveDownloadTasks = false;
|
||||
boolean haveRemoveTasks = false;
|
||||
boolean haveDownloadingTasks = false;
|
||||
boolean haveQueuedTasks = false;
|
||||
boolean haveRemovingTasks = false;
|
||||
for (int i = 0; i < downloads.size(); i++) {
|
||||
Download download = downloads.get(i);
|
||||
if (download.state == Download.STATE_REMOVING) {
|
||||
haveRemoveTasks = true;
|
||||
continue;
|
||||
switch (download.state) {
|
||||
case Download.STATE_REMOVING:
|
||||
haveRemovingTasks = true;
|
||||
break;
|
||||
case Download.STATE_QUEUED:
|
||||
haveQueuedTasks = true;
|
||||
break;
|
||||
case Download.STATE_RESTARTING:
|
||||
case Download.STATE_DOWNLOADING:
|
||||
haveDownloadingTasks = true;
|
||||
float downloadPercentage = download.getPercentDownloaded();
|
||||
if (downloadPercentage != C.PERCENTAGE_UNSET) {
|
||||
allDownloadPercentagesUnknown = false;
|
||||
totalPercentage += downloadPercentage;
|
||||
}
|
||||
haveDownloadedBytes |= download.getBytesDownloaded() > 0;
|
||||
downloadTaskCount++;
|
||||
break;
|
||||
// Terminal states aren't expected, but if we encounter them we do nothing.
|
||||
case Download.STATE_STOPPED:
|
||||
case Download.STATE_COMPLETED:
|
||||
case Download.STATE_FAILED:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (download.state != Download.STATE_RESTARTING
|
||||
&& download.state != Download.STATE_DOWNLOADING) {
|
||||
continue;
|
||||
}
|
||||
haveDownloadTasks = true;
|
||||
float downloadPercentage = download.getPercentDownloaded();
|
||||
if (downloadPercentage != C.PERCENTAGE_UNSET) {
|
||||
allDownloadPercentagesUnknown = false;
|
||||
totalPercentage += downloadPercentage;
|
||||
}
|
||||
haveDownloadedBytes |= download.getBytesDownloaded() > 0;
|
||||
downloadTaskCount++;
|
||||
}
|
||||
|
||||
int titleStringId =
|
||||
haveDownloadTasks
|
||||
? R.string.exo_download_downloading
|
||||
: (haveRemoveTasks ? R.string.exo_download_removing : NULL_STRING_ID);
|
||||
int progress = 0;
|
||||
boolean indeterminate = true;
|
||||
if (haveDownloadTasks) {
|
||||
progress = (int) (totalPercentage / downloadTaskCount);
|
||||
indeterminate = allDownloadPercentagesUnknown && haveDownloadedBytes;
|
||||
int titleStringId;
|
||||
boolean showProgress = true;
|
||||
if (haveDownloadingTasks) {
|
||||
titleStringId = R.string.exo_download_downloading;
|
||||
} else if (haveQueuedTasks && notMetRequirements != 0) {
|
||||
showProgress = false;
|
||||
if ((notMetRequirements & Requirements.NETWORK_UNMETERED) != 0) {
|
||||
// Note: This assumes that "unmetered" == "WiFi", since it provides a clearer message that's
|
||||
// correct in the majority of cases.
|
||||
titleStringId = R.string.exo_download_paused_for_wifi;
|
||||
} else if ((notMetRequirements & Requirements.NETWORK) != 0) {
|
||||
titleStringId = R.string.exo_download_paused_for_network;
|
||||
} else {
|
||||
titleStringId = R.string.exo_download_paused;
|
||||
}
|
||||
} else if (haveRemovingTasks) {
|
||||
titleStringId = R.string.exo_download_removing;
|
||||
} else {
|
||||
// There are either no downloads, or all downloads are in terminal states.
|
||||
titleStringId = NULL_STRING_ID;
|
||||
}
|
||||
|
||||
int maxProgress = 0;
|
||||
int currentProgress = 0;
|
||||
boolean indeterminateProgress = false;
|
||||
if (showProgress) {
|
||||
maxProgress = 100;
|
||||
if (haveDownloadingTasks) {
|
||||
currentProgress = (int) (totalPercentage / downloadTaskCount);
|
||||
indeterminateProgress = allDownloadPercentagesUnknown && haveDownloadedBytes;
|
||||
} else {
|
||||
indeterminateProgress = true;
|
||||
}
|
||||
}
|
||||
|
||||
return buildNotification(
|
||||
context,
|
||||
smallIcon,
|
||||
contentIntent,
|
||||
message,
|
||||
titleStringId,
|
||||
/* maxProgress= */ 100,
|
||||
progress,
|
||||
indeterminate,
|
||||
maxProgress,
|
||||
currentProgress,
|
||||
indeterminateProgress,
|
||||
/* ongoing= */ true,
|
||||
/* showWhen= */ false);
|
||||
}
|
||||
|
@ -91,6 +91,12 @@
|
||||
<string name="exo_download_failed">Download failed</string>
|
||||
<!-- Shown in a notification or UI component to indicate downloads are being removed from the device. [CHAR LIMIT=40] -->
|
||||
<string name="exo_download_removing">Removing downloads</string>
|
||||
<!-- Shown in a notification or UI component to indicate downloads are paused. [CHAR LIMIT=40] -->
|
||||
<string name="exo_download_paused">Downloads paused</string>
|
||||
<!-- Shown in a notification or UI component to indicate downloads are paused waiting for network connectivity. [CHAR LIMIT=40] -->
|
||||
<string name="exo_download_paused_for_network">Downloads waiting for network</string>
|
||||
<!-- Shown in a notification or UI component to indicate downloads are paused waiting for WiFi connectivity. [CHAR LIMIT=40] -->
|
||||
<string name="exo_download_paused_for_wifi">Downloads waiting for WiFi</string>
|
||||
|
||||
<!-- The title of a track selection view for video tracks. [CHAR LIMIT=20] -->
|
||||
<string name="exo_track_selection_title_video">Video</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user