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