Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4263ab66 authored by Steven Ng's avatar Steven Ng
Browse files

[Night] Add a new night theme trigger: bedtime

This CL expands the existing UiModeManager APIs to allow users activate
dark mode at their configured bedtime schedule on supported devices,
i.e. devices with Digital Wellbeing preinstalled.

The CL added granular types for NIGHT_MODE_CUSTOM. There are two types
1. MODE_NIGHT_CUSTOM_TYPE_SCHEDULE
   This is the type for a schedule set up by users via the Settings
   app.
2. MODE_NIGHT_CUSTOM_TYPE_BEDTIME
   This is the type for a bedtime schedule set up by users via the
   Digital Wellbeing bedtime settings. Unlike
   MODE_NIGHT_CUSTOM_TYPE_SCHEDULE, Android framework doesn't have
   any information of the bedtime schedule. The activation of dark
   theme lives inside Digital Wellbeing

Test: unit tests: atest FrameworksUiServicesTests:UiModeManagerServiceTest
      CTS: atest CtsAppTestCases:UiModeManagerTest
      Manual testing via a system app integration
Bug: 210975231
Change-Id: I3b16e649a048f8a485b9e9d6464d4f251839320d
parent d0719926
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -896,15 +896,21 @@ package android.app {
    method @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public void addOnProjectionStateChangedListener(int, @NonNull java.util.concurrent.Executor, @NonNull android.app.UiModeManager.OnProjectionStateChangedListener);
    method @RequiresPermission(android.Manifest.permission.ENTER_CAR_MODE_PRIORITIZED) public void enableCarMode(@IntRange(from=0) int, int);
    method @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public int getActiveProjectionTypes();
    method @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) public int getNightModeCustomType();
    method @NonNull @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public java.util.Set<java.lang.String> getProjectingPackages(int);
    method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean releaseProjection(int);
    method @RequiresPermission(android.Manifest.permission.READ_PROJECTION_STATE) public void removeOnProjectionStateChangedListener(@NonNull android.app.UiModeManager.OnProjectionStateChangedListener);
    method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean requestProjection(int);
    method @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) public boolean setNightModeActivatedForCustomMode(int, boolean);
    method @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) public void setNightModeCustomType(int);
    field public static final String ACTION_ENTER_CAR_MODE_PRIORITIZED = "android.app.action.ENTER_CAR_MODE_PRIORITIZED";
    field public static final String ACTION_EXIT_CAR_MODE_PRIORITIZED = "android.app.action.EXIT_CAR_MODE_PRIORITIZED";
    field public static final int DEFAULT_PRIORITY = 0; // 0x0
    field public static final String EXTRA_CALLING_PACKAGE = "android.app.extra.CALLING_PACKAGE";
    field public static final String EXTRA_PRIORITY = "android.app.extra.PRIORITY";
    field public static final int MODE_NIGHT_CUSTOM_TYPE_BEDTIME = 1; // 0x1
    field public static final int MODE_NIGHT_CUSTOM_TYPE_SCHEDULE = 0; // 0x0
    field public static final int MODE_NIGHT_CUSTOM_TYPE_UNKNOWN = -1; // 0xffffffff
    field public static final int PROJECTION_TYPE_ALL = -1; // 0xffffffff
    field public static final int PROJECTION_TYPE_AUTOMOTIVE = 1; // 0x1
    field public static final int PROJECTION_TYPE_NONE = 0; // 0x0
