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