chromium/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebApkInstallService.java

// Copyright 2017 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.webapps;

import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;

import androidx.annotation.VisibleForTesting;
import androidx.core.app.NotificationCompat;

import org.jni_zero.CalledByNative;

import org.chromium.base.ContextUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.notifications.NotificationUmaTracker;
import org.chromium.chrome.browser.notifications.NotificationUmaTracker.SystemNotificationType;
import org.chromium.chrome.browser.notifications.NotificationWrapperBuilderFactory;
import org.chromium.chrome.browser.notifications.channels.ChromeChannelDefinitions;
import org.chromium.components.browser_ui.notifications.BaseNotificationManagerProxyFactory;
import org.chromium.components.browser_ui.notifications.NotificationMetadata;
import org.chromium.components.browser_ui.notifications.NotificationWrapper;
import org.chromium.components.browser_ui.notifications.NotificationWrapperBuilder;
import org.chromium.components.browser_ui.notifications.PendingIntentProvider;
import org.chromium.components.url_formatter.SchemeDisplay;
import org.chromium.components.url_formatter.UrlFormatter;
import org.chromium.components.webapps.WebApkInstallResult;
import org.chromium.components.webapps.WebappsIconUtils;
import org.chromium.components.webapps.WebappsUtils;
import org.chromium.webapk.lib.client.WebApkNavigationClient;

/** Java counterpart to webapk_install_service.h. */
public class WebApkInstallService {
    /** Prefix used for generating a unique notification tag. */
    static final String WEBAPK_INSTALL_NOTIFICATION_TAG_PREFIX =
            "webapk_install_notification_tag_prefix.";

    /** We always use the same platform id for notifications. */
    private static final int PLATFORM_ID = -1;

