Allow dark mode to turn on/off automatically while dreaming.

Currently dark mode schedules will wait until the screen turns off prior
to switching between light/dark themes. This avoids interrupting the
user while they are using the device. However, on devices where
screensavers are prevalent, the screen may never turn off and therefore
we will never switch between modes. This change ensures that the switch
can occur while the device is dreaming.

Fixes: 306272618
Test: atest UiModeManagerServiceTest
Change-Id: Ide068813075fc410fc6c6b4531cb09b7d6e11a84
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index dc0f901..66a10e4 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -80,6 +80,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Settings.Secure;
+import android.service.dreams.DreamManagerInternal;
 import android.service.dreams.Sandman;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
@@ -142,10 +143,10 @@
     private boolean mCarModeEnabled = false;
     private boolean mCharging = false;
     private boolean mPowerSave = false;
-    // Do not change configuration now. wait until screen turns off.
+    // Do not change configuration now. wait until the device is inactive (eg screen off, dreaming)
     // This prevents jank and activity restart when the user
     // is actively using the device
-    private boolean mWaitForScreenOff = false;
+    private boolean mWaitForDeviceInactive = false;
     private int mDefaultUiModeType;
     private boolean mCarModeKeepsScreenOn;
     private boolean mDeskModeKeepsScreenOn;
@@ -199,6 +200,7 @@
 
     private final LocalService mLocalService = new LocalService();
     private PowerManagerInternal mLocalPowerManager;
+    private DreamManagerInternal mDreamManagerInternal;
 
     private final IUiModeManager.Stub mService;
 
@@ -295,7 +297,7 @@
                     if (shouldApplyAutomaticChangesImmediately()) {
                         updateLocked(0, 0);
                     } else {
-                        registerScreenOffEventLocked();
+                        registerDeviceInactiveListenerLocked();
                     }
                 }
             }
@@ -306,12 +308,12 @@
      * DO NOT USE DIRECTLY
      * see register registerScreenOffEvent and unregisterScreenOffEvent
      */
-    private final BroadcastReceiver mOnScreenOffHandler = new BroadcastReceiver() {
+    private final BroadcastReceiver mDeviceInactiveListener = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             synchronized (mLock) {
                 // must unregister first before updating
-                unregisterScreenOffEventLocked();
+                unregisterDeviceInactiveListenerLocked();
                 updateLocked(0, 0);
             }
         }
@@ -432,6 +434,7 @@
                 if (twilightManager != null) mTwilightManager = twilightManager;
                 mLocalPowerManager =
                         LocalServices.getService(PowerManagerInternal.class);
+                mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class);
                 initPowerSave();
                 mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
                 registerVrStateListener();
@@ -582,7 +585,7 @@
         if (shouldApplyAutomaticChangesImmediately()) {
             updateLocked(0, 0);
         } else {
-            registerScreenOffEventLocked();
+            registerDeviceInactiveListenerLocked();
         }
         scheduleNextCustomTimeListener();
     }
@@ -631,22 +634,23 @@
         return LocalTime.ofNanoOfDay(t * 1000);
     }
 
