• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
onReceive(Context context, Intent intent)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 
doReceive(Context context, Intent intent)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@example.com").
263      */
startSipCallOptionHandler(Context context, Intent intent, Uri uri, String number)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
onCreate(Bundle icicle)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      */
processIntent(Intent intent)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
onStop()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      */
handleNonVoiceCapable(Intent intent)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
onCreateDialog(int id)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
onClick(DialogInterface dialog, int id)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
onCancel(DialogInterface dialog)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
onConfigurationChanged(Configuration newConfig)719     public void onConfigurationChanged(Configuration newConfig) {
720         super.onConfigurationChanged(newConfig);
721         if (DBG) Log.v(TAG, "onConfigurationChanged: newConfig = " + newConfig);
722     }
723 }
724