// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.download;
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
import org.chromium.components.browser_ui.notifications.PendingNotificationTask;
import org.chromium.components.browser_ui.notifications.ThrottlingNotificationScheduler;
import org.chromium.components.offline_items_collection.ContentId;
import org.chromium.components.offline_items_collection.PendingState;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* DownloadNotifier implementation that creates and updates download notifications.
* This class creates the {@link DownloadNotificationService} when needed, and binds
* to the latter to issue calls to show and update notifications.
*/
public class SystemDownloadNotifier implements DownloadNotifier {
private DownloadNotificationService mDownloadNotificationService;
/**
* Notification type for constructing the notification later on.
* TODO(qinmin): this is very ugly and it doesn't scale if we want a more general notification
* frame work. A better solution is to pass a notification builder or a notification into the
* queue, so we don't need the switch statement in updateNotification().
*/
@IntDef({
NotificationType.PROGRESS,
NotificationType.PAUSED,
NotificationType.SUCCEEDED,
NotificationType.FAILED,
NotificationType.INTERRUPTED
})
@Retention(RetentionPolicy.SOURCE)
public @interface NotificationType {
int PROGRESS = 0;
int PAUSED = 1;
int SUCCEEDED = 2;
int FAILED = 3;
int INTERRUPTED = 4;
}
/** Information related to a notification. */
private static final class NotificationInfo {
final @NotificationType int mType;
final DownloadInfo mInfo;
final @PendingNotificationTask.Priority int mPriority;
long mStartTime;
long mSystemDownloadId;
boolean mCanResolve;
boolean mIsSupportedMimeType;
boolean mCanDownloadWhileMetered;
boolean mIsAutoResumable;
@PendingState int mPendingState;
NotificationInfo(
@NotificationType int type,
DownloadInfo info,
@PendingNotificationTask.Priority int priority) {
mType = type;
mInfo = info;
mPriority = priority;
}
}
/** Constructor. */
public SystemDownloadNotifier() {}
DownloadNotificationService getDownloadNotificationService() {
if (mDownloadNotificationService == null) {
mDownloadNotificationService = DownloadNotificationService.getInstance();
}
return mDownloadNotificationService;
}
@VisibleForTesting
void setDownloadNotificationService(DownloadNotificationService downloadNotificationService) {
mDownloadNotificationService = downloadNotificationService;
}
@Override
public void notifyDownloadCanceled(ContentId id) {
ThrottlingNotificationScheduler.getInstance().cancelPendingNotificationTask(id);
getDownloadNotificationService().notifyDownloadCanceled(id, false);
}
@Override
public void notifyDownloadSuccessful(
DownloadInfo info,
long systemDownloadId,
boolean canResolve,
boolean isSupportedMimeType) {
NotificationInfo notificationInfo =
new NotificationInfo(
NotificationType.SUCCEEDED, info, PendingNotificationTask.Priority.HIGH);
notificationInfo.mSystemDownloadId = systemDownloadId;
notificationInfo.mCanResolve = canResolve;
notificationInfo.mIsSupportedMimeType = isSupportedMimeType;
addPendingNotification(notificationInfo);
}
@Override
public void notifyDownloadFailed(DownloadInfo info) {
NotificationInfo notificationInfo =
new NotificationInfo(
NotificationType.FAILED, info, PendingNotificationTask.Priority.HIGH);
addPendingNotification(notificationInfo);
}
@Override
public void notifyDownloadProgress(
DownloadInfo info, long startTime, boolean canDownloadWhileMetered) {
NotificationInfo notificationInfo =
new NotificationInfo(
NotificationType.PROGRESS, info, PendingNotificationTask.Priority.LOW);
notificationInfo.mStartTime = startTime;
notificationInfo.mCanDownloadWhileMetered = canDownloadWhileMetered;
addPendingNotification(notificationInfo);
}
@Override
public void notifyDownloadPaused(DownloadInfo info) {
NotificationInfo notificationInfo =
new NotificationInfo(
NotificationType.PAUSED, info, PendingNotificationTask.Priority.HIGH);
addPendingNotification(notificationInfo);
}
@Override
public void notifyDownloadInterrupted(
DownloadInfo info, boolean isAutoResumable, @PendingState int pendingState) {
NotificationInfo notificationInfo =
new NotificationInfo(
NotificationType.INTERRUPTED, info, PendingNotificationTask.Priority.HIGH);
notificationInfo.mIsAutoResumable = isAutoResumable;
notificationInfo.mPendingState = pendingState;
addPendingNotification(notificationInfo);
}
@Override
public void removeDownloadNotification(int notificationId, DownloadInfo info) {
ThrottlingNotificationScheduler.getInstance()
.cancelPendingNotificationTask(info.getContentId());
getDownloadNotificationService().cancelNotification(notificationId, info.getContentId());
}
/**
* Add a new notification to be handled. If there is currently a posted task to handle pending
* notifications, adding the new notification to the pending queue. Otherwise, process the
* notification immediately and post a task to handle incoming ones.
* @param notificationInfo Notification to be displayed.
*/
void addPendingNotification(NotificationInfo notificationInfo) {
ThrottlingNotificationScheduler.getInstance()
.addPendingNotificationTask(
new PendingNotificationTask(
notificationInfo.mInfo.getContentId(),
notificationInfo.mPriority,
() -> {
updateNotification(notificationInfo);
}));
}
/**
* Sends a notification update to Android NotificationManager.
* @param notificationInfo Information about the notification to be updated.
*/
private void updateNotification(NotificationInfo notificationInfo) {
DownloadInfo info = notificationInfo.mInfo;
switch (notificationInfo.mType) {
case NotificationType.PROGRESS:
getDownloadNotificationService()
.notifyDownloadProgress(
info.getContentId(),
info.getFileName(),
info.getProgress(),
info.getBytesReceived(),
info.getTimeRemainingInMillis(),
notificationInfo.mStartTime,
info.getOTRProfileId(),
notificationInfo.mCanDownloadWhileMetered,
info.getIsTransient(),
info.getIcon(),
info.getOriginalUrl(),
info.getShouldPromoteOrigin());
break;
case NotificationType.PAUSED:
getDownloadNotificationService()
.notifyDownloadPaused(
info.getContentId(),
info.getFileName(),
true,
false,
info.getOTRProfileId(),
info.getIsTransient(),
info.getIcon(),
info.getOriginalUrl(),
info.getShouldPromoteOrigin(),
false,
true,
info.getPendingState());
break;
case NotificationType.SUCCEEDED:
final int notificationId =
getDownloadNotificationService()
.notifyDownloadSuccessful(
info.getContentId(),
info.getFilePath(),
info.getFileName(),
notificationInfo.mSystemDownloadId,
info.getOTRProfileId(),
notificationInfo.mIsSupportedMimeType,
info.getIsOpenable(),
info.getIcon(),
info.getOriginalUrl(),
info.getShouldPromoteOrigin(),
info.getReferrer(),
info.getBytesTotalSize());
if (info.getIsOpenable()) {
DownloadManagerService.getDownloadManagerService()
.onSuccessNotificationShown(
info,
notificationInfo.mCanResolve,
notificationId,
notificationInfo.mSystemDownloadId);
}
break;
case NotificationType.FAILED:
getDownloadNotificationService()
.notifyDownloadFailed(
info.getContentId(),
info.getFileName(),
info.getIcon(),
info.getOriginalUrl(),
info.getShouldPromoteOrigin(),
info.getOTRProfileId(),
info.getFailState());
break;
case NotificationType.INTERRUPTED:
getDownloadNotificationService()
.notifyDownloadPaused(
info.getContentId(),
info.getFileName(),
info.isResumable(),
notificationInfo.mIsAutoResumable,
info.getOTRProfileId(),
info.getIsTransient(),
info.getIcon(),
info.getOriginalUrl(),
info.getShouldPromoteOrigin(),
false,
false,
notificationInfo.mPendingState);
break;
}
}
}