1 /* 2 * Copyright (C) 2008 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.phone; 18 19 import android.app.Activity; 20 import android.app.AlertDialog; 21 import android.app.Dialog; 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.DialogInterface; 25 import android.content.Intent; 26 import android.content.res.Configuration; 27 import android.net.Uri; 28 import android.os.Bundle; 29 import android.os.Handler; 30 import android.os.Message; 31 import android.os.SystemProperties; 32 import android.telephony.PhoneNumberUtils; 33 import android.text.TextUtils; 34 import android.util.Log; 35 import android.view.View; 36 import android.widget.ProgressBar; 37 38 import com.android.internal.telephony.Phone; 39 import com.android.internal.telephony.TelephonyCapabilities; 40 41 /** 42 * OutgoingCallBroadcaster receives CALL and CALL_PRIVILEGED Intents, and 43 * broadcasts the ACTION_NEW_OUTGOING_CALL intent which allows other 44 * applications to monitor, redirect, or prevent the outgoing call. 45 46 * After the other applications have had a chance to see the 47 * ACTION_NEW_OUTGOING_CALL intent, it finally reaches the 48 * {@link OutgoingCallReceiver}, which passes the (possibly modified) 49 * intent on to the {@link SipCallOptionHandler}, which will 50 * ultimately start the call using the CallController.placeCall() API. 51 * 52 * Emergency calls and calls where no number is present (like for a CDMA 53 * "empty flash" or a nonexistent voicemail number) are exempt from being 54 * broadcast. 55 */ 56 public class OutgoingCallBroadcaster extends Activity 57 implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener { 58 59 private static final String PERMISSION = android.Manifest.permission.PROCESS_OUTGOING_CALLS; 60 private static final String TAG = "OutgoingCallBroadcaster"; 61 private static final boolean DBG = 62 (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1); 63 // Do not check in with VDBG = true, since that may write PII to the system log. 64 private static final boolean VDBG = false; 65 66 public static final String ACTION_SIP_SELECT_PHONE = "com.android.phone.SIP_SELECT_PHONE"; 67 public static final String EXTRA_ALREADY_CALLED = "android.phone.extra.ALREADY_CALLED"; 68 public static final String EXTRA_ORIGINAL_URI = "android.phone.extra.ORIGINAL_URI"; 69 public static final String EXTRA_NEW_CALL_INTENT = "android.phone.extra.NEW_CALL_INTENT"; 70 public static final String EXTRA_SIP_PHONE_URI = "android.phone.extra.SIP_PHONE_URI"; 71 public static final String EXTRA_ACTUAL_NUMBER_TO_DIAL = 72 "android.phone.extra.ACTUAL_NUMBER_TO_DIAL"; 73 74 /** 75 * Identifier for intent extra for sending an empty Flash message for 76 * CDMA networks. This message is used by the network to simulate a 77 * press/depress of the "hookswitch" of a landline phone. Aka "empty flash". 78 * 79 * TODO: Receiving an intent extra to tell the phone to send this flash is a 80 * temporary measure. To be replaced with an external ITelephony call in the future. 81 * TODO: Keep in sync with the string defined in TwelveKeyDialer.java in Contacts app 82 * until this is replaced with the ITelephony API. 83 */ 84 public static final String EXTRA_SEND_EMPTY_FLASH = 85 "com.android.phone.extra.SEND_EMPTY_FLASH"; 86 87 // Dialog IDs 88 private static final int DIALOG_NOT_VOICE_CAPABLE = 1; 89 90 /** Note message codes < 100 are reserved for the PhoneApp. */ 91 private static final int EVENT_OUTGOING_CALL_TIMEOUT = 101; 92 private static final int OUTGOING_CALL_TIMEOUT_THRESHOLD = 2000; // msec 93 /** 94 * ProgressBar object with "spinner" style, which will be shown if we take more than 95 * {@link #EVENT_OUTGOING_CALL_TIMEOUT} msec to handle the incoming Intent. 96 */ 97 private ProgressBar mWaitingSpinner; 98 private final Handler mHandler = new Handler() { 99 @Override 100 public void handleMessage(Message msg) { 101 if (msg.what == EVENT_OUTGOING_CALL_TIMEOUT) { 102 Log.i(TAG, "Outgoing call takes too long. Showing the spinner."); 103 mWaitingSpinner.setVisibility(View.VISIBLE); 104 } else { 105 Log.wtf(TAG, "Unknown message id: " + msg.what); 106 } 107 } 108 }; 109 110 /** 111 * OutgoingCallReceiver finishes NEW_OUTGOING_CALL broadcasts, starting 112 * the InCallScreen if the broadcast has not been canceled, possibly with 113 * a modified phone number and optional provider info (uri + package name + remote views.) 114 */ 115 public class OutgoingCallReceiver extends BroadcastReceiver { 116 private static final String TAG = "OutgoingCallReceiver"; 117 118 @Override 119 public void onReceive(Context context, Intent intent) { 120 mHandler.removeMessages(EVENT_OUTGOING_CALL_TIMEOUT); 121 doReceive(context, intent); 122 if (DBG) Log.v(TAG, "OutgoingCallReceiver is going to finish the Activity itself."); 123 finish(); 124 } 125 126 public void doReceive(Context context, Intent intent) { 127 if (DBG) Log.v(TAG, "doReceive: " + intent); 128 129 boolean alreadyCalled; 130 String number; 131 String originalUri; 132 133 alreadyCalled = intent.getBooleanExtra( 134 OutgoingCallBroadcaster.EXTRA_ALREADY_CALLED, false); 135 if (alreadyCalled) { 136 if (DBG) Log.v(TAG, "CALL already placed -- returning."); 137 return; 138 } 139 140 // Once the NEW_OUTGOING_CALL broadcast is finished, the resultData 141 // is used as the actual number to call. (If null, no call will be 142 // placed.) 143 144 number = getResultData(); 145 if (VDBG) Log.v(TAG, "- got number from resultData: '" + number + "'"); 146 147 final PhoneApp app = PhoneApp.getInstance(); 148 149 // OTASP-specific checks. 150 // TODO: This should probably all happen in 151 // OutgoingCallBroadcaster.onCreate(), since there's no reason to 152 // even bother with the NEW_OUTGOING_CALL broadcast if we're going 153 // to disallow the outgoing call anyway... 154 if (TelephonyCapabilities.supportsOtasp(app.phone)) { 155 boolean activateState = (app.cdmaOtaScreenState.otaScreenState 156 == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION); 157 boolean dialogState = (app.cdmaOtaScreenState.otaScreenState 158 == OtaUtils.CdmaOtaScreenState.OtaScreenState 159 .OTA_STATUS_SUCCESS_FAILURE_DLG); 160 boolean isOtaCallActive = false; 161 162 // TODO: Need cleaner way to check if OTA is active. 163 // Also, this check seems to be broken in one obscure case: if 164 // you interrupt an OTASP call by pressing Back then Skip, 165 // otaScreenState somehow gets left in either PROGRESS or 166 // LISTENING. 167 if ((app.cdmaOtaScreenState.otaScreenState 168 == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_PROGRESS) 169 || (app.cdmaOtaScreenState.otaScreenState 170 == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_LISTENING)) { 171 isOtaCallActive = true; 172 } 173 174 if (activateState || dialogState) { 175 // The OTASP sequence is active, but either (1) the call 176 // hasn't started yet, or (2) the call has ended and we're 177 // showing the success/failure screen. In either of these 178 // cases it's OK to make a new outgoing call, but we need 179 // to take down any OTASP-related UI first. 180 if (dialogState) app.dismissOtaDialogs(); 181 app.clearOtaState(); 182 app.clearInCallScreenMode(); 183 } else if (isOtaCallActive) { 184 // The actual OTASP call is active. Don't allow new 185 // outgoing calls at all from this state. 186 Log.w(TAG, "OTASP call is active: disallowing a new outgoing call."); 187 return; 188 } 189 } 190 191 if (number == null) { 192 if (DBG) Log.v(TAG, "CALL cancelled (null number), returning..."); 193 return; 194 } else if (TelephonyCapabilities.supportsOtasp(app.phone) 195 && (app.phone.getState() != Phone.State.IDLE) 196 && (app.phone.isOtaSpNumber(number))) { 197 if (DBG) Log.v(TAG, "Call is active, a 2nd OTA call cancelled -- returning."); 198 return; 199 } else if (PhoneNumberUtils.isPotentialLocalEmergencyNumber(number, context)) { 200 // Just like 3rd-party apps aren't allowed to place emergency 201 // calls via the ACTION_CALL intent, we also don't allow 3rd 202 // party apps to use the NEW_OUTGOING_CALL broadcast to rewrite 203 // an outgoing call into an emergency number. 204 Log.w(TAG, "Cannot modify outgoing call to emergency number " + number + "."); 205 return; 206 } 207 208 originalUri = intent.getStringExtra( 209 OutgoingCallBroadcaster.EXTRA_ORIGINAL_URI); 210 if (originalUri == null) { 211 Log.e(TAG, "Intent is missing EXTRA_ORIGINAL_URI -- returning."); 212 return; 213 } 214 215 Uri uri = Uri.parse(originalUri); 216 217 // We already called convertKeypadLettersToDigits() and 218 // stripSeparators() way back in onCreate(), before we sent out the 219 // NEW_OUTGOING_CALL broadcast. But we need to do it again here 220 // too, since the number might have been modified/rewritten during 221 // the broadcast (and may now contain letters or separators again.) 222 number = PhoneNumberUtils.convertKeypadLettersToDigits(number); 223 number = PhoneNumberUtils.stripSeparators(number); 224 225 if (DBG) Log.v(TAG, "doReceive: proceeding with call..."); 226 if (VDBG) Log.v(TAG, "- uri: " + uri); 227 if (VDBG) Log.v(TAG, "- actual number to dial: '" + number + "'"); 228 229 startSipCallOptionHandler(context, intent, uri, number); 230 } 231 } 232 233 /** 234 * Launch the SipCallOptionHandler, which is the next step(*) in the 235 * outgoing-call sequence after the outgoing call broadcast is 236 * complete. 237 * 238 * (*) We now know exactly what phone number we need to dial, so the next 239 * step is for the SipCallOptionHandler to decide which Phone type (SIP 240 * or PSTN) should be used. (Depending on the user's preferences, this 241 * decision may also involve popping up a dialog to ask the user to 242 * choose what type of call this should be.) 243 * 244 * @param context used for the startActivity() call 245 * 246 * @param intent the intent from the previous step of the outgoing-call 247 * sequence. Normally this will be the NEW_OUTGOING_CALL broadcast intent 248 * that came in to the OutgoingCallReceiver, although it can also be the 249 * original ACTION_CALL intent that started the whole sequence (in cases 250 * where we don't do the NEW_OUTGOING_CALL broadcast at all, like for 251 * emergency numbers or SIP addresses). 252 * 253 * @param uri the data URI from the original CALL intent, presumably either 254 * a tel: or sip: URI. For tel: URIs, note that the scheme-specific part 255 * does *not* necessarily have separators and keypad letters stripped (so 256 * we might see URIs like "tel:(650)%20555-1234" or "tel:1-800-GOOG-411" 257 * here.) 258 * 259 * @param number the actual number (or SIP address) to dial. This is 260 * guaranteed to be either a PSTN phone number with separators stripped 261 * out and keypad letters converted to digits (like "16505551234"), or a 262 * raw SIP address (like "user (at) example.com"). 263 */ 264 private void startSipCallOptionHandler(Context context, Intent intent, 265 Uri uri, String number) { 266 if (VDBG) { 267 Log.i(TAG, "startSipCallOptionHandler..."); 268 Log.i(TAG, "- intent: " + intent); 269 Log.i(TAG, "- uri: " + uri); 270 Log.i(TAG, "- number: " + number); 271 } 272 273 // Create a copy of the original CALL intent that started the whole 274 // outgoing-call sequence. This intent will ultimately be passed to 275 // CallController.placeCall() after the SipCallOptionHandler step. 276 277 Intent newIntent = new Intent(Intent.ACTION_CALL, uri); 278 newIntent.putExtra(EXTRA_ACTUAL_NUMBER_TO_DIAL, number); 279 PhoneUtils.checkAndCopyPhoneProviderExtras(intent, newIntent); 280 281 // Finally, launch the SipCallOptionHandler, with the copy of the 282 // original CALL intent stashed away in the EXTRA_NEW_CALL_INTENT 283 // extra. 284 285 Intent selectPhoneIntent = new Intent(ACTION_SIP_SELECT_PHONE, uri); 286 selectPhoneIntent.setClass(context, SipCallOptionHandler.class); 287 selectPhoneIntent.putExtra(EXTRA_NEW_CALL_INTENT, newIntent); 288 selectPhoneIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 289 if (DBG) { 290 Log.v(TAG, "startSipCallOptionHandler(): " + 291 "calling startActivity: " + selectPhoneIntent); 292 } 293 context.startActivity(selectPhoneIntent); 294 // ...and see SipCallOptionHandler.onCreate() for the next step of the sequence. 295 } 296 297 /** 298 * This method is the single point of entry for the CALL intent, which is used (by built-in 299 * apps like Contacts / Dialer, as well as 3rd-party apps) to initiate an outgoing voice call. 300 * 301 * 302 */ 303 @Override 304 protected void onCreate(Bundle icicle) { 305 super.onCreate(icicle); 306 setContentView(R.layout.outgoing_call_broadcaster); 307 mWaitingSpinner = (ProgressBar) findViewById(R.id.spinner); 308 309 Intent intent = getIntent(); 310 if (DBG) { 311 final Configuration configuration = getResources().getConfiguration(); 312 Log.v(TAG, "onCreate: this = " + this + ", icicle = " + icicle); 313 Log.v(TAG, " - getIntent() = " + intent); 314 Log.v(TAG, " - configuration = " + configuration); 315 } 316 317 if (icicle != null) { 318 // A non-null icicle means that this activity is being 319 // re-initialized after previously being shut down. 320 // 321 // In practice this happens very rarely (because the lifetime 322 // of this activity is so short!), but it *can* happen if the 323 // framework detects a configuration change at exactly the 324 // right moment; see bug 2202413. 325 // 326 // In this case, do nothing. Our onCreate() method has already 327 // run once (with icicle==null the first time), which means 328 // that the NEW_OUTGOING_CALL broadcast for this new call has 329 // already been sent. 330 Log.i(TAG, "onCreate: non-null icicle! " 331 + "Bailing out, not sending NEW_OUTGOING_CALL broadcast..."); 332 333 // No need to finish() here, since the OutgoingCallReceiver from 334 // our original instance will do that. (It'll actually call 335 // finish() on our original instance, which apparently works fine 336 // even though the ActivityManager has already shut that instance 337 // down. And note that if we *do* call finish() here, that just 338 // results in an "ActivityManager: Duplicate finish request" 339 // warning when the OutgoingCallReceiver runs.) 340 341 return; 342 } 343 344 processIntent(intent); 345 346 // isFinishing() return false when 1. broadcast is still ongoing, or 2. dialog is being 347 // shown. Otherwise finish() is called inside processIntent(), is isFinishing() here will 348 // return true. 349 if (DBG) Log.v(TAG, "At the end of onCreate(). isFinishing(): " + isFinishing()); 350 } 351 352 /** 353 * Interprets a given Intent and starts something relevant to the Intent. 354 * 355 * This method will handle three kinds of actions: 356 * 357 * - CALL (action for usual outgoing voice calls) 358 * - CALL_PRIVILEGED (can come from built-in apps like contacts / voice dialer / bluetooth) 359 * - CALL_EMERGENCY (from the EmergencyDialer that's reachable from the lockscreen.) 360 * 361 * The exact behavior depends on the intent's data: 362 * 363 * - The most typical is a tel: URI, which we handle by starting the 364 * NEW_OUTGOING_CALL broadcast. That broadcast eventually triggers 365 * the sequence OutgoingCallReceiver -> SipCallOptionHandler -> 366 * InCallScreen. 367 * 368 * - Or, with a sip: URI we skip the NEW_OUTGOING_CALL broadcast and 369 * go directly to SipCallOptionHandler, which then leads to the 370 * InCallScreen. 371 * 372 * - voicemail: URIs take the same path as regular tel: URIs. 373 * 374 * Other special cases: 375 * 376 * - Outgoing calls are totally disallowed on non-voice-capable 377 * devices (see handleNonVoiceCapable()). 378 * 379 * - A CALL intent with the EXTRA_SEND_EMPTY_FLASH extra (and 380 * presumably no data at all) means "send an empty flash" (which 381 * is only meaningful on CDMA devices while a call is already 382 * active.) 383 * 384 */ 385 private void processIntent(Intent intent) { 386 if (DBG) { 387 Log.v(TAG, "processIntent() = " + intent + ", thread: " + Thread.currentThread()); 388 } 389 final Configuration configuration = getResources().getConfiguration(); 390 391 // Outgoing phone calls are only allowed on "voice-capable" devices. 392 if (!PhoneApp.sVoiceCapable) { 393 Log.i(TAG, "This device is detected as non-voice-capable device."); 394 handleNonVoiceCapable(intent); 395 return; 396 } 397 398 String action = intent.getAction(); 399 String number = PhoneNumberUtils.getNumberFromIntent(intent, this); 400 // Check the number, don't convert for sip uri 401 // TODO put uriNumber under PhoneNumberUtils 402 if (number != null) { 403 if (!PhoneNumberUtils.isUriNumber(number)) { 404 number = PhoneNumberUtils.convertKeypadLettersToDigits(number); 405 number = PhoneNumberUtils.stripSeparators(number); 406 } 407 } else { 408 Log.w(TAG, "The number obtained from Intent is null."); 409 } 410 411 // If true, this flag will indicate that the current call is a special kind 412 // of call (most likely an emergency number) that 3rd parties aren't allowed 413 // to intercept or affect in any way. (In that case, we start the call 414 // immediately rather than going through the NEW_OUTGOING_CALL sequence.) 415 boolean callNow; 416 417 if (getClass().getName().equals(intent.getComponent().getClassName())) { 418 // If we were launched directly from the OutgoingCallBroadcaster, 419 // not one of its more privileged aliases, then make sure that 420 // only the non-privileged actions are allowed. 421 if (!Intent.ACTION_CALL.equals(intent.getAction())) { 422 Log.w(TAG, "Attempt to deliver non-CALL action; forcing to CALL"); 423 intent.setAction(Intent.ACTION_CALL); 424 } 425 } 426 427 // Check whether or not this is an emergency number, in order to 428 // enforce the restriction that only the CALL_PRIVILEGED and 429 // CALL_EMERGENCY intents are allowed to make emergency calls. 430 // 431 // (Note that the ACTION_CALL check below depends on the result of 432 // isPotentialLocalEmergencyNumber() rather than just plain 433 // isLocalEmergencyNumber(), to be 100% certain that we *don't* 434 // allow 3rd party apps to make emergency calls by passing in an 435 // "invalid" number like "9111234" that isn't technically an 436 // emergency number but might still result in an emergency call 437 // with some networks.) 438 final boolean isExactEmergencyNumber = 439 (number != null) && PhoneNumberUtils.isLocalEmergencyNumber(number, this); 440 final boolean isPotentialEmergencyNumber = 441 (number != null) && PhoneNumberUtils.isPotentialLocalEmergencyNumber(number, this); 442 if (VDBG) { 443 Log.v(TAG, " - Checking restrictions for number '" + number + "':"); 444 Log.v(TAG, " isExactEmergencyNumber = " + isExactEmergencyNumber); 445 Log.v(TAG, " isPotentialEmergencyNumber = " + isPotentialEmergencyNumber); 446 } 447 448 /* Change CALL_PRIVILEGED into CALL or CALL_EMERGENCY as needed. */ 449 // TODO: This code is redundant with some code in InCallScreen: refactor. 450 if (Intent.ACTION_CALL_PRIVILEGED.equals(action)) { 451 // We're handling a CALL_PRIVILEGED intent, so we know this request came 452 // from a trusted source (like the built-in dialer.) So even a number 453 // that's *potentially* an emergency number can safely be promoted to 454 // CALL_EMERGENCY (since we *should* allow you to dial "91112345" from 455 // the dialer if you really want to.) 456 if (isPotentialEmergencyNumber) { 457 Log.i(TAG, "ACTION_CALL_PRIVILEGED is used while the number is a potential" 458 + " emergency number. Use ACTION_CALL_EMERGENCY as an action instead."); 459 action = Intent.ACTION_CALL_EMERGENCY; 460 } else { 461 action = Intent.ACTION_CALL; 462 } 463 if (DBG) Log.v(TAG, " - updating action from CALL_PRIVILEGED to " + action); 464 intent.setAction(action); 465 } 466 467 if (Intent.ACTION_CALL.equals(action)) { 468 if (isPotentialEmergencyNumber) { 469 Log.w(TAG, "Cannot call potential emergency number '" + number 470 + "' with CALL Intent " + intent + "."); 471 Log.i(TAG, "Launching default dialer instead..."); 472 473 Intent invokeFrameworkDialer = new Intent(); 474 475 // TwelveKeyDialer is in a tab so we really want 476 // DialtactsActivity. Build the intent 'manually' to 477 // use the java resolver to find the dialer class (as 478 // opposed to a Context which look up known android 479 // packages only) 480 invokeFrameworkDialer.setClassName("com.android.contacts", 481 "com.android.contacts.DialtactsActivity"); 482 invokeFrameworkDialer.setAction(Intent.ACTION_DIAL); 483 invokeFrameworkDialer.setData(intent.getData()); 484 485 if (DBG) Log.v(TAG, "onCreate(): calling startActivity for Dialer: " 486 + invokeFrameworkDialer); 487 startActivity(invokeFrameworkDialer); 488 finish(); 489 return; 490 } 491 callNow = false; 492 } else if (Intent.ACTION_CALL_EMERGENCY.equals(action)) { 493 // ACTION_CALL_EMERGENCY case: this is either a CALL_PRIVILEGED 494 // intent that we just turned into a CALL_EMERGENCY intent (see 495 // above), or else it really is an CALL_EMERGENCY intent that 496 // came directly from some other app (e.g. the EmergencyDialer 497 // activity built in to the Phone app.) 498 // Make sure it's at least *possible* that this is really an 499 // emergency number. 500 if (!isPotentialEmergencyNumber) { 501 Log.w(TAG, "Cannot call non-potential-emergency number " + number 502 + " with EMERGENCY_CALL Intent " + intent + "." 503 + " Finish the Activity immediately."); 504 finish(); 505 return; 506 } 507 callNow = true; 508 } else { 509 Log.e(TAG, "Unhandled Intent " + intent + ". Finish the Activity immediately."); 510 finish(); 511 return; 512 } 513 514 // Make sure the screen is turned on. This is probably the right 515 // thing to do, and more importantly it works around an issue in the 516 // activity manager where we will not launch activities consistently 517 // when the screen is off (since it is trying to keep them paused 518 // and has... issues). 519 // 520 // Also, this ensures the device stays awake while doing the following 521 // broadcast; technically we should be holding a wake lock here 522 // as well. 523 PhoneApp.getInstance().wakeUpScreen(); 524 525 // If number is null, we're probably trying to call a non-existent voicemail number, 526 // send an empty flash or something else is fishy. Whatever the problem, there's no 527 // number, so there's no point in allowing apps to modify the number. 528 if (TextUtils.isEmpty(number)) { 529 if (intent.getBooleanExtra(EXTRA_SEND_EMPTY_FLASH, false)) { 530 Log.i(TAG, "onCreate: SEND_EMPTY_FLASH..."); 531 PhoneUtils.sendEmptyFlash(PhoneApp.getPhone()); 532 finish(); 533 return; 534 } else { 535 Log.i(TAG, "onCreate: null or empty number, setting callNow=true..."); 536 callNow = true; 537 } 538 } 539 540 if (callNow) { 541 // This is a special kind of call (most likely an emergency number) 542 // that 3rd parties aren't allowed to intercept or affect in any way. 543 // So initiate the outgoing call immediately. 544 545 Log.i(TAG, "onCreate(): callNow case! Calling placeCall(): " + intent); 546 547 // Initiate the outgoing call, and simultaneously launch the 548 // InCallScreen to display the in-call UI: 549 PhoneApp.getInstance().callController.placeCall(intent); 550 551 // Note we do *not* "return" here, but instead continue and 552 // send the ACTION_NEW_OUTGOING_CALL broadcast like for any 553 // other outgoing call. (But when the broadcast finally 554 // reaches the OutgoingCallReceiver, we'll know not to 555 // initiate the call again because of the presence of the 556 // EXTRA_ALREADY_CALLED extra.) 557 } 558 559 // Remember the call origin so that users will be able to see an appropriate screen 560 // after the phone call. This should affect both phone calls and SIP calls. 561 final String callOrigin = intent.getStringExtra(PhoneApp.EXTRA_CALL_ORIGIN); 562 if (callOrigin != null) { 563 if (DBG) Log.v(TAG, " - Call origin is passed (" + callOrigin + ")"); 564 PhoneApp.getInstance().setLatestActiveCallOrigin(callOrigin); 565 } else { 566 if (DBG) Log.v(TAG, " - Call origin is not passed. Reset current one."); 567 PhoneApp.getInstance().resetLatestActiveCallOrigin(); 568 } 569 570 // For now, SIP calls will be processed directly without a 571 // NEW_OUTGOING_CALL broadcast. 572 // 573 // TODO: In the future, though, 3rd party apps *should* be allowed to 574 // intercept outgoing calls to SIP addresses as well. To do this, we should 575 // (1) update the NEW_OUTGOING_CALL intent documentation to explain this 576 // case, and (2) pass the outgoing SIP address by *not* overloading the 577 // EXTRA_PHONE_NUMBER extra, but instead using a new separate extra to hold 578 // the outgoing SIP address. (Be sure to document whether it's a URI or just 579 // a plain address, whether it could be a tel: URI, etc.) 580 Uri uri = intent.getData(); 581 String scheme = uri.getScheme(); 582 if (Constants.SCHEME_SIP.equals(scheme) || PhoneNumberUtils.isUriNumber(number)) { 583 Log.i(TAG, "The requested number was detected as SIP call."); 584 startSipCallOptionHandler(this, intent, uri, number); 585 finish(); 586 return; 587 588 // TODO: if there's ever a way for SIP calls to trigger a 589 // "callNow=true" case (see above), we'll need to handle that 590 // case here too (most likely by just doing nothing at all.) 591 } 592 593 Intent broadcastIntent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL); 594 if (number != null) { 595 broadcastIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number); 596 } 597 PhoneUtils.checkAndCopyPhoneProviderExtras(intent, broadcastIntent); 598 broadcastIntent.putExtra(EXTRA_ALREADY_CALLED, callNow); 599 broadcastIntent.putExtra(EXTRA_ORIGINAL_URI, uri.toString()); 600 // Need to raise foreground in-call UI as soon as possible while allowing 3rd party app 601 // to intercept the outgoing call. 602 broadcastIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 603 if (DBG) Log.v(TAG, " - Broadcasting intent: " + broadcastIntent + "."); 604 605 // Set a timer so that we can prepare for unexpected delay introduced by the broadcast. 606 // If it takes too much time, the timer will show "waiting" spinner. 607 // This message will be removed when OutgoingCallReceiver#onReceive() is called before the 608 // timeout. 609 mHandler.sendEmptyMessageDelayed(EVENT_OUTGOING_CALL_TIMEOUT, 610 OUTGOING_CALL_TIMEOUT_THRESHOLD); 611 sendOrderedBroadcast(broadcastIntent, PERMISSION, new OutgoingCallReceiver(), 612 null, // scheduler 613 Activity.RESULT_OK, // initialCode 614 number, // initialData: initial value for the result data 615 null); // initialExtras 616 } 617 618 @Override 619 protected void onStop() { 620 // Clean up (and dismiss if necessary) any managed dialogs. 621 // 622 // We don't do this in onPause() since we can be paused/resumed 623 // due to orientation changes (in which case we don't want to 624 // disturb the dialog), but we *do* need it here in onStop() to be 625 // sure we clean up if the user hits HOME while the dialog is up. 626 // 627 // Note it's safe to call removeDialog() even if there's no dialog 628 // associated with that ID. 629 removeDialog(DIALOG_NOT_VOICE_CAPABLE); 630 631 super.onStop(); 632 } 633 634 /** 635 * Handle the specified CALL or CALL_* intent on a non-voice-capable 636 * device. 637 * 638 * This method may launch a different intent (if there's some useful 639 * alternative action to take), or otherwise display an error dialog, 640 * and in either case will finish() the current activity when done. 641 */ 642 private void handleNonVoiceCapable(Intent intent) { 643 if (DBG) Log.v(TAG, "handleNonVoiceCapable: handling " + intent 644 + " on non-voice-capable device..."); 645 String action = intent.getAction(); 646 Uri uri = intent.getData(); 647 String scheme = uri.getScheme(); 648 649 // Handle one special case: If this is a regular CALL to a tel: URI, 650 // bring up a UI letting you do something useful with the phone number 651 // (like "Add to contacts" if it isn't a contact yet.) 652 // 653 // This UI is provided by the contacts app in response to a DIAL 654 // intent, so we bring it up here by demoting this CALL to a DIAL and 655 // relaunching. 656 // 657 // TODO: it's strange and unintuitive to manually launch a DIAL intent 658 // to do this; it would be cleaner to have some shared UI component 659 // that we could bring up directly. (But for now at least, since both 660 // Contacts and Phone are built-in apps, this implementation is fine.) 661 662 if (Intent.ACTION_CALL.equals(action) && (Constants.SCHEME_TEL.equals(scheme))) { 663 Intent newIntent = new Intent(Intent.ACTION_DIAL, uri); 664 if (DBG) Log.v(TAG, "- relaunching as a DIAL intent: " + newIntent); 665 startActivity(newIntent); 666 finish(); 667 return; 668 } 669 670 // In all other cases, just show a generic "voice calling not 671 // supported" dialog. 672 showDialog(DIALOG_NOT_VOICE_CAPABLE); 673 // ...and we'll eventually finish() when the user dismisses 674 // or cancels the dialog. 675 } 676 677 @Override 678 protected Dialog onCreateDialog(int id) { 679 Dialog dialog; 680 switch(id) { 681 case DIALOG_NOT_VOICE_CAPABLE: 682 dialog = new AlertDialog.Builder(this) 683 .setTitle(R.string.not_voice_capable) 684 .setIconAttribute(android.R.attr.alertDialogIcon) 685 .setPositiveButton(android.R.string.ok, this) 686 .setOnCancelListener(this) 687 .create(); 688 break; 689 default: 690 Log.w(TAG, "onCreateDialog: unexpected ID " + id); 691 dialog = null; 692 break; 693 } 694 return dialog; 695 } 696 697 /** DialogInterface.OnClickListener implementation */ 698 @Override 699 public void onClick(DialogInterface dialog, int id) { 700 // DIALOG_NOT_VOICE_CAPABLE is the only dialog we ever use (so far 701 // at least), and its only button is "OK". 702 finish(); 703 } 704 705 /** DialogInterface.OnCancelListener implementation */ 706 @Override 707 public void onCancel(DialogInterface dialog) { 708 // DIALOG_NOT_VOICE_CAPABLE is the only dialog we ever use (so far 709 // at least), and canceling it is just like hitting "OK". 710 finish(); 711 } 712 713 /** 714 * Implement onConfigurationChanged() purely for debugging purposes, 715 * to make sure that the android:configChanges element in our manifest 716 * is working properly. 717 */ 718 @Override 719 public void onConfigurationChanged(Configuration newConfig) { 720 super.onConfigurationChanged(newConfig); 721 if (DBG) Log.v(TAG, "onConfigurationChanged: newConfig = " + newConfig); 722 } 723 } 724