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 @Override processOutgoingCallIntent(Context context, CallsManager callsManager, Intent intent, String callingPackage)42 public void processOutgoingCallIntent(Context context, CallsManager callsManager, 43 Intent intent, String callingPackage) { 44 CallIntentProcessor.processOutgoingCallIntent(context, callsManager, intent, 45 callingPackage); 46 } 47 48 @Override processIncomingCallIntent(CallsManager callsManager, Intent intent)49 public void processIncomingCallIntent(CallsManager callsManager, Intent intent) { 50 CallIntentProcessor.processIncomingCallIntent(callsManager, intent); 51 } 52 53 @Override processUnknownCallIntent(CallsManager callsManager, Intent intent)54 public void processUnknownCallIntent(CallsManager callsManager, Intent intent) { 55 CallIntentProcessor.processUnknownCallIntent(callsManager, intent); 56 } 57 } 58 59 public static final String KEY_IS_UNKNOWN_CALL = "is_unknown_call"; 60 public static final String KEY_IS_INCOMING_CALL = "is_incoming_call"; 61 /* 62 * Whether or not the dialer initiating this outgoing call is the default dialer, or system 63 * dialer and thus allowed to make emergency calls. 64 */ 65 public static final String KEY_IS_PRIVILEGED_DIALER = "is_privileged_dialer"; 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 CallIntentProcessor(Context context, CallsManager callsManager)76 public CallIntentProcessor(Context context, CallsManager callsManager) { 77 this.mContext = context; 78 this.mCallsManager = callsManager; 79 } 80 processIntent(Intent intent, String callingPackage)81 public void processIntent(Intent intent, String callingPackage) { 82 final boolean isUnknownCall = intent.getBooleanExtra(KEY_IS_UNKNOWN_CALL, false); 83 Log.i(this, "onReceive - isUnknownCall: %s", isUnknownCall); 84 85 Trace.beginSection("processNewCallCallIntent"); 86 if (isUnknownCall) { 87 processUnknownCallIntent(mCallsManager, intent); 88 } else { 89 processOutgoingCallIntent(mContext, mCallsManager, intent, callingPackage); 90 } 91 Trace.endSection(); 92 } 93 94 95 /** 96 * Processes CALL, CALL_PRIVILEGED, and CALL_EMERGENCY intents. 97 * 98 * @param intent Call intent containing data about the handle to call. 99 * @param callingPackage The package which initiated the outgoing call (if known). 100 */ processOutgoingCallIntent( Context context, CallsManager callsManager, Intent intent, String callingPackage)101 static void processOutgoingCallIntent( 102 Context context, 103 CallsManager callsManager, 104 Intent intent, 105 String callingPackage) { 106 107 Uri handle = intent.getData(); 108 String scheme = handle.getScheme(); 109 String uriString = handle.getSchemeSpecificPart(); 110 111 // Ensure sip URIs dialed using TEL scheme get converted to SIP scheme. 112 if (PhoneAccount.SCHEME_TEL.equals(scheme) && PhoneNumberUtils.isUriNumber(uriString)) { 113 handle = Uri.fromParts(PhoneAccount.SCHEME_SIP, uriString, null); 114 } 115 116 PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra( 117 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); 118 119 Bundle clientExtras = null; 120 if (intent.hasExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)) { 121 clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS); 122 } 123 if (clientExtras == null) { 124 clientExtras = new Bundle(); 125 } 126 127 if (intent.hasExtra(TelecomManager.EXTRA_IS_USER_INTENT_EMERGENCY_CALL)) { 128 clientExtras.putBoolean(TelecomManager.EXTRA_IS_USER_INTENT_EMERGENCY_CALL, 129 intent.getBooleanExtra(TelecomManager.EXTRA_IS_USER_INTENT_EMERGENCY_CALL, 130 false)); 131 } 132 133 // Ensure call subject is passed on to the connection service. 134 if (intent.hasExtra(TelecomManager.EXTRA_CALL_SUBJECT)) { 135 String callsubject = intent.getStringExtra(TelecomManager.EXTRA_CALL_SUBJECT); 136 clientExtras.putString(TelecomManager.EXTRA_CALL_SUBJECT, callsubject); 137 } 138 139 final int videoState = intent.getIntExtra( TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, 140 VideoProfile.STATE_AUDIO_ONLY); 141 clientExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState); 142 143 if (!callsManager.isSelfManaged(phoneAccountHandle, 144 (UserHandle) intent.getParcelableExtra(KEY_INITIATING_USER))) { 145 boolean fixedInitiatingUser = fixInitiatingUserIfNecessary(context, intent); 146 // Show the toast to warn user that it is a personal call though initiated in work 147 // profile. 148 if (fixedInitiatingUser) { 149 Toast.makeText(context, Looper.getMainLooper(), 150 context.getString(R.string.toast_personal_call_msg), 151 Toast.LENGTH_LONG).show(); 152 } 153 } else { 154 Log.i(CallIntentProcessor.class, 155 "processOutgoingCallIntent: skip initiating user check"); 156 } 157 158 UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER); 159 160 // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns 161 CompletableFuture<Call> callFuture = callsManager 162 .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser, 163 intent, callingPackage); 164 165 final Session logSubsession = Log.createSubsession(); 166 callFuture.thenAccept((call) -> { 167 if (call != null) { 168 Log.continueSession(logSubsession, "CIP.sNOCI"); 169 try { 170 sendNewOutgoingCallIntent(context, call, callsManager, intent); 171 } finally { 172 Log.endSession(); 173 } 174 } 175 }); 176 } 177 sendNewOutgoingCallIntent(Context context, Call call, CallsManager callsManager, Intent intent)178 static void sendNewOutgoingCallIntent(Context context, Call call, CallsManager callsManager, 179 Intent intent) { 180 // Asynchronous calls should not usually be made inside a BroadcastReceiver because once 181 // onReceive is complete, the BroadcastReceiver's process runs the risk of getting 182 // killed if memory is scarce. However, this is OK here because the entire Telecom 183 // process will be running throughout the duration of the phone call and should never 184 // be killed. 185 final boolean isPrivilegedDialer = intent.getBooleanExtra(KEY_IS_PRIVILEGED_DIALER, false); 186 187 NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster( 188 context, callsManager, call, intent, callsManager.getPhoneNumberUtilsAdapter(), 189 isPrivilegedDialer); 190 191 // If the broadcaster comes back with an immediate error, disconnect and show a dialog. 192 NewOutgoingCallIntentBroadcaster.CallDisposition disposition = broadcaster.evaluateCall(); 193 if (disposition.disconnectCause != DisconnectCause.NOT_DISCONNECTED) { 194 disconnectCallAndShowErrorDialog(context, call, disposition.disconnectCause); 195 return; 196 } 197 198 broadcaster.processCall(disposition); 199 } 200 201 /** 202 * If the call is initiated from managed profile but there is no work dialer installed, treat 203 * the call is initiated from its parent user. 204 * 205 * @return whether the initiating user is fixed. 206 */ fixInitiatingUserIfNecessary(Context context, Intent intent)207 static boolean fixInitiatingUserIfNecessary(Context context, Intent intent) { 208 final UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER); 209 if (UserUtil.isManagedProfile(context, initiatingUser)) { 210 boolean noDialerInstalled = DefaultDialerManager.getInstalledDialerApplications(context, 211 initiatingUser.getIdentifier()).size() == 0; 212 if (noDialerInstalled) { 213 final UserManager userManager = UserManager.get(context); 214 UserHandle parentUserHandle = 215 userManager.getProfileParent( 216 initiatingUser.getIdentifier()).getUserHandle(); 217 intent.putExtra(KEY_INITIATING_USER, parentUserHandle); 218 219 Log.i(CallIntentProcessor.class, "fixInitiatingUserIfNecessary: no dialer installed" 220 + " for current user; setting initiator to parent %s" + parentUserHandle); 221 return true; 222 } 223 } 224 return false; 225 } 226 processIncomingCallIntent(CallsManager callsManager, Intent intent)227 static void processIncomingCallIntent(CallsManager callsManager, Intent intent) { 228 PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra( 229 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); 230 231 if (phoneAccountHandle == null) { 232 Log.w(CallIntentProcessor.class, 233 "Rejecting incoming call due to null phone account"); 234 return; 235 } 236 if (phoneAccountHandle.getComponentName() == null) { 237 Log.w(CallIntentProcessor.class, 238 "Rejecting incoming call due to null component name"); 239 return; 240 } 241 242 Bundle clientExtras = null; 243 if (intent.hasExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS)) { 244 clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS); 245 } 246 if (clientExtras == null) { 247 clientExtras = new Bundle(); 248 } 249 250 Log.d(CallIntentProcessor.class, 251 "Processing incoming call from connection service [%s]", 252 phoneAccountHandle.getComponentName()); 253 callsManager.processIncomingCallIntent(phoneAccountHandle, clientExtras); 254 } 255 processUnknownCallIntent(CallsManager callsManager, Intent intent)256 static void processUnknownCallIntent(CallsManager callsManager, Intent intent) { 257 PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra( 258 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); 259 260 if (phoneAccountHandle == null) { 261 Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null phone account"); 262 return; 263 } 264 if (phoneAccountHandle.getComponentName() == null) { 265 Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null component name"); 266 return; 267 } 268 269 callsManager.addNewUnknownCall(phoneAccountHandle, intent.getExtras()); 270 } 271 disconnectCallAndShowErrorDialog( Context context, Call call, int errorCode)272 private static void disconnectCallAndShowErrorDialog( 273 Context context, Call call, int errorCode) { 274 call.disconnect(); 275 final Intent errorIntent = new Intent(context, ErrorDialogActivity.class); 276 int errorMessageId = -1; 277 switch (errorCode) { 278 case DisconnectCause.INVALID_NUMBER: 279 case DisconnectCause.NO_PHONE_NUMBER_SUPPLIED: 280 errorMessageId = R.string.outgoing_call_error_no_phone_number_supplied; 281 break; 282 } 283 if (errorMessageId != -1) { 284 errorIntent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, errorMessageId); 285 errorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 286 context.startActivityAsUser(errorIntent, UserHandle.CURRENT); 287 } 288 } 289 } 290