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

Commit 525e2f6b 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-dev am: 2c7b3864

parents a26040cd 2c7b3864
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -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. -->
+1 −0
Original line number Diff line number Diff line
@@ -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" />
+32 −0
Original line number Diff line number Diff line
@@ -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");
                }
            });
+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();
    }
}