• 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.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.res.Configuration;
24 import android.net.Uri;
25 import android.os.Bundle;
26 import android.os.SystemProperties;
27 import android.telephony.PhoneNumberUtils;
28 import android.text.TextUtils;
29 import android.util.Log;
30 
31 import com.android.internal.telephony.Phone;
32 
33 /**
34  * OutgoingCallBroadcaster receives CALL and CALL_PRIVILEGED Intents, and
35  * broadcasts the ACTION_NEW_OUTGOING_CALL intent which allows other
36  * applications to monitor, redirect, or prevent the outgoing call.
37 
38  * After the other applications have had a chance to see the
39  * ACTION_NEW_OUTGOING_CALL intent, it finally reaches the
40  * {@link OutgoingCallReceiver}, which passes the (possibly modified)
41  * intent on to the {@link InCallScreen}.
42  *
43  * Emergency calls and calls where no number is present (like for a CDMA
44  * "empty flash" or a nonexistent voicemail number) are exempt from being
45  * broadcast.
46  */
47 public class OutgoingCallBroadcaster extends Activity {
48 
49     private static final String PERMISSION = android.Manifest.permission.PROCESS_OUTGOING_CALLS;
50     private static final String TAG = "OutgoingCallBroadcaster";
51     private static final boolean DBG =
52             (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
53 
54     public static final String EXTRA_ALREADY_CALLED = "android.phone.extra.ALREADY_CALLED";
55     public static final String EXTRA_ORIGINAL_URI = "android.phone.extra.ORIGINAL_URI";
56     public static final String EXTRA_NEW_CALL_INTENT = "android.phone.extra.NEW_CALL_INTENT";
57     public static final String EXTRA_SIP_PHONE_URI = "android.phone.extra.SIP_PHONE_URI";
58 
59     /**
60      * Identifier for intent extra for sending an empty Flash message for
61      * CDMA networks. This message is used by the network to simulate a
62      * press/depress of the "hookswitch" of a landline phone. Aka "empty flash".
63      *
64      * TODO: Receiving an intent extra to tell the phone to send this flash is a
65      * temporary measure. To be replaced with an external ITelephony call in the future.
66      * TODO: Keep in sync with the string defined in TwelveKeyDialer.java in Contacts app
67      * until this is replaced with the ITelephony API.
68      */
69     public static final String EXTRA_SEND_EMPTY_FLASH = "com.android.phone.extra.SEND_EMPTY_FLASH";
70 
71     /**
72      * OutgoingCallReceiver finishes NEW_OUTGOING_CALL broadcasts, starting
73      * the InCallScreen if the broadcast has not been canceled, possibly with
74      * a modified phone number and optional provider info (uri + package name + remote views.)
75      */
76     public class OutgoingCallReceiver extends BroadcastReceiver {
77         private static final String TAG = "OutgoingCallReceiver";
78 
onReceive(Context context, Intent intent)79         public void onReceive(Context context, Intent intent) {
80             doReceive(context, intent);
81             finish();
82         }
83 
doReceive(Context context, Intent intent)84         public void doReceive(Context context, Intent intent) {
85             if (DBG) Log.v(TAG, "doReceive: " + intent);
86 
87             boolean alreadyCalled;
88             String number;
89             String originalUri;
90 
91             alreadyCalled = intent.getBooleanExtra(
92                     OutgoingCallBroadcaster.EXTRA_ALREADY_CALLED, false);
93             if (alreadyCalled) {
94                 if (DBG) Log.v(TAG, "CALL already placed -- returning.");
95                 return;
96             }
97 
98             number = getResultData();
99             final PhoneApp app = PhoneApp.getInstance();
100 
101             if (TelephonyCapabilities.supportsOtasp(app.phone)) {
102                 boolean activateState = (app.cdmaOtaScreenState.otaScreenState
103                         == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION);
104                 boolean dialogState = (app.cdmaOtaScreenState.otaScreenState
105                         == OtaUtils.CdmaOtaScreenState.OtaScreenState
106                         .OTA_STATUS_SUCCESS_FAILURE_DLG);
107                 boolean isOtaCallActive = false;
108 
109                 if ((app.cdmaOtaScreenState.otaScreenState
110                         == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_PROGRESS)
111                         || (app.cdmaOtaScreenState.otaScreenState
112                         == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_LISTENING)) {
113                     isOtaCallActive = true;
114                 }
115 
116                 if (activateState || dialogState) {
117                     if (dialogState) app.dismissOtaDialogs();
118                     app.clearOtaState();
119                     app.clearInCallScreenMode();
120                 } else if (isOtaCallActive) {
121                     if (DBG) Log.v(TAG, "OTA call is active, a 2nd CALL cancelled -- returning.");
122                     return;
123                 }
124             }
125 
126             if (number == null) {
127                 if (DBG) Log.v(TAG, "CALL cancelled (null number), returning...");
128                 return;
129             } else if (TelephonyCapabilities.supportsOtasp(app.phone)
130                     && (app.phone.getState() != Phone.State.IDLE)
131                     && (app.phone.isOtaSpNumber(number))) {
132                 if (DBG) Log.v(TAG, "Call is active, a 2nd OTA call cancelled -- returning.");
133                 return;
134             } else if (PhoneNumberUtils.isEmergencyNumber(number)) {
135                 Log.w(TAG, "Cannot modify outgoing call to emergency number " + number + ".");
136                 return;
137             }
138 
139             originalUri = intent.getStringExtra(
140                     OutgoingCallBroadcaster.EXTRA_ORIGINAL_URI);
141             if (originalUri == null) {
142                 Log.e(TAG, "Intent is missing EXTRA_ORIGINAL_URI -- returning.");
143                 return;
144             }
145 
146             Uri uri = Uri.parse(originalUri);
147 
148             // Since the number could be modified/rewritten by the broadcast,
149             // we have to strip the unwanted characters here.
150             number = PhoneNumberUtils.stripSeparators(
151                     PhoneNumberUtils.convertKeypadLettersToDigits(number));
152 
153             if (DBG) Log.v(TAG, "CALL to " + /*number*/ "xxxxxxx" + " proceeding.");
154 
155             startSipCallOptionsHandler(context, intent, uri, number);
156         }
157     }
158 
startSipCallOptionsHandler(Context context, Intent intent, Uri uri, String number)159     private void startSipCallOptionsHandler(Context context, Intent intent,
160             Uri uri, String number) {
161         Intent newIntent = new Intent(Intent.ACTION_CALL, uri);
162         newIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
163 
164         PhoneUtils.checkAndCopyPhoneProviderExtras(intent, newIntent);
165 
166         newIntent.setClass(context, InCallScreen.class);
167         newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
168 
169         Intent selectPhoneIntent = new Intent(EXTRA_NEW_CALL_INTENT, uri);
170         selectPhoneIntent.setClass(context, SipCallOptionHandler.class);
171         selectPhoneIntent.putExtra(EXTRA_NEW_CALL_INTENT, newIntent);
172         selectPhoneIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
173         if (DBG) Log.v(TAG, "startSipCallOptionsHandler(): " +
174                 "calling startActivity: " + selectPhoneIntent);
175         context.startActivity(selectPhoneIntent);
176     }
177 
178     @Override
onCreate(Bundle icicle)179     protected void onCreate(Bundle icicle) {
180         super.onCreate(icicle);
181 
182         Intent intent = getIntent();
183         final Configuration configuration = getResources().getConfiguration();
184 
185         if (DBG) Log.v(TAG, "onCreate: this = " + this + ", icicle = " + icicle);
186         if (DBG) Log.v(TAG, " - getIntent() = " + intent);
187         if (DBG) Log.v(TAG, " - configuration = " + configuration);
188 
189         if (icicle != null) {
190             // A non-null icicle means that this activity is being
191             // re-initialized after previously being shut down.
192             //
193             // In practice this happens very rarely (because the lifetime
194             // of this activity is so short!), but it *can* happen if the
195             // framework detects a configuration change at exactly the
196             // right moment; see bug 2202413.
197             //
198             // In this case, do nothing.  Our onCreate() method has already
199             // run once (with icicle==null the first time), which means
200             // that the NEW_OUTGOING_CALL broadcast for this new call has
201             // already been sent.
202             Log.i(TAG, "onCreate: non-null icicle!  "
203                   + "Bailing out, not sending NEW_OUTGOING_CALL broadcast...");
204 
205             // No need to finish() here, since the OutgoingCallReceiver from
206             // our original instance will do that.  (It'll actually call
207             // finish() on our original instance, which apparently works fine
208             // even though the ActivityManager has already shut that instance
209             // down.  And note that if we *do* call finish() here, that just
210             // results in an "ActivityManager: Duplicate finish request"
211             // warning when the OutgoingCallReceiver runs.)
212 
213             return;
214         }
215 
216         String action = intent.getAction();
217         String number = PhoneNumberUtils.getNumberFromIntent(intent, this);
218         // Check the number, don't convert for sip uri
219         // TODO put uriNumber under PhoneNumberUtils
220         if (number != null) {
221             if (!PhoneNumberUtils.isUriNumber(number)) {
222                 number = PhoneNumberUtils.convertKeypadLettersToDigits(number);
223                 number = PhoneNumberUtils.stripSeparators(number);
224             }
225         }
226         final boolean emergencyNumber =
227                 (number != null) && PhoneNumberUtils.isEmergencyNumber(number);
228 
229         boolean callNow;
230 
231         if (getClass().getName().equals(intent.getComponent().getClassName())) {
232             // If we were launched directly from the OutgoingCallBroadcaster,
233             // not one of its more privileged aliases, then make sure that
234             // only the non-privileged actions are allowed.
235             if (!Intent.ACTION_CALL.equals(intent.getAction())) {
236                 Log.w(TAG, "Attempt to deliver non-CALL action; forcing to CALL");
237                 intent.setAction(Intent.ACTION_CALL);
238             }
239         }
240 
241         /* Change CALL_PRIVILEGED into CALL or CALL_EMERGENCY as needed. */
242         // TODO: This code is redundant with some code in InCallScreen: refactor.
243         if (Intent.ACTION_CALL_PRIVILEGED.equals(action)) {
244             action = emergencyNumber
245                     ? Intent.ACTION_CALL_EMERGENCY
246                     : Intent.ACTION_CALL;
247             if (DBG) Log.v(TAG, "- updating action from CALL_PRIVILEGED to " + action);
248             intent.setAction(action);
249         }
250 
251         if (Intent.ACTION_CALL.equals(action)) {
252             if (emergencyNumber) {
253                 Log.w(TAG, "Cannot call emergency number " + number
254                         + " with CALL Intent " + intent + ".");
255 
256                 Intent invokeFrameworkDialer = new Intent();
257 
258                 // TwelveKeyDialer is in a tab so we really want
259                 // DialtactsActivity.  Build the intent 'manually' to
260                 // use the java resolver to find the dialer class (as
261                 // opposed to a Context which look up known android
262                 // packages only)
263                 invokeFrameworkDialer.setClassName("com.android.contacts",
264                                                    "com.android.contacts.DialtactsActivity");
265                 invokeFrameworkDialer.setAction(Intent.ACTION_DIAL);
266                 invokeFrameworkDialer.setData(intent.getData());
267 
268                 if (DBG) Log.v(TAG, "onCreate(): calling startActivity for Dialer: "
269                                + invokeFrameworkDialer);
270                 startActivity(invokeFrameworkDialer);
271                 finish();
272                 return;
273             }
274             callNow = false;
275         } else if (Intent.ACTION_CALL_EMERGENCY.equals(action)) {
276             // ACTION_CALL_EMERGENCY case: this is either a CALL_PRIVILEGED
277             // intent that we just turned into a CALL_EMERGENCY intent (see
278             // above), or else it really is an CALL_EMERGENCY intent that
279             // came directly from some other app (e.g. the EmergencyDialer
280             // activity built in to the Phone app.)
281             if (!emergencyNumber) {
282                 Log.w(TAG, "Cannot call non-emergency number " + number
283                         + " with EMERGENCY_CALL Intent " + intent + ".");
284                 finish();
285                 return;
286             }
287             callNow = true;
288         } else {
289             Log.e(TAG, "Unhandled Intent " + intent + ".");
290             finish();
291             return;
292         }
293 
294         // Make sure the screen is turned on.  This is probably the right
295         // thing to do, and more importantly it works around an issue in the
296         // activity manager where we will not launch activities consistently
297         // when the screen is off (since it is trying to keep them paused
298         // and has...  issues).
299         //
300         // Also, this ensures the device stays awake while doing the following
301         // broadcast; technically we should be holding a wake lock here
302         // as well.
303         PhoneApp.getInstance().wakeUpScreen();
304 
305         /* If number is null, we're probably trying to call a non-existent voicemail number,
306          * send an empty flash or something else is fishy.  Whatever the problem, there's no
307          * number, so there's no point in allowing apps to modify the number. */
308         if (number == null || TextUtils.isEmpty(number)) {
309             if (intent.getBooleanExtra(EXTRA_SEND_EMPTY_FLASH, false)) {
310                 Log.i(TAG, "onCreate: SEND_EMPTY_FLASH...");
311                 PhoneUtils.sendEmptyFlash(PhoneApp.getPhone());
312                 finish();
313                 return;
314             } else {
315                 Log.i(TAG, "onCreate: null or empty number, setting callNow=true...");
316                 callNow = true;
317             }
318         }
319 
320         if (callNow) {
321             intent.setClass(this, InCallScreen.class);
322             if (DBG) Log.v(TAG, "onCreate(): callNow case, calling startActivity: " + intent);
323             startActivity(intent);
324         }
325 
326         // For now, SIP calls will be processed directly without a
327         // NEW_OUTGOING_CALL broadcast.
328         //
329         // TODO: In the future, though, 3rd party apps *should* be allowed to
330         // intercept outgoing calls to SIP addresses as well.  To do this, we should
331         // (1) update the NEW_OUTGOING_CALL intent documentation to explain this
332         // case, and (2) pass the outgoing SIP address by *not* overloading the
333         // EXTRA_PHONE_NUMBER extra, but instead using a new separate extra to hold
334         // the outgoing SIP address.  (Be sure to document whether it's a URI or just
335         // a plain address, whether it could be a tel: URI, etc.)
336         Uri uri = intent.getData();
337         String scheme = uri.getScheme();
338         if ("sip".equals(scheme) || PhoneNumberUtils.isUriNumber(number)) {
339             startSipCallOptionsHandler(this, intent, uri, number);
340             finish();
341             return;
342         }
343 
344         Intent broadcastIntent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL);
345         if (number != null) {
346             broadcastIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
347         }
348         PhoneUtils.checkAndCopyPhoneProviderExtras(intent, broadcastIntent);
349         broadcastIntent.putExtra(EXTRA_ALREADY_CALLED, callNow);
350         broadcastIntent.putExtra(EXTRA_ORIGINAL_URI, uri.toString());
351 
352         if (DBG) Log.v(TAG, "Broadcasting intent: " + broadcastIntent + ".");
353         sendOrderedBroadcast(broadcastIntent, PERMISSION, new OutgoingCallReceiver(),
354                 null, Activity.RESULT_OK, number, null);
355     }
356 
357     // Implement onConfigurationChanged() purely for debugging purposes,
358     // to make sure that the android:configChanges element in our manifest
359     // is working properly.
360     @Override
onConfigurationChanged(Configuration newConfig)361     public void onConfigurationChanged(Configuration newConfig) {
362         super.onConfigurationChanged(newConfig);
363         if (DBG) Log.v(TAG, "onConfigurationChanged: newConfig = " + newConfig);
364     }
365 }
366