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