Merge "Add new power button behavior that asks keyguard to show the hub" into main
diff --git a/core/java/android/service/dreams/DreamManagerInternal.java b/core/java/android/service/dreams/DreamManagerInternal.java
index e9bb28c..ad022c5 100644
--- a/core/java/android/service/dreams/DreamManagerInternal.java
+++ b/core/java/android/service/dreams/DreamManagerInternal.java
@@ -60,6 +60,12 @@
public abstract boolean canStartDreaming(boolean isScreenOn);
/**
+ * Whether or not the device is currently in the user's "when to dream" state, ex.
+ * docked & charging.
+ */
+ public abstract boolean dreamConditionActive();
+
+ /**
* Register a {@link DreamManagerStateListener}, which will be called when there are changes to
* dream state.
*
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9f731fe..8db94a4 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1214,6 +1214,9 @@
3 - Really go to sleep and go home (don't doze)
4 - Go to home
5 - Dismiss IME if shown. Otherwise go to home
+ 6 - Lock if keyguard enabled or go to sleep (doze)
+ 7 - Dream if possible or go to sleep (doze)
+ 8 - Go to glanceable hub or dream if possible, or sleep if neither is available (doze)
-->
<integer name="config_shortPressOnPowerBehavior">1</integer>
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index c0e266f..4c6a1ba 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -177,7 +177,7 @@
VALIDATORS.put(Global.REQUIRE_PASSWORD_TO_DECRYPT, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.DEVICE_DEMO_MODE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.AWARE_ALLOWED, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Global.POWER_BUTTON_SHORT_PRESS, new InclusiveIntegerRangeValidator(0, 7));
+ VALIDATORS.put(Global.POWER_BUTTON_SHORT_PRESS, new InclusiveIntegerRangeValidator(0, 8));
VALIDATORS.put(Global.POWER_BUTTON_DOUBLE_PRESS, new InclusiveIntegerRangeValidator(0, 3));
VALIDATORS.put(Global.POWER_BUTTON_TRIPLE_PRESS, new InclusiveIntegerRangeValidator(0, 3));
VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS, new InclusiveIntegerRangeValidator(0, 5));
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
index 11b7e9d..a7c078f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -47,7 +47,7 @@
constructor(
configurationInteractor: ConfigurationInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
- fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel,
+ fromGlanceableHubTransitionViewModel: GlanceableHubToDreamingTransitionViewModel,
toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor,
@@ -74,7 +74,7 @@
val dreamOverlayTranslationX: Flow<Float> =
merge(
toGlanceableHubTransitionViewModel.dreamOverlayTranslationX,
- fromGlanceableHubTransitionInteractor.dreamOverlayTranslationX,
+ fromGlanceableHubTransitionViewModel.dreamOverlayTranslationX,
)
.distinctUntilChanged()
@@ -97,7 +97,7 @@
merge(
toLockscreenTransitionViewModel.dreamOverlayAlpha,
toGlanceableHubTransitionViewModel.dreamOverlayAlpha,
- fromGlanceableHubTransitionInteractor.dreamOverlayAlpha,
+ fromGlanceableHubTransitionViewModel.dreamOverlayAlpha,
)
.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 372fdca..efa9c21 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -118,6 +118,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.internal.policy.IKeyguardExitCallback;
+import com.android.internal.policy.IKeyguardService;
import com.android.internal.policy.IKeyguardStateCallback;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.statusbar.IStatusBarService;
@@ -139,6 +140,7 @@
import com.android.systemui.animation.TransitionAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -256,6 +258,15 @@
private static final String DELAYED_LOCK_PROFILE_ACTION =
"com.android.internal.policy.impl.PhoneWindowManager.DELAYED_LOCK";
+ /**
+ * String extra key passed in the bundle of {@link IKeyguardService#doKeyguardTimeout(Bundle)}
+ * if the value is {@code true}, indicates to keyguard that the device should show the
+ * glanceable hub upon locking. If the hub is already visible, the device should go to sleep.
+ *
+ * Mirrored from PhoneWindowManager which sends the extra.
+ */
+ public static final String EXTRA_TRIGGER_HUB = "extra_trigger_hub";
+
private static final String SYSTEMUI_PERMISSION = "com.android.systemui.permission.SELF";
// used for handler messages
@@ -354,6 +365,7 @@
private final ScreenOffAnimationController mScreenOffAnimationController;
private final Lazy<NotificationShadeDepthController> mNotificationShadeDepthController;
private final Lazy<ShadeController> mShadeController;
+ private final Lazy<CommunalSceneInteractor> mCommunalSceneInteractor;
/*
* Records the user id on request to go away, for validation when WM calls back to start the
* exit animation.
@@ -1556,6 +1568,7 @@
SelectedUserInteractor selectedUserInteractor,
KeyguardInteractor keyguardInteractor,
KeyguardTransitionBootInteractor transitionBootInteractor,
+ Lazy<CommunalSceneInteractor> communalSceneInteractor,
WindowManagerOcclusionManager wmOcclusionManager) {
mContext = context;
mUserTracker = userTracker;
@@ -1597,6 +1610,7 @@
mSelectedUserInteractor = selectedUserInteractor;
mKeyguardInteractor = keyguardInteractor;
mTransitionBootInteractor = transitionBootInteractor;
+ mCommunalSceneInteractor = communalSceneInteractor;
mStatusBarStateController = statusBarStateController;
statusBarStateController.addCallback(this);
@@ -2411,6 +2425,13 @@
* Enable the keyguard if the settings are appropriate.
*/
private void doKeyguardLocked(Bundle options) {
+ // If the power button behavior requests to open the glanceable hub.
+ if (options != null && options.getBoolean(EXTRA_TRIGGER_HUB)) {
+ // Set the hub to show immediately when the SysUI window shows, then continue to lock
+ // the device.
+ mCommunalSceneInteractor.get().showHubFromPowerButton();
+ }
+
int currentUserId = mSelectedUserInteractor.getSelectedUserId();
if (options != null && options.getBinder(LOCK_ON_USER_SWITCH_CALLBACK) != null) {
LockNowCallback callback = new LockNowCallback(currentUserId,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 908413d..97ad2d7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -41,6 +41,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingModule;
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
@@ -180,6 +181,7 @@
SelectedUserInteractor selectedUserInteractor,
KeyguardInteractor keyguardInteractor,
KeyguardTransitionBootInteractor transitionBootInteractor,
+ Lazy<CommunalSceneInteractor> communalSceneInteractor,
WindowManagerOcclusionManager windowManagerOcclusionManager) {
return new KeyguardViewMediator(
context,
@@ -231,6 +233,7 @@
selectedUserInteractor,
keyguardInteractor,
transitionBootInteractor,
+ communalSceneInteractor,
windowManagerOcclusionManager);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 0c9213c..e8054c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -1502,6 +1502,7 @@
mSelectedUserInteractor,
mKeyguardInteractor,
mKeyguardTransitionBootInteractor,
+ mKosmos::getCommunalSceneInteractor,
mock(WindowManagerOcclusionManager.class));
mViewMediator.mUserChangedCallback = mUserTrackerCallback;
mViewMediator.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTestKt.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTestKt.kt
new file mode 100644
index 0000000..86f7966
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTestKt.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard
+
+import android.app.IActivityTaskManager
+import android.internal.statusbar.statusBarService
+import android.os.Bundle
+import android.os.PowerManager
+import android.os.powerManager
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLogger
+import com.android.internal.widget.lockPatternUtils
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.keyguardUnlockAnimationController
+import com.android.keyguard.mediator.ScreenOnCoordinator
+import com.android.keyguard.trustManager
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.activityTransitionAnimator
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.communal.data.repository.communalSceneRepository
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.dreams.DreamOverlayStateController
+import com.android.systemui.dreams.ui.viewmodel.dreamViewModel
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.flags.systemPropertiesHelper
+import com.android.systemui.jank.interactionJankMonitor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionBootInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundScope
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.log.sessionTracker
+import com.android.systemui.navigationbar.navigationModeController
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.process.processWrapper
+import com.android.systemui.settings.userTracker
+import com.android.systemui.shade.shadeController
+import com.android.systemui.statusbar.notificationShadeDepthController
+import com.android.systemui.statusbar.notificationShadeWindowController
+import com.android.systemui.statusbar.phone.dozeParameters
+import com.android.systemui.statusbar.phone.screenOffAnimationController
+import com.android.systemui.statusbar.phone.scrimController
+import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
+import com.android.systemui.statusbar.policy.keyguardStateController
+import com.android.systemui.statusbar.policy.userSwitcherController
+import com.android.systemui.testKosmos
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+import com.android.systemui.util.DeviceConfigProxy
+import com.android.systemui.util.kotlin.JavaAdapter
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.util.time.systemClock
+import com.android.systemui.wallpapers.data.repository.wallpaperRepository
+import com.android.wm.shell.keyguard.KeyguardTransitions
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+
+/** Kotlin version of KeyguardViewMediatorTest to allow for coroutine testing. */
+@SmallTest
+@RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidTestingRunner::class)
+class KeyguardViewMediatorTestKt : SysuiTestCase() {
+ private val kosmos =
+ testKosmos().useUnconfinedTestDispatcher().also {
+ it.powerManager =
+ mock<PowerManager> {
+ on { newWakeLock(anyInt(), any()) } doReturn mock<PowerManager.WakeLock>()
+ }
+ }
+
+ private lateinit var testableLooper: TestableLooper
+
+ private val Kosmos.underTest by
+ Kosmos.Fixture {
+ KeyguardViewMediator(
+ mContext,
+ uiEventLogger,
+ sessionTracker,
+ userTracker,
+ falsingCollector,
+ lockPatternUtils,
+ broadcastDispatcher,
+ { statusBarKeyguardViewManager },
+ dismissCallbackRegistry,
+ mock<KeyguardUpdateMonitor>(),
+ dumpManager,
+ fakeExecutor,
+ powerManager,
+ trustManager,
+ userSwitcherController,
+ DeviceConfigProxy(),
+ navigationModeController,
+ keyguardDisplayManager,
+ dozeParameters,
+ statusBarStateController,
+ keyguardStateController,
+ { keyguardUnlockAnimationController },
+ screenOffAnimationController,
+ { notificationShadeDepthController },
+ mock<ScreenOnCoordinator>(),
+ mock<KeyguardTransitions>(),
+ interactionJankMonitor,
+ mock<DreamOverlayStateController>(),
+ JavaAdapter(backgroundScope),
+ wallpaperRepository,
+ { shadeController },
+ { notificationShadeWindowController },
+ { activityTransitionAnimator },
+ { scrimController },
+ mock<IActivityTaskManager>(),
+ statusBarService,
+ featureFlagsClassic,
+ fakeSettings,
+ fakeSettings,
+ systemClock,
+ processWrapper,
+ testDispatcher,
+ { dreamViewModel },
+ { communalTransitionViewModel },
+ systemPropertiesHelper,
+ { mock<WindowManagerLockscreenVisibilityManager>() },
+ selectedUserInteractor,
+ keyguardInteractor,
+ keyguardTransitionBootInteractor,
+ { communalSceneInteractor },
+ mock<WindowManagerOcclusionManager>(),
+ )
+ }
+
+ @Before
+ fun setUp() {
+ testableLooper = TestableLooper.get(this)
+ }
+
+ @Test
+ fun doKeyguardTimeout_changesCommunalScene() =
+ kosmos.runTest {
+ // doKeyguardTimeout message received.
+ val timeoutOptions = Bundle()
+ timeoutOptions.putBoolean(KeyguardViewMediator.EXTRA_TRIGGER_HUB, true)
+ underTest.doKeyguardTimeout(timeoutOptions)
+ testableLooper.processAllMessages()
+
+ // Hub scene is triggered.
+ assertThat(communalSceneRepository.currentScene.value)
+ .isEqualTo(CommunalScenes.Communal)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModelKosmos.kt
new file mode 100644
index 0000000..de36493
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModelKosmos.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dreams.ui.viewmodel
+
+import com.android.keyguard.keyguardUpdateMonitor
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.keyguard.domain.interactor.fromDreamingTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.viewmodel.dreamingToGlanceableHubTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToDreamingTransitionViewModel
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.settings.userTracker
+
+val Kosmos.dreamViewModel by
+ Kosmos.Fixture {
+ DreamViewModel(
+ communalInteractor = communalInteractor,
+ communalSettingsInteractor = communalSettingsInteractor,
+ configurationInteractor = configurationInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
+ fromGlanceableHubTransitionViewModel = glanceableHubToDreamingTransitionViewModel,
+ toGlanceableHubTransitionViewModel = dreamingToGlanceableHubTransitionViewModel,
+ toLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
+ fromDreamingTransitionInteractor = fromDreamingTransitionInteractor,
+ keyguardUpdateMonitor = keyguardUpdateMonitor,
+ userTracker = userTracker,
+ dumpManager = dumpManager,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/KeyguardDisplayManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/KeyguardDisplayManagerKosmos.kt
new file mode 100644
index 0000000..1e46d48
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/KeyguardDisplayManagerKosmos.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard
+
+import android.content.testableContext
+import com.android.keyguard.ConnectedDisplayKeyguardPresentation
+import com.android.keyguard.KeyguardDisplayManager
+import com.android.keyguard.KeyguardDisplayManager.DeviceStateHelper
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.navigationbar.navigationBarController
+import com.android.systemui.settings.displayTracker
+import com.android.systemui.shade.data.repository.shadeDisplaysRepository
+import com.android.systemui.statusbar.policy.keyguardStateController
+import org.mockito.kotlin.mock
+
+var Kosmos.keyguardDisplayManager by
+ Kosmos.Fixture {
+ KeyguardDisplayManager(
+ testableContext,
+ { navigationBarController },
+ displayTracker,
+ fakeExecutor,
+ fakeExecutor,
+ mock<DeviceStateHelper>(),
+ keyguardStateController,
+ mock<ConnectedDisplayKeyguardPresentation.Factory>(),
+ { shadeDisplaysRepository },
+ applicationCoroutineScope,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/UserSwitcherControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/UserSwitcherControllerKosmos.kt
new file mode 100644
index 0000000..51168d6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/UserSwitcherControllerKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.content.applicationContext
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.user.domain.interactor.guestUserInteractor
+import com.android.systemui.user.domain.interactor.userSwitcherInteractor
+
+val Kosmos.userSwitcherController by Fixture {
+ UserSwitcherController(
+ applicationContext = applicationContext,
+ userSwitcherInteractorLazy = { userSwitcherInteractor },
+ guestUserInteractorLazy = { guestUserInteractor },
+ keyguardInteractorLazy = { keyguardInteractor },
+ activityStarter = activityStarter,
+ )
+}
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 67b1ec3..7e8bb28 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -495,6 +495,34 @@
}
}
+ @VisibleForTesting
+ boolean dreamConditionActiveInternal() {
+ synchronized (mLock) {
+ return dreamConditionActiveInternalLocked();
+ }
+ }
+
+ private boolean dreamConditionActiveInternalLocked() {
+ if ((mWhenToDream & DREAM_ON_CHARGE) == DREAM_ON_CHARGE) {
+ return mIsCharging;
+ }
+
+ if ((mWhenToDream & DREAM_ON_DOCK) == DREAM_ON_DOCK) {
+ return mIsDocked;
+ }
+
+ if ((mWhenToDream & DREAM_ON_POSTURED) == DREAM_ON_POSTURED) {
+ return mIsPostured;
+ }
+
+ return false;
+ }
+
+ @VisibleForTesting
+ boolean dreamsEnabled() {
+ return mDreamsEnabledSetting;
+ }
+
/** Whether dreaming can start given user settings and the current dock/charge state. */
private boolean canStartDreamingInternal(boolean isScreenOn) {
synchronized (mLock) {
@@ -524,19 +552,9 @@
return false;
}
- if ((mWhenToDream & DREAM_ON_CHARGE) == DREAM_ON_CHARGE) {
- return mIsCharging;
- }
-
- if ((mWhenToDream & DREAM_ON_DOCK) == DREAM_ON_DOCK) {
- return mIsDocked;
- }
-
- if ((mWhenToDream & DREAM_ON_POSTURED) == DREAM_ON_POSTURED) {
- return mIsPostured;
- }
-
- return false;
+ // All dream prerequisites fulfilled, check if device state matches "when to dream"
+ // setting.
+ return dreamConditionActiveInternalLocked();
}
}
@@ -674,7 +692,8 @@
}
}
- private void setDevicePosturedInternal(boolean isPostured) {
+ @VisibleForTesting
+ void setDevicePosturedInternal(boolean isPostured) {
Slog.d(TAG, "Device postured: " + isPostured);
synchronized (mLock) {
mIsPostured = isPostured;
@@ -1390,6 +1409,11 @@
}
@Override
+ public boolean dreamConditionActive() {
+ return dreamConditionActiveInternal();
+ }
+
+ @Override
public void requestDream() {
requestDreamInternal();
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 4860b7cd..0aaa0fe 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -231,6 +231,7 @@
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.os.RoSystemProperties;
import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.internal.policy.IKeyguardService;
import com.android.internal.policy.IShortcutService;
import com.android.internal.policy.KeyInterceptionInfo;
import com.android.internal.policy.LogDecelerateInterpolator;
@@ -309,6 +310,7 @@
static final int SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME = 5;
static final int SHORT_PRESS_POWER_LOCK_OR_SLEEP = 6;
static final int SHORT_PRESS_POWER_DREAM_OR_SLEEP = 7;
+ static final int SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP = 8;
// must match: config_LongPressOnPowerBehavior in config.xml
// The config value can be overridden using Settings.Global.POWER_BUTTON_LONG_PRESS
@@ -403,6 +405,13 @@
public static final String TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD = "waitForAllWindowsDrawn";
+ /**
+ * String extra key passed in the bundle of {@link IKeyguardService#doKeyguardTimeout(Bundle)}
+ * if the value is {@code true}, indicates to keyguard that the device should show the
+ * glanceable hub upon locking. If the hub is already visible, the device should go to sleep.
+ */
+ public static final String EXTRA_TRIGGER_HUB = "extra_trigger_hub";
+
private static final int POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS = 800;
/**
@@ -1154,7 +1163,8 @@
}
}
- private void powerPress(long eventTime, int count, int displayId) {
+ @VisibleForTesting
+ void powerPress(long eventTime, int count, int displayId) {
// SideFPS still needs to know about suppressed power buttons, in case it needs to block
// an auth attempt.
if (count == 1) {
@@ -1229,6 +1239,43 @@
() -> sleepDefaultDisplayFromPowerButton(eventTime, 0));
break;
}
+ case SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP: {
+ // With this power button behavior, the following behavior is expected from each
+ // system space on a power button short press:
+ // - Unlocked: go to hub if available, dream if not, screen off if neither
+ // - Lock screen, hub, or dream: go to screen off
+ // - Screen off: go to hub if available, dream if not, lock screen if enabled,
+ // unlocked if lockscreen is disabled
+ // TODO(b/394657933): consolidate policy into SysUI
+ final boolean hubEnabled = Settings.Secure.getIntForUser(
+ mContext.getContentResolver(), Settings.Secure.GLANCEABLE_HUB_ENABLED,
+ 1, mCurrentUserId) == 1;
+
+ if (mDreamManagerInternal.isDreaming() || isKeyguardShowing()) {
+ // If the device is already dreaming or on keyguard, go to sleep.
+ sleepDefaultDisplayFromPowerButton(eventTime, 0);
+ break;
+ }
+
+ // Check isLockScreenDisabled to exclude NONE lock screen option, which cannot
+ // show hub.
+ boolean keyguardAvailable = !mLockPatternUtils.isLockScreenDisabled(
+ mCurrentUserId);
+ if (mUserManagerInternal.isUserUnlocked(mCurrentUserId) && hubEnabled
+ && keyguardAvailable && mDreamManagerInternal.dreamConditionActive()) {
+ // If the hub can be launched, send a message to keyguard.
+ Bundle options = new Bundle();
+ options.putBoolean(EXTRA_TRIGGER_HUB, true);
+ lockNow(options);
+ } else {
+ // If the hub cannot be run, attempt to dream instead.
+ attemptToDreamFromShortPowerButtonPress(
+ /* isScreenOn */ true,
+ /* noDreamAction */
+ () -> sleepDefaultDisplayFromPowerButton(eventTime, 0));
+ }
+ break;
+ }
}
}
}
@@ -1279,7 +1326,10 @@
*/
private void attemptToDreamFromShortPowerButtonPress(
boolean isScreenOn, Runnable noDreamAction) {
- if (mShortPressOnPowerBehavior != SHORT_PRESS_POWER_DREAM_OR_SLEEP) {
+ if (mShortPressOnPowerBehavior != SHORT_PRESS_POWER_DREAM_OR_SLEEP
+ && mShortPressOnPowerBehavior != SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP) {
+ // If the power button behavior isn't one that should be able to trigger the dream, give
+ // up.
noDreamAction.run();
return;
}
@@ -5132,8 +5182,7 @@
}
/**
- * Updates the occluded state of the Keyguard immediately via
- * {@link com.android.internal.policy.IKeyguardService}.
+ * Updates the occluded state of the Keyguard immediately via {@link IKeyguardService}.
*
* @param isOccluded Whether the Keyguard is occluded by another window.
* @return Whether the flags have changed and we have to redo the layout.
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java
deleted file mode 100644
index 992b853..0000000
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceMockingTest.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.dreams;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManagerInternal;
-import android.content.ContextWrapper;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.os.PowerManagerInternal;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.internal.util.test.LocalServiceKeeperRule;
-import com.android.server.SystemService;
-import com.android.server.testutils.TestHandler;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.MockitoSession;
-import org.mockito.quality.Strictness;
-
-/**
- * Collection of tests for exercising the {@link DreamManagerService} lifecycle.
- */
-public class DreamManagerServiceMockingTest {
- private ContextWrapper mContextSpy;
- private Resources mResourcesSpy;
-
- @Mock
- private ActivityManagerInternal mActivityManagerInternalMock;
-
- @Mock
- private PowerManagerInternal mPowerManagerInternalMock;
-
- @Mock
- private UserManager mUserManagerMock;
-
- @Rule
- public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule();
-
- private TestHandler mTestHandler;
- private MockitoSession mMockitoSession;
-
- @Before
- public void setUp() throws Exception {
- mTestHandler = new TestHandler(/* callback= */ null);
- MockitoAnnotations.initMocks(this);
- mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
- mResourcesSpy = spy(mContextSpy.getResources());
- when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
-
- mLocalServiceKeeperRule.overrideLocalService(
- ActivityManagerInternal.class, mActivityManagerInternalMock);
- mLocalServiceKeeperRule.overrideLocalService(
- PowerManagerInternal.class, mPowerManagerInternalMock);
-
- when(mContextSpy.getSystemService(UserManager.class)).thenReturn(mUserManagerMock);
- mMockitoSession = mockitoSession()
- .initMocks(this)
- .strictness(Strictness.LENIENT)
- .mockStatic(Settings.Secure.class)
- .startMocking();
- }
-
- @After
- public void tearDown() throws Exception {
- mMockitoSession.finishMocking();
- }
-
- private DreamManagerService createService() {
- return new DreamManagerService(mContextSpy, mTestHandler);
- }
-
- @Test
- public void testSettingsQueryUserChange() {
- final DreamManagerService service = createService();
- final SystemService.TargetUser from =
- new SystemService.TargetUser(mock(UserInfo.class));
- final SystemService.TargetUser to =
- new SystemService.TargetUser(mock(UserInfo.class));
- service.onUserSwitching(from, to);
- verify(() -> Settings.Secure.getIntForUser(any(),
- eq(Settings.Secure.SCREENSAVER_ENABLED),
- anyInt(),
- eq(UserHandle.USER_CURRENT)));
- }
-}
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceTest.java
new file mode 100644
index 0000000..4efc258
--- /dev/null
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamManagerServiceTest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.dreams;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManagerInternal;
+import android.content.BroadcastReceiver;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.os.BatteryManager;
+import android.os.BatteryManagerInternal;
+import android.os.PowerManagerInternal;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.testing.TestableContext;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+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 org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Test class for {@link DreamManagerService}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DreamManagerServiceTest {
+ private ContextWrapper mContextSpy;
+
+ @Mock
+ private ActivityManagerInternal mActivityManagerInternalMock;
+ @Mock
+ private BatteryManagerInternal mBatteryManagerInternal;
+
+ @Mock
+ private InputManagerInternal mInputManagerInternal;
+ @Mock
+ private PowerManagerInternal mPowerManagerInternalMock;
+
+ @Mock
+ private BatteryManager mBatteryManager;
+ @Mock
+ private UserManager mUserManagerMock;
+
+ @Rule
+ public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule();
+
+
+ @Rule
+ public final TestableContext mContext = new TestableContext(
+ getInstrumentation().getContext());
+
+ private TestHandler mTestHandler;
+
+ @Before
+ public void setUp() throws Exception {
+ mTestHandler = new TestHandler(/* callback= */ null);
+ MockitoAnnotations.initMocks(this);
+ mContextSpy = spy(mContext);
+
+ mLocalServiceKeeperRule.overrideLocalService(
+ ActivityManagerInternal.class, mActivityManagerInternalMock);
+ mLocalServiceKeeperRule.overrideLocalService(
+ BatteryManagerInternal.class, mBatteryManagerInternal);
+ mLocalServiceKeeperRule.overrideLocalService(
+ InputManagerInternal.class, mInputManagerInternal);
+ mLocalServiceKeeperRule.overrideLocalService(
+ PowerManagerInternal.class, mPowerManagerInternalMock);
+
+ when(mContextSpy.getSystemService(BatteryManager.class)).thenReturn(mBatteryManager);
+ when(mContextSpy.getSystemService(UserManager.class)).thenReturn(mUserManagerMock);
+ }
+
+ private DreamManagerService createService() {
+ return new DreamManagerService(mContextSpy, mTestHandler);
+ }
+
+ @Test
+ public void testSettingsQueryUserChange() {
+ // Enable dreams.
+ Settings.Secure.putIntForUser(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ENABLED, 1,
+ UserHandle.USER_CURRENT);
+
+ // Initialize dream service so settings are read.
+ final DreamManagerService service = createService();
+ service.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
+
+ // Dreams are enabled.
+ assertThat(service.dreamsEnabled()).isTrue();
+
+ // Disable dreams.
+ Settings.Secure.putIntForUser(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ENABLED, 0,
+ UserHandle.USER_CURRENT);
+
+ // Switch users, dreams are disabled.
+ service.onUserSwitching(null, null);
+ assertThat(service.dreamsEnabled()).isFalse();
+ }
+
+ @Test
+ public void testDreamConditionActive_onDock() {
+ // Enable dreaming on dock.
+ Settings.Secure.putIntForUser(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, 1,
+ UserHandle.USER_CURRENT);
+
+ // Initialize service so settings are read.
+ final DreamManagerService service = createService();
+ service.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
+ assertThat(service.dreamConditionActiveInternal()).isFalse();
+
+ // Dock event receiver is registered.
+ ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor.forClass(
+ BroadcastReceiver.class);
+ verify(mContextSpy).registerReceiver(receiverCaptor.capture(),
+ argThat((arg) -> arg.hasAction(Intent.ACTION_DOCK_EVENT)));
+
+ // Device is docked.
+ Intent dockIntent = new Intent(Intent.ACTION_DOCK_EVENT);
+ dockIntent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_HE_DESK);
+ receiverCaptor.getValue().onReceive(null, dockIntent);
+
+ // Dream condition is active.
+ assertThat(service.dreamConditionActiveInternal()).isTrue();
+ }
+
+ @Test
+ public void testDreamConditionActive_postured() {
+ // Enable dreaming while postured.
+ Settings.Secure.putIntForUser(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, 0,
+ UserHandle.USER_CURRENT);
+ Settings.Secure.putIntForUser(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, 1,
+ UserHandle.USER_CURRENT);
+
+ // Initialize service so settings are read.
+ final DreamManagerService service = createService();
+ service.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
+ assertThat(service.dreamConditionActiveInternal()).isFalse();
+
+ // Device is postured.
+ service.setDevicePosturedInternal(true);
+
+ // Dream condition is active.
+ assertThat(service.dreamConditionActiveInternal()).isTrue();
+ }
+
+ @Test
+ public void testDreamConditionActive_charging() {
+ // Enable dreaming while charging only.
+ Settings.Secure.putIntForUser(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1,
+ UserHandle.USER_CURRENT);
+
+ // Device is charging.
+ when(mBatteryManager.isCharging()).thenReturn(true);
+
+ // Initialize service so settings are read.
+ final DreamManagerService service = createService();
+ service.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
+
+ // Dream condition is active.
+ assertThat(service.dreamConditionActiveInternal()).isTrue();
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
index c73ce23..32a3b7f 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
@@ -16,6 +16,7 @@
package com.android.server.policy;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
@@ -33,6 +34,8 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.policy.PhoneWindowManager.EXTRA_TRIGGER_HUB;
+import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP;
import static com.google.common.truth.Truth.assertThat;
@@ -47,13 +50,20 @@
import android.app.AppOpsManager;
import android.content.Context;
import android.hardware.input.InputManager;
+import android.os.Bundle;
import android.os.PowerManager;
+import android.os.PowerManagerInternal;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
+import android.service.dreams.DreamManagerInternal;
+import android.testing.TestableContext;
+import android.view.contentprotection.flags.Flags;
import androidx.test.filters.SmallTest;
-import com.android.server.LocalServices;
+import com.android.internal.util.test.LocalServiceKeeperRule;
+import com.android.server.input.InputManagerInternal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.policy.keyguard.KeyguardServiceDelegate;
import com.android.server.statusbar.StatusBarManagerInternal;
@@ -66,6 +76,9 @@
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
/**
* Test class for {@link PhoneWindowManager}.
@@ -76,28 +89,62 @@
@Presubmit
@SmallTest
public class PhoneWindowManagerTests {
-
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
+ public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule();
+
+ @Rule
+ public final TestableContext mContext = spy(
+ new TestableContext(getInstrumentation().getContext()));
+
PhoneWindowManager mPhoneWindowManager;
+ @Mock
private ActivityTaskManagerInternal mAtmInternal;
+ @Mock
+ private DreamManagerInternal mDreamManagerInternal;
+ @Mock
+ private InputManagerInternal mInputManagerInternal;
+ @Mock
+ private PowerManagerInternal mPowerManagerInternal;
+ @Mock
private StatusBarManagerInternal mStatusBarManagerInternal;
- private Context mContext;
+ @Mock
+ private UserManagerInternal mUserManagerInternal;
+
+ @Mock
+ private PowerManager mPowerManager;
+ @Mock
+ private DisplayPolicy mDisplayPolicy;
+ @Mock
+ private KeyguardServiceDelegate mKeyguardServiceDelegate;
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
+
mPhoneWindowManager = spy(new PhoneWindowManager());
spyOn(ActivityManager.getService());
- mContext = getInstrumentation().getTargetContext();
- spyOn(mContext);
- mAtmInternal = mock(ActivityTaskManagerInternal.class);
- LocalServices.addService(ActivityTaskManagerInternal.class, mAtmInternal);
+
+ mLocalServiceKeeperRule.overrideLocalService(ActivityTaskManagerInternal.class,
+ mAtmInternal);
mPhoneWindowManager.mActivityTaskManagerInternal = mAtmInternal;
- LocalServices.addService(WindowManagerInternal.class, mock(WindowManagerInternal.class));
- mStatusBarManagerInternal = mock(StatusBarManagerInternal.class);
- LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal);
- mPhoneWindowManager.mKeyguardDelegate = mock(KeyguardServiceDelegate.class);
+ mLocalServiceKeeperRule.overrideLocalService(DreamManagerInternal.class,
+ mDreamManagerInternal);
+ mLocalServiceKeeperRule.overrideLocalService(InputManagerInternal.class,
+ mInputManagerInternal);
+ mLocalServiceKeeperRule.overrideLocalService(PowerManagerInternal.class,
+ mPowerManagerInternal);
+ mLocalServiceKeeperRule.overrideLocalService(StatusBarManagerInternal.class,
+ mStatusBarManagerInternal);
+ mLocalServiceKeeperRule.overrideLocalService(UserManagerInternal.class,
+ mUserManagerInternal);
+ mLocalServiceKeeperRule.overrideLocalService(WindowManagerInternal.class,
+ mock(WindowManagerInternal.class));
+
+ mPhoneWindowManager.mKeyguardDelegate = mKeyguardServiceDelegate;
final InputManager im = mock(InputManager.class);
doNothing().when(im).registerKeyGestureEventHandler(any());
doReturn(im).when(mContext).getSystemService(eq(Context.INPUT_SERVICE));
@@ -107,9 +154,6 @@
public void tearDown() {
reset(ActivityManager.getService());
reset(mContext);
- LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
- LocalServices.removeServiceForTest(WindowManagerInternal.class);
- LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
}
@Test
@@ -138,28 +182,20 @@
public void testScreenTurnedOff() {
doNothing().when(mPhoneWindowManager).updateSettings(any());
doNothing().when(mPhoneWindowManager).initializeHdmiState();
- final boolean[] isScreenTurnedOff = { false };
- final DisplayPolicy displayPolicy = mock(DisplayPolicy.class);
- doAnswer(invocation -> isScreenTurnedOff[0] = true).when(displayPolicy).screenTurnedOff(
+ final boolean[] isScreenTurnedOff = {false};
+ doAnswer(invocation -> isScreenTurnedOff[0] = true).when(mDisplayPolicy).screenTurnedOff(
anyBoolean());
- doAnswer(invocation -> !isScreenTurnedOff[0]).when(displayPolicy).isScreenOnEarly();
- doAnswer(invocation -> !isScreenTurnedOff[0]).when(displayPolicy).isScreenOnFully();
+ doAnswer(invocation -> !isScreenTurnedOff[0]).when(mDisplayPolicy).isScreenOnEarly();
+ doAnswer(invocation -> !isScreenTurnedOff[0]).when(mDisplayPolicy).isScreenOnFully();
- mPhoneWindowManager.mDefaultDisplayPolicy = displayPolicy;
- mPhoneWindowManager.mDefaultDisplayRotation = mock(DisplayRotation.class);
- final PowerManager pm = mock(PowerManager.class);
- doReturn(true).when(pm).isInteractive();
- doReturn(pm).when(mContext).getSystemService(eq(Context.POWER_SERVICE));
-
- mContext.getMainThreadHandler().runWithScissors(() -> mPhoneWindowManager.init(
- new PhoneWindowManager.Injector(mContext,
- mock(WindowManagerPolicy.WindowManagerFuncs.class))), 0);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ initPhoneWindowManager();
assertThat(isScreenTurnedOff[0]).isFalse();
assertThat(mPhoneWindowManager.mIsGoingToSleepDefaultDisplay).isFalse();
// Skip sleep-token for non-sleep-screen-off.
mPhoneWindowManager.screenTurnedOff(DEFAULT_DISPLAY, true /* isSwappingDisplay */);
- verify(displayPolicy).screenTurnedOff(false /* acquireSleepToken */);
+ verify(mDisplayPolicy).screenTurnedOff(false /* acquireSleepToken */);
assertThat(isScreenTurnedOff[0]).isTrue();
// Apply sleep-token for sleep-screen-off.
@@ -167,7 +203,7 @@
mPhoneWindowManager.startedGoingToSleep(DEFAULT_DISPLAY, 0 /* reason */);
assertThat(mPhoneWindowManager.mIsGoingToSleepDefaultDisplay).isTrue();
mPhoneWindowManager.screenTurnedOff(DEFAULT_DISPLAY, true /* isSwappingDisplay */);
- verify(displayPolicy).screenTurnedOff(true /* acquireSleepToken */);
+ verify(mDisplayPolicy).screenTurnedOff(true /* acquireSleepToken */);
mPhoneWindowManager.finishedGoingToSleep(DEFAULT_DISPLAY, 0 /* reason */);
assertThat(mPhoneWindowManager.mIsGoingToSleepDefaultDisplay).isFalse();
@@ -175,8 +211,7 @@
@Test
public void testCheckAddPermission_withoutAccessibilityOverlay_noAccessibilityAppOpLogged() {
- mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
- .FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
int[] outAppOp = new int[1];
assertEquals(ADD_OKAY, mPhoneWindowManager.checkAddPermission(TYPE_WALLPAPER,
/* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp, DEFAULT_DISPLAY));
@@ -185,8 +220,7 @@
@Test
public void testCheckAddPermission_withAccessibilityOverlay() {
- mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
- .FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
+ mSetFlagsRule.enableFlags(Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
int[] outAppOp = new int[1];
assertEquals(ADD_OKAY, mPhoneWindowManager.checkAddPermission(TYPE_ACCESSIBILITY_OVERLAY,
/* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp, DEFAULT_DISPLAY));
@@ -195,8 +229,7 @@
@Test
public void testCheckAddPermission_withAccessibilityOverlay_flagDisabled() {
- mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags
- .FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
+ mSetFlagsRule.disableFlags(Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
int[] outAppOp = new int[1];
assertEquals(ADD_OKAY, mPhoneWindowManager.checkAddPermission(TYPE_ACCESSIBILITY_OVERLAY,
/* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp, DEFAULT_DISPLAY));
@@ -217,10 +250,107 @@
verify(mStatusBarManagerInternal, never()).dismissKeyboardShortcutsMenu();
}
+ @Test
+ public void powerPress_hubOrDreamOrSleep_goesToSleepFromDream() {
+ when(mDisplayPolicy.isAwake()).thenReturn(true);
+ initPhoneWindowManager();
+
+ // Set power button behavior.
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.POWER_BUTTON_SHORT_PRESS, SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP);
+ mPhoneWindowManager.updateSettings(null);
+
+ // Device is dreaming.
+ when(mDreamManagerInternal.isDreaming()).thenReturn(true);
+
+ // Power button pressed.
+ int eventTime = 0;
+ mPhoneWindowManager.powerPress(eventTime, 1, 0);
+
+ // Device goes to sleep.
+ verify(mPowerManager).goToSleep(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0);
+ }
+
+ @Test
+ public void powerPress_hubOrDreamOrSleep_hubAvailableLocks() {
+ when(mDisplayPolicy.isAwake()).thenReturn(true);
+ mContext.getTestablePermissions().setPermission(android.Manifest.permission.DEVICE_POWER,
+ PERMISSION_GRANTED);
+ initPhoneWindowManager();
+
+ // Set power button behavior.
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.POWER_BUTTON_SHORT_PRESS, SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP);
+ mPhoneWindowManager.updateSettings(null);
+
+ // Set up hub prerequisites.
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.GLANCEABLE_HUB_ENABLED, 1);
+ when(mUserManagerInternal.isUserUnlocked(any(Integer.class))).thenReturn(true);
+ when(mDreamManagerInternal.dreamConditionActive()).thenReturn(true);
+
+ // Power button pressed.
+ int eventTime = 0;
+ mPhoneWindowManager.powerPress(eventTime, 1, 0);
+
+ // Lock requested with the proper bundle options.
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mPhoneWindowManager).lockNow(bundleCaptor.capture());
+ assertThat(bundleCaptor.getValue().getBoolean(EXTRA_TRIGGER_HUB)).isTrue();
+ }
+
+ @Test
+ public void powerPress_hubOrDreamOrSleep_hubNotAvailableDreams() {
+ when(mDisplayPolicy.isAwake()).thenReturn(true);
+ initPhoneWindowManager();
+
+ // Set power button behavior.
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.POWER_BUTTON_SHORT_PRESS, SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP);
+ mPhoneWindowManager.updateSettings(null);
+
+ // Hub is not available.
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.GLANCEABLE_HUB_ENABLED, 0);
+ when(mDreamManagerInternal.canStartDreaming(any(Boolean.class))).thenReturn(true);
+
+ // Power button pressed.
+ int eventTime = 0;
+ mPhoneWindowManager.powerPress(eventTime, 1, 0);
+
+ // Dream is requested.
+ verify(mDreamManagerInternal).requestDream();
+ }
+
+ private void initPhoneWindowManager() {
+ mPhoneWindowManager.mDefaultDisplayPolicy = mDisplayPolicy;
+ mPhoneWindowManager.mDefaultDisplayRotation = mock(DisplayRotation.class);
+ mContext.getMainThreadHandler().runWithScissors(() -> mPhoneWindowManager.init(
+ new TestInjector(mContext, mock(WindowManagerPolicy.WindowManagerFuncs.class))), 0);
+ }
+
private void mockStartDockOrHome() throws Exception {
doNothing().when(ActivityManager.getService()).stopAppSwitches();
when(mAtmInternal.startHomeOnDisplay(
anyInt(), anyString(), anyInt(), anyBoolean(), anyBoolean())).thenReturn(false);
mPhoneWindowManager.mUserManagerInternal = mock(UserManagerInternal.class);
}
+
+ private class TestInjector extends PhoneWindowManager.Injector {
+ TestInjector(Context context, WindowManagerPolicy.WindowManagerFuncs funcs) {
+ super(context, funcs);
+ }
+
+ KeyguardServiceDelegate getKeyguardServiceDelegate() {
+ return mKeyguardServiceDelegate;
+ }
+
+ /**
+ * {@code WindowWakeUpPolicy} registers a local service in its constructor, easier to just
+ * mock it out so we don't have to unregister it after every test.
+ */
+ WindowWakeUpPolicy getWindowWakeUpPolicy() {
+ return mock(WindowWakeUpPolicy.class);
+ }
+ }
}