chromium/chrome/android/java/src/org/chromium/chrome/browser/browserservices/ui/view/DisclosureNotification.java

// Copyright 2020 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.browserservices.ui.view;

import static org.chromium.chrome.browser.browserservices.ui.TrustedWebActivityModel.DISCLOSURE_EVENTS_CALLBACK;
import static org.chromium.chrome.browser.browserservices.ui.TrustedWebActivityModel.DISCLOSURE_FIRST_TIME;
import static org.chromium.chrome.browser.browserservices.ui.TrustedWebActivityModel.DISCLOSURE_SCOPE;
import static org.chromium.chrome.browser.browserservices.ui.TrustedWebActivityModel.DISCLOSURE_STATE;
import static org.chromium.chrome.browser.browserservices.ui.TrustedWebActivityModel.DISCLOSURE_STATE_NOT_SHOWN;
import static org.chromium.chrome.browser.browserservices.ui.TrustedWebActivityModel.DISCLOSURE_STATE_SHOWN;
import static org.chromium.chrome.browser.browserservices.ui.TrustedWebActivityModel.PACKAGE_NAME;
import static org.chromium.chrome.browser.dependency_injection.ChromeCommonQualifiers.APP_CONTEXT;
import static org.chromium.chrome.browser.notifications.NotificationConstants.NOTIFICATION_ID_TWA_DISCLOSURE_INITIAL;
import static org.chromium.chrome.browser.notifications.NotificationConstants.NOTIFICATION_ID_TWA_DISCLOSURE_SUBSEQUENT;

import android.content.Context;
import android.content.res.Resources;

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

import org.chromium.chrome.R;
import org.chromium.chrome.browser.browserservices.ui.TrustedWebActivityModel;
import org.chromium.chrome.browser.browserservices.ui.trustedwebactivity.DisclosureAcceptanceBroadcastReceiver;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.StartStopWithNativeObserver;
import org.chromium.chrome.browser.notifications.NotificationUmaTracker;
import org.chromium.chrome.browser.notifications.NotificationWrapperBuilderFactory;
import org.chromium.chrome.browser.notifications.channels.ChromeChannelDefinitions;
import org.chromium.components.browser_ui.notifications.NotificationManagerProxy;
import org.chromium.components.browser_ui.notifications.NotificationMetadata;
import org.chromium.components.browser_ui.notifications.NotificationWrapper;
import org.chromium.components.browser_ui.notifications.PendingIntentProvider;
import org.chromium.components.url_formatter.UrlFormatter;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyObservable;

import javax.inject.Inject;
import javax.inject.Named;

/**
 * Displays a notification when the user is on the verified domain. The first such notification (per
 * TWA) is urgent priority, subsequent ones are low priority.
 */