-    private void registerScreenOffEventLocked() {
+    private void registerDeviceInactiveListenerLocked() {
         if (mPowerSave) return;
-        mWaitForScreenOff = true;
+        mWaitForDeviceInactive = true;
         final IntentFilter intentFilter =
                 new IntentFilter(Intent.ACTION_SCREEN_OFF);
-        getContext().registerReceiver(mOnScreenOffHandler, intentFilter);
+        intentFilter.addAction(Intent.ACTION_DREAMING_STARTED);
+        getContext().registerReceiver(mDeviceInactiveListener, intentFilter);
     }
 
     private void cancelCustomAlarm() {
         mAlarmManager.cancel(mCustomTimeListener);
     }
 
-    private void unregisterScreenOffEventLocked() {
-        mWaitForScreenOff = false;
+    private void unregisterDeviceInactiveListenerLocked() {
+        mWaitForDeviceInactive = false;
         try {
-            getContext().unregisterReceiver(mOnScreenOffHandler);
+            getContext().unregisterReceiver(mDeviceInactiveListener);
         } catch (IllegalArgumentException e) {
             // we ignore this exception if the receiver is unregistered already.
         }
@@ -828,7 +832,7 @@
                 synchronized (mLock) {
                     if (mNightMode != mode || mNightModeCustomType != customModeType) {
                         if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
-                            unregisterScreenOffEventLocked();
+                            unregisterDeviceInactiveListenerLocked();
                             cancelCustomAlarm();
                         }
                         mNightModeCustomType = mode == MODE_NIGHT_CUSTOM
@@ -840,10 +844,10 @@
                         // on screen off will update configuration instead
                         if ((mNightMode != MODE_NIGHT_AUTO && mNightMode != MODE_NIGHT_CUSTOM)
                                 || shouldApplyAutomaticChangesImmediately()) {
-                            unregisterScreenOffEventLocked();
+                            unregisterDeviceInactiveListenerLocked();
                             updateLocked(0, 0);
                         } else {
-                            registerScreenOffEventLocked();
+                            registerDeviceInactiveListenerLocked();
                         }
                     }
                 }
@@ -967,7 +971,7 @@
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     if (mNightMode == MODE_NIGHT_AUTO || mNightMode == MODE_NIGHT_CUSTOM) {
-                        unregisterScreenOffEventLocked();
+                        unregisterDeviceInactiveListenerLocked();
                         mOverrideNightModeOff = !active;
                         mOverrideNightModeOn = active;
                         mOverrideNightModeUser = mCurrentUser;
@@ -1011,7 +1015,7 @@
                 persistNightMode(user);
                 onCustomTimeUpdated(user);
             } catch (DateTimeException e) {
-                unregisterScreenOffEventLocked();
+                unregisterDeviceInactiveListenerLocked();
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -1038,7 +1042,7 @@
                 mCustomAutoNightModeEndMilliseconds = newTime;
                 onCustomTimeUpdated(user);
             } catch (DateTimeException e) {
-                unregisterScreenOffEventLocked();
+                unregisterDeviceInactiveListenerLocked();
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -1383,10 +1387,10 @@
         persistNightMode(user);
         if (mNightMode != MODE_NIGHT_CUSTOM) return;
         if (shouldApplyAutomaticChangesImmediately()) {
-            unregisterScreenOffEventLocked();
+            unregisterDeviceInactiveListenerLocked();
             updateLocked(0, 0);
         } else {
-            registerScreenOffEventLocked();
+            registerDeviceInactiveListenerLocked();
         }
     }
 
@@ -1414,7 +1418,7 @@
                 pw.print(" ");
             }
             pw.println("");
-            pw.print(" waitScreenOff="); pw.print(mWaitForScreenOff);
+            pw.print(" mWaitForDeviceInactive="); pw.print(mWaitForDeviceInactive);
             pw.print(" mComputedNightMode="); pw.print(mComputedNightMode);
             pw.print(" customStart="); pw.print(mCustomAutoNightModeStartMilliseconds);
             pw.print(" customEnd"); pw.print(mCustomAutoNightModeEndMilliseconds);
@@ -1675,7 +1679,7 @@
         }
 
         mCurUiMode = uiMode;
-        if (!mHoldingConfiguration && (!mWaitForScreenOff || mPowerSave)) {
+        if (!mHoldingConfiguration && (!mWaitForDeviceInactive || mPowerSave)) {
             mConfiguration.uiMode = uiMode;
         }
     }
