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

Commit bf96f973 authored by William Xiao's avatar William Xiao
Browse files

Add new wake reason for doze stopped

When the doze dream is stopped using DreamManager APIs, explicitly
request a wakeUp. Before this CL, this would result in the device going
to sleep instead.

See the bug for more context.

Bug: 298073718
Fixes: 298073718
Test: atest DreamManagerServiceTest
Flag: android.service.dreams.wake_on_stopping_doze
Change-Id: I299c907bd4d16272cf2eb1de852ba19eace3c403
parent 5c69b10f
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -687,6 +687,7 @@ public final class PowerManager {
            WAKE_REASON_LIFT,
            WAKE_REASON_BIOMETRIC,
            WAKE_REASON_DOCK,
            WAKE_REASON_DOZE_STOPPED,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface WakeReason{}
@@ -833,6 +834,13 @@ public final class PowerManager {
     */
    public static final int WAKE_REASON_DOCK = 18;

    /**
     * Wake up reason code: Waking the dream because the dozing was stopped directly through dream
     * APIs rather than some other more specific reason.
     * @hide
     */
    public static final int WAKE_REASON_DOZE_STOPPED = 19;

    /**
     * Convert the wake reason to a string for debugging purposes.
     * @hide
@@ -858,6 +866,7 @@ public final class PowerManager {
            case WAKE_REASON_LIFT: return "WAKE_REASON_LIFT";
            case WAKE_REASON_BIOMETRIC: return "WAKE_REASON_BIOMETRIC";
            case WAKE_REASON_DOCK: return "WAKE_REASON_DOCK";
            case WAKE_REASON_DOZE_STOPPED: return "WAKE_REASON_DOZE_STOPPED";
            default: return Integer.toString(wakeReason);
        }
    }
+10 −0
Original line number Diff line number Diff line
@@ -140,3 +140,13 @@ flag {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "wake_on_stopping_doze"
    namespace: "systemui"
    description: "Sends a wakeUp when explicitly stopping the doze dream"
    bug: "298073718"
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}
+67 −15
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import static android.service.dreams.Flags.cleanupDreamSettingsOnUninstall;
import static android.service.dreams.Flags.disallowDreamOnAutoProjection;
import static android.service.dreams.Flags.dreamHandlesBeingObscured;
import static android.service.dreams.Flags.dreamsV2;
import static android.service.dreams.Flags.wakeOnStoppingDoze;

import static com.android.server.wm.ActivityInterceptorCallback.DREAM_MANAGER_ORDERED_ID;

@@ -267,29 +268,29 @@ public final class DreamManagerService extends SystemService {
    }

    public DreamManagerService(Context context) {
        this(context, new DreamHandler(FgThread.get().getLooper()));
        this(new DefaultInjector(context, new DreamHandler(FgThread.get().getLooper())));
    }

    @VisibleForTesting
    DreamManagerService(Context context, Handler handler) {
        super(context);
        mContext = context;
        mHandler = handler;
        mController = new DreamController(context, mHandler, mControllerListener);
    DreamManagerService(Injector injector) {
        super(injector.getContext());
        mContext = injector.getContext();
        mHandler = injector.getHandler();
        mController = injector.getDreamController(mControllerListener);

        mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
        mPowerManager = mContext.getSystemService(PowerManager.class);
        mPowerManagerInternal = getLocalService(PowerManagerInternal.class);
        mUiModeManager = (UiModeManager) context.getSystemService(Context.UI_MODE_SERVICE);
        mUiModeManager = mContext.getSystemService(UiModeManager.class);
        mAtmInternal = getLocalService(ActivityTaskManagerInternal.class);
        mPmInternal = getLocalService(PackageManagerInternal.class);
        mUserManager = context.getSystemService(UserManager.class);
        mUserManager = mContext.getSystemService(UserManager.class);
        mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, DOZE_WAKE_LOCK_TAG);
        mDozeConfig = new AmbientDisplayConfiguration(mContext);
        mDozeConfig = injector.getDozeConfig();
        mUiEventLogger = new UiEventLoggerImpl();
        mDreamUiEventLogger = new DreamUiEventLoggerImpl(
                mContext.getResources().getStringArray(R.array.config_loggable_dream_prefixes));
        AmbientDisplayConfiguration adc = new AmbientDisplayConfiguration(mContext);
        mAmbientDisplayComponent = ComponentName.unflattenFromString(adc.ambientDisplayComponent());
        mAmbientDisplayComponent =
                ComponentName.unflattenFromString(mDozeConfig.ambientDisplayComponent());
        mDreamsOnlyEnabledForDockUser =
                mContext.getResources().getBoolean(R.bool.config_dreamsOnlyEnabledForDockUser);
        mDismissDreamOnActivityStart = mContext.getResources().getBoolean(
@@ -702,7 +703,8 @@ public final class DreamManagerService extends SystemService {
        }
    }

    private void startDreamInternal(boolean doze, String reason) {
    @VisibleForTesting
    void startDreamInternal(boolean doze, String reason) {
        final int userId = ActivityManager.getCurrentUser();
        final ComponentName dream = chooseDreamForUser(doze, userId);
        if (dream != null) {
@@ -716,13 +718,15 @@ public final class DreamManagerService extends SystemService {
        stopDreamInternal(false, "stopping dream from shell");
    }

    private void stopDreamInternal(boolean immediate, String reason) {
    @VisibleForTesting
    void stopDreamInternal(boolean immediate, String reason) {
        synchronized (mLock) {
            stopDreamLocked(immediate, reason);
        }
    }

    private void startDozingInternal(IBinder token, int screenState,
    @VisibleForTesting
    void startDozingInternal(IBinder token, int screenState,
            @Display.StateReason int reason, float screenBrightness,
            boolean useNormalBrightnessForDoze) {
        Slog.d(TAG, "Dream requested to start dozing: " + token
@@ -1008,6 +1012,12 @@ public final class DreamManagerService extends SystemService {
                    mCurrentDream.name.flattenToString());
        }
        if (mCurrentDream.isDozing) {
            if (wakeOnStoppingDoze()) {
                mPowerManager.wakeUp(
                        SystemClock.uptimeMillis(),
                        PowerManager.WAKE_REASON_DOZE_STOPPED,
                        "android.server.dreams:requestAwaken");
            }
            mDozeWakeLock.release();
        }
        mCurrentDream = null;
@@ -1085,6 +1095,48 @@ public final class DreamManagerService extends SystemService {
        }
    };

    /**
     * A helper interface to inject dependencies into {@link DreamManagerService}.
     * @hide
     */
    @VisibleForTesting
    interface Injector {
        Context getContext();
        Handler getHandler();
        AmbientDisplayConfiguration getDozeConfig();
        DreamController getDreamController(DreamController.Listener controllerListener);
    }

    private static final class DefaultInjector implements Injector {
        private final Context mContext;
        private final Handler mHandler;

        DefaultInjector(Context context, Handler handler) {
            mContext = context;
            mHandler = handler;
        }

        @Override
        public Context getContext() {
            return mContext;
        }

        @Override
        public Handler getHandler() {
            return mHandler;
        }

        @Override
        public AmbientDisplayConfiguration getDozeConfig() {
            return new AmbientDisplayConfiguration(mContext);
        }

        @Override
        public DreamController getDreamController(DreamController.Listener controllerListener) {
            return new DreamController(mContext, mHandler, controllerListener);
        }
    }

    /**
     * Handler for asynchronous operations performed by the dream manager.
     * Ensures operations to {@link DreamController} are single-threaded.
+153 −17
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.os.BatteryManager.EXTRA_CHARGING_STATUS;
import static android.service.dreams.Flags.FLAG_ALLOW_DREAM_WITH_CHARGE_LIMIT;
import static android.service.dreams.Flags.FLAG_DISALLOW_DREAM_ON_AUTO_PROJECTION;
import static android.service.dreams.Flags.FLAG_DREAMS_V2;
import static android.service.dreams.Flags.FLAG_WAKE_ON_STOPPING_DOZE;

import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;

@@ -29,8 +30,13 @@ import static com.android.server.dreams.DreamManagerService.CHARGE_LIMIT_PERCENT

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

@@ -41,9 +47,16 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.hardware.display.AmbientDisplayConfiguration;
import android.hardware.health.BatteryChargingState;
import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
import android.os.Binder;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.UserHandle;
import android.os.UserManager;
@@ -59,6 +72,7 @@ import com.android.internal.util.test.LocalServiceKeeperRule;
import com.android.server.SystemService;
import com.android.server.input.InputManagerInternal;
import com.android.server.testutils.TestHandler;
import com.android.server.wm.ActivityTaskManagerInternal;

import org.junit.Before;
import org.junit.Rule;
@@ -79,19 +93,32 @@ public class DreamManagerServiceTest {

    private ContextWrapper mContextSpy;

    @Mock
    private DreamController mDreamControllerMock;

    @Mock
    private ActivityManagerInternal mActivityManagerInternalMock;
    @Mock
    private ActivityTaskManagerInternal mActivityTaskManagerInternalMock;
    @Mock
    private BatteryManagerInternal mBatteryManagerInternal;

    @Mock
    private InputManagerInternal mInputManagerInternal;
    @Mock
    private PackageManager mPackageManagerMock;
    @Mock
    private PowerManagerInternal mPowerManagerInternalMock;
    @Mock
    private PowerManager mPowerManagerMock;
    @Mock
    private UiModeManager mUiModeManagerMock;
    @Mock
    private UserManager mUserManagerMock;
    @Mock
    private PowerManager.WakeLock mWakeLockMock;
    @Mock
    private AmbientDisplayConfiguration mDozeConfigMock;

    @Rule
    public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule();
@@ -113,6 +140,8 @@ public class DreamManagerServiceTest {

        mLocalServiceKeeperRule.overrideLocalService(ActivityManagerInternal.class,
                mActivityManagerInternalMock);
        mLocalServiceKeeperRule.overrideLocalService(ActivityTaskManagerInternal.class,
                mActivityTaskManagerInternalMock);
        mLocalServiceKeeperRule.overrideLocalService(BatteryManagerInternal.class,
                mBatteryManagerInternal);
        mLocalServiceKeeperRule.overrideLocalService(InputManagerInternal.class,
@@ -127,12 +156,56 @@ public class DreamManagerServiceTest {
        Settings.Secure.putInt(mContextSpy.getContentResolver(),
                Settings.Secure.SCREENSAVER_RESTRICT_TO_WIRELESS_CHARGING, 0);

        when(mPowerManagerMock.newWakeLock(anyInt(), any())).thenReturn(mWakeLockMock);

        doReturn(mContextSpy).when(mContextSpy).createContextAsUser(any(), anyInt());
        when(mContextSpy.getPackageManager()).thenReturn(mPackageManagerMock);
        when(mContextSpy.getSystemService(PowerManager.class)).thenReturn(mPowerManagerMock);
        when(mContextSpy.getSystemService(UserManager.class)).thenReturn(mUserManagerMock);
        when(mContextSpy.getSystemService(Context.UI_MODE_SERVICE)).thenReturn(mUiModeManagerMock);
        when(mContextSpy.getSystemService(UiModeManager.class)).thenReturn(mUiModeManagerMock);

        when(mDozeConfigMock.ambientDisplayComponent())
                .thenReturn("test.doze.component/.TestDozeService");
    }

    private DreamManagerService createService() {
        return new DreamManagerService(mContextSpy, mTestHandler);
        return new DreamManagerService(
                new TestInjector(mContextSpy, mTestHandler, mDreamControllerMock, mDozeConfigMock));
    }

    /**
     * Starts dreaming and returns the dream token.
     */
    private Binder startDream(DreamManagerService service) {
        service.startDreamInternal(/*doze=*/ true, "testing");

        ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
        verify(mWakeLockMock).wrap(runnableCaptor.capture());
        runnableCaptor.getValue().run();

        ArgumentCaptor<Binder> dreamTokenCaptor = ArgumentCaptor.forClass(Binder.class);
        verify(mDreamControllerMock)
                .startDream(
                        dreamTokenCaptor.capture(),
                        any(),
                        anyBoolean(),
                        anyBoolean(),
                        anyInt(),
                        any(),
                        any(),
                        any());
        return dreamTokenCaptor.getValue();
    }

    /**
     * Trigger battery change event so charging state is read.
     */
    private void sendBatteryChangeEvent() {
        ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor.forClass(
                BroadcastReceiver.class);
        verify(mContextSpy).registerReceiver(receiverCaptor.capture(),
                argThat((arg) -> arg.hasAction(Intent.ACTION_BATTERY_CHANGED)));
        receiverCaptor.getValue().onReceive(mContextSpy, new Intent());
    }

    @Test
@@ -176,11 +249,7 @@ public class DreamManagerServiceTest {
        service.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);

        // Battery changed event is received.
        ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor.forClass(
                BroadcastReceiver.class);
        verify(mContextSpy).registerReceiver(receiverCaptor.capture(),
                argThat((arg) -> arg.hasAction(Intent.ACTION_BATTERY_CHANGED)));
        receiverCaptor.getValue().onReceive(mContext, new Intent());
        sendBatteryChangeEvent();

        // Can start dreaming is true.
        assertThat(service.canStartDreamingInternal(/*isScreenOn=*/ true)).isTrue();
@@ -207,16 +276,52 @@ public class DreamManagerServiceTest {
        service.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);

        // Battery changed event is received.
        ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor.forClass(
                BroadcastReceiver.class);
        verify(mContextSpy).registerReceiver(receiverCaptor.capture(),
                argThat((arg) -> arg.hasAction(Intent.ACTION_BATTERY_CHANGED)));
        receiverCaptor.getValue().onReceive(mContext, new Intent());
        sendBatteryChangeEvent();

        // Can start dreaming is true.
        assertThat(service.canStartDreamingInternal(/*isScreenOn=*/ true)).isFalse();
    }

    @EnableFlags(FLAG_WAKE_ON_STOPPING_DOZE)
    @Test
    public void testStopDream_sendsWakeIfDozing() throws PackageManager.NameNotFoundException {
        // Enable dreaming while charging only.
        Settings.Secure.putIntForUser(mContextSpy.getContentResolver(),
                Settings.Secure.SCREENSAVER_ENABLED, 1, UserHandle.USER_CURRENT);
        Settings.Secure.putIntForUser(mContextSpy.getContentResolver(),
                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1, UserHandle.USER_CURRENT);

        // Set up preconditions.
        ServiceInfo dozeServiceInfo = new ServiceInfo();
        dozeServiceInfo.applicationInfo = new ApplicationInfo();
        when(mUserManagerMock.isUserUnlocked()).thenReturn(true);
        when(mDozeConfigMock.enabled(anyInt())).thenReturn(true);
        when(mPackageManagerMock.getServiceInfo(any(), anyInt())).thenReturn(dozeServiceInfo);

        // Device is charging.
        when(mBatteryManagerInternal.isPowered(anyInt())).thenReturn(true);

        // Initialize service so settings are read.
        final DreamManagerService service = createService();
        service.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);

        // Battery changed event is received.
        sendBatteryChangeEvent();

        // Start dream.
        final Binder dreamToken = startDream(service);

        // Start dozing.
        service.startDozingInternal(dreamToken, 0, 0, 0f, false);

        // Stop dreaming.
        service.stopDreamInternal(true, "testing");

        // wakeUp is sent.
        verify(mPowerManagerMock)
                .wakeUp(anyLong(), eq(PowerManager.WAKE_REASON_DOZE_STOPPED), any());
    }

    @Test
    public void testDreamConditionActive_onDock() {
        // Enable dreaming on dock.
@@ -277,11 +382,7 @@ public class DreamManagerServiceTest {
        service.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);

        // Battery changed event is received.
        ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor.forClass(
                BroadcastReceiver.class);
        verify(mContextSpy).registerReceiver(receiverCaptor.capture(),
                argThat((arg) -> arg.hasAction(Intent.ACTION_BATTERY_CHANGED)));
        receiverCaptor.getValue().onReceive(mContext, new Intent());
        sendBatteryChangeEvent();

        // Dream condition is active.
        assertThat(service.dreamConditionActiveInternal()).isTrue();
@@ -361,4 +462,39 @@ public class DreamManagerServiceTest {
        // Dream condition is active.
        assertThat(service.dreamConditionActiveInternal()).isTrue();
    }

    private static final class TestInjector implements DreamManagerService.Injector {
        private final Context mContext;
        private final Handler mHandler;
        private final DreamController mDreamController;
        private final AmbientDisplayConfiguration mDozeConfig;

        TestInjector(Context context, Handler handler, DreamController dreamController,
                AmbientDisplayConfiguration dozeConfig) {
            mContext = context;
            mHandler = handler;
            mDreamController = dreamController;
            mDozeConfig = dozeConfig;
        }

        @Override
        public Context getContext() {
            return mContext;
        }

        @Override
        public Handler getHandler() {
            return mHandler;
        }

        @Override
        public AmbientDisplayConfiguration getDozeConfig() {
            return mDozeConfig;
        }

        @Override
        public DreamController getDreamController(DreamController.Listener controllerListener) {
            return mDreamController;
        }
    }
}