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

Commit 7a3312ae authored by William Xiao's avatar William Xiao Committed by Automerger Merge Worker
Browse files

Merge "Send user activity when dream quits unexpectedly" into udc-qpr-dev am:...

Merge "Send user activity when dream quits unexpectedly" into udc-qpr-dev am: 12710e83 am: b152ea4c

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/24086127



Change-Id: Iabc7c59e21119346d63df49a55021370de1d6ff4
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents e66dbd3b b152ea4c
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -2624,6 +2624,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. -->
+1 −0
Original line number Diff line number Diff line
@@ -2219,6 +2219,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" />
+32 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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);
    }

    /**
@@ -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.
     *
@@ -448,6 +474,9 @@ final class DreamController {
            mHandler.post(() -> {
                mService = null;
                if (mCurrentDream == DreamRecord.this) {
                    if (mResetScreenTimeoutOnUnexpectedDreamExit) {
                        resetScreenTimeout();
                    }
                    stopDream(true /*immediate*/, "binder died");
                }
            });
@@ -473,6 +502,9 @@ final class DreamController {
            mHandler.post(() -> {
                mService = null;
                if (mCurrentDream == DreamRecord.this) {
                    if (mResetScreenTimeoutOnUnexpectedDreamExit) {
                        resetScreenTimeout();
                    }
                    stopDream(true /*immediate*/, "service disconnected");
                }
            });
+58 −0
Original line number Diff line number Diff line
@@ -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();
    }
}