public class DisclosureNotification
        implements PropertyObservable.PropertyObserver<PropertyKey>, StartStopWithNativeObserver {
    private final Context mContext;
    private final Resources mResources;
    private final TrustedWebActivityModel mModel;
    private final NotificationManagerProxy mNotificationManager;
    private String mCurrentScope;

    @Inject
    DisclosureNotification(
            @Named(APP_CONTEXT) Context context,
            Resources resources,
            NotificationManagerProxy notificationManager,
            TrustedWebActivityModel model,
            ActivityLifecycleDispatcher lifecycleDispatcher) {
        mContext = context;
        mResources = resources;
        mNotificationManager = notificationManager;
        mModel = model;

        mModel.addObserver(this);
        lifecycleDispatcher.register(this);
    }

    private void show() {
        mCurrentScope = mModel.get(DISCLOSURE_SCOPE);
        boolean firstTime = mModel.get(DISCLOSURE_FIRST_TIME);
        String packageName = mModel.get(PACKAGE_NAME);

        NotificationWrapper notification =
                createNotification(firstTime, mCurrentScope, packageName);
        mNotificationManager.notify(notification);
        NotificationUmaTracker.getInstance()
                .onNotificationShown(
                        firstTime
                                ? NotificationUmaTracker.SystemNotificationType
                                        .TWA_DISCLOSURE_INITIAL
                                : NotificationUmaTracker.SystemNotificationType
                                        .TWA_DISCLOSURE_SUBSEQUENT,
                        notification.getNotification());

        mModel.get(DISCLOSURE_EVENTS_CALLBACK).onDisclosureShown();
    }

    private void dismiss() {
        mNotificationManager.cancel(mCurrentScope, NOTIFICATION_ID_TWA_DISCLOSURE_INITIAL);
        mNotificationManager.cancel(mCurrentScope, NOTIFICATION_ID_TWA_DISCLOSURE_SUBSEQUENT);
        mCurrentScope = null;
    }

    private NotificationWrapper createNotification(
            boolean firstTime, String scope, String packageName) {
        int umaType;
        int preOPriority;
        int notificationId;
        String channelId;
        if (firstTime) {
            umaType = NotificationUmaTracker.SystemNotificationType.TWA_DISCLOSURE_INITIAL;
            preOPriority = NotificationCompat.PRIORITY_MAX;
            channelId = ChromeChannelDefinitions.ChannelId.WEBAPPS;
            notificationId = NOTIFICATION_ID_TWA_DISCLOSURE_INITIAL;
        } else {
            umaType = NotificationUmaTracker.SystemNotificationType.TWA_DISCLOSURE_SUBSEQUENT;
            preOPriority = NotificationCompat.PRIORITY_MIN;
            channelId = ChromeChannelDefinitions.ChannelId.WEBAPPS_QUIET;
            notificationId = NOTIFICATION_ID_TWA_DISCLOSURE_SUBSEQUENT;
        }

        // We use the TWA's package name as the notification tag so that multiple different TWAs
        // don't interfere with each other.
        NotificationMetadata metadata = new NotificationMetadata(umaType, scope, notificationId);

        String title = mResources.getString(R.string.twa_running_in_chrome);
        String scopeForDisplay =
                UrlFormatter.formatUrlForDisplayOmitSchemeOmitTrivialSubdomains(scope);
        String text = mResources.getString(R.string.twa_running_in_chrome_v2, scopeForDisplay);

        PendingIntentProvider intent =
                DisclosureAcceptanceBroadcastReceiver.createPendingIntent(
                        mContext, scope, notificationId, packageName);

        // We don't have an icon to display.
        int icon = 0;

        return NotificationWrapperBuilderFactory.createNotificationWrapperBuilder(
                        channelId, metadata)
                .setSmallIcon(R.drawable.ic_chrome)
                .setContentTitle(title)
                .setContentText(text)
                .setContentIntent(intent)
                .addAction(
                        icon,
                        mResources.getString(R.string.got_it),
                        intent,
                        NotificationUmaTracker.ActionType.TWA_NOTIFICATION_ACCEPTANCE)
                .setShowWhen(false)
                .setAutoCancel(false)
                .setSound(null)
                .setBigTextStyle(text)
                .setOngoing(!firstTime)
                .setPriorityBeforeO(preOPriority)
                .buildNotificationWrapper();
    }

    @Override
    public void onPropertyChanged(
            PropertyObservable<PropertyKey> source, @Nullable PropertyKey propertyKey) {
        if (propertyKey != DISCLOSURE_STATE) return;

        switch (mModel.get(DISCLOSURE_STATE)) {
            case DISCLOSURE_STATE_SHOWN:
                show();
                break;
            case DISCLOSURE_STATE_NOT_SHOWN:
                dismiss();
                break;
        }
    }

    @Override
    public void onStartWithNative() {
        if (mModel.get(DISCLOSURE_STATE) == DISCLOSURE_STATE_SHOWN) show();
    }

    @Override
    public void onStopWithNative() {
        dismiss();
    }
}