chromium/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabNightModeStateController.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.customtabs;

import android.content.Intent;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.browser.customtabs.CustomTabsIntent;

import org.chromium.base.IntentUtils;
import org.chromium.base.ObserverList;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.DestroyObserver;
import org.chromium.chrome.browser.night_mode.NightModeStateProvider;
import org.chromium.chrome.browser.night_mode.NightModeUtils;
import org.chromium.chrome.browser.night_mode.PowerSavingModeMonitor;
import org.chromium.chrome.browser.night_mode.SystemNightModeMonitor;

/** Maintains and provides the night mode state for {@link CustomTabActivity}. */
public class CustomTabNightModeStateController implements DestroyObserver, NightModeStateProvider {
    private final ObserverList<Observer> mObservers = new ObserverList<>();
    private final PowerSavingModeMonitor mPowerSavingModeMonitor;
    private final SystemNightModeMonitor mSystemNightModeMonitor;
    private final SystemNightModeMonitor.Observer mSystemNightModeObserver = this::updateNightMode;
    private final Runnable mPowerSaveModeObserver = this::updateNightMode;

    /**
     * The color scheme requested for the CCT. Only {@link CustomTabsIntent#COLOR_SCHEME_LIGHT}
     * and {@link CustomTabsIntent#COLOR_SCHEME_DARK} should be considered - fall back to the
     * system status for {@link CustomTabsIntent#COLOR_SCHEME_SYSTEM} when enabled.
     */
    private int mRequestedColorScheme;

    private AppCompatDelegate mAppCompatDelegate;

    @Nullable // Null initially, so that the first update is always applied (see updateNightMode()).
    private Boolean mIsInNightMode;

    CustomTabNightModeStateController(
            ActivityLifecycleDispatcher lifecycleDispatcher,
            SystemNightModeMonitor systemNightModeMonitor,
            PowerSavingModeMonitor powerSavingModeMonitor) {
        mSystemNightModeMonitor = systemNightModeMonitor;
        mPowerSavingModeMonitor = powerSavingModeMonitor;
        lifecycleDispatcher.register(this);
    }

    /**
     * Initializes the initial night mode state.
     * @param delegate The {@link AppCompatDelegate} that controls night mode state in support
     *                 library.
     * @param intent  The {@link Intent} to retrieve information about the initial state.
     */
    void initialize(AppCompatDelegate delegate, Intent intent) {
        if (!NightModeUtils.isNightModeSupported()) {
            // Always stay in light mode if night mode is not available.
            mRequestedColorScheme = CustomTabsIntent.COLOR_SCHEME_LIGHT;
            return;
        }

        mRequestedColorScheme = getRequestedColorScheme(intent);
        mAppCompatDelegate = delegate;

        updateNightMode();

        // No need to observe system settings if the intent specifies a light/dark color scheme.
        if (mRequestedColorScheme == CustomTabsIntent.COLOR_SCHEME_SYSTEM) {
            mSystemNightModeMonitor.addObserver(mSystemNightModeObserver);
            mPowerSavingModeMonitor.addObserver(mPowerSaveModeObserver);
        }
    }

    private int getRequestedColorScheme(Intent intent) {
        if (AuthTabIntentDataProvider.isAuthTabIntent(intent)) {
            return CustomTabsIntent.COLOR_SCHEME_SYSTEM;
        }

        return IntentUtils.safeGetIntExtra(
                intent, CustomTabsIntent.EXTRA_COLOR_SCHEME, CustomTabsIntent.COLOR_SCHEME_SYSTEM);
    }

    // DestroyObserver implementation.
    @Override
    public void onDestroy() {
        mSystemNightModeMonitor.removeObserver(mSystemNightModeObserver);
        mPowerSavingModeMonitor.removeObserver(mPowerSaveModeObserver);
    }

    // NightModeStateProvider implementation.
    @Override
    public boolean isInNightMode() {
        return mIsInNightMode != null && mIsInNightMode;
    }

    @Override
    public void addObserver(@NonNull Observer observer) {
        mObservers.addObserver(observer);
    }

    @Override
    public void removeObserver(@NonNull Observer observer) {
        mObservers.removeObserver(observer);
    }

    @Override
    public boolean shouldOverrideConfiguration() {
        // Don't override configuration because the initial night mode state is only available
        // during CustomTabActivity#onCreate().
        return false;
    }

    private void updateNightMode() {
        boolean shouldBeInNightMode = shouldBeInNightMode();
        if (mIsInNightMode != null && mIsInNightMode == shouldBeInNightMode) return;

        mIsInNightMode = shouldBeInNightMode;
        mAppCompatDelegate.setLocalNightMode(
                mIsInNightMode
                        ? AppCompatDelegate.MODE_NIGHT_YES
                        : AppCompatDelegate.MODE_NIGHT_NO);
        for (Observer observer : mObservers) {
            observer.onNightModeStateChanged();
        }
    }

    private boolean shouldBeInNightMode() {
        return switch (mRequestedColorScheme) {
            case CustomTabsIntent.COLOR_SCHEME_LIGHT -> false;
            case CustomTabsIntent.COLOR_SCHEME_DARK -> true;
            default -> mSystemNightModeMonitor.isSystemNightModeOn()
                    || mPowerSavingModeMonitor.powerSavingIsOn();
        };
    }
}