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.Trace; 10 import android.os.UserHandle; 11 import android.telecom.PhoneAccount; 12 import android.telecom.PhoneAccountHandle; 13 import android.telecom.TelecomManager; 14 import android.telecom.VideoProfile; 15 import android.telephony.DisconnectCause; 16 import android.telephony.PhoneNumberUtils; 17 import android.widget.Toast; 18 19 /** 20 * Single point of entry for all outgoing and incoming calls. 21 * {@link com.android.server.telecom.components.UserCallIntentProcessor} serves as a trampoline that 22 * captures call intents for individual users and forwards it to the {@link CallIntentProcessor} 23 * which interacts with the rest of Telecom, both of which run only as the primary user. 24 */ 25 public class CallIntentProcessor { 26 27 public static final String KEY_IS_UNKNOWN_CALL = "is_unknown_call"; 28 public static final String KEY_IS_INCOMING_CALL = "is_incoming_call"; 29 /* 30 * Whether or not the dialer initiating this outgoing call is the default dialer, or system 31 * dialer and thus allowed to make emergency calls. 32 */ 33 public static final String KEY_IS_PRIVILEGED_DIALER = "is_privileged_dialer"; 34 35 private final Context mContext; 36 private final CallsManager mCallsManager; 37 38 public CallIntentProcessor(Context context, CallsManager callsManager) { 39 this.mContext = context; 40 this.mCallsManager = callsManager; 41 } 42 43 public void processIntent(Intent intent) { 44 final boolean isUnknownCall = intent.getBooleanExtra(KEY_IS_UNKNOWN_CALL, false); 45 Log.i(this, "onReceive - isUnknownCall: %s", isUnknownCall); 46 47 Trace.beginSection("processNewCallCallIntent"); 48 if (isUnknownCall) { 49 processUnknownCallIntent(mCallsManager, intent); 50 } else { 51 processOutgoingCallIntent(mContext, mCallsManager, intent); 52 } 53 Trace.endSection(); 54 } 55 56 57 /** 58 * Processes CALL, CALL_PRIVILEGED, and CALL_EMERGENCY intents. 59 * 60 * @param intent Call intent containing data about the handle to call. 61 */ 62 static void processOutgoingCallIntent( 63 Context context, 64 CallsManager callsManager, 65 Intent intent) { 66 if (shouldPreventDuplicateVideoCall(context, callsManager, intent)) { 67 return; 68 } 69 70 Uri handle = intent.getData(); 71 String scheme = handle.getScheme(); 72 String uriString = handle.getSchemeSpecificPart(); 73 74 if (!PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) { 75 handle = Uri.fromParts(PhoneNumberUtils.isUriNumber(uriString) ? 76 PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, uriString, null); 77 } 78 79 PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra( 80 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); 81 82 Bundle clientExtras = null; 83 if (intent.hasExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)) { 84 clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS); 85 } 86 if (clientExtras == null) { 87 clientExtras = new Bundle(); 88 } 89 90 final boolean isPrivilegedDialer = intent.getBooleanExtra(KEY_IS_PRIVILEGED_DIALER, false); 91 92 // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns 93 Call call = callsManager.startOutgoingCall(handle, phoneAccountHandle, clientExtras); 94 95 if (call != null) { 96 // Asynchronous calls should not usually be made inside a BroadcastReceiver because once 97 // onReceive is complete, the BroadcastReceiver's process runs the risk of getting 98 // killed if memory is scarce. However, this is OK here because the entire Telecom 99 // process will be running throughout the duration of the phone call and should never 100 // be killed. 101 NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster( 102 context, callsManager, call, intent, isPrivilegedDialer); 103 final int result = broadcaster.processIntent(); 104 final boolean success = result == DisconnectCause.NOT_DISCONNECTED; 105 106 if (!success && call != null) { 107 disconnectCallAndShowErrorDialog(context, call, result); 108 } 109 } 110 } 111 112 static void processIncomingCallIntent(CallsManager callsManager, Intent intent) { 113 PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra( 114 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); 115 116 if (phoneAccountHandle == null) { 117 Log.w(CallIntentProcessor.class, 118 "Rejecting incoming call due to null phone account"); 119 return; 120 } 121 if (phoneAccountHandle.getComponentName() == null) { 122 Log.w(CallIntentProcessor.class, 123 "Rejecting incoming call due to null component name"); 124 return; 125 } 126 127 Bundle clientExtras = null; 128 if (intent.hasExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS)) { 129 clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS); 130 } 131 if (clientExtras == null) { 132 clientExtras = new Bundle(); 133 } 134 135 Log.d(CallIntentProcessor.class, 136 "Processing incoming call from connection service [%s]", 137 phoneAccountHandle.getComponentName()); 138 callsManager.processIncomingCallIntent(phoneAccountHandle, clientExtras); 139 } 140 141 static void processUnknownCallIntent(CallsManager callsManager, Intent intent) { 142 PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra( 143 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); 144 145 if (phoneAccountHandle == null) { 146 Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null phone account"); 147 return; 148 } 149 if (phoneAccountHandle.getComponentName() == null) { 150 Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null component name"); 151 return; 152 } 153 154 callsManager.addNewUnknownCall(phoneAccountHandle, intent.getExtras()); 155 } 156 157 private static void disconnectCallAndShowErrorDialog( 158 Context context, Call call, int errorCode) { 159 call.disconnect(); 160 final Intent errorIntent = new Intent(context, ErrorDialogActivity.class); 161 int errorMessageId = -1; 162 switch (errorCode) { 163 case DisconnectCause.INVALID_NUMBER: 164 case DisconnectCause.NO_PHONE_NUMBER_SUPPLIED: 165 errorMessageId = R.string.outgoing_call_error_no_phone_number_supplied; 166 break; 167 } 168 if (errorMessageId != -1) { 169 errorIntent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, errorMessageId); 170 errorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 171 context.startActivityAsUser(errorIntent, UserHandle.CURRENT); 172 } 173 } 174 175 /** 176 * Whether an outgoing video call should be prevented from going out. Namely, don't allow an 177 * outgoing video call if there is already an ongoing video call. Notify the user if their call 178 * is not sent. 179 * 180 * @return {@code true} if the outgoing call is a video call and should be prevented from going 181 * out, {@code false} otherwise. 182 */ 183 private static boolean shouldPreventDuplicateVideoCall( 184 Context context, 185 CallsManager callsManager, 186 Intent intent) { 187 int intentVideoState = intent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, 188 VideoProfile.STATE_AUDIO_ONLY); 189 if (VideoProfile.isAudioOnly(intentVideoState) 190 || !callsManager.hasVideoCall()) { 191 return false; 192 } else { 193 // Display an error toast to the user. 194 Toast.makeText( 195 context, 196 context.getResources().getString(R.string.duplicate_video_call_not_allowed), 197 Toast.LENGTH_LONG).show(); 198 return true; 199 } 200 } 201 } 202