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 final int videoState = intent.getIntExtra( TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, 145 VideoProfile.STATE_AUDIO_ONLY); 146 clientExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState); 147 148 if (!callsManager.isSelfManaged(phoneAccountHandle, 149 (UserHandle) intent.getParcelableExtra(KEY_INITIATING_USER))) { 150 boolean fixedInitiatingUser = fixInitiatingUserIfNecessary(context, intent); 151 // Show the toast to warn user that it is a personal call though initiated in work 152 // profile. 153 if (fixedInitiatingUser) { 154 Toast.makeText(context, Looper.getMainLooper(), 155 context.getString(R.string.toast_personal_call_msg), 156 Toast.LENGTH_LONG).show(); 157 } 158 } else { 159 Log.i(CallIntentProcessor.class, 160 "processOutgoingCallIntent: skip initiating user check"); 161 } 162 163 UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER); 164 165 boolean isPrivilegedDialer = defaultDialerCache.isDefaultOrSystemDialer(callingPackage, 166 initiatingUser.getIdentifier()); 167 168 NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster( 169 context, callsManager, intent, callsManager.getPhoneNumberUtilsAdapter(), 170 isPrivilegedDialer, defaultDialerCache); 171 172 // If the broadcaster comes back with an immediate error, disconnect and show a dialog. 173 NewOutgoingCallIntentBroadcaster.CallDisposition disposition = broadcaster.evaluateCall(); 174 if (disposition.disconnectCause != DisconnectCause.NOT_DISCONNECTED) { 175 showErrorDialog(context, disposition.disconnectCause); 176 return; 177 } 178 179 // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns 180 CompletableFuture<Call> callFuture = callsManager 181 .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser, 182 intent, callingPackage); 183 184 final Session logSubsession = Log.createSubsession(); 185 callFuture.thenAccept((call) -> { 186 if (call != null) { 187 Log.continueSession(logSubsession, "CIP.sNOCI"); 188 try { 189 broadcaster.processCall(call, disposition); 190 } finally { 191 Log.endSession(); 192 } 193 } 194 }); 195 } 196 197 /** 198 * If the call is initiated from managed profile but there is no work dialer installed, treat 199 * the call is initiated from its parent user. 200 * 201 * @return whether the initiating user is fixed. 202 */ fixInitiatingUserIfNecessary(Context context, Intent intent)203 static boolean fixInitiatingUserIfNecessary(Context context, Intent intent) { 204 final UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER); 205 if (UserUtil.isManagedProfile(context, initiatingUser)) { 206 boolean noDialerInstalled = DefaultDialerManager.getInstalledDialerApplications(context, 207 initiatingUser.getIdentifier()).size() == 0; 208 if (noDialerInstalled) { 209 final UserManager userManager = UserManager.get(context); 210 UserHandle parentUserHandle = 211 userManager.getProfileParent( 212 initiatingUser.getIdentifier()).getUserHandle(); 213 intent.putExtra(KEY_INITIATING_USER, parentUserHandle); 214 215 Log.i(CallIntentProcessor.class, "fixInitiatingUserIfNecessary: no dialer installed" 216 + " for current user; setting initiator to parent %s" + parentUserHandle); 217 return true; 218 } 219 } 220 return false; 221 } 222 processIncomingCallIntent(CallsManager callsManager, Intent intent)223 static void processIncomingCallIntent(CallsManager callsManager, Intent intent) { 224 PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra( 225 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); 226 227 if (phoneAccountHandle == null) { 228 Log.w(CallIntentProcessor.class, 229 "Rejecting incoming call due to null phone account"); 230 return; 231 } 232 if (phoneAccountHandle.getComponentName() == null) { 233 Log.w(CallIntentProcessor.class, 234 "Rejecting incoming call due to null component name"); 235 return; 236 } 237 238 Bundle clientExtras = null; 239 if (intent.hasExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS)) { 240 clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS); 241 } 242 if (clientExtras == null) { 243 clientExtras = new Bundle(); 244 } 245 246 Log.d(CallIntentProcessor.class, 247 "Processing incoming call from connection service [%s]", 248 phoneAccountHandle.getComponentName()); 249 callsManager.processIncomingCallIntent(phoneAccountHandle, clientExtras); 250 } 251 processUnknownCallIntent(CallsManager callsManager, Intent intent)252 static void processUnknownCallIntent(CallsManager callsManager, Intent intent) { 253 PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra( 254 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); 255 256 if (phoneAccountHandle == null) { 257 Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null phone account"); 258 return; 259 } 260 if (phoneAccountHandle.getComponentName() == null) { 261 Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null component name"); 262 return; 263 } 264 265 callsManager.addNewUnknownCall(phoneAccountHandle, intent.getExtras()); 266 } 267 showErrorDialog(Context context, int errorCode)268 private static void showErrorDialog(Context context, int errorCode) { 269 final Intent errorIntent = new Intent(context, ErrorDialogActivity.class); 270 int errorMessageId = -1; 271 switch (errorCode) { 272 case DisconnectCause.INVALID_NUMBER: 273 case DisconnectCause.NO_PHONE_NUMBER_SUPPLIED: 274 errorMessageId = R.string.outgoing_call_error_no_phone_number_supplied; 275 break; 276 } 277 if (errorMessageId != -1) { 278 errorIntent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, errorMessageId); 279 errorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 280 context.startActivityAsUser(errorIntent, UserHandle.CURRENT); 281 } 282 } 283 } 284