• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.bouncer.domain.interactor
18 
19 import android.annotation.SuppressLint
20 import android.app.ActivityOptions
21 import android.app.ActivityTaskManager
22 import android.content.Context
23 import android.content.Intent
24 import android.os.UserHandle
25 import android.telecom.TelecomManager
26 import com.android.internal.R
27 import com.android.internal.logging.MetricsLogger
28 import com.android.internal.logging.nano.MetricsProto.MetricsEvent
29 import com.android.internal.util.EmergencyAffordanceManager
30 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
31 import com.android.systemui.bouncer.data.repository.EmergencyServicesRepository
32 import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
33 import com.android.systemui.dagger.SysUISingleton
34 import com.android.systemui.dagger.qualifiers.Application
35 import com.android.systemui.dagger.qualifiers.Background
36 import com.android.systemui.doze.DozeLogger
37 import com.android.systemui.scene.domain.interactor.SceneInteractor
38 import com.android.systemui.scene.shared.flag.SceneContainerFlag
39 import com.android.systemui.scene.shared.model.Scenes
40 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
41 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
42 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
43 import com.android.systemui.util.EmergencyDialerConstants
44 import dagger.Lazy
45 import javax.inject.Inject
46 import kotlinx.coroutines.CoroutineDispatcher
47 import kotlinx.coroutines.flow.Flow
48 import kotlinx.coroutines.flow.distinctUntilChanged
49 import kotlinx.coroutines.flow.flowOf
50 import kotlinx.coroutines.flow.map
51 import kotlinx.coroutines.flow.merge
52 import kotlinx.coroutines.withContext
53 
54 /**
55  * Encapsulates business logic and application state for the bouncer action button. The action
56  * button can support multiple different actions, depending on device state.
57  */
58 @SysUISingleton
59 class BouncerActionButtonInteractor
60 @Inject
61 constructor(
62     @Application private val applicationContext: Context,
63     @Background private val backgroundDispatcher: CoroutineDispatcher,
64     private val repository: EmergencyServicesRepository,
65     // TODO(b/307977401): Replace with `MobileConnectionsInteractor` when available.
66     private val mobileConnectionsRepository: MobileConnectionsRepository,
67     private val telephonyInteractor: TelephonyInteractor,
68     private val authenticationInteractor: AuthenticationInteractor,
69     private val selectedUserInteractor: SelectedUserInteractor,
70     private val activityTaskManager: ActivityTaskManager,
71     private val telecomManager: TelecomManager?,
72     private val emergencyAffordanceManager: EmergencyAffordanceManager,
73     private val emergencyDialerIntentFactory: EmergencyDialerIntentFactory,
74     private val metricsLogger: MetricsLogger,
75     private val dozeLogger: DozeLogger,
76     private val sceneInteractor: Lazy<SceneInteractor>,
77 ) {
78     /** The bouncer action button. If `null`, the button should not be shown. */
79     val actionButton: Flow<BouncerActionButtonModel?> =
80         if (telecomManager == null || !telephonyInteractor.hasTelephonyRadio) {
81             flowOf(null)
82         } else {
83             merge(
84                     telephonyInteractor.isInCall.asUnitFlow,
85                     mobileConnectionsRepository.isAnySimSecure.asUnitFlow,
86                     authenticationInteractor.authenticationMethod.asUnitFlow,
87                     repository.enableEmergencyCallWhileSimLocked.asUnitFlow,
88                 )
<lambda>null89                 .map {
90                     when {
91                         isReturnToCallButton() ->
92                             BouncerActionButtonModel.ReturnToCallButtonModel(
93                                 labelResourceId = R.string.lockscreen_return_to_call
94                             )
95                         isEmergencyCallButton() ->
96                             BouncerActionButtonModel.EmergencyButtonModel(
97                                 labelResourceId = R.string.lockscreen_emergency_call
98                             )
99                         else -> null // Do not show the button.
100                     }
101                 }
102                 .distinctUntilChanged()
103         }
104 
onReturnToCallButtonClickednull105     fun onReturnToCallButtonClicked() {
106         prepareToPerformAction()
107         returnToCall()
108     }
109 
onEmergencyButtonClickednull110     fun onEmergencyButtonClicked() {
111         prepareToPerformAction()
112         dozeLogger.logEmergencyCall()
113         startEmergencyDialerActivity()
114     }
115 
onEmergencyButtonLongClickednull116     fun onEmergencyButtonLongClicked() {
117         if (emergencyAffordanceManager.needsEmergencyAffordance()) {
118             prepareToPerformAction()
119 
120             // TODO(b/369767936): Check that !longPressWasDragged before invoking.
121             emergencyAffordanceManager.performEmergencyCall()
122         }
123     }
124 
startEmergencyDialerActivitynull125     private fun startEmergencyDialerActivity() {
126         emergencyDialerIntentFactory()?.apply {
127             flags =
128                 Intent.FLAG_ACTIVITY_NEW_TASK or
129                     Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS or
130                     Intent.FLAG_ACTIVITY_CLEAR_TOP
131 
132             putExtra(
133                 EmergencyDialerConstants.EXTRA_ENTRY_TYPE,
134                 EmergencyDialerConstants.ENTRY_TYPE_LOCKSCREEN_BUTTON,
135             )
136 
137             // TODO(b/25189994): Use the ActivityStarter interface instead.
138             applicationContext.startActivityAsUser(
139                 this,
140                 ActivityOptions.makeCustomAnimation(applicationContext, 0, 0).toBundle(),
141                 UserHandle(selectedUserInteractor.getSelectedUserId()),
142             )
143         }
144     }
145 
isReturnToCallButtonnull146     private fun isReturnToCallButton() = telephonyInteractor.isInCall.value
147 
148     private suspend fun isEmergencyCallButton(): Boolean {
149         return if (mobileConnectionsRepository.getIsAnySimSecure()) {
150             // Some countries can't handle emergency calls while SIM is locked.
151             repository.enableEmergencyCallWhileSimLocked.value
152         } else {
153             // Only show if there is a secure screen (password/pin/pattern/SIM pin/SIM puk).
154             withContext(backgroundDispatcher) {
155                 authenticationInteractor.getAuthenticationMethod().isSecure
156             }
157         }
158     }
159 
prepareToPerformActionnull160     private fun prepareToPerformAction() {
161         if (SceneContainerFlag.isEnabled) {
162             sceneInteractor.get().changeScene(Scenes.Lockscreen, "Bouncer action button clicked")
163         }
164 
165         metricsLogger.action(MetricsEvent.ACTION_EMERGENCY_CALL)
166         activityTaskManager.stopSystemLockTaskMode()
167     }
168 
169     @SuppressLint("MissingPermission")
returnToCallnull170     private fun returnToCall() {
171         telecomManager?.showInCallScreen(/* showDialpad= */ false)
172     }
173 
174     private val <T> Flow<T>.asUnitFlow: Flow<Unit>
<lambda>null175         get() = map {}
176 }
177 
178 /**
179  * Creates an intent to launch the Emergency Services dialer. If no [TelecomManager] is present,
180  * returns `null`.
181  */
182 interface EmergencyDialerIntentFactory {
invokenull183     operator fun invoke(): Intent?
184 }
185