summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Alejandro Nijamkin <nijamkin@google.com> 2024-06-14 14:53:49 -0700
committer Alejandro Nijamkin <nijamkin@google.com> 2024-06-17 17:01:51 -0700
commitaad4d32908e3eaecada5f6e3a3852a2bef57cd65 (patch)
tree5083c18d2d5d0f61c49ca41564da412e0bc01331
parent6df7a3863f7cc040965bdc849cb50fbf93c0c09b (diff)
[flexiglass] Enable overview/recents
Fixes issue where the recents gesture was unavailable in Flexiglass after unlocking the device. The idea is to hydrate StatusBarService with the correct disable flags at the right time and do so exclusively from Flexiglass code. Fix: 343297387 Flag: com.android.systemui.scene_container Test: manually verified with CLs on the chain Test: unit tests included Change-Id: Iee583e1013793a720d1f2a9558ad7f2b2b436c9f
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/StatusBarStartableTest.kt365
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/StatusBarStartable.kt180
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/StatusBarStartableKosmos.kt54
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt2
7 files changed, 623 insertions, 9 deletions
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 000000000000..9601f20add8e
--- /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 2d60fcca58cc..b70dbe240ce0 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.log.SessionTracker;
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 com.android.wm.shell.keyguard.KeyguardTransitions;
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.Objects;
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 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
+ " --> 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 323ca871e8b0..08462d79b2fb 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.NotifShadeSceneFamilyResolverM
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 @@ interface KeyguardlessSceneContainerFrameworkModule {
@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 4691ebaf95b3..17dc9a52f9b7 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.NotifShadeSceneFamilyResolverM
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 @@ interface SceneContainerFrameworkModule {
@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 000000000000..893f030fab4d
--- /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 000000000000..ee69c30fe6b9
--- /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 1f2ecb7d172d..ed335f9a1834 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 @@ class FakeUserRepository @Inject constructor() : UserRepository {
// 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,