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 { 34 void processOutgoingCallIntent(Context context, CallsManager callsManager, 35 Intent intent, String callingPackage); 36 void processIncomingCallIntent(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; 42 public AdapterImpl(DefaultDialerCache cache) { 43 mDefaultDialerCache = cache; 44 } 45 46 @Override 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 54 public void processIncomingCallIntent(CallsManager callsManager, Intent intent) { 55 CallIntentProcessor.processIncomingCallIntent(callsManager, intent); 56 } 57 58 @Override 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 77 public CallIntentProcessor(Context context, CallsManager callsManager, 78 DefaultDialerCache defaultDialerCache) { 79 this.mContext = context; 80 this.mCallsManager = callsManager; 81 this.mDefaultDialerCache = defaultDialerCache; 82 } 83 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 */ 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 NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster( 186 context, callsManager, intent, callsManager.getPhoneNumberUtilsAdapter(), 187 isPrivilegedDialer, defaultDialerCache); 188 189 // If the broadcaster comes back with an immediate error, disconnect and show a dialog. 190 NewOutgoingCallIntentBroadcaster.CallDisposition disposition = broadcaster.evaluateCall(); 191 if (disposition.disconnectCause != DisconnectCause.NOT_DISCONNECTED) { 192 showErrorDialog(context, disposition.disconnectCause); 193 return; 194 } 195 196 // Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns 197 CompletableFuture<Call> callFuture = callsManager 198 .startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser, 199 intent, callingPackage); 200 201 final Session logSubsession = Log.createSubsession(); 202 callFuture.thenAccept((call) -> { 203 if (call != null) { 204 Log.continueSession(logSubsession, "CIP.sNOCI"); 205 try { 206 broadcaster.processCall(call, disposition); 207 } finally { 208 Log.endSession(); 209 } 210 } 211 }); 212 } 213 214 /** 215 * If the call is initiated from managed profile but there is no work dialer installed, treat 216 * the call is initiated from its parent user. 217 * 218 * @return whether the initiating user is fixed. 219 */ 220 static boolean fixInitiatingUserIfNecessary(Context context, Intent intent) { 221 final UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER); 222 if (UserUtil.isManagedProfile(context, initiatingUser)) { 223 boolean noDialerInstalled = DefaultDialerManager.getInstalledDialerApplications(context, 224 initiatingUser.getIdentifier()).size() == 0; 225 if (noDialerInstalled) { 226 final UserManager userManager = UserManager.get(context); 227 UserHandle parentUserHandle = 228 userManager.getProfileParent( 229 initiatingUser.getIdentifier()).getUserHandle(); 230 intent.putExtra(KEY_INITIATING_USER, parentUserHandle); 231 232 Log.i(CallIntentProcessor.class, "fixInitiatingUserIfNecessary: no dialer installed" 233 + " for current user; setting initiator to parent %s" + parentUserHandle); 234 return true; 235 } 236 } 237 return false; 238 } 239 240 static void processIncomingCallIntent(CallsManager callsManager, Intent intent) { 241 PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra( 242 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); 243 244 if (phoneAccountHandle == null) { 245 Log.w(CallIntentProcessor.class, 246 "Rejecting incoming call due to null phone account"); 247 return; 248 } 249 if (phoneAccountHandle.getComponentName() == null) { 250 Log.w(CallIntentProcessor.class, 251 "Rejecting incoming call due to null component name"); 252 return; 253 } 254 255 Bundle clientExtras = null; 256 if (intent.hasExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS)) { 257 clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS); 258 } 259 if (clientExtras == null) { 260 clientExtras = new Bundle(); 261 } 262 263 Log.d(CallIntentProcessor.class, 264 "Processing incoming call from connection service [%s]", 265 phoneAccountHandle.getComponentName()); 266 callsManager.processIncomingCallIntent(phoneAccountHandle, clientExtras); 267 } 268 269 static void processUnknownCallIntent(CallsManager callsManager, Intent intent) { 270 PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra( 271 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); 272 273 if (phoneAccountHandle == null) { 274 Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null phone account"); 275 return; 276 } 277 if (phoneAccountHandle.getComponentName() == null) { 278 Log.w(CallIntentProcessor.class, "Rejecting unknown call due to null component name"); 279 return; 280 } 281 282 callsManager.addNewUnknownCall(phoneAccountHandle, intent.getExtras()); 283 } 284 285 private static void showErrorDialog(Context context, int errorCode) { 286 final Intent errorIntent = new Intent(context, ErrorDialogActivity.class); 287 int errorMessageId = -1; 288 switch (errorCode) { 289 case DisconnectCause.INVALID_NUMBER: 290 case DisconnectCause.NO_PHONE_NUMBER_SUPPLIED: 291 errorMessageId = R.string.outgoing_call_error_no_phone_number_supplied; 292 break; 293 } 294 if (errorMessageId != -1) { 295 errorIntent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_ID_EXTRA, errorMessageId); 296 errorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 297 context.startActivityAsUser(errorIntent, UserHandle.CURRENT); 298 } 299 } 300 } 301