1 package com.android.server.telecom; 2 3 import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; 4 5 import com.android.internal.app.IntentForwarderActivity; 6 import com.android.server.telecom.components.ErrorDialogActivity; 7 import com.android.server.telecom.flags.FeatureFlags; 8 9 import android.content.ActivityNotFoundException; 10 import android.content.Context; 11 import android.content.Intent; 12 import android.content.pm.PackageManager; 13 import android.content.pm.ResolveInfo; 14 import android.net.Uri; 15 import android.os.Bundle; 16 import android.os.Looper; 17 import android.os.UserHandle; 18 import android.os.UserManager; 19 import android.telecom.DefaultDialerManager; 20 import android.telecom.Log; 21 import android.telecom.Logging.Session; 22 import android.telecom.PhoneAccount; 23 import android.telecom.PhoneAccountHandle; 24 import android.telecom.TelecomManager; 25 import android.telecom.VideoProfile; 26 import android.telephony.DisconnectCause; 27 import android.telephony.PhoneNumberUtils; 28 import android.telephony.TelephonyManager; 29 import android.widget.Toast; 30 31 import java.util.concurrent.CompletableFuture; 32 33 /** 34 * Single point of entry for all outgoing and incoming calls. 35 * {@link com.android.server.telecom.components.UserCallIntentProcessor} serves as a trampoline that 36 * captures call intents for individual users and forwards it to the {@link CallIntentProcessor} 37 * which interacts with the rest of Telecom, both of which run only as the primary user. 38 */ 39 public class CallIntentProcessor { 40 public interface Adapter { processOutgoingCallIntent(Context context, CallsManager callsManager, Intent intent, String callingPackage, FeatureFlags featureFlags)41 void processOutgoingCallIntent(Context context, CallsManager callsManager, 42 Intent intent, String callingPackage, FeatureFlags featureFlags); processIncomingCallIntent(CallsManager callsManager, Intent intent)43 void processIncomingCallIntent(CallsManager callsManager, Intent intent); processUnknownCallIntent(CallsManager callsManager, Intent intent)44 void processUnknownCallIntent(CallsManager callsManager, Intent intent); 45 } 46 47 public static class AdapterImpl implements Adapter { 48 private final DefaultDialerCache mDefaultDialerCache; AdapterImpl(DefaultDialerCache cache)49 public AdapterImpl(DefaultDialerCache cache) { 50 mDefaultDialerCache = cache; 51 } 52 53 @Override processOutgoingCallIntent(Context context, CallsManager callsManager, Intent intent, String callingPackage, FeatureFlags featureFlags)54 public void processOutgoingCallIntent(Context context, CallsManager callsManager, 55 Intent intent, String callingPackage, FeatureFlags featureFlags) { 56 CallIntentProcessor.processOutgoingCallIntent(context, callsManager, intent, 57 callingPackage, mDefaultDialerCache, featureFlags); 58 } 59 60 @Override processIncomingCallIntent(CallsManager callsManager, Intent intent)61 public void processIncomingCallIntent(CallsManager callsManager, Intent intent) { 62 CallIntentProcessor.processIncomingCallIntent(callsManager, intent); 63 } 64 65 @Override processUnknownCallIntent(CallsManager callsManager, Intent intent)66 public void processUnknownCallIntent(CallsManager callsManager, Intent intent) { 67 CallIntentProcessor.processUnknownCallIntent(callsManager, intent); 68 } 69 } 70 71 public static final String KEY_IS_UNKNOWN_CALL = "is_unknown_call"; 72 public static final String KEY_IS_INCOMING_CALL = "is_incoming_call"; 73 74 /** 75 * The user initiating the outgoing call. 76 */ 77 public static final String KEY_INITIATING_USER = "initiating_user"; 78 79 80 private final Context mContext; 81 private final CallsManager mCallsManager; 82 private final DefaultDialerCache mDefaultDialerCache; 83 private final FeatureFlags mFeatureFlags; 84 CallIntentProcessor(Context context, CallsManager callsManager, DefaultDialerCache defaultDialerCache, FeatureFlags featureFlags)85 public CallIntentProcessor(Context context, CallsManager callsManager, 86 DefaultDialerCache defaultDialerCache, FeatureFlags featureFlags) { 87 this.mContext = context; 88 this.mCallsManager = callsManager; 89 this.mDefaultDialerCache = defaultDialerCache; 90 this.mFeatureFlags = featureFlags; 91 } 92 processIntent(Intent intent, String callingPackage)93 public void processIntent(Intent intent, String callingPackage) { 94 final boolean isUnknownCall = intent.getBooleanExtra(KEY_IS_UNKNOWN_CALL, false); 95 Log.i(this, "onReceive - isUnknownCall: %s", isUnknownCall); 96 97 if (isUnknownCall) { 98 processUnknownCallIntent(mCallsManager, intent); 99 } else { 100 processOutgoingCallIntent(mContext, mCallsManager, intent, callingPackage, 101 mDefaultDialerCache, mFeatureFlags); 102 } 103 } 104 105 106 /** 107 * Processes CALL, CALL_PRIVILEGED, and CALL_EMERGENCY intents. 108 * 109 * @param intent Call intent containing data about the handle to call. 110 * @param callingPackage The package which initiated the outgoing call (if known). 111 */ processOutgoingCallIntent( Context context, CallsManager callsManager, Intent intent, String callingPackage, DefaultDialerCache defaultDialerCache, FeatureFlags featureFlags)112 static void processOutgoingCallIntent( 113 Context context, 114 CallsManager callsManager, 115 Intent intent, 116 String callingPackage, 117 DefaultDialerCache defaultDialerCache, 118 FeatureFlags featureFlags) { 119 120 Uri handle = intent.getData(); 121 String scheme = handle.getScheme(); 122 String uriString = handle.getSchemeSpecificPart(); 123 124 // Ensure sip URIs dialed using TEL scheme get converted to SIP scheme. 125 if (PhoneAccount.SCHEME_TEL.equals(scheme) && PhoneNumberUtils.isUriNumber(uriString)) { 126 handle = Uri.fromParts(PhoneAccount.SCHEME_SIP, uriString, null); 127 } 128 129 PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra( 130 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); 131 132 Bundle clientExtras = null; 133 if (intent.hasExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)) { 134 clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS); 135 } 136 if (clientExtras == null) { 137 clientExtras = new Bundle(); 138 } 139 140 if (intent.hasExtra(TelecomManager.EXTRA_IS_USER_INTENT_EMERGENCY_CALL)) { 141 clientExtras.putBoolean(TelecomManager.EXTRA_IS_USER_INTENT_EMERGENCY_CALL, 142 intent.getBooleanExtra(TelecomManager.EXTRA_IS_USER_INTENT_EMERGENCY_CALL, 143 false)); 144 } 145 146 // Ensure call subject is passed on to the connection service. 147 if (intent.hasExtra(TelecomManager.EXTRA_CALL_SUBJECT)) { 148 String callsubject = intent.getStringExtra(TelecomManager.EXTRA_CALL_SUBJECT); 149 clientExtras.putString(TelecomManager.EXTRA_CALL_SUBJECT, callsubject); 150 } 151 152 if (intent.hasExtra(android.telecom.TelecomManager.EXTRA_PRIORITY)) { 153 clientExtras.putInt(android.telecom.TelecomManager.EXTRA_PRIORITY, intent.getIntExtra( 154 android.telecom.TelecomManager.EXTRA_PRIORITY, 155 android.telecom.TelecomManager.PRIORITY_NORMAL)); 156 } 157 158 if (intent.hasExtra(android.telecom.TelecomManager.EXTRA_LOCATION)) { 159 clientExtras.putParcelable(android.telecom.TelecomManager.EXTRA_LOCATION, 160 intent.getParcelableExtra(android.telecom.TelecomManager.EXTRA_LOCATION)); 161 } 162 163 if (intent.hasExtra(android.telecom.TelecomManager.EXTRA_OUTGOING_PICTURE)) { 164 clientExtras.putParcelable(android.telecom.TelecomManager.EXTRA_OUTGOING_PICTURE, 165 intent.getParcelableExtra( 166 android.telecom.TelecomManager.EXTRA_OUTGOING_PICTURE)); 167 } 168 169 final int videoState = intent.getIntExtra( TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, 170 VideoProfile.STATE_AUDIO_ONLY); 171 clientExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState); 172 173 if (!callsManager.isSelfManaged(phoneAccountHandle, 174 (UserHandle) intent.getParcelableExtra(KEY_INITIATING_USER))) { 175 boolean fixedInitiatingUser = fixInitiatingUserIfNecessary( 176 context, intent, featureFlags); 177 // Show the toast to warn user that it is a personal call though initiated in work 178 // profile. 179 if (fixedInitiatingUser) { 180 if (featureFlags.telecomResolveHiddenDependencies()) { 181 context.getMainExecutor().execute(() -> 182 Toast.makeText(context, context.getString( 183 R.string.toast_personal_call_msg), Toast.LENGTH_LONG).show()); 184 } else { 185 Toast.makeText(context, Looper.getMainLooper(), 186 context.getString(R.string.toast_personal_call_msg), 187 Toast.LENGTH_LONG).show(); 188 } 189 } 190 } else { 191 Log.i(CallIntentProcessor.class, 192 "processOutgoingCallIntent: skip initiating user check"); 193 } 194 195 UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER); 196 197 boolean isPrivilegedDialer = defaultDialerCache.isDefaultOrSystemDialer(callingPackage, 198 initiatingUser.getIdentifier()); 199 200 if (privateSpaceFlagsEnabled()) { 201 if (!callsManager.isSelfManaged(phoneAccountHandle, initiatingUser) 202 && !TelephonyUtil.shouldProcessAsEmergency(context, handle) 203 && UserUtil.isPrivateProfile(initiatingUser, context)) { 204 boolean dialogShown = maybeRedirectToIntentForwarderForPrivate(context, intent, 205 initiatingUser); 206 if (dialogShown) { 207 return; 208 } 209 } 210 } 211 212 NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster( 213 context, callsManager, intent, callsManager.getPhoneNumberUtilsAdapter(), 214 isPrivilegedDialer, defaultDialerCache, new MmiUtils(), featureFlags); 215 216 // If the broadcaster comes back with an immediate error, disconnect and show a dialog. 217 NewOutgoingCallIntentBroadcaster.CallDisposition disposition = broadcaster.evaluateCall(); 218 if (disposition.disconnectCause != DisconnectCause.NOT_DISCONNECTED) { 219 showErrorDialog(context, disposition.disconnectCause); 220 return; 221 } 222 223 // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns 224 CompletableFuture<Call> callFuture = callsManager 225 .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser, 226 intent, callingPackage); 227 228 final Session logSubsession = Log.createSubsession(); 229 callFuture.thenAccept((call) -> { 230 if (call != null) { 231 Log.continueSession(logSubsession, "CIP.sNOCI"); 232 try { 233 broadcaster.processCall(call, disposition); 234 } finally { 235 Log.endSession(); 236 } 237 } 238 }); 239 } 240 241 /** 242 * If the call is initiated from managed profile but there is no work dialer installed, treat 243 * the call is initiated from its parent user. 244 * 245 * @return whether the initiating user is fixed. 246 */ fixInitiatingUserIfNecessary(Context context, Intent intent, FeatureFlags featureFlags)247 static boolean fixInitiatingUserIfNecessary(Context context, Intent intent, 248 FeatureFlags featureFlags) { 249 final UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER); 250 if (UserUtil.isManagedProfile(context, initiatingUser, featureFlags)) { 251 boolean noDialerInstalled = DefaultDialerManager.getInstalledDialerApplications(context, 252 initiatingUser.getIdentifier()).size() == 0; 253 if (noDialerInstalled) { 254 final UserManager userManager = context.getSystemService(UserManager.class); 255 UserHandle parentUserHandle = featureFlags.telecomResolveHiddenDependencies() 256 ? userManager.getProfileParent(initiatingUser) 257 : userManager.getProfileParent(initiatingUser.getIdentifier()) 258 .getUserHandle(); 259 intent.putExtra(KEY_INITIATING_USER, parentUserHandle); 260 261 Log.i(CallIntentProcessor.class, "fixInitiatingUserIfNecessary: no dialer installed" 262 + " for current user; setting initiator to parent %s" + parentUserHandle); 263 return true; 264 } 265 } 266 return false; 267 } 268 processIncomingCallIntent(CallsManager callsManager, Intent intent)269 static void processIncomingCallIntent(CallsManager callsManager, Intent intent) { 270 PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra( 271 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); 272 273 if (phoneAccountHandle == null) { 274 Log.w(CallIntentProcessor.class, 275 "Rejecting incoming call due to null phone account"); 276 return; 277 } 278 if (phoneAccountHandle.getComponentName() == null) { 279 Log.w(CallIntentProcessor.class, 280 "Rejecting incoming call due to null component name"); 281 return; 282 } 283 284 Bundle clientExtras = null; 285 if (intent.hasExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS)) { 286 clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS); 287 } 288 if (clientExtras == null) { 289 clientExtras = new Bundle(); 290 } 291 292 Log.d(CallIntentProcessor.class, 293 "Processing incoming call from connection service [%s]", 294 phoneAccountHandle.getComponentName()); 295 callsManager.processIncomingCallIntent(phoneAccountHandle, clientExtras); 296 } 297 processUnknownCallIntent(CallsManager callsManager, Intent intent)298 static void processUnknownCallIntent(CallsManager callsManager, Intent intent) { 299 PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra( 300 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); 301 302 if (phoneAccountHandle == null) { 303 Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null phone account"); 304 return; 305 } 306 if (phoneAccountHandle.getComponentName() == null) { 307 Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null component name"); 308 return; 309 } 310 311 callsManager.addNewUnknownCall(phoneAccountHandle, intent.getExtras()); 312 } 313 showErrorDialog(Context context, int errorCode)314 private static void showErrorDialog(Context context, int errorCode) { 315 final Intent errorIntent = new Intent(context, ErrorDialogActivity.class); 316 int errorMessageId = -1; 317 switch (errorCode) { 318 case DisconnectCause.INVALID_NUMBER: 319 case DisconnectCause.NO_PHONE_NUMBER_SUPPLIED: 320 errorMessageId = R.string.outgoing_call_error_no_phone_number_supplied; 321 break; 322 } 323 if (errorMessageId != -1) { 324 errorIntent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, errorMessageId); 325 errorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 326 context.startActivityAsUser(errorIntent, UserHandle.CURRENT); 327 } 328 } 329 privateSpaceFlagsEnabled()330 private static boolean privateSpaceFlagsEnabled() { 331 return android.multiuser.Flags.enablePrivateSpaceFeatures() 332 && android.multiuser.Flags.enablePrivateSpaceIntentRedirection(); 333 } 334 maybeRedirectToIntentForwarderForPrivate( Context context, Intent forwardCallIntent, UserHandle initiatingUser)335 private static boolean maybeRedirectToIntentForwarderForPrivate( 336 Context context, 337 Intent forwardCallIntent, 338 UserHandle initiatingUser) { 339 340 // If CALL intent filters are set to SKIP_CURRENT_PROFILE, PM will resolve this to an 341 // intent forwarder activity. 342 forwardCallIntent.setComponent(null); 343 forwardCallIntent.setPackage(null); 344 ResolveInfo resolveInfos = 345 context.getPackageManager() 346 .resolveActivityAsUser( 347 forwardCallIntent, 348 PackageManager.ResolveInfoFlags.of(MATCH_DEFAULT_ONLY), 349 initiatingUser.getIdentifier()); 350 351 if (resolveInfos == null 352 || !resolveInfos 353 .getComponentInfo() 354 .getComponentName() 355 .getShortClassName() 356 .equals(IntentForwarderActivity.FORWARD_INTENT_TO_PARENT)) { 357 return false; 358 } 359 360 try { 361 context.startActivityAsUser(forwardCallIntent, initiatingUser); 362 return true; 363 } catch (ActivityNotFoundException e) { 364 Log.e(CallIntentProcessor.class, e, "Unable to start call intent in the main user"); 365 return false; 366 } 367 } 368 } 369