• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.keyguard.domain.interactor
18 
19 import android.annotation.SuppressLint
20 import android.app.StatusBarManager
21 import android.content.Context
22 import android.os.Binder
23 import android.os.IBinder
24 import android.os.RemoteException
25 import android.os.UserManager
26 import android.provider.DeviceConfig
27 import android.util.Log
28 import com.android.app.tracing.coroutines.launchTraced as launch
29 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
30 import com.android.internal.statusbar.IStatusBarService
31 import com.android.systemui.CoreStartable
32 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
33 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
34 import com.android.systemui.dagger.SysUISingleton
35 import com.android.systemui.dagger.qualifiers.Application
36 import com.android.systemui.dagger.qualifiers.Background
37 import com.android.systemui.deviceconfig.domain.interactor.DeviceConfigInteractor
38 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
39 import com.android.systemui.keyguard.KeyguardWmStateRefactor
40 import com.android.systemui.keyguard.shared.model.KeyguardState
41 import com.android.systemui.navigation.domain.interactor.NavigationInteractor
42 import com.android.systemui.power.domain.interactor.PowerInteractor
43 import com.android.systemui.power.shared.model.WakeSleepReason
44 import com.android.systemui.power.shared.model.WakefulnessModel
45 import com.android.systemui.process.ProcessWrapper
46 import com.android.systemui.scene.shared.flag.SceneContainerFlag
47 import com.android.systemui.shade.ShadeDisplayAware
48 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
49 import javax.inject.Inject
50 import kotlinx.coroutines.CoroutineDispatcher
51 import kotlinx.coroutines.CoroutineScope
52 import kotlinx.coroutines.flow.combine
53 import kotlinx.coroutines.flow.distinctUntilChanged
54 import kotlinx.coroutines.flow.flowOf
55 import kotlinx.coroutines.flow.map
56 import kotlinx.coroutines.withContext
57 
58 private val TAG = StatusBarDisableFlagsInteractor::class.simpleName
59 
60 /**
61  * Logic around StatusBarService#disableForUser, which is used to disable the home and recents
62  * button in certain device states.
63  *
64  * TODO(b/362313975): Remove post-Flexiglass, this duplicates StatusBarStartable logic.
65  */
66 @SysUISingleton
67 class StatusBarDisableFlagsInteractor
68 @Inject
69 constructor(
70     @Application private val scope: CoroutineScope,
71     @ShadeDisplayAware private val context: Context,
72     @Background private val backgroundDispatcher: CoroutineDispatcher,
73     private val deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor,
74     private val statusBarService: IStatusBarService,
75     private val processWrapper: ProcessWrapper,
76     keyguardTransitionInteractor: KeyguardTransitionInteractor,
77     selectedUserInteractor: SelectedUserInteractor,
78     deviceConfigInteractor: DeviceConfigInteractor,
79     navigationInteractor: NavigationInteractor,
80     authenticationInteractor: AuthenticationInteractor,
81     powerInteractor: PowerInteractor,
82 ) : CoreStartable {
83 
84     private val disableToken: IBinder = Binder()
85 
86     private val disableFlagsForUserId =
87         if (!KeyguardWmStateRefactor.isEnabled || SceneContainerFlag.isEnabled) {
88             flowOf(Pair(0, StatusBarManager.DISABLE_NONE))
89         } else {
90             combine(
91                     selectedUserInteractor.selectedUser,
92                     keyguardTransitionInteractor.startedKeyguardTransitionStep.map { it.to },
93                     deviceConfigInteractor.property(
94                         namespace = DeviceConfig.NAMESPACE_SYSTEMUI,
95                         name = SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_SHOW_OVER_LOCKSCREEN,
96                         default = true,
97                     ),
98                     navigationInteractor.isGesturalMode,
99                     authenticationInteractor.authenticationMethod,
100                     powerInteractor.detailedWakefulness,
101                 ) { values ->
102                     val selectedUserId = values[0] as Int
103                     val startedState = values[1] as KeyguardState
104                     val isShowHomeOverLockscreen = values[2] as Boolean
105                     val isGesturalMode = values[3] as Boolean
106                     val authenticationMethod = values[4] as AuthenticationMethodModel
107                     val wakefulnessModel = values[5] as WakefulnessModel
108                     val isOccluded = startedState == KeyguardState.OCCLUDED
109 
110                     val hideHomeAndRecentsForBouncer =
111                         startedState == KeyguardState.PRIMARY_BOUNCER ||
112                             startedState == KeyguardState.ALTERNATE_BOUNCER
113                     val isKeyguardShowing = startedState != KeyguardState.GONE
114                     val isPowerGestureIntercepted =
115                         with(wakefulnessModel) {
116                             isAwake() &&
117                                 powerButtonLaunchGestureTriggered &&
118                                 lastSleepReason == WakeSleepReason.POWER_BUTTON
119                         }
120 
121                     var flags = StatusBarManager.DISABLE_NONE
122 
123                     if (hideHomeAndRecentsForBouncer || (isKeyguardShowing && !isOccluded)) {
124                         // Hide the home button/nav handle if we're on keyguard and either:
125                         // - Going to AOD, in which case the handle should animate away.
126                         // - Nav handle is configured not to show on lockscreen.
127                         // - There is no nav handle.
128                         if (
129                             startedState == KeyguardState.AOD ||
130                                 !isShowHomeOverLockscreen ||
131                                 !isGesturalMode
132                         ) {
133                             flags = flags or StatusBarManager.DISABLE_HOME
134                         }
135                         flags = flags or StatusBarManager.DISABLE_RECENT
136                     }
137 
138                     if (
139                         isPowerGestureIntercepted &&
140                             isOccluded &&
141                             authenticationMethod.isSecure &&
142                             deviceEntryFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()
143                     ) {
144                         flags = flags or StatusBarManager.DISABLE_RECENT
145                     }
146 
147                     selectedUserId to flags
148                 }
149                 .distinctUntilChanged()
150         }
151 
152     @SuppressLint("WrongConstant", "NonInjectedService")
153     override fun start() {
154         if (!KeyguardWmStateRefactor.isEnabled || SceneContainerFlag.isEnabled) {
155             return
156         }
157 
158         //  TODO(b/341604160): Remove this blocking logic once StatusBarManagerService supports
159         //  visible background users properly.
160         if (
161             UserManager.isVisibleBackgroundUsersEnabled() &&
162                 !processWrapper.isSystemUser() &&
163                 !processWrapper.isForegroundUserOrProfile()
164         ) {
165             // Currently, only one SysUI process can register with IStatusBarService to listen
166             // for the CommandQueue events.
167             // In the Multi Display configuration with concurrent multi users (primarily used
168             // in Automotive), a visible background user (Automotive Multi Display passengers)
169             // could also access this code path. Given this limitation and we only allow the
170             // current user's SysUI process to register with IStatusBarService, we need to prevent
171             // calls into IStatusBarService from visible background users.
172             Log.d(TAG, "Status bar manager is disabled for visible background users")
173             return
174         }
175 
176         scope.launch {
177             disableFlagsForUserId.collect { (selectedUserId, flags) ->
178                 if (context.getSystemService(Context.STATUS_BAR_SERVICE) == null) {
179                     return@collect
180                 }
181 
182                 withContext(backgroundDispatcher) {
183                     try {
184                         statusBarService.disableForUser(
185                             flags,
186                             disableToken,
187                             context.packageName,
188                             selectedUserId,
189                         )
190                     } catch (e: RemoteException) {
191                         e.printStackTrace()
192                     }
193                 }
194             }
195         }
196     }
197 }
198