Merge "[flexiglass] Enable overview/recents" into main
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/StatusBarStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/StatusBarStartableTest.kt
new file mode 100644
index 0000000..9601f20
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/StatusBarStartableTest.kt
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.domain.startable
+
+import android.app.StatusBarManager
+import android.provider.DeviceConfig
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
+import com.android.internal.statusbar.statusBarService
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.navigationbar.NavigationModeController
+import com.android.systemui.navigationbar.navigationModeController
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.scene.data.repository.setSceneTransition
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
+import com.android.systemui.testKosmos
+import com.android.systemui.util.fakeDeviceConfigProxy
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlin.reflect.full.memberProperties
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.atLeastOnce
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.Parameter
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+
+@SmallTest
+@RunWith(ParameterizedAndroidJunit4::class)
+@EnableSceneContainer
+class StatusBarStartableTest : SysuiTestCase() {
+
+ companion object {
+ @Parameters(name = "{0}")
+ @JvmStatic
+ fun testSpecs(): List<TestSpec> {
+ return listOf(
+ TestSpec(
+ id = 0,
+ expectedFlags = StatusBarManager.DISABLE_NONE,
+ Preconditions(
+ isForceHideHomeAndRecents = false,
+ isKeyguardShowing = false,
+ isPowerGestureIntercepted = false,
+ ),
+ ),
+ TestSpec(
+ id = 1,
+ expectedFlags = StatusBarManager.DISABLE_NONE,
+ Preconditions(
+ isForceHideHomeAndRecents = false,
+ isKeyguardShowing = true,
+ isOccluded = true,
+ isPowerGestureIntercepted = false,
+ ),
+ ),
+ TestSpec(
+ id = 2,
+ expectedFlags = StatusBarManager.DISABLE_NONE,
+ Preconditions(
+ isForceHideHomeAndRecents = false,
+ isKeyguardShowing = false,
+ isPowerGestureIntercepted = true,
+ isOccluded = false,
+ ),
+ ),
+ TestSpec(
+ id = 3,
+ expectedFlags = StatusBarManager.DISABLE_NONE,
+ Preconditions(
+ isForceHideHomeAndRecents = false,
+ isKeyguardShowing = true,
+ isOccluded = true,
+ isPowerGestureIntercepted = true,
+ isAuthenticationMethodSecure = false,
+ ),
+ ),
+ TestSpec(
+ id = 4,
+ expectedFlags = StatusBarManager.DISABLE_NONE,
+ Preconditions(
+ isForceHideHomeAndRecents = false,
+ isKeyguardShowing = true,
+ isOccluded = true,
+ isPowerGestureIntercepted = true,
+ isAuthenticationMethodSecure = true,
+ isFaceEnrolledAndEnabled = false,
+ ),
+ ),
+ TestSpec(
+ id = 5,
+ expectedFlags = StatusBarManager.DISABLE_RECENT,
+ Preconditions(
+ isForceHideHomeAndRecents = false,
+ isKeyguardShowing = true,
+ isOccluded = true,
+ isPowerGestureIntercepted = true,
+ isAuthenticationMethodSecure = true,
+ isFaceEnrolledAndEnabled = true,
+ ),
+ ),
+ TestSpec(
+ id = 6,
+ expectedFlags = StatusBarManager.DISABLE_RECENT,
+ Preconditions(
+ isForceHideHomeAndRecents = true,
+ isShowHomeOverLockscreen = true,
+ isGesturalMode = true,
+ isPowerGestureIntercepted = false,
+ ),
+ ),
+ TestSpec(
+ id = 7,
+ expectedFlags = StatusBarManager.DISABLE_RECENT,
+ Preconditions(
+ isForceHideHomeAndRecents = false,
+ isKeyguardShowing = true,
+ isOccluded = false,
+ isShowHomeOverLockscreen = true,
+ isGesturalMode = true,
+ isPowerGestureIntercepted = false,
+ ),
+ ),
+ TestSpec(
+ id = 8,
+ expectedFlags =
+ StatusBarManager.DISABLE_RECENT or StatusBarManager.DISABLE_HOME,
+ Preconditions(
+ isForceHideHomeAndRecents = true,
+ isShowHomeOverLockscreen = true,
+ isGesturalMode = false,
+ isPowerGestureIntercepted = false,
+ ),
+ ),
+ TestSpec(
+ id = 9,
+ expectedFlags =
+ StatusBarManager.DISABLE_RECENT or StatusBarManager.DISABLE_HOME,
+ Preconditions(
+ isForceHideHomeAndRecents = false,
+ isKeyguardShowing = true,
+ isOccluded = false,
+ isShowHomeOverLockscreen = false,
+ isPowerGestureIntercepted = false,
+ ),
+ ),
+ )
+ }
+
+ @BeforeClass
+ @JvmStatic
+ fun setUpClass() {
+ val seenIds = mutableSetOf<Int>()
+ testSpecs().forEach { testSpec ->
+ assertWithMessage("Duplicate TestSpec id=${testSpec.id}")
+ .that(seenIds)
+ .doesNotContain(testSpec.id)
+ seenIds.add(testSpec.id)
+ }
+ }
+ }
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private val statusBarServiceMock = kosmos.statusBarService
+ private val flagsCaptor = argumentCaptor<Int>()
+
+ private val navigationModeControllerMock = kosmos.navigationModeController
+ private var currentNavigationMode = WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
+ set(value) {
+ field = value
+ modeChangedListeners.forEach { listener -> listener.onNavigationModeChanged(field) }
+ }
+
+ private val modeChangedListeners = mutableListOf<NavigationModeController.ModeChangedListener>()
+
+ private val underTest = kosmos.statusBarStartable
+
+ @JvmField @Parameter(0) var testSpec: TestSpec? = null
+
+ @Before
+ fun setUp() {
+ whenever(navigationModeControllerMock.addListener(any())).thenAnswer { invocation ->
+ val listener = invocation.arguments[0] as NavigationModeController.ModeChangedListener
+ modeChangedListeners.add(listener)
+ currentNavigationMode
+ }
+
+ underTest.start()
+ }
+
+ @Test
+ fun test() =
+ testScope.runTest {
+ val preconditions = checkNotNull(testSpec).preconditions
+ preconditions.assertValid()
+
+ setUpWith(preconditions)
+
+ runCurrent()
+
+ verify(statusBarServiceMock, atLeastOnce())
+ .disableForUser(flagsCaptor.capture(), any(), any(), anyInt())
+ assertThat(flagsCaptor.lastValue).isEqualTo(checkNotNull(testSpec).expectedFlags)
+ }
+
+ /** Sets up the state to match what's specified in the given [preconditions]. */
+ private fun TestScope.setUpWith(
+ preconditions: Preconditions,
+ ) {
+ if (!preconditions.isKeyguardShowing) {
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ }
+ if (preconditions.isForceHideHomeAndRecents) {
+ whenIdle(Scenes.Bouncer)
+ } else if (preconditions.isKeyguardShowing) {
+ whenIdle(Scenes.Lockscreen)
+ } else {
+ whenIdle(Scenes.Gone)
+ }
+ runCurrent()
+
+ kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop(
+ showWhenLockedActivityOnTop = preconditions.isOccluded,
+ taskInfo = if (preconditions.isOccluded) mock() else null,
+ )
+
+ kosmos.fakeDeviceConfigProxy.setProperty(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN,
+ preconditions.isShowHomeOverLockscreen.toString(),
+ /* makeDefault= */ false,
+ )
+ kosmos.fakeExecutor.runAllReady()
+
+ currentNavigationMode =
+ if (preconditions.isGesturalMode) {
+ WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL
+ } else {
+ WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON
+ }
+
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ if (preconditions.isAuthenticationMethodSecure) {
+ AuthenticationMethodModel.Pin
+ } else {
+ AuthenticationMethodModel.None
+ }
+ )
+
+ kosmos.fakePowerRepository.updateWakefulness(
+ rawState =
+ if (preconditions.isPowerGestureIntercepted) WakefulnessState.AWAKE
+ else WakefulnessState.ASLEEP,
+ lastWakeReason = WakeSleepReason.POWER_BUTTON,
+ lastSleepReason = WakeSleepReason.POWER_BUTTON,
+ powerButtonLaunchGestureTriggered = preconditions.isPowerGestureIntercepted,
+ )
+
+ kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(
+ preconditions.isFaceEnrolledAndEnabled
+ )
+
+ runCurrent()
+ }
+
+ /** Sets up an idle state on the given [on] scene. */
+ private fun whenIdle(on: SceneKey) {
+ kosmos.setSceneTransition(ObservableTransitionState.Idle(on))
+ kosmos.sceneInteractor.changeScene(on, "")
+ }
+
+ data class Preconditions(
+ val isForceHideHomeAndRecents: Boolean = false,
+ val isKeyguardShowing: Boolean = true,
+ val isOccluded: Boolean = false,
+ val isPowerGestureIntercepted: Boolean = false,
+ val isShowHomeOverLockscreen: Boolean = false,
+ val isGesturalMode: Boolean = true,
+ val isAuthenticationMethodSecure: Boolean = true,
+ val isFaceEnrolledAndEnabled: Boolean = false,
+ ) {
+ override fun toString(): String {
+ // Only include values set to true:
+ return buildString {
+ append("(")
+ append(
+ Preconditions::class
+ .memberProperties
+ .filter { it.get(this@Preconditions) == true }
+ .joinToString(", ") { "${it.name}=true" }
+ )
+ append(")")
+ }
+ }
+
+ fun assertValid() {
+ assertWithMessage(
+ "isForceHideHomeAndRecents means that the bouncer is showing so keyguard must" +
+ " be showing"
+ )
+ .that(!isForceHideHomeAndRecents || isKeyguardShowing)
+ .isTrue()
+ assertWithMessage("Cannot be occluded if the keyguard isn't showing")
+ .that(!isOccluded || isKeyguardShowing)
+ .isTrue()
+ }
+ }
+
+ data class TestSpec(
+ val id: Int,
+ val expectedFlags: Int,
+ val preconditions: Preconditions,
+ ) {
+ override fun toString(): String {
+ return "id=$id, expected=$expectedFlags, preconditions=$preconditions"
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 2d60fcc..b70dbe2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -151,6 +151,7 @@
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -178,6 +179,8 @@
import dagger.Lazy;
+import kotlinx.coroutines.CoroutineDispatcher;
+
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -187,8 +190,6 @@
import java.util.concurrent.Executor;
import java.util.function.Consumer;
-import kotlinx.coroutines.CoroutineDispatcher;
-
/**
* Mediates requests related to the keyguard. This includes queries about the
* state of the keyguard, power management events that effect whether the keyguard
@@ -3502,12 +3503,14 @@
+ " --> flags=0x" + Integer.toHexString(flags));
}
- try {
- mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
- mContext.getPackageName(),
- mSelectedUserInteractor.getSelectedUserId(true));
- } catch (RemoteException e) {
- Log.d(TAG, "Failed to set disable flags: " + flags, e);
+ if (!SceneContainerFlag.isEnabled()) {
+ try {
+ mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
+ mContext.getPackageName(),
+ mSelectedUserInteractor.getSelectedUserId(true));
+ } catch (RemoteException e) {
+ Log.d(TAG, "Failed to set disable flags: " + flags, e);
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index 323ca87..08462d7 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -25,6 +25,7 @@
import com.android.systemui.scene.domain.resolver.QuickSettingsSceneFamilyResolverModule
import com.android.systemui.scene.domain.startable.SceneContainerStartable
import com.android.systemui.scene.domain.startable.ScrimStartable
+import com.android.systemui.scene.domain.startable.StatusBarStartable
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.shared.flag.DualShade
@@ -66,6 +67,11 @@
@Binds
@IntoMap
+ @ClassKey(StatusBarStartable::class)
+ fun statusBarStartable(impl: StatusBarStartable): CoreStartable
+
+ @Binds
+ @IntoMap
@ClassKey(WindowRootViewVisibilityInteractor::class)
fun bindWindowRootViewVisibilityInteractor(
impl: WindowRootViewVisibilityInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index 4691eba..17dc9a5 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -26,6 +26,7 @@
import com.android.systemui.scene.domain.resolver.QuickSettingsSceneFamilyResolverModule
import com.android.systemui.scene.domain.startable.SceneContainerStartable
import com.android.systemui.scene.domain.startable.ScrimStartable
+import com.android.systemui.scene.domain.startable.StatusBarStartable
import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.shared.flag.DualShade
@@ -72,6 +73,11 @@
@Binds
@IntoMap
+ @ClassKey(StatusBarStartable::class)
+ fun statusBarStartable(impl: StatusBarStartable): CoreStartable
+
+ @Binds
+ @IntoMap
@ClassKey(WindowRootViewVisibilityInteractor::class)
fun bindWindowRootViewVisibilityInteractor(
impl: WindowRootViewVisibilityInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/StatusBarStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/StatusBarStartable.kt
new file mode 100644
index 0000000..893f030
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/StatusBarStartable.kt
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.domain.startable
+
+import android.annotation.SuppressLint
+import android.app.StatusBarManager
+import android.content.Context
+import android.os.Binder
+import android.os.IBinder
+import android.os.RemoteException
+import android.provider.DeviceConfig
+import android.util.Log
+import com.android.compose.animation.scene.SceneKey
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
+import com.android.internal.statusbar.IStatusBarService
+import com.android.systemui.CoreStartable
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.deviceconfig.domain.interactor.DeviceConfigInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.navigation.domain.interactor.NavigationInteractor
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessModel
+import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+@SysUISingleton
+class StatusBarStartable
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Application private val applicationContext: Context,
+ private val selectedUserInteractor: SelectedUserInteractor,
+ private val sceneInteractor: SceneInteractor,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
+ private val sceneContainerOcclusionInteractor: SceneContainerOcclusionInteractor,
+ private val deviceConfigInteractor: DeviceConfigInteractor,
+ private val navigationInteractor: NavigationInteractor,
+ private val authenticationInteractor: AuthenticationInteractor,
+ private val powerInteractor: PowerInteractor,
+ private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
+ private val statusBarService: IStatusBarService,
+) : CoreStartable {
+
+ private val disableToken: IBinder = Binder()
+
+ override fun start() {
+ if (!SceneContainerFlag.isEnabled) {
+ return
+ }
+
+ applicationScope.launch {
+ combine(
+ selectedUserInteractor.selectedUser,
+ sceneInteractor.currentScene,
+ deviceEntryInteractor.isDeviceEntered,
+ sceneContainerOcclusionInteractor.invisibleDueToOcclusion,
+ deviceConfigInteractor.property(
+ namespace = DeviceConfig.NAMESPACE_SYSTEMUI,
+ name = SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN,
+ default = true,
+ ),
+ navigationInteractor.isGesturalMode,
+ authenticationInteractor.authenticationMethod,
+ powerInteractor.detailedWakefulness,
+ ) { values ->
+ val selectedUserId = values[0] as Int
+ val currentScene = values[1] as SceneKey
+ val isDeviceEntered = values[2] as Boolean
+ val isOccluded = values[3] as Boolean
+ val isShowHomeOverLockscreen = values[4] as Boolean
+ val isGesturalMode = values[5] as Boolean
+ val authenticationMethod = values[6] as AuthenticationMethodModel
+ val wakefulnessModel = values[7] as WakefulnessModel
+
+ val isForceHideHomeAndRecents = currentScene == Scenes.Bouncer
+ val isKeyguardShowing = !isDeviceEntered
+ val isPowerGestureIntercepted =
+ with(wakefulnessModel) {
+ isAwake() &&
+ powerButtonLaunchGestureTriggered &&
+ lastSleepReason == WakeSleepReason.POWER_BUTTON
+ }
+
+ var flags = StatusBarManager.DISABLE_NONE
+
+ if (isForceHideHomeAndRecents || (isKeyguardShowing && !isOccluded)) {
+ if (!isShowHomeOverLockscreen || !isGesturalMode) {
+ flags = flags or StatusBarManager.DISABLE_HOME
+ }
+ flags = flags or StatusBarManager.DISABLE_RECENT
+ }
+
+ if (
+ isPowerGestureIntercepted &&
+ isOccluded &&
+ authenticationMethod.isSecure &&
+ deviceEntryFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()
+ ) {
+ flags = flags or StatusBarManager.DISABLE_RECENT
+ }
+
+ selectedUserId to flags
+ }
+ .distinctUntilChanged()
+ .collect { (selectedUserId, flags) ->
+ @SuppressLint("WrongConstant", "NonInjectedService")
+ if (applicationContext.getSystemService(Context.STATUS_BAR_SERVICE) == null) {
+ Log.w(TAG, "Could not get status bar manager")
+ return@collect
+ }
+
+ withContext(backgroundDispatcher) {
+ try {
+ statusBarService.disableForUser(
+ flags,
+ disableToken,
+ applicationContext.packageName,
+ selectedUserId,
+ )
+ } catch (e: RemoteException) {
+ Log.d(TAG, "Failed to set disable flags: $flags", e)
+ }
+ }
+ }
+ }
+ }
+
+ override fun onBootCompleted() {
+ applicationScope.launch(backgroundDispatcher) {
+ try {
+ statusBarService.disableForUser(
+ StatusBarManager.DISABLE_NONE,
+ disableToken,
+ applicationContext.packageName,
+ selectedUserInteractor.getSelectedUserId(true),
+ )
+ } catch (e: RemoteException) {
+ Log.d(TAG, "Failed to clear flags", e)
+ }
+ }
+ }
+
+ companion object {
+ private const val TAG = "StatusBarStartable"
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/StatusBarStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/StatusBarStartableKosmos.kt
new file mode 100644
index 0000000..ee69c30
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/StatusBarStartableKosmos.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene.domain.startable
+
+import android.content.applicationContext
+import com.android.internal.statusbar.statusBarService
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.deviceconfig.domain.interactor.deviceConfigInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.navigation.domain.interactor.navigationInteractor
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.statusBarStartable by Fixture {
+ StatusBarStartable(
+ applicationScope = applicationCoroutineScope,
+ backgroundDispatcher = testDispatcher,
+ applicationContext = applicationContext,
+ selectedUserInteractor = selectedUserInteractor,
+ sceneInteractor = sceneInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
+ sceneContainerOcclusionInteractor = sceneContainerOcclusionInteractor,
+ deviceConfigInteractor = deviceConfigInteractor,
+ navigationInteractor = navigationInteractor,
+ authenticationInteractor = authenticationInteractor,
+ powerInteractor = powerInteractor,
+ deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor,
+ statusBarService = statusBarService,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index 1f2ecb7..ed335f9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -39,7 +39,7 @@
// User id to represent a non system (human) user id. We presume this is the main user.
const val MAIN_USER_ID = 10
- private const val DEFAULT_SELECTED_USER = 0
+ const val DEFAULT_SELECTED_USER = 0
private val DEFAULT_SELECTED_USER_INFO =
UserInfo(
/* id= */ DEFAULT_SELECTED_USER,