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