Loading core/res/res/values/config.xml +11 −0 Original line number Diff line number Diff line Loading @@ -2613,6 +2613,17 @@ assistant activities (ACTIVITY_TYPE_ASSISTANT) --> <bool name="config_dismissDreamOnActivityStart">false</bool> <!-- Whether to send a user activity event to PowerManager when a dream quits unexpectedly so that the screen won't immediately shut off. When a dream stops unexpectedly, such as due to an app update, if the device has been inactive less than the user's screen timeout, the device goes to keyguard and times out back to dreaming after a few seconds. If the device has been inactive longer, the screen will immediately turn off. With this flag on, the device will go back to keyguard in all scenarios rather than turning off, which gives the device a chance to start dreaming again. --> <bool name="config_resetScreenTimeoutOnUnexpectedDreamExit">false</bool> <!-- The prefixes of dream component names that are loggable. Matched against ComponentName#flattenToString() for dream components. If empty, logs "other" for all. --> Loading core/res/res/values/symbols.xml +1 −0 Original line number Diff line number Diff line Loading @@ -2211,6 +2211,7 @@ <java-symbol type="array" name="config_supportedDreamComplications" /> <java-symbol type="array" name="config_disabledDreamComponents" /> <java-symbol type="bool" name="config_dismissDreamOnActivityStart" /> <java-symbol type="bool" name="config_resetScreenTimeoutOnUnexpectedDreamExit" /> <java-symbol type="integer" name="config_dreamOverlayReconnectTimeoutMs" /> <java-symbol type="integer" name="config_dreamOverlayMaxReconnectAttempts" /> <java-symbol type="integer" name="config_minDreamOverlayDurationMs" /> Loading services/core/java/com/android/server/dreams/DreamController.java +32 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ package com.android.server.dreams; import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; import static android.os.PowerManager.USER_ACTIVITY_EVENT_OTHER; import static android.os.PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS; import android.app.ActivityTaskManager; import android.app.BroadcastOptions; Loading Loading @@ -72,6 +74,7 @@ final class DreamController { private final Handler mHandler; private final Listener mListener; private final ActivityTaskManager mActivityTaskManager; private final PowerManager mPowerManager; private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED) .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | FLAG_RECEIVER_FOREGROUND); Loading @@ -84,6 +87,15 @@ final class DreamController { private final Intent mCloseNotificationShadeIntent; private final Bundle mCloseNotificationShadeOptions; /** * If this flag is on, we report user activity to {@link PowerManager} so that the screen * doesn't shut off immediately when a dream quits unexpectedly. The device will instead go to * keyguard and time out back to dreaming shortly. * * This allows the dream a second chance to relaunch in case of an app update or other crash. */ private final boolean mResetScreenTimeoutOnUnexpectedDreamExit; private DreamRecord mCurrentDream; // Whether a dreaming started intent has been broadcast. Loading @@ -101,6 +113,7 @@ final class DreamController { mHandler = handler; mListener = listener; mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class); mPowerManager = mContext.getSystemService(PowerManager.class); mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); mCloseNotificationShadeIntent.putExtra(EXTRA_REASON_KEY, EXTRA_REASON_VALUE); mCloseNotificationShadeIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); Loading @@ -110,6 +123,8 @@ final class DreamController { EXTRA_REASON_VALUE) .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE) .toBundle(); mResetScreenTimeoutOnUnexpectedDreamExit = context.getResources().getBoolean( com.android.internal.R.bool.config_resetScreenTimeoutOnUnexpectedDreamExit); } /** Loading Loading @@ -234,6 +249,17 @@ final class DreamController { mCurrentDream.mAppTask = appTask; } /** * Sends a user activity signal to PowerManager to stop the screen from turning off immediately * if there hasn't been any user interaction in a while. */ private void resetScreenTimeout() { Slog.i(TAG, "Resetting screen timeout"); long time = SystemClock.uptimeMillis(); mPowerManager.userActivity(time, USER_ACTIVITY_EVENT_OTHER, USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS); } /** * Stops dreaming. * Loading Loading @@ -448,6 +474,9 @@ final class DreamController { mHandler.post(() -> { mService = null; if (mCurrentDream == DreamRecord.this) { if (mResetScreenTimeoutOnUnexpectedDreamExit) { resetScreenTimeout(); } stopDream(true /*immediate*/, "binder died"); } }); Loading @@ -473,6 +502,9 @@ final class DreamController { mHandler.post(() -> { mService = null; if (mCurrentDream == DreamRecord.this) { if (mResetScreenTimeoutOnUnexpectedDreamExit) { resetScreenTimeout(); } stopDream(true /*immediate*/, "service disconnected"); } }); Loading services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java +58 −0 Original line number Diff line number Diff line Loading @@ -16,7 +16,11 @@ package com.android.server.dreams; import static android.os.PowerManager.USER_ACTIVITY_EVENT_OTHER; import static android.os.PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; Loading @@ -32,7 +36,9 @@ import android.content.ServiceConnection; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.IPowerManager; import android.os.IRemoteCallback; import android.os.PowerManager; import android.os.RemoteException; import android.os.test.TestLooper; import android.service.dreams.IDreamService; Loading @@ -58,6 +64,8 @@ public class DreamControllerTest { @Mock private ActivityTaskManager mActivityTaskManager; @Mock private IPowerManager mPowerManager; @Mock private IBinder mIBinder; Loading @@ -67,6 +75,8 @@ public class DreamControllerTest { @Captor private ArgumentCaptor<ServiceConnection> mServiceConnectionACaptor; @Captor private ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientCaptor; @Captor private ArgumentCaptor<IRemoteCallback> mRemoteCallbackCaptor; private final TestLooper mLooper = new TestLooper(); Loading @@ -90,6 +100,12 @@ public class DreamControllerTest { when(mContext.getSystemServiceName(ActivityTaskManager.class)) .thenReturn(Context.ACTIVITY_TASK_SERVICE); final PowerManager powerManager = new PowerManager(mContext, mPowerManager, null, null); when(mContext.getSystemService(Context.POWER_SERVICE)) .thenReturn(powerManager); when(mContext.getSystemServiceName(PowerManager.class)) .thenReturn(Context.POWER_SERVICE); mToken = new Binder(); mDreamName = ComponentName.unflattenFromString("dream"); mOverlayName = ComponentName.unflattenFromString("dream_overlay"); Loading Loading @@ -209,9 +225,51 @@ public class DreamControllerTest { verify(mIDreamService).detach(); } @Test public void serviceDisconnect_resetsScreenTimeout() throws RemoteException { // Start dream. mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); ServiceConnection serviceConnection = captureServiceConnection(); serviceConnection.onServiceConnected(mDreamName, mIBinder); mLooper.dispatchAll(); // Dream disconnects unexpectedly. serviceConnection.onServiceDisconnected(mDreamName); mLooper.dispatchAll(); // Power manager receives user activity signal. verify(mPowerManager).userActivity(/*displayId=*/ anyInt(), /*time=*/ anyLong(), eq(USER_ACTIVITY_EVENT_OTHER), eq(USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS)); } @Test public void binderDied_resetsScreenTimeout() throws RemoteException { // Start dream. mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); captureServiceConnection().onServiceConnected(mDreamName, mIBinder); mLooper.dispatchAll(); // Dream binder dies. captureDeathRecipient().binderDied(); mLooper.dispatchAll(); // Power manager receives user activity signal. verify(mPowerManager).userActivity(/*displayId=*/ anyInt(), /*time=*/ anyLong(), eq(USER_ACTIVITY_EVENT_OTHER), eq(USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS)); } private ServiceConnection captureServiceConnection() { verify(mContext).bindServiceAsUser(any(), mServiceConnectionACaptor.capture(), anyInt(), any()); return mServiceConnectionACaptor.getValue(); } private IBinder.DeathRecipient captureDeathRecipient() throws RemoteException { verify(mIBinder).linkToDeath(mDeathRecipientCaptor.capture(), anyInt()); return mDeathRecipientCaptor.getValue(); } } Loading
core/res/res/values/config.xml +11 −0 Original line number Diff line number Diff line Loading @@ -2613,6 +2613,17 @@ assistant activities (ACTIVITY_TYPE_ASSISTANT) --> <bool name="config_dismissDreamOnActivityStart">false</bool> <!-- Whether to send a user activity event to PowerManager when a dream quits unexpectedly so that the screen won't immediately shut off. When a dream stops unexpectedly, such as due to an app update, if the device has been inactive less than the user's screen timeout, the device goes to keyguard and times out back to dreaming after a few seconds. If the device has been inactive longer, the screen will immediately turn off. With this flag on, the device will go back to keyguard in all scenarios rather than turning off, which gives the device a chance to start dreaming again. --> <bool name="config_resetScreenTimeoutOnUnexpectedDreamExit">false</bool> <!-- The prefixes of dream component names that are loggable. Matched against ComponentName#flattenToString() for dream components. If empty, logs "other" for all. --> Loading
core/res/res/values/symbols.xml +1 −0 Original line number Diff line number Diff line Loading @@ -2211,6 +2211,7 @@ <java-symbol type="array" name="config_supportedDreamComplications" /> <java-symbol type="array" name="config_disabledDreamComponents" /> <java-symbol type="bool" name="config_dismissDreamOnActivityStart" /> <java-symbol type="bool" name="config_resetScreenTimeoutOnUnexpectedDreamExit" /> <java-symbol type="integer" name="config_dreamOverlayReconnectTimeoutMs" /> <java-symbol type="integer" name="config_dreamOverlayMaxReconnectAttempts" /> <java-symbol type="integer" name="config_minDreamOverlayDurationMs" /> Loading
services/core/java/com/android/server/dreams/DreamController.java +32 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,8 @@ package com.android.server.dreams; import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; import static android.os.PowerManager.USER_ACTIVITY_EVENT_OTHER; import static android.os.PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS; import android.app.ActivityTaskManager; import android.app.BroadcastOptions; Loading Loading @@ -72,6 +74,7 @@ final class DreamController { private final Handler mHandler; private final Listener mListener; private final ActivityTaskManager mActivityTaskManager; private final PowerManager mPowerManager; private final Intent mDreamingStartedIntent = new Intent(Intent.ACTION_DREAMING_STARTED) .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | FLAG_RECEIVER_FOREGROUND); Loading @@ -84,6 +87,15 @@ final class DreamController { private final Intent mCloseNotificationShadeIntent; private final Bundle mCloseNotificationShadeOptions; /** * If this flag is on, we report user activity to {@link PowerManager} so that the screen * doesn't shut off immediately when a dream quits unexpectedly. The device will instead go to * keyguard and time out back to dreaming shortly. * * This allows the dream a second chance to relaunch in case of an app update or other crash. */ private final boolean mResetScreenTimeoutOnUnexpectedDreamExit; private DreamRecord mCurrentDream; // Whether a dreaming started intent has been broadcast. Loading @@ -101,6 +113,7 @@ final class DreamController { mHandler = handler; mListener = listener; mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class); mPowerManager = mContext.getSystemService(PowerManager.class); mCloseNotificationShadeIntent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); mCloseNotificationShadeIntent.putExtra(EXTRA_REASON_KEY, EXTRA_REASON_VALUE); mCloseNotificationShadeIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); Loading @@ -110,6 +123,8 @@ final class DreamController { EXTRA_REASON_VALUE) .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE) .toBundle(); mResetScreenTimeoutOnUnexpectedDreamExit = context.getResources().getBoolean( com.android.internal.R.bool.config_resetScreenTimeoutOnUnexpectedDreamExit); } /** Loading Loading @@ -234,6 +249,17 @@ final class DreamController { mCurrentDream.mAppTask = appTask; } /** * Sends a user activity signal to PowerManager to stop the screen from turning off immediately * if there hasn't been any user interaction in a while. */ private void resetScreenTimeout() { Slog.i(TAG, "Resetting screen timeout"); long time = SystemClock.uptimeMillis(); mPowerManager.userActivity(time, USER_ACTIVITY_EVENT_OTHER, USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS); } /** * Stops dreaming. * Loading Loading @@ -448,6 +474,9 @@ final class DreamController { mHandler.post(() -> { mService = null; if (mCurrentDream == DreamRecord.this) { if (mResetScreenTimeoutOnUnexpectedDreamExit) { resetScreenTimeout(); } stopDream(true /*immediate*/, "binder died"); } }); Loading @@ -473,6 +502,9 @@ final class DreamController { mHandler.post(() -> { mService = null; if (mCurrentDream == DreamRecord.this) { if (mResetScreenTimeoutOnUnexpectedDreamExit) { resetScreenTimeout(); } stopDream(true /*immediate*/, "service disconnected"); } }); Loading
services/tests/servicestests/src/com/android/server/dreams/DreamControllerTest.java +58 −0 Original line number Diff line number Diff line Loading @@ -16,7 +16,11 @@ package com.android.server.dreams; import static android.os.PowerManager.USER_ACTIVITY_EVENT_OTHER; import static android.os.PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; Loading @@ -32,7 +36,9 @@ import android.content.ServiceConnection; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.IPowerManager; import android.os.IRemoteCallback; import android.os.PowerManager; import android.os.RemoteException; import android.os.test.TestLooper; import android.service.dreams.IDreamService; Loading @@ -58,6 +64,8 @@ public class DreamControllerTest { @Mock private ActivityTaskManager mActivityTaskManager; @Mock private IPowerManager mPowerManager; @Mock private IBinder mIBinder; Loading @@ -67,6 +75,8 @@ public class DreamControllerTest { @Captor private ArgumentCaptor<ServiceConnection> mServiceConnectionACaptor; @Captor private ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientCaptor; @Captor private ArgumentCaptor<IRemoteCallback> mRemoteCallbackCaptor; private final TestLooper mLooper = new TestLooper(); Loading @@ -90,6 +100,12 @@ public class DreamControllerTest { when(mContext.getSystemServiceName(ActivityTaskManager.class)) .thenReturn(Context.ACTIVITY_TASK_SERVICE); final PowerManager powerManager = new PowerManager(mContext, mPowerManager, null, null); when(mContext.getSystemService(Context.POWER_SERVICE)) .thenReturn(powerManager); when(mContext.getSystemServiceName(PowerManager.class)) .thenReturn(Context.POWER_SERVICE); mToken = new Binder(); mDreamName = ComponentName.unflattenFromString("dream"); mOverlayName = ComponentName.unflattenFromString("dream_overlay"); Loading Loading @@ -209,9 +225,51 @@ public class DreamControllerTest { verify(mIDreamService).detach(); } @Test public void serviceDisconnect_resetsScreenTimeout() throws RemoteException { // Start dream. mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); ServiceConnection serviceConnection = captureServiceConnection(); serviceConnection.onServiceConnected(mDreamName, mIBinder); mLooper.dispatchAll(); // Dream disconnects unexpectedly. serviceConnection.onServiceDisconnected(mDreamName); mLooper.dispatchAll(); // Power manager receives user activity signal. verify(mPowerManager).userActivity(/*displayId=*/ anyInt(), /*time=*/ anyLong(), eq(USER_ACTIVITY_EVENT_OTHER), eq(USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS)); } @Test public void binderDied_resetsScreenTimeout() throws RemoteException { // Start dream. mDreamController.startDream(mToken, mDreamName, false /*isPreview*/, false /*doze*/, 0 /*userId*/, null /*wakeLock*/, mOverlayName, "test" /*reason*/); captureServiceConnection().onServiceConnected(mDreamName, mIBinder); mLooper.dispatchAll(); // Dream binder dies. captureDeathRecipient().binderDied(); mLooper.dispatchAll(); // Power manager receives user activity signal. verify(mPowerManager).userActivity(/*displayId=*/ anyInt(), /*time=*/ anyLong(), eq(USER_ACTIVITY_EVENT_OTHER), eq(USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS)); } private ServiceConnection captureServiceConnection() { verify(mContext).bindServiceAsUser(any(), mServiceConnectionACaptor.capture(), anyInt(), any()); return mServiceConnectionACaptor.getValue(); } private IBinder.DeathRecipient captureDeathRecipient() throws RemoteException { verify(mIBinder).linkToDeath(mDeathRecipientCaptor.capture(), anyInt()); return mDeathRecipientCaptor.getValue(); } }