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.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
38 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
39 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
40 import com.android.systemui.util.EmergencyDialerConstants
41 import javax.inject.Inject
42 import kotlinx.coroutines.CoroutineDispatcher
43 import kotlinx.coroutines.flow.Flow
44 import kotlinx.coroutines.flow.distinctUntilChanged
45 import kotlinx.coroutines.flow.flowOf
46 import kotlinx.coroutines.flow.map
47 import kotlinx.coroutines.flow.merge
48 import kotlinx.coroutines.withContext
49 
50 /**
51  * Encapsulates business logic and application state for the bouncer action button. The action
52  * button can support multiple different actions, depending on device state.
53  */
54 @SysUISingleton
55 class BouncerActionButtonInteractor
56 @Inject
57 constructor(
58     @Application private val applicationContext: Context,
59     @Background private val backgroundDispatcher: CoroutineDispatcher,
60     private val repository: EmergencyServicesRepository,
61     // TODO(b/307977401): Replace with `MobileConnectionsInteractor` when available.
62     private val mobileConnectionsRepository: MobileConnectionsRepository,
63     private val telephonyInteractor: TelephonyInteractor,
64     private val authenticationInteractor: AuthenticationInteractor,
65     private val selectedUserInteractor: SelectedUserInteractor,
66     private val activityTaskManager: ActivityTaskManager,
67     private val telecomManager: TelecomManager?,
68     private val emergencyAffordanceManager: EmergencyAffordanceManager,
69     private val emergencyDialerIntentFactory: EmergencyDialerIntentFactory,
70     private val metricsLogger: MetricsLogger,
71     private val dozeLogger: DozeLogger,
72 ) {
73     /** The bouncer action button. If `null`, the button should not be shown. */
74     val actionButton: Flow<BouncerActionButtonModel?> =
75         if (telecomManager == null || !telephonyInteractor.hasTelephonyRadio) {
76             flowOf(null)
77         } else {
78             merge(
79                     telephonyInteractor.isInCall.asUnitFlow,
80                     mobileConnectionsRepository.isAnySimSecure.asUnitFlow,
81                     authenticationInteractor.authenticationMethod.asUnitFlow,
82                     repository.enableEmergencyCallWhileSimLocked.asUnitFlow,
83                 )
<lambda>null84                 .map {
85                     when {
86                         isReturnToCallButton() -> returnToCallButtonModel
87                         isEmergencyCallButton() -> emergencyCallButtonModel
88                         else -> null // Do not show the button.
89                     }
90                 }
91                 .distinctUntilChanged()
92         }
93 
<lambda>null94     private val returnToCallButtonModel: BouncerActionButtonModel by lazy {
95         BouncerActionButtonModel(
96             label = applicationContext.getString(R.string.lockscreen_return_to_call),
97             onClick = {
98                 prepareToPerformAction()
99                 returnToCall()
100             },
101             onLongClick = null
102         )
103     }
104 
<lambda>null105     private val emergencyCallButtonModel: BouncerActionButtonModel by lazy {
106         BouncerActionButtonModel(
107             label = applicationContext.getString(R.string.lockscreen_emergency_call),
108             onClick = {
109                 prepareToPerformAction()
110                 dozeLogger.logEmergencyCall()
111                 startEmergencyDialerActivity()
112             },
113             // TODO(b/308001302): The long click detector doesn't work properly, investigate.
114             onLongClick = {
115                 if (emergencyAffordanceManager.needsEmergencyAffordance()) {
116                     prepareToPerformAction()
117 
118                     // TODO(b/298026988): Check that !longPressWasDragged before invoking.
119                     emergencyAffordanceManager.performEmergencyCall()
120                 }
121             }
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         // TODO(b/308001302): Trigger occlusion and resetting bouncer state.
162         metricsLogger.action(MetricsEvent.ACTION_EMERGENCY_CALL)
163         activityTaskManager.stopSystemLockTaskMode()
164     }
165 
166     @SuppressLint("MissingPermission")
returnToCallnull167     private fun returnToCall() {
168         telecomManager?.showInCallScreen(/* showDialpad = */ false)
169     }
170 
171     private val <T> Flow<T>.asUnitFlow: Flow<Unit>
<lambda>null172         get() = map {}
173 }
174 
175 /**
176  * Creates an intent to launch the Emergency Services dialer. If no [TelecomManager] is present,
177  * returns `null`.
178  */
179 interface EmergencyDialerIntentFactory {
invokenull180     operator fun invoke(): Intent?
181 }
182