diff options
16 files changed, 803 insertions, 176 deletions
diff --git a/core/java/android/service/dreams/DreamManagerInternal.java b/core/java/android/service/dreams/DreamManagerInternal.java index e9bb28c252e0..ad022c57345d 100644 --- a/core/java/android/service/dreams/DreamManagerInternal.java +++ b/core/java/android/service/dreams/DreamManagerInternal.java @@ -60,6 +60,12 @@ public abstract class DreamManagerInternal { 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 9f731fe04472..8db94a420e4c 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 c0e266fa269f..4c6a1ba7db0a 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 @@ public class GlobalSettingsValidators { 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 11b7e9dfe319..a7c078f235b4 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 @@ class DreamViewModel constructor( configurationInteractor: ConfigurationInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, - fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel, + fromGlanceableHubTransitionViewModel: GlanceableHubToDreamingTransitionViewModel, toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel, private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel, private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor, @@ -74,7 +74,7 @@ constructor( val dreamOverlayTranslationX: Flow<Float> = merge( toGlanceableHubTransitionViewModel.dreamOverlayTranslationX, - fromGlanceableHubTransitionInteractor.dreamOverlayTranslationX, + fromGlanceableHubTransitionViewModel.dreamOverlayTranslationX, ) .distinctUntilChanged() @@ -97,7 +97,7 @@ constructor( 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 372fdca20ed9..efa9c21f96b4 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.jank.InteractionJankMonitor.Configuration; 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.ActivityTransitionAnimator; 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 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, 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 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, 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 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, SelectedUserInteractor selectedUserInteractor, KeyguardInteractor keyguardInteractor, KeyguardTransitionBootInteractor transitionBootInteractor, + Lazy<CommunalSceneInteractor> communalSceneInteractor, WindowManagerOcclusionManager wmOcclusionManager) { mContext = context; mUserTracker = userTracker; @@ -1597,6 +1610,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mSelectedUserInteractor = selectedUserInteractor; mKeyguardInteractor = keyguardInteractor; mTransitionBootInteractor = transitionBootInteractor; + mCommunalSceneInteractor = communalSceneInteractor; mStatusBarStateController = statusBarStateController; statusBarStateController.addCallback(this); @@ -2411,6 +2425,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, * 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 908413db22dd..97ad2d7d36ff 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.bouncer.dagger.BouncerLoggerModule; 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 @@ public interface KeyguardModule { SelectedUserInteractor selectedUserInteractor, KeyguardInteractor keyguardInteractor, KeyguardTransitionBootInteractor transitionBootInteractor, + Lazy<CommunalSceneInteractor> communalSceneInteractor, WindowManagerOcclusionManager windowManagerOcclusionManager) { return new KeyguardViewMediator( context, @@ -231,6 +233,7 @@ public interface KeyguardModule { 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 0c9213c3a722..e8054c07eac8 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 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { 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 000000000000..86f7966d4ada --- /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 000000000000..de36493f6047 --- /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 000000000000..1e46d48e5cb3 --- /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 000000000000..51168d69d8e2 --- /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 67b1ec305d7f..7e8bb28b6a37 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 @@ public final class DreamManagerService extends SystemService { } } + @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 @@ public final class DreamManagerService extends SystemService { 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 @@ public final class DreamManagerService extends SystemService { } } - private void setDevicePosturedInternal(boolean isPostured) { + @VisibleForTesting + void setDevicePosturedInternal(boolean isPostured) { Slog.d(TAG, "Device postured: " + isPostured); synchronized (mLock) { mIsPostured = isPostured; @@ -1390,6 +1409,11 @@ public final class DreamManagerService extends SystemService { } @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 4860b7cdfcd3..0aaa0fea3740 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.MetricsLogger; 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { () -> 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { */ 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { } /** - * 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 992b8534accc..000000000000 --- 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 000000000000..4efc2582d796 --- /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 c73ce23fe6b5..32a3b7f2c9cc 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.spy; 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.ActivityManager; 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.After; 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 @@ import org.junit.Test; @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 class PhoneWindowManagerTests { 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 class PhoneWindowManagerTests { 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 @@ public class PhoneWindowManagerTests { 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 @@ public class PhoneWindowManagerTests { @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 @@ public class PhoneWindowManagerTests { @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 @@ public class PhoneWindowManagerTests { @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 @@ public class PhoneWindowManagerTests { 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); + } + } } |