@@ -1712,7 +1716,8 @@
 
     private boolean shouldApplyAutomaticChangesImmediately() {
         return mCar || !mPowerManager.isInteractive()
-                || mNightModeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
+                || mNightModeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME
+                || mDreamManagerInternal.isDreaming();
     }
 
     private void scheduleNextCustomTimeListener() {
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index 8a0a764..db46532 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -27,10 +27,14 @@
 import static android.app.UiModeManager.PROJECTION_TYPE_ALL;
 import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE;
 import static android.app.UiModeManager.PROJECTION_TYPE_NONE;
+
 import static com.android.server.UiModeManagerService.SUPPORTED_NIGHT_MODE_CUSTOM_TYPES;
+
 import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.TestCase.assertFalse;
 import static junit.framework.TestCase.assertTrue;
+
 import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.empty;
 import static org.junit.Assert.assertEquals;
@@ -81,6 +85,7 @@
 import android.os.UserHandle;
 import android.os.test.FakePermissionEnforcer;
 import android.provider.Settings;
+import android.service.dreams.DreamManagerInternal;
 import android.test.mock.MockContentResolver;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -131,6 +136,9 @@
     private TwilightState mTwilightState;
     @Mock
     PowerManagerInternal mLocalPowerManager;
+
+    @Mock
+    DreamManagerInternal mDreamManagerInternal;
     @Mock
     private PackageManager mPackageManager;
     @Mock
@@ -143,6 +151,7 @@
     private ArgumentCaptor<BroadcastReceiver> mOrderedBroadcastReceiver;
 
     private BroadcastReceiver mScreenOffCallback;
+    private BroadcastReceiver mDreamingStartedCallback;
     private BroadcastReceiver mTimeChangedCallback;
     private BroadcastReceiver mDockStateChangedCallback;
     private AlarmManager.OnAlarmListener mCustomListener;
@@ -187,6 +196,9 @@
             if (filter.hasAction(Intent.ACTION_SCREEN_OFF)) {
                 mScreenOffCallback = inv.getArgument(0);
             }
+            if (filter.hasAction(Intent.ACTION_DREAMING_STARTED)) {
+                mDreamingStartedCallback = inv.getArgument(0);
+            }
             if (filter.hasAction(Intent.ACTION_DOCK_EVENT)) {
                 mDockStateChangedCallback = inv.getArgument(0);
             }
@@ -210,6 +222,7 @@
         addLocalService(WindowManagerInternal.class, mWindowManager);
         addLocalService(PowerManagerInternal.class, mLocalPowerManager);
         addLocalService(TwilightManager.class, mTwilightManager);
+        addLocalService(DreamManagerInternal.class, mDreamManagerInternal);
 
         mInjector = spy(new TestInjector());
         mUiManagerService = new UiModeManagerService(mContext, /* setupWizardComplete= */ true,
@@ -281,7 +294,7 @@
     }
 
     @Test
-    public void setAutoMode_screenOffRegistered() throws RemoteException {
+    public void setAutoMode_deviceInactiveRegistered() throws RemoteException {
         try {
             mService.setNightMode(MODE_NIGHT_NO);
         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
@@ -291,7 +304,7 @@
 
     @Ignore // b/152719290 - Fails on stage-aosp-master
     @Test
-    public void setAutoMode_screenOffUnRegistered() throws RemoteException {
+    public void setAutoMode_deviceInactiveUnRegistered() throws RemoteException {
         try {
             mService.setNightMode(MODE_NIGHT_AUTO);
         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
@@ -776,7 +789,7 @@
     }
 
     @Test
-    public void customTime_darkThemeOn() throws RemoteException {
+    public void customTime_darkThemeOn_afterScreenOff() throws RemoteException {
         LocalTime now = LocalTime.now();
         mService.setNightMode(MODE_NIGHT_NO);
         mService.setCustomNightModeStart(now.minusHours(1L).toNanoOfDay() / 1000);
@@ -787,7 +800,7 @@
     }
 
     @Test
-    public void customTime_darkThemeOff() throws RemoteException {
+    public void customTime_darkThemeOff_afterScreenOff() throws RemoteException {
         LocalTime now = LocalTime.now();
         mService.setNightMode(MODE_NIGHT_YES);
         mService.setCustomNightModeStart(now.plusHours(1L).toNanoOfDay() / 1000);
@@ -798,6 +811,28 @@
     }
 
     @Test
+    public void customTime_darkThemeOn_afterDreamingStarted() throws RemoteException {
+        LocalTime now = LocalTime.now();
+        mService.setNightMode(MODE_NIGHT_NO);
+        mService.setCustomNightModeStart(now.minusHours(1L).toNanoOfDay() / 1000);
+        mService.setCustomNightModeEnd(now.plusHours(1L).toNanoOfDay() / 1000);
+        mService.setNightMode(MODE_NIGHT_CUSTOM);
+        mDreamingStartedCallback.onReceive(mContext, new Intent(Intent.ACTION_DREAMING_STARTED));
+        assertTrue(isNightModeActivated());
+    }
+
+    @Test
+    public void customTime_darkThemeOff_afterDreamingStarted() throws RemoteException {
+        LocalTime now = LocalTime.now();
+        mService.setNightMode(MODE_NIGHT_YES);
+        mService.setCustomNightModeStart(now.plusHours(1L).toNanoOfDay() / 1000);
+        mService.setCustomNightModeEnd(now.minusHours(1L).toNanoOfDay() / 1000);
+        mService.setNightMode(MODE_NIGHT_CUSTOM);
+        mDreamingStartedCallback.onReceive(mContext, new Intent(Intent.ACTION_DREAMING_STARTED));
+        assertFalse(isNightModeActivated());
+    }
+
+    @Test
     public void customTime_darkThemeOff_afterStartEnd() throws RemoteException {
         LocalTime now = LocalTime.now();
         mService.setNightMode(MODE_NIGHT_YES);