+43 −7
Original line number Diff line number Diff line
@@ -48,19 +48,42 @@ interface IUiModeManager {
    
    /**
     * Sets the night mode.
     * <p>
     * The mode can be one of:
     *   1 - notnight mode
     *   2 - night mode
     *   3 - automatic mode switching
     * <ol>notnight mode</ol>
     * <ol>night mode</ol>
     * <ol>custom schedule mode switching</ol>
     */
    void setNightMode(int mode);

    /**
     * Gets the currently configured night mode.  Return 1 for notnight,
     * 2 for night, and 3 for automatic mode switching.
     * Gets the currently configured night mode.
     * <p>
     * Returns
     * <ol>notnight mode</ol>
     * <ol>night mode</ol>
     * <ol>custom schedule mode switching</ol>
     */
    int getNightMode();

    /**
     * Sets the current night mode to {@link #MODE_NIGHT_CUSTOM} with the custom night mode type
     * {@code nightModeCustomType}.
     *
     * @param nightModeCustomType
     * @hide
     */
    void setNightModeCustomType(int nightModeCustomType);

    /**
     * Returns the custom night mode type.
     * <p>
     * If the current night mode is not {@link #MODE_NIGHT_CUSTOM}, returns
     * {@link #MODE_NIGHT_CUSTOM_TYPE_UNKNOWN}.
     * @hide
     */
    int getNightModeCustomType();

    /**
     * Sets the dark mode for the given application. This setting is persisted and will override the
     * system configuration for this application.
@@ -81,7 +104,20 @@ interface IUiModeManager {
    boolean isNightModeLocked();

    /**
    * [De]Activates night mode
     * [De]activating night mode for the current user if the current night mode is custom and the
     * custom type matches {@code nightModeCustomType}.
     *
     * @param nightModeCustomType the specify type of custom mode
     * @param active {@code true} to activate night mode. Otherwise, deactivate night mode
     * @return {@code true} if night mode has successfully activated for the requested
     *         {@code nightModeCustomType}.
     * @hide
     */
    boolean setNightModeActivatedForCustomMode(int nightModeCustom, boolean active);

    /**
     * [De]Activates night mode.
     * @hide
     */
    boolean setNightModeActivated(boolean active);

+103 −0
Original line number Diff line number Diff line
@@ -243,6 +243,45 @@ public class UiModeManager {
     */
    public static final int MODE_NIGHT_YES = 2;

    /**
     * Granular types for {@link MODE_NIGHT_CUSTOM_TYPE_BEDTIME}
     * @hide
     */
    @IntDef(prefix = { "MODE_NIGHT_CUSTOM_TYPE_" }, value = {
            MODE_NIGHT_CUSTOM_TYPE_UNKNOWN,
            MODE_NIGHT_CUSTOM_TYPE_SCHEDULE,
            MODE_NIGHT_CUSTOM_TYPE_BEDTIME,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface NightModeCustomType {}

    /**
     * A granular type for {@link #MODE_NIGHT_CUSTOM} which is unknown.
     * <p>
     * This is the default value when the night mode is set to value other than
     * {@link #MODE_NIGHT_CUSTOM}.
     * @hide
     */
    @SystemApi
    public static final int MODE_NIGHT_CUSTOM_TYPE_UNKNOWN = -1;

    /**
     * A granular type for {@link #MODE_NIGHT_CUSTOM} which is based on a custom schedule.
     * <p>
     * This is the default value when night mode is set to {@link #MODE_NIGHT_CUSTOM} unless the
     * the night mode custom type is specified by calling {@link #setNightModeCustomType(int)}.
     * @hide
     */
    @SystemApi
    public static final int MODE_NIGHT_CUSTOM_TYPE_SCHEDULE = 0;

    /**
     * A granular type for {@link #MODE_NIGHT_CUSTOM} which is based on the bedtime schedule.
     * @hide
     */
    @SystemApi
    public static final int MODE_NIGHT_CUSTOM_TYPE_BEDTIME = 1;

    private IUiModeManager mService;

    /**
@@ -495,6 +534,45 @@ public class UiModeManager {
        }
    }

    /**
     * Sets the current night mode to {@link #MODE_NIGHT_CUSTOM} with the custom night mode type
     * {@code nightModeCustomType}.
     *
     * @param nightModeCustomType
     * @hide
     */
    @SystemApi
    @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
    public void setNightModeCustomType(@NightModeCustomType int nightModeCustomType) {
        if (mService != null) {
            try {
                mService.setNightModeCustomType(nightModeCustomType);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }

    /**
     * Returns the custom night mode type.
     * <p>
     * If the current night mode is not {@link #MODE_NIGHT_CUSTOM}, returns
     * {@link #MODE_NIGHT_CUSTOM_TYPE_UNKNOWN}.
     * @hide
     */
    @SystemApi
    @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
    public int getNightModeCustomType() {
        if (mService != null) {
            try {
                return mService.getNightModeCustomType();
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        return MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
    }

    /**
     * Sets and persist the night mode for this application.
     * <p>
@@ -598,12 +676,37 @@ public class UiModeManager {
        return true;
    }

    /**
     * [De]activating night mode for the current user if the current night mode is custom and the
     * custom type matches {@code nightModeCustomType}.
     *
     * @param nightModeCustomType the specify type of custom mode
     * @param active {@code true} to activate night mode. Otherwise, deactivate night mode
     * @return {@code true} if night mode has successfully activated for the requested
     *         {@code nightModeCustomType}.
     * @hide
     */
    @SystemApi
    @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
    public boolean setNightModeActivatedForCustomMode(@NightModeCustomType int nightModeCustomType,
            boolean active) {
        if (mService != null) {
            try {
                return mService.setNightModeActivatedForCustomMode(nightModeCustomType, active);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        return false;
    }

    /**
     * Activating night mode for the current user
     *
     * @return {@code true} if the change is successful
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
    public boolean setNightModeActivated(boolean active) {
        if (mService != null) {
            try {
+9 −0
Original line number Diff line number Diff line
@@ -8898,6 +8898,15 @@ public final class Settings {
        @Readable
        public static final String UI_NIGHT_MODE = "ui_night_mode";
        /**
         * The current night mode custom type that has been selected by the user.  Owned
         * and controlled by UiModeManagerService. Constants are as per UiModeManager.
         * @hide
         */
        @Readable
        @SuppressLint("NoSettingsProvider")
        public static final String UI_NIGHT_MODE_CUSTOM_TYPE = "ui_night_mode_custom_type";
        /**
         * The current night mode that has been overridden to turn on by the system.  Owned
         * and controlled by UiModeManagerService.  Constants are as per
+77 −10
Original line number Diff line number Diff line
@@ -19,6 +19,9 @@ package com.android.server;
import static android.app.UiModeManager.DEFAULT_PRIORITY;
import static android.app.UiModeManager.MODE_NIGHT_AUTO;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_SCHEDULE;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
import static android.app.UiModeManager.MODE_NIGHT_NO;
import static android.app.UiModeManager.MODE_NIGHT_YES;
import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE;
@@ -40,6 +43,7 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.app.UiModeManager;
import android.app.UiModeManager.NightModeCustomType;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -115,6 +119,7 @@ final class UiModeManagerService extends SystemService {

    private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
    private int mNightMode = UiModeManager.MODE_NIGHT_NO;
    private int mNightModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
    private final LocalTime DEFAULT_CUSTOM_NIGHT_START_TIME = LocalTime.of(22, 0);
    private final LocalTime DEFAULT_CUSTOM_NIGHT_END_TIME = LocalTime.of(6, 0);
    private LocalTime mCustomAutoNightModeStartMilliseconds = DEFAULT_CUSTOM_NIGHT_START_TIME;
@@ -136,6 +141,7 @@ final class UiModeManagerService extends SystemService {
    private boolean mWatch;
    private boolean mVrHeadset;
    private boolean mComputedNightMode;
    private boolean mLastBedtimeRequestedNightMode = false;
    private int mCarModeEnableFlags;
    private boolean mSetupWizardComplete;

@@ -541,6 +547,8 @@ final class UiModeManagerService extends SystemService {
            mNightMode = Secure.getIntForUser(context.getContentResolver(),
                    Secure.UI_NIGHT_MODE, res.getInteger(
                            com.android.internal.R.integer.config_defaultNightMode), userId);
            mNightModeCustomType = Secure.getIntForUser(context.getContentResolver(),
                    Secure.UI_NIGHT_MODE_CUSTOM_TYPE, MODE_NIGHT_CUSTOM_TYPE_UNKNOWN, userId);
                    mOverrideNightModeOn = Secure.getIntForUser(context.getContentResolver(),
                    Secure.UI_NIGHT_MODE_OVERRIDE_ON, 0, userId) != 0;
            mOverrideNightModeOff = Secure.getIntForUser(context.getContentResolver(),
@@ -702,6 +710,14 @@ final class UiModeManagerService extends SystemService {

        @Override
        public void setNightMode(int mode) {
            // MODE_NIGHT_CUSTOM_TYPE_SCHEDULE is the default for MODE_NIGHT_CUSTOM.
            int customModeType = mode == MODE_NIGHT_CUSTOM
                    ? MODE_NIGHT_CUSTOM_TYPE_SCHEDULE
                    : MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
            setNightModeInternal(mode, customModeType);
        }

        private void setNightModeInternal(int mode, int customModeType) {
            if (isNightModeLocked() && (getContext().checkCallingOrSelfPermission(
                    android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
                    != PackageManager.PERMISSION_GRANTED)) {
@@ -722,12 +738,14 @@ final class UiModeManagerService extends SystemService {
            final long ident = Binder.clearCallingIdentity();
            try {
                synchronized (mLock) {
                    if (mNightMode != mode) {
                    if (mNightMode != mode || mNightModeCustomType != customModeType) {
                        if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
                            unregisterScreenOffEventLocked();
                            cancelCustomAlarm();
                        }

                        mNightModeCustomType = mode == MODE_NIGHT_CUSTOM
                                ? customModeType
                                : MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
                        mNightMode = mode;
                        resetNightModeOverrideLocked();
                        persistNightMode(user);
@@ -753,6 +771,30 @@ final class UiModeManagerService extends SystemService {
            }
        }

        @Override
        public void setNightModeCustomType(@NightModeCustomType int nightModeCustomType) {
            if (getContext().checkCallingOrSelfPermission(
                    android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
                    != PackageManager.PERMISSION_GRANTED) {
                throw new SecurityException(
                        "setNightModeCustomType requires MODIFY_DAY_NIGHT_MODE permission");
            }
            setNightModeInternal(MODE_NIGHT_CUSTOM, nightModeCustomType);
        }

        @Override
        public int getNightModeCustomType() {
            if (getContext().checkCallingOrSelfPermission(
                    android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
                    != PackageManager.PERMISSION_GRANTED) {
                throw new SecurityException(
                        "getNightModeCustomType requires MODIFY_DAY_NIGHT_MODE permission");
            }
            synchronized (mLock) {
                return mNightModeCustomType;
            }
        }

        @Override
        public void setApplicationNightMode(@UiModeManager.NightMode int mode) {
            switch (mode) {
@@ -807,11 +849,20 @@ final class UiModeManagerService extends SystemService {
            dumpImpl(pw);
        }

        @Override
        public boolean setNightModeActivatedForCustomMode(int modeNightCustomType, boolean active) {
            return setNightModeActivatedForModeInternal(modeNightCustomType, active);
        }

        @Override
        public boolean setNightModeActivated(boolean active) {
            if (isNightModeLocked() && (getContext().checkCallingOrSelfPermission(
            return setNightModeActivatedForModeInternal(mNightModeCustomType, active);
        }

        private boolean setNightModeActivatedForModeInternal(int modeCustomType, boolean active) {
            if (getContext().checkCallingOrSelfPermission(
                    android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
                    != PackageManager.PERMISSION_GRANTED)) {
                    != PackageManager.PERMISSION_GRANTED) {
                Slog.e(TAG, "Night mode locked, requires MODIFY_DAY_NIGHT_MODE permission");
                return false;
            }
@@ -824,6 +875,14 @@ final class UiModeManagerService extends SystemService {
                return false;

            }
            // Store the last requested bedtime night mode state so that we don't need to notify
            // anyone if the user decides to switch to the night mode to bedtime.
            if (modeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME) {
                mLastBedtimeRequestedNightMode = active;
            }
            if (modeCustomType != mNightModeCustomType) {
                return false;
            }
            synchronized (mLock) {
                final long ident = Binder.clearCallingIdentity();
                try {
@@ -1421,6 +1480,8 @@ final class UiModeManagerService extends SystemService {
        if (mCarModeEnabled || mCar) return;
        Secure.putIntForUser(getContext().getContentResolver(),
                Secure.UI_NIGHT_MODE, mNightMode, user);
        Secure.putLongForUser(getContext().getContentResolver(),
                Secure.UI_NIGHT_MODE_CUSTOM_TYPE, mNightModeCustomType, user);
        Secure.putLongForUser(getContext().getContentResolver(),
                Secure.DARK_THEME_CUSTOM_START_TIME,
                mCustomAutoNightModeStartMilliseconds.toNanoOfDay() / 1000, user);
@@ -1473,10 +1534,14 @@ final class UiModeManagerService extends SystemService {
        }

        if (mNightMode == MODE_NIGHT_CUSTOM) {
            if (mNightModeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME) {
                updateComputedNightModeLocked(mLastBedtimeRequestedNightMode);
            } else {
                registerTimeChangeEvent();
                final boolean activate = computeCustomNightMode();
                updateComputedNightModeLocked(activate);
                scheduleNextCustomTimeListener();
            }
        } else {
            unregisterTimeChangeEvent();
        }
@@ -1494,6 +1559,7 @@ final class UiModeManagerService extends SystemService {
                    "updateConfigurationLocked: mDockState=" + mDockState
                    + "; mCarMode=" + mCarModeEnabled
                    + "; mNightMode=" + mNightMode
                    + "; mNightModeCustomType=" + mNightModeCustomType
                    + "; uiMode=" + uiMode);
        }

@@ -1534,7 +1600,8 @@ final class UiModeManagerService extends SystemService {
    }

    private boolean shouldApplyAutomaticChangesImmediately() {
        return mCar || !mPowerManager.isInteractive();
        return mCar || !mPowerManager.isInteractive()
                || mNightModeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
    }

    private void scheduleNextCustomTimeListener() {
Loading