1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.telecom; 18 19 import android.Manifest; 20 import android.app.Activity; 21 import android.app.AppOpsManager; 22 import android.app.BroadcastOptions; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.net.Uri; 27 import android.os.Bundle; 28 import android.os.UserHandle; 29 import android.telecom.GatewayInfo; 30 import android.telecom.Log; 31 import android.telecom.PhoneAccount; 32 import android.telecom.PhoneAccountHandle; 33 import android.telecom.TelecomManager; 34 import android.telecom.VideoProfile; 35 import android.telephony.DisconnectCause; 36 import android.telephony.TelephonyManager; 37 import android.text.TextUtils; 38 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.server.telecom.flags.FeatureFlags; 41 import com.android.server.telecom.callredirection.CallRedirectionProcessor; 42 43 // TODO: Needed for move to system service: import com.android.internal.R; 44 45 /** 46 * OutgoingCallIntentBroadcaster receives CALL and CALL_PRIVILEGED Intents, and broadcasts the 47 * ACTION_NEW_OUTGOING_CALL intent. ACTION_NEW_OUTGOING_CALL is an ordered broadcast intent which 48 * contains the phone number being dialed. Applications can use this intent to (1) see which numbers 49 * are being dialed, (2) redirect a call (change the number being dialed), or (3) prevent a call 50 * from being placed. 51 * 52 * After the other applications have had a chance to see the ACTION_NEW_OUTGOING_CALL intent, it 53 * finally reaches the {@link NewOutgoingCallBroadcastIntentReceiver}. 54 * 55 * Calls where no number is present (like for a CDMA "empty flash" or a nonexistent voicemail 56 * number) are exempt from being broadcast. 57 * 58 * Calls to emergency numbers are still broadcast for informative purposes. The call is placed 59 * prior to sending ACTION_NEW_OUTGOING_CALL and cannot be redirected nor prevented. 60 */ 61 @VisibleForTesting 62 public class NewOutgoingCallIntentBroadcaster { 63 /** 64 * Legacy string constants used to retrieve gateway provider extras from intents. These still 65 * need to be copied from the source call intent to the destination intent in order to 66 * support third party gateway providers that are still using old string constants in 67 * Telephony. 68 */ 69 public static final String EXTRA_GATEWAY_PROVIDER_PACKAGE = 70 "com.android.phone.extra.GATEWAY_PROVIDER_PACKAGE"; 71 public static final String EXTRA_GATEWAY_URI = "com.android.phone.extra.GATEWAY_URI"; 72 73 private final CallsManager mCallsManager; 74 private Call mCall; 75 private final Intent mIntent; 76 private final Context mContext; 77 private final PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter; 78 private final TelecomSystem.SyncRoot mLock; 79 private final DefaultDialerCache mDefaultDialerCache; 80 private final MmiUtils mMmiUtils; 81 private final FeatureFlags mFeatureFlags; 82 83 /* 84 * Whether or not the outgoing call intent originated from the default phone application. If 85 * so, it will be allowed to make emergency calls, even with the ACTION_CALL intent. 86 */ 87 private final boolean mIsDefaultOrSystemPhoneApp; 88 89 public static class CallDisposition { 90 // True for certain types of numbers that are not intended to be intercepted or modified 91 // by third parties (e.g. emergency numbers). 92 public boolean callImmediately = false; 93 // True for all managed calls, false for self-managed calls. 94 public boolean sendBroadcast = true; 95 // True for requesting call redirection, false for not requesting it. 96 public boolean requestRedirection = true; 97 public int disconnectCause = DisconnectCause.NOT_DISCONNECTED; 98 String number; 99 Uri callingAddress; 100 } 101 102 @VisibleForTesting NewOutgoingCallIntentBroadcaster(Context context, CallsManager callsManager, Intent intent, PhoneNumberUtilsAdapter phoneNumberUtilsAdapter, boolean isDefaultPhoneApp, DefaultDialerCache defaultDialerCache, MmiUtils mmiUtils, FeatureFlags featureFlags)103 public NewOutgoingCallIntentBroadcaster(Context context, CallsManager callsManager, 104 Intent intent, PhoneNumberUtilsAdapter phoneNumberUtilsAdapter, 105 boolean isDefaultPhoneApp, DefaultDialerCache defaultDialerCache, MmiUtils mmiUtils, 106 FeatureFlags featureFlags) { 107 mContext = context; 108 mCallsManager = callsManager; 109 mIntent = intent; 110 mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter; 111 mIsDefaultOrSystemPhoneApp = isDefaultPhoneApp; 112 mLock = mCallsManager.getLock(); 113 mDefaultDialerCache = defaultDialerCache; 114 mMmiUtils = mmiUtils; 115 mFeatureFlags = featureFlags; 116 } 117 118 /** 119 * Processes the result of the outgoing call broadcast intent, and performs callbacks to 120 * the OutgoingCallIntentBroadcasterListener as necessary. 121 */ 122 public class NewOutgoingCallBroadcastIntentReceiver extends BroadcastReceiver { 123 124 @Override onReceive(Context context, Intent intent)125 public void onReceive(Context context, Intent intent) { 126 try { 127 Log.startSession("NOCBIR.oR"); 128 synchronized (mLock) { 129 Log.v(this, "onReceive: %s", intent); 130 131 // Once the NEW_OUTGOING_CALL broadcast is finished, the resultData is 132 // used as the actual number to call. (If null, no call will be placed.) 133 String resultNumber = getResultData(); 134 Log.i(NewOutgoingCallIntentBroadcaster.this, 135 "Received new-outgoing-call-broadcast for %s with data %s", mCall, 136 Log.pii(resultNumber)); 137 138 boolean endEarly = false; 139 long disconnectTimeout = 140 Timeouts.getNewOutgoingCallCancelMillis(mContext.getContentResolver()); 141 if (resultNumber == null) { 142 Log.v(this, "Call cancelled (null number), returning..."); 143 disconnectTimeout = getDisconnectTimeoutFromApp( 144 getResultExtras(false), disconnectTimeout); 145 endEarly = true; 146 } else if (isEmergencyNumber(resultNumber)) { 147 Log.w(this, "Cannot modify outgoing call to emergency number %s.", 148 resultNumber); 149 disconnectTimeout = 0; 150 endEarly = true; 151 } 152 153 if (endEarly) { 154 if (mCall != null) { 155 mCall.disconnect(disconnectTimeout); 156 } 157 return; 158 } 159 160 // If this call is already disconnected then we have nothing more to do. 161 if (mCall.isDisconnected()) { 162 Log.w(this, "Call has already been disconnected," + 163 " ignore the broadcast Call %s", mCall); 164 return; 165 } 166 167 // TODO: Remove the assumption that phone numbers are either SIP or TEL. 168 // This does not impact self-managed ConnectionServices as they do not use the 169 // NewOutgoingCallIntentBroadcaster. 170 Uri resultHandleUri = Uri.fromParts( 171 mPhoneNumberUtilsAdapter.isUriNumber(resultNumber) ? 172 PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, 173 resultNumber, null); 174 175 Uri originalUri = mIntent.getData(); 176 177 if (originalUri.getSchemeSpecificPart().equals(resultNumber)) { 178 Log.v(this, "Call number unmodified after" + 179 " new outgoing call intent broadcast."); 180 } else { 181 Log.v(this, "Retrieved modified handle after outgoing call intent" + 182 " broadcast: Original: %s, Modified: %s", 183 Log.pii(originalUri), 184 Log.pii(resultHandleUri)); 185 } 186 187 GatewayInfo gatewayInfo = getGateWayInfoFromIntent(intent, resultHandleUri); 188 placeOutgoingCallImmediately(mCall, resultHandleUri, gatewayInfo, 189 mIntent.getBooleanExtra( 190 TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false), 191 mIntent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, 192 VideoProfile.STATE_AUDIO_ONLY)); 193 } 194 } finally { 195 Log.endSession(); 196 } 197 } 198 } 199 200 /** 201 * Processes the supplied intent and starts the outgoing call broadcast process relevant to the 202 * intent. 203 * 204 * This method will handle three kinds of actions: 205 * 206 * - CALL (intent launched by all third party dialers) 207 * - CALL_PRIVILEGED (intent launched by system apps e.g. system Dialer, voice Dialer) 208 * - CALL_EMERGENCY (intent launched by lock screen emergency dialer) 209 * 210 * @return {@link DisconnectCause#NOT_DISCONNECTED} if the call succeeded, and an appropriate 211 * {@link DisconnectCause} if the call did not, describing why it failed. 212 */ 213 @VisibleForTesting evaluateCall()214 public CallDisposition evaluateCall() { 215 CallDisposition result = new CallDisposition(); 216 217 Intent intent = mIntent; 218 String action = intent.getAction(); 219 final Uri handle = intent.getData(); 220 221 if (handle == null) { 222 Log.w(this, "Empty handle obtained from the call intent."); 223 result.disconnectCause = DisconnectCause.INVALID_NUMBER; 224 return result; 225 } 226 227 boolean isVoicemailNumber = PhoneAccount.SCHEME_VOICEMAIL.equals(handle.getScheme()); 228 if (isVoicemailNumber) { 229 if (Intent.ACTION_CALL.equals(action) 230 || Intent.ACTION_CALL_PRIVILEGED.equals(action)) { 231 // Voicemail calls will be handled directly by the telephony connection manager 232 Log.i(this, "Voicemail number dialed. Skipping redirection and broadcast", intent); 233 mIntent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, 234 VideoProfile.STATE_AUDIO_ONLY); 235 result.callImmediately = true; 236 result.requestRedirection = false; 237 result.sendBroadcast = false; 238 result.callingAddress = handle; 239 return result; 240 } else { 241 Log.i(this, "Unhandled intent %s. Ignoring and not placing call.", intent); 242 result.disconnectCause = DisconnectCause.OUTGOING_CANCELED; 243 return result; 244 } 245 } 246 247 PhoneAccountHandle targetPhoneAccount = mIntent.getParcelableExtra( 248 TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); 249 boolean isSelfManaged = false; 250 if (targetPhoneAccount != null) { 251 PhoneAccount phoneAccount = 252 mCallsManager.getPhoneAccountRegistrar().getPhoneAccountUnchecked( 253 targetPhoneAccount); 254 if (phoneAccount != null) { 255 isSelfManaged = phoneAccount.isSelfManaged(); 256 } 257 } 258 259 result.number = ""; 260 result.callingAddress = handle; 261 262 if (isSelfManaged) { 263 // Self-managed call. 264 result.callImmediately = true; 265 result.sendBroadcast = false; 266 result.requestRedirection = false; 267 Log.i(this, "Skipping NewOutgoingCallBroadcast for self-managed call."); 268 return result; 269 } 270 271 // Placing a managed call 272 String number = getNumberFromCallIntent(intent); 273 result.number = number; 274 if (number == null) { 275 result.disconnectCause = DisconnectCause.NO_PHONE_NUMBER_SUPPLIED; 276 return result; 277 } 278 279 final boolean isEmergencyNumber = isEmergencyNumber(number); 280 Log.v(this, "isEmergencyNumber = %s", isEmergencyNumber); 281 282 action = calculateCallIntentAction(intent, isEmergencyNumber); 283 intent.setAction(action); 284 285 if (Intent.ACTION_CALL.equals(action)) { 286 if (isEmergencyNumber) { 287 if (!mIsDefaultOrSystemPhoneApp) { 288 Log.w(this, "Cannot call potential emergency number %s with CALL Intent %s " 289 + "unless caller is system or default dialer.", number, intent); 290 launchSystemDialer(intent.getData()); 291 result.disconnectCause = DisconnectCause.OUTGOING_CANCELED; 292 return result; 293 } else { 294 result.callImmediately = true; 295 result.requestRedirection = false; 296 } 297 } else if (mMmiUtils.isDangerousMmiOrVerticalCode(intent.getData())) { 298 if (!mIsDefaultOrSystemPhoneApp) { 299 Log.w(this, 300 "Potentially dangerous MMI code %s with CALL Intent %s can only be " 301 + "sent if caller is the system or default dialer", 302 number, intent); 303 launchSystemDialer(intent.getData()); 304 result.disconnectCause = DisconnectCause.OUTGOING_CANCELED; 305 return result; 306 } 307 } 308 } else if (Intent.ACTION_CALL_EMERGENCY.equals(action)) { 309 if (!isEmergencyNumber) { 310 Log.w(this, "Cannot call non-potential-emergency number %s with EMERGENCY_CALL " 311 + "Intent %s.", number, intent); 312 result.disconnectCause = DisconnectCause.OUTGOING_CANCELED; 313 return result; 314 } 315 result.callImmediately = true; 316 result.requestRedirection = false; 317 } else { 318 Log.w(this, "Unhandled Intent %s. Ignoring and not placing call.", intent); 319 result.disconnectCause = DisconnectCause.INVALID_NUMBER; 320 return result; 321 } 322 323 String scheme = mPhoneNumberUtilsAdapter.isUriNumber(number) 324 ? PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL; 325 result.callingAddress = Uri.fromParts(scheme, number, null); 326 327 return result; 328 } 329 getNumberFromCallIntent(Intent intent)330 private String getNumberFromCallIntent(Intent intent) { 331 String number = null; 332 333 Uri uri = intent.getData(); 334 if (uri != null) { 335 String scheme = uri.getScheme(); 336 if (scheme != null) { 337 if (scheme.equals("tel") || scheme.equals("sip")) { 338 number = uri.getSchemeSpecificPart(); 339 } 340 } 341 } 342 343 if (TextUtils.isEmpty(number)) { 344 Log.w(this, "Empty number obtained from the call intent."); 345 return null; 346 } 347 348 boolean isUriNumber = mPhoneNumberUtilsAdapter.isUriNumber(number); 349 if (!isUriNumber) { 350 number = mPhoneNumberUtilsAdapter.convertKeypadLettersToDigits(number); 351 number = mPhoneNumberUtilsAdapter.stripSeparators(number); 352 } 353 return number; 354 } 355 processCall(Call call, CallDisposition disposition)356 public void processCall(Call call, CallDisposition disposition) { 357 mCall = call; 358 359 // If the new outgoing call broadast doesn't block, trigger the legacy process call 360 // behavior and exit out here. 361 if (!mFeatureFlags.isNewOutgoingCallBroadcastUnblocking()) { 362 legacyProcessCall(disposition); 363 return; 364 } 365 boolean callRedirectionWithService = false; 366 // Only try to do redirection if it was requested and we're not calling immediately. 367 // We can expect callImmediately to be true for emergency calls and voip calls. 368 if (disposition.requestRedirection && !disposition.callImmediately) { 369 CallRedirectionProcessor callRedirectionProcessor = new CallRedirectionProcessor( 370 mContext, mCallsManager, mCall, disposition.callingAddress, 371 mCallsManager.getPhoneAccountRegistrar(), 372 getGateWayInfoFromIntent(mIntent, mIntent.getData()), 373 mIntent.getBooleanExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, 374 false), 375 mIntent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, 376 VideoProfile.STATE_AUDIO_ONLY)); 377 /** 378 * If there is an available {@link android.telecom.CallRedirectionService}, use the 379 * {@link CallRedirectionProcessor} to perform call redirection instead of using 380 * broadcasting. 381 */ 382 callRedirectionWithService = callRedirectionProcessor 383 .canMakeCallRedirectionWithServiceAsUser(mCall.getAssociatedUser()); 384 if (callRedirectionWithService) { 385 callRedirectionProcessor.performCallRedirection(mCall.getAssociatedUser()); 386 } 387 } 388 389 // If no redirection was kicked off, place the call now. 390 if (!callRedirectionWithService) { 391 callImmediately(disposition); 392 } 393 394 // Finally, send the non-blocking broadcast if we're supposed to (ie for any non-voip call). 395 if (disposition.sendBroadcast) { 396 UserHandle targetUser = mCall.getAssociatedUser(); 397 broadcastIntent(mIntent, disposition.number, false /* receiverRequired */, targetUser); 398 } 399 } 400 401 /** 402 * The legacy non-flagged version of processing a call. Although there is some code duplication 403 * if makes the new flow cleaner to read. 404 * @param disposition 405 */ legacyProcessCall(CallDisposition disposition)406 private void legacyProcessCall(CallDisposition disposition) { 407 if (disposition.callImmediately) { 408 callImmediately(disposition); 409 410 // Don't return but instead continue and send the ACTION_NEW_OUTGOING_CALL broadcast 411 // so that third parties can still inspect (but not intercept) the outgoing call. When 412 // the broadcast finally reaches the OutgoingCallBroadcastReceiver, we'll know not to 413 // initiate the call again because of the presence of the EXTRA_ALREADY_CALLED extra. 414 } 415 416 boolean callRedirectionWithService = false; 417 if (disposition.requestRedirection) { 418 CallRedirectionProcessor callRedirectionProcessor = new CallRedirectionProcessor( 419 mContext, mCallsManager, mCall, disposition.callingAddress, 420 mCallsManager.getPhoneAccountRegistrar(), 421 getGateWayInfoFromIntent(mIntent, mIntent.getData()), 422 mIntent.getBooleanExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, 423 false), 424 mIntent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, 425 VideoProfile.STATE_AUDIO_ONLY)); 426 /** 427 * If there is an available {@link android.telecom.CallRedirectionService}, use the 428 * {@link CallRedirectionProcessor} to perform call redirection instead of using 429 * broadcasting. 430 */ 431 callRedirectionWithService = callRedirectionProcessor 432 .canMakeCallRedirectionWithServiceAsUser(mCall.getAssociatedUser()); 433 if (callRedirectionWithService) { 434 callRedirectionProcessor.performCallRedirection(mCall.getAssociatedUser()); 435 } 436 } 437 438 if (disposition.sendBroadcast) { 439 UserHandle targetUser = mCall.getAssociatedUser(); 440 broadcastIntent(mIntent, disposition.number, 441 !disposition.callImmediately && !callRedirectionWithService, targetUser); 442 } 443 } 444 445 /** 446 * Place a call immediately. 447 * @param disposition The disposition; used for retrieving the address of the call. 448 */ callImmediately(CallDisposition disposition)449 private void callImmediately(CallDisposition disposition) { 450 boolean speakerphoneOn = mIntent.getBooleanExtra( 451 TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false); 452 int videoState = mIntent.getIntExtra( 453 TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, 454 VideoProfile.STATE_AUDIO_ONLY); 455 placeOutgoingCallImmediately(mCall, disposition.callingAddress, null, 456 speakerphoneOn, videoState); 457 } 458 459 /** 460 * Sends a new outgoing call ordered broadcast so that third party apps can cancel the 461 * placement of the call or redirect it to a different number. 462 * 463 * @param originalCallIntent The original call intent. 464 * @param number Call number that was stored in the original call intent. 465 * @param receiverRequired Whether or not the result from the ordered broadcast should be 466 * processed using a {@link NewOutgoingCallIntentBroadcaster}. 467 * @param targetUser User that the broadcast sent to. 468 */ broadcastIntent( Intent originalCallIntent, String number, boolean receiverRequired, UserHandle targetUser)469 private void broadcastIntent( 470 Intent originalCallIntent, 471 String number, 472 boolean receiverRequired, 473 UserHandle targetUser) { 474 Intent broadcastIntent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL); 475 if (number != null) { 476 broadcastIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number); 477 } 478 Log.v(this, "Broadcasting intent: %s.", broadcastIntent); 479 480 checkAndCopyProviderExtras(originalCallIntent, broadcastIntent); 481 482 if (mFeatureFlags.isNewOutgoingCallBroadcastUnblocking()) { 483 // Where the new outgoing call broadcast is unblocking, do not give receiver FG priority 484 // and do not allow background activity starts. 485 broadcastIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 486 Log.i(this, "broadcastIntent: Sending non-blocking for %s to %s", mCall.getId(), 487 targetUser); 488 if (mFeatureFlags.telecomResolveHiddenDependencies()) { 489 mContext.sendBroadcastAsUser( 490 broadcastIntent, 491 targetUser, 492 Manifest.permission.PROCESS_OUTGOING_CALLS); 493 } else { 494 mContext.sendBroadcastAsUser( 495 broadcastIntent, 496 targetUser, 497 android.Manifest.permission.PROCESS_OUTGOING_CALLS, 498 AppOpsManager.OP_PROCESS_OUTGOING_CALLS); // initialExtras 499 } 500 } else { 501 Log.i(this, "broadcastIntent: Sending ordered for %s to %s, waitForResult=%b", 502 mCall.getId(), targetUser, receiverRequired); 503 final BroadcastOptions options = BroadcastOptions.makeBasic(); 504 options.setBackgroundActivityStartsAllowed(true); 505 // Force receivers of this broadcast intent to run at foreground priority because we 506 // want to finish processing the broadcast intent as soon as possible. 507 broadcastIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND 508 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 509 510 mContext.sendOrderedBroadcastAsUser( 511 broadcastIntent, 512 targetUser, 513 android.Manifest.permission.PROCESS_OUTGOING_CALLS, 514 AppOpsManager.OP_PROCESS_OUTGOING_CALLS, 515 options.toBundle(), 516 receiverRequired ? new NewOutgoingCallBroadcastIntentReceiver() : null, 517 null, // scheduler 518 Activity.RESULT_OK, // initialCode 519 number, // initialData: initial value for the result data (number to be 520 // modified) 521 null); // initialExtras 522 } 523 } 524 525 /** 526 * Copy all the expected extras set when a 3rd party gateway provider is to be used, from the 527 * source intent to the destination one. 528 * 529 * @param src Intent which may contain the provider's extras. 530 * @param dst Intent where a copy of the extras will be added if applicable. 531 */ checkAndCopyProviderExtras(Intent src, Intent dst)532 public void checkAndCopyProviderExtras(Intent src, Intent dst) { 533 if (src == null) { 534 return; 535 } 536 if (hasGatewayProviderExtras(src)) { 537 dst.putExtra(EXTRA_GATEWAY_PROVIDER_PACKAGE, 538 src.getStringExtra(EXTRA_GATEWAY_PROVIDER_PACKAGE)); 539 dst.putExtra(EXTRA_GATEWAY_URI, 540 src.getStringExtra(EXTRA_GATEWAY_URI)); 541 Log.d(this, "Found and copied gateway provider extras to broadcast intent."); 542 return; 543 } 544 545 Log.d(this, "No provider extras found in call intent."); 546 } 547 548 /** 549 * Check if valid gateway provider information is stored as extras in the intent 550 * 551 * @param intent to check for 552 * @return true if the intent has all the gateway information extras needed. 553 */ hasGatewayProviderExtras(Intent intent)554 private boolean hasGatewayProviderExtras(Intent intent) { 555 final String name = intent.getStringExtra(EXTRA_GATEWAY_PROVIDER_PACKAGE); 556 final String uriString = intent.getStringExtra(EXTRA_GATEWAY_URI); 557 558 return !TextUtils.isEmpty(name) && !TextUtils.isEmpty(uriString); 559 } 560 getGatewayUriFromString(String gatewayUriString)561 private static Uri getGatewayUriFromString(String gatewayUriString) { 562 return TextUtils.isEmpty(gatewayUriString) ? null : Uri.parse(gatewayUriString); 563 } 564 565 /** 566 * Extracts gateway provider information from a provided intent.. 567 * 568 * @param intent to extract gateway provider information from. 569 * @param trueHandle The actual call handle that the user is trying to dial 570 * @return GatewayInfo object containing extracted gateway provider information as well as 571 * the actual handle the user is trying to dial. 572 */ getGateWayInfoFromIntent(Intent intent, Uri trueHandle)573 public static GatewayInfo getGateWayInfoFromIntent(Intent intent, Uri trueHandle) { 574 if (intent == null) { 575 return null; 576 } 577 578 // Check if gateway extras are present. 579 String gatewayPackageName = intent.getStringExtra(EXTRA_GATEWAY_PROVIDER_PACKAGE); 580 Uri gatewayUri = getGatewayUriFromString(intent.getStringExtra(EXTRA_GATEWAY_URI)); 581 if (!TextUtils.isEmpty(gatewayPackageName) && gatewayUri != null) { 582 return new GatewayInfo(gatewayPackageName, gatewayUri, trueHandle); 583 } 584 585 return null; 586 } 587 placeOutgoingCallImmediately(Call call, Uri handle, GatewayInfo gatewayInfo, boolean speakerphoneOn, int videoState)588 private void placeOutgoingCallImmediately(Call call, Uri handle, GatewayInfo gatewayInfo, 589 boolean speakerphoneOn, int videoState) { 590 Log.i(this, 591 "Placing call immediately instead of waiting for OutgoingCallBroadcastReceiver"); 592 // Since we are not going to go through "Outgoing call broadcast", make sure 593 // we mark it as ready. 594 mCall.setNewOutgoingCallIntentBroadcastIsDone(); 595 mCallsManager.placeOutgoingCall(call, handle, gatewayInfo, speakerphoneOn, videoState); 596 } 597 launchSystemDialer(Uri handle)598 private void launchSystemDialer(Uri handle) { 599 Intent systemDialerIntent = new Intent(); 600 systemDialerIntent.setComponent(mDefaultDialerCache.getDialtactsSystemDialerComponent()); 601 systemDialerIntent.setAction(Intent.ACTION_DIAL); 602 systemDialerIntent.setData(handle); 603 systemDialerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 604 Log.v(this, "calling startActivity for default dialer: %s", systemDialerIntent); 605 mContext.startActivityAsUser(systemDialerIntent, UserHandle.CURRENT); 606 } 607 608 /** 609 * Check whether or not this is an emergency number, in order to enforce the restriction 610 * that only the CALL_PRIVILEGED and CALL_EMERGENCY intents are allowed to make emergency 611 * calls. 612 * 613 * @param number number to inspect in order to determine whether or not an emergency number 614 * is being dialed 615 * @return True if the handle is an emergency number. 616 */ isEmergencyNumber(String number)617 private boolean isEmergencyNumber(String number) { 618 Log.v(this, "Checking restrictions for number : %s", Log.pii(number)); 619 if (number == null) return false; 620 try { 621 return mContext.getSystemService(TelephonyManager.class).isEmergencyNumber( 622 number); 623 } catch (UnsupportedOperationException uoe) { 624 Log.w(this, "isEmergencyNumber: Telephony not supported"); 625 return false; 626 } catch (Exception e) { 627 Log.e(this, e, "isEmergencyNumber: Telephony threw an exception."); 628 return false; 629 } 630 } 631 632 /** 633 * Given a call intent and whether or not the number to dial is an emergency number, determine 634 * the appropriate call intent action. 635 * 636 * @param intent Intent to evaluate 637 * @param isEmergencyNumber Whether or not the number is an emergency 638 * number. 639 * @return The appropriate action. 640 */ calculateCallIntentAction(Intent intent, boolean isEmergencyNumber)641 private String calculateCallIntentAction(Intent intent, boolean isEmergencyNumber) { 642 String action = intent.getAction(); 643 644 /* Change CALL_PRIVILEGED into CALL or CALL_EMERGENCY as needed. */ 645 if (Intent.ACTION_CALL_PRIVILEGED.equals(action)) { 646 if (isEmergencyNumber) { 647 Log.i(this, "ACTION_CALL_PRIVILEGED is used while the number is a" 648 + " emergency number. Using ACTION_CALL_EMERGENCY as an action instead."); 649 action = Intent.ACTION_CALL_EMERGENCY; 650 } else { 651 action = Intent.ACTION_CALL; 652 } 653 Log.v(this, " - updating action from CALL_PRIVILEGED to %s", action); 654 } 655 return action; 656 } 657 getDisconnectTimeoutFromApp(Bundle resultExtras, long defaultTimeout)658 private long getDisconnectTimeoutFromApp(Bundle resultExtras, long defaultTimeout) { 659 if (resultExtras != null) { 660 long disconnectTimeout = resultExtras.getLong( 661 TelecomManager.EXTRA_NEW_OUTGOING_CALL_CANCEL_TIMEOUT, defaultTimeout); 662 if (disconnectTimeout < 0) { 663 disconnectTimeout = 0; 664 } 665 return Math.min(disconnectTimeout, 666 Timeouts.getMaxNewOutgoingCallCancelMillis(mContext.getContentResolver())); 667 } else { 668 return defaultTimeout; 669 } 670 } 671 } 672