    /** Displays a notification when a WebAPK is successfully installed. */
    @CalledByNative
    @VisibleForTesting
    static void showInstalledNotification(
            String webApkPackage,
            String notificationId,
            String shortName,
            String url,
            Bitmap icon,
            boolean isIconMaskable) {
        Context context = ContextUtils.getApplicationContext();
        Intent intent =
                WebApkNavigationClient.createLaunchWebApkIntent(
                        webApkPackage, url, false
                        /* forceNavigation= */ );
        PendingIntentProvider clickPendingIntent =
                PendingIntentProvider.getActivity(
                        context, /* requestCode= */ 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

        if (isIconMaskable && WebappsIconUtils.doesAndroidSupportMaskableIcons()) {
            icon = WebappsIconUtils.generateAdaptiveIconBitmap(icon);
        }

        showNotification(
                notificationId,
                SystemNotificationType.WEBAPK_INSTALL_COMPLETE,
                shortName,
                url,
                icon,
                context.getResources().getString(R.string.notification_webapk_installed),
                clickPendingIntent);
    }

    /** Display a notification when an install starts. */
    @CalledByNative
    @VisibleForTesting
    static void showInstallInProgressNotification(
            String notificationId,
            String shortName,
            String url,
            Bitmap icon,
            boolean isIconMaskable) {
        String message =
                ContextUtils.getApplicationContext()
                        .getResources()
                        .getString(R.string.notification_webapk_install_in_progress, shortName);
        if (isIconMaskable && WebappsIconUtils.doesAndroidSupportMaskableIcons()) {
            icon = WebappsIconUtils.generateAdaptiveIconBitmap(icon);
        }
        showNotification(
                notificationId,
                SystemNotificationType.WEBAPK_INSTALL_IN_PROGRESS,
                shortName,
                url,
                icon,
                message,
                null);
        WebappsUtils.showToast(message);
    }

    /** Display a notification when an install failed. */
    @CalledByNative
    @VisibleForTesting
    static void showInstallFailedNotification(
            String notificationId,
            String shortName,
            String url,
            Bitmap icon,
            boolean isIconMaskable,
            @WebApkInstallResult int resultCode) {
        Context context = ContextUtils.getApplicationContext();
        String titleMessage =
                context.getResources()
                        .getString(R.string.notification_webapk_install_failed, shortName);
        String contentMessage = getInstallErrorMessage(resultCode);

        PendingIntentProvider openUrlIntent =
                WebApkInstallBroadcastReceiver.createPendingIntent(
                        context,
                        notificationId,
                        url,
                        WebApkInstallBroadcastReceiver.ACTION_OPEN_IN_BROWSER);
        if (isIconMaskable && WebappsIconUtils.doesAndroidSupportMaskableIcons()) {
            icon = WebappsIconUtils.generateAdaptiveIconBitmap(icon);
        }
        showNotification(
                notificationId,
                SystemNotificationType.WEBAPK_INSTALL_FAILED,
                titleMessage,
                url,
                icon,
                contentMessage,
                openUrlIntent);
    }

    private static void showNotification(
            String notificationId,
            @SystemNotificationType int type,
            String shortName,
            String url,
            Bitmap icon,
            String message,
            PendingIntentProvider clickPendingIntent) {
        Context context = ContextUtils.getApplicationContext();

        String channelId;
        int preOPriority;
        if (type == SystemNotificationType.WEBAPK_INSTALL_IN_PROGRESS) {
            channelId = ChromeChannelDefinitions.ChannelId.BROWSER;
            preOPriority = NotificationCompat.PRIORITY_DEFAULT;
        } else {
            channelId = ChromeChannelDefinitions.ChannelId.WEBAPPS;
            preOPriority = NotificationCompat.PRIORITY_HIGH;
        }

        NotificationMetadata metadata =
                new NotificationMetadata(
                        type, getInstallNotificationTag(notificationId), PLATFORM_ID);

        NotificationWrapperBuilder notificationBuilder =
                NotificationWrapperBuilderFactory.createNotificationWrapperBuilder(
                        channelId, metadata);
        notificationBuilder
                .setContentTitle(shortName)
                .setContentText(message)
                .setLargeIcon(icon)
                .setSmallIcon(R.drawable.ic_chrome)
                .setContentIntent(clickPendingIntent)
                .setPriorityBeforeO(preOPriority)
                .setWhen(System.currentTimeMillis())
                .setSubText(
                        UrlFormatter.formatUrlForSecurityDisplay(
                                url, SchemeDisplay.OMIT_HTTP_AND_HTTPS))
                .setAutoCancel(true);

        if (type == SystemNotificationType.WEBAPK_INSTALL_FAILED) {
            notificationBuilder.addAction(
                    0 /* no icon */,
                    context.getResources().getString(R.string.webapk_install_failed_action_open),
                    clickPendingIntent,
                    NotificationUmaTracker.ActionType.WEB_APK_ACTION_BACK_TO_SITE);
        }

        NotificationWrapper notification = notificationBuilder.buildNotificationWrapper();
        BaseNotificationManagerProxyFactory.create(context).notify(notification);
        NotificationUmaTracker.getInstance()
                .onNotificationShown(type, notification.getNotification());
    }

    /** Cancels any ongoing notification for the WebAPK. */
    @CalledByNative
    static void cancelNotification(String notificationId) {
        BaseNotificationManagerProxyFactory.create(ContextUtils.getApplicationContext())
                .cancel(getInstallNotificationTag(notificationId), PLATFORM_ID);
    }

    private static String getInstallErrorMessage(@WebApkInstallResult int resultCode) {
        String message;
        if (resultCode == WebApkInstallResult.NOT_ENOUGH_SPACE) {
            message =
                    ContextUtils.getApplicationContext()
                            .getResources()
                            .getString(R.string.notification_webapk_install_failed_space);
        } else {
            message =
                    ContextUtils.getApplicationContext()
                            .getResources()
                            .getString(
                                    R.string.notification_webapk_install_failed_contents_general);
        }
        return message;
    }

    static String getInstallNotificationTag(String notificationId) {
        return WebApkInstallService.WEBAPK_INSTALL_NOTIFICATION_TAG_PREFIX + notificationId;
    }
}