Merge "Send user activity when dream quits unexpectedly" into udc-dev
This commit is contained in:
@@ -2560,6 +2560,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. -->
|
||||
|
||||
@@ -2207,6 +2207,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" />
|
||||
|
||||
@@ -18,6 +18,8 @@ package com.android.server.dreams;
|
||||
|
||||
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
|
||||
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;
|
||||
@@ -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);
|
||||
@@ -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.
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,6 +228,17 @@ final class DreamController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@@ -420,6 +446,9 @@ final class DreamController {
|
||||
mHandler.post(() -> {
|
||||
mService = null;
|
||||
if (mCurrentDream == DreamRecord.this) {
|
||||
if (mResetScreenTimeoutOnUnexpectedDreamExit) {
|
||||
resetScreenTimeout();
|
||||
}
|
||||
stopDream(true /*immediate*/, "binder died");
|
||||
}
|
||||
});
|
||||
@@ -445,6 +474,9 @@ final class DreamController {
|
||||
mHandler.post(() -> {
|
||||
mService = null;
|
||||
if (mCurrentDream == DreamRecord.this) {
|
||||
if (mResetScreenTimeoutOnUnexpectedDreamExit) {
|
||||
resetScreenTimeout();
|
||||
}
|
||||
stopDream(true /*immediate*/, "service disconnected");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -58,6 +64,8 @@ public class DreamControllerTest {
|
||||
|
||||
@Mock
|
||||
private ActivityTaskManager mActivityTaskManager;
|
||||
@Mock
|
||||
private IPowerManager mPowerManager;
|
||||
|
||||
@Mock
|
||||
private IBinder mIBinder;
|
||||
@@ -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();
|
||||
@@ -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");
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user