chromium/chrome/android/java/src/org/chromium/chrome/browser/browserservices/permissiondelegation/NotificationChannelPreserver.java

// Copyright 2019 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.permissiondelegation;

import android.os.Build;

import dagger.Lazy;

import org.chromium.chrome.browser.notifications.NotificationChannelStatus;
import org.chromium.chrome.browser.notifications.channels.ChromeChannelDefinitions;
import org.chromium.chrome.browser.notifications.channels.SiteChannelsManager;
import org.chromium.components.content_settings.ContentSettingValues;
import org.chromium.components.embedder_support.util.Origin;

import javax.inject.Inject;
import javax.inject.Singleton;

/**
 * If an origin is associated with an installed webapp (TWAs on Android O+, WebAPKs on Android T+)
 * then we want to remove its Android channel because the APKs notification status takes precedence
 * and we don't want the confuse the user with conflicting UI.
 *
 * It's recommended to hold a {@link Lazy} version of this class and pass this to static methods
 * such as {@link #restoreChannelIfNeeded} to not create instances of this class on Android versions
 * when it is not required.
 *
 * Lifecycle: Singleton.
 * Thread safety: Only call methods on a single thread.
 * Native: Does not require native.
 */
@Singleton
public class NotificationChannelPreserver {
    private final InstalledWebappPermissionStore mStore;
    private final SiteChannelsManager mSiteChannelsManager;

    @Inject
    NotificationChannelPreserver(
            InstalledWebappPermissionStore store, SiteChannelsManager siteChannelsManager) {
        assert !beforeAndroidO()
                : "This class should not be instantiated on Android versions before O";

        mStore = store;
        mSiteChannelsManager = siteChannelsManager;
    }

    void deleteChannel(Origin origin) {
        if (beforeAndroidO()) return;

        String channelId = mSiteChannelsManager.getChannelIdForOrigin(origin.toString());
        if (ChromeChannelDefinitions.ChannelId.SITES.equals(channelId)) {
            // If we were given the generic "sites" channel that meant no origin-specific channel
            // existed. We don't need to do anything.
            return;
        }

        @NotificationChannelStatus int status = mSiteChannelsManager.getChannelStatus(channelId);
        if (status == NotificationChannelStatus.UNAVAILABLE) {
            // This shouldn't happen if we passed the above conditional return - but it just means
            // that the channel doesn't exist, so again, we don't need to do anything.
            return;
        }

        assert status == NotificationChannelStatus.ENABLED
                || status == NotificationChannelStatus.BLOCKED;

        @ContentSettingValues
        int settingValue =
                status == NotificationChannelStatus.ENABLED
                        ? ContentSettingValues.ALLOW
                        : ContentSettingValues.BLOCK;
        mStore.setPreInstallNotificationPermission(origin, settingValue);
        mSiteChannelsManager.deleteSiteChannel(channelId);
    }

    void restoreChannel(Origin origin) {
        if (beforeAndroidO()) return;

        @ContentSettingValues
        Integer settingValue = mStore.getAndRemovePreInstallNotificationPermission(origin);

        if (settingValue == null) {
            // If no previous value was stored, a channel didn't previously exist.
            return;
        }

        boolean enabled = settingValue == ContentSettingValues.ALLOW;
        mSiteChannelsManager.createSiteChannel(
                origin.toString(), System.currentTimeMillis(), enabled);
    }

    /**
     * Resolves the {@link Lazy} {@code preserver} and calls {@link #deleteChannel} on it if called
     * on a version of Android that requires it. Does not resolve the {@code preserver} otherwise.
     */
    static void deleteChannelIfNeeded(Lazy<NotificationChannelPreserver> preserver, Origin origin) {
        if (beforeAndroidO()) return;
        preserver.get().deleteChannel(origin);
    }

    /** Similar to {@link #deleteChannelIfNeeded}, but calling {@link #restoreChannel}. */
    static void restoreChannelIfNeeded(
            Lazy<NotificationChannelPreserver> preserver, Origin origin) {
        if (beforeAndroidO()) return;
        preserver.get().restoreChannel(origin);
    }

    private static boolean beforeAndroidO() {
        return Build.VERSION.SDK_INT < Build.VERSION_CODES.O;
    }
}