/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.exoplayer.offline;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat;
import androidx.media3.common.C;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.R;
import androidx.media3.exoplayer.scheduler.Requirements;
import java.util.List;
/** Helper for creating download notifications. */
@UnstableApi
public final class DownloadNotificationHelper {
private static final @StringRes int NULL_STRING_ID = 0;
private final NotificationCompat.Builder notificationBuilder;
/**
* @param context A context.
* @param channelId The id of the notification channel to use.
*/
public DownloadNotificationHelper(Context context, String channelId) {
this.notificationBuilder =
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.
*
* @param context A context.
* @param smallIcon A small icon for the notification.
* @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(
Context context,
@DrawableRes int smallIcon,
@Nullable PendingIntent contentIntent,
@Nullable String message,
List<Download> downloads,
@Requirements.RequirementFlags int notMetRequirements) {
float totalPercentage = 0;
int downloadTaskCount = 0;
boolean allDownloadPercentagesUnknown = true;
boolean haveDownloadedBytes = false;
boolean haveDownloadingTasks = false;
boolean haveQueuedTasks = false;
boolean haveRemovingTasks = false;
for (int i = 0; i < downloads.size(); i++) {
Download download = downloads.get(i);
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;
}
}
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,
currentProgress,
indeterminateProgress,
/* ongoing= */ true,
/* showWhen= */ false);
}
/**
* Returns a notification for a completed download.
*
* @param context A context.
* @param smallIcon A small icon for the notifications.
* @param contentIntent An optional content intent to send when the notification is clicked.
* @param message An optional message to display on the notification.
* @return The notification.
*/
public Notification buildDownloadCompletedNotification(
Context context,
@DrawableRes int smallIcon,
@Nullable PendingIntent contentIntent,
@Nullable String message) {
int titleStringId = R.string.exo_download_completed;
return buildEndStateNotification(context, smallIcon, contentIntent, message, titleStringId);
}
/**
* Returns a notification for a failed download.
*
* @param context A context.
* @param smallIcon A small icon for the notifications.
* @param contentIntent An optional content intent to send when the notification is clicked.
* @param message An optional message to display on the notification.
* @return The notification.
*/
public Notification buildDownloadFailedNotification(
Context context,
@DrawableRes int smallIcon,
@Nullable PendingIntent contentIntent,
@Nullable String message) {
@StringRes int titleStringId = R.string.exo_download_failed;
return buildEndStateNotification(context, smallIcon, contentIntent, message, titleStringId);
}
private Notification buildEndStateNotification(
Context context,
@DrawableRes int smallIcon,
@Nullable PendingIntent contentIntent,
@Nullable String message,
@StringRes int titleStringId) {
return buildNotification(
context,
smallIcon,
contentIntent,
message,
titleStringId,
/* maxProgress= */ 0,
/* currentProgress= */ 0,
/* indeterminateProgress= */ false,
/* ongoing= */ false,
/* showWhen= */ true);
}
private Notification buildNotification(
Context context,
@DrawableRes int smallIcon,
@Nullable PendingIntent contentIntent,
@Nullable String message,
@StringRes int titleStringId,
int maxProgress,
int currentProgress,
boolean indeterminateProgress,
boolean ongoing,
boolean showWhen) {
notificationBuilder.setSmallIcon(smallIcon);
notificationBuilder.setContentTitle(
titleStringId == NULL_STRING_ID ? null : context.getResources().getString(titleStringId));
notificationBuilder.setContentIntent(contentIntent);
notificationBuilder.setStyle(
message == null ? null : new NotificationCompat.BigTextStyle().bigText(message));
notificationBuilder.setProgress(maxProgress, currentProgress, indeterminateProgress);
notificationBuilder.setOngoing(ongoing);
notificationBuilder.setShowWhen(showWhen);
return notificationBuilder.build();
}
}