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