• 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 
57     /**
58      * Identifier for intent extra for sending an empty Flash message for
59      * CDMA networks. This message is used by the network to simulate a
60      * press/depress of the "hookswitch" of a landline phone. Aka "empty flash".
61      *
62      * TODO: Receiving an intent extra to tell the phone to send this flash is a
63      * temporary measure. To be replaced with an external ITelephony call in the future.
64      * TODO: Keep in sync with the string defined in TwelveKeyDialer.java in Contacts app
65      * until this is replaced with the ITelephony API.
66      */
67     public static final String EXTRA_SEND_EMPTY_FLASH = "com.android.phone.extra.SEND_EMPTY_FLASH";
68 
69     /**
70      * OutgoingCallReceiver finishes NEW_OUTGOING_CALL broadcasts, starting
71      * the InCallScreen if the broadcast has not been canceled, possibly with
72      * a modified phone number and optional provider info (uri + package name + remote views.)
73      */
74     public class OutgoingCallReceiver extends BroadcastReceiver {
75         private static final String TAG = "OutgoingCallReceiver";
76 
onReceive(Context context, Intent intent)77         public void onReceive(Context context, Intent intent) {
78             doReceive(context, intent);
79             finish();
80         }
81 
doReceive(Context context, Intent intent)82         public void doReceive(Context context, Intent intent) {
83             if (DBG) Log.v(TAG, "doReceive: " + intent);
84 
85             boolean alreadyCalled;
86             String number;
87             String originalUri;
88 
89             alreadyCalled = intent.getBooleanExtra(
90                     OutgoingCallBroadcaster.EXTRA_ALREADY_CALLED, false);
91             if (alreadyCalled) {
92                 if (DBG) Log.v(TAG, "CALL already placed -- returning.");
93                 return;
94             }
95 
96             number = getResultData();
97             final PhoneApp app = PhoneApp.getInstance();
98             int phoneType = app.phone.getPhoneType();
99             if (phoneType == Phone.PHONE_TYPE_CDMA) {
100                 boolean activateState = (app.cdmaOtaScreenState.otaScreenState
101                         == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION);
102                 boolean dialogState = (app.cdmaOtaScreenState.otaScreenState
103                         == OtaUtils.CdmaOtaScreenState.OtaScreenState
104                         .OTA_STATUS_SUCCESS_FAILURE_DLG);
105                 boolean isOtaCallActive = false;
106 
107                 if ((app.cdmaOtaScreenState.otaScreenState
108                         == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_PROGRESS)
109                         || (app.cdmaOtaScreenState.otaScreenState
110                         == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_LISTENING)) {
111                     isOtaCallActive = true;
112                 }
113 
114                 if (activateState || dialogState) {
115                     if (dialogState) app.dismissOtaDialogs();
116                     app.clearOtaState();
117                     app.clearInCallScreenMode();
118                 } else if (isOtaCallActive) {
119                     if (DBG) Log.v(TAG, "OTA call is active, a 2nd CALL cancelled -- returning.");
120                     return;
121                 }
122             }
123 
124             if (number == null) {
125                 if (DBG) Log.v(TAG, "CALL cancelled (null number), returning...");
126                 return;
127             } else if ((phoneType == Phone.PHONE_TYPE_CDMA)
128                     && ((app.phone.getState() != Phone.State.IDLE)
129                     && (app.phone.isOtaSpNumber(number)))) {
130                 if (DBG) Log.v(TAG, "Call is active, a 2nd OTA call cancelled -- returning.");
131                 return;
132             } else if (PhoneNumberUtils.isEmergencyNumber(number)) {
133                 Log.w(TAG, "Cannot modify outgoing call to emergency number " + number + ".");
134                 return;
135             }
136 
137             originalUri = intent.getStringExtra(
138                     OutgoingCallBroadcaster.EXTRA_ORIGINAL_URI);
139             if (originalUri == null) {
140                 Log.e(TAG, "Intent is missing EXTRA_ORIGINAL_URI -- returning.");
141                 return;
142             }
143 
144             Uri uri = Uri.parse(originalUri);
145 
146             if (DBG) Log.v(TAG, "CALL to " + number + " proceeding.");
147 
148             Intent newIntent = new Intent(Intent.ACTION_CALL, uri);
149             newIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
150 
151             PhoneUtils.checkAndCopyPhoneProviderExtras(intent, newIntent);
152 
153             newIntent.setClass(context, InCallScreen.class);
154             newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
155 
156             if (DBG) Log.v(TAG, "doReceive(): calling startActivity: " + newIntent);
157             context.startActivity(newIntent);
158         }
159     }
160 
161     @Override
onCreate(Bundle icicle)162     protected void onCreate(Bundle icicle) {
163         super.onCreate(icicle);
164 
165         Intent intent = getIntent();
166         final Configuration configuration = getResources().getConfiguration();
167 
168         if (DBG) Log.v(TAG, "onCreate: this = " + this + ", icicle = " + icicle);
169         if (DBG) Log.v(TAG, " - getIntent() = " + intent);
170         if (DBG) Log.v(TAG, " - configuration = " + configuration);
171 
172         if (icicle != null) {
173             // A non-null icicle means that this activity is being
174             // re-initialized after previously being shut down.
175             //
176             // In practice this happens very rarely (because the lifetime
177             // of this activity is so short!), but it *can* happen if the
178             // framework detects a configuration change at exactly the
179             // right moment; see bug 2202413.
180             //
181             // In this case, do nothing.  Our onCreate() method has already
182             // run once (with icicle==null the first time), which means
183             // that the NEW_OUTGOING_CALL broadcast for this new call has
184             // already been sent.
185             Log.i(TAG, "onCreate: non-null icicle!  "
186                   + "Bailing out, not sending NEW_OUTGOING_CALL broadcast...");
187 
188             // No need to finish() here, since the OutgoingCallReceiver from
189             // our original instance will do that.  (It'll actually call
190             // finish() on our original instance, which apparently works fine
191             // even though the ActivityManager has already shut that instance
192             // down.  And note that if we *do* call finish() here, that just
193             // results in an "ActivityManager: Duplicate finish request"
194             // warning when the OutgoingCallReceiver runs.)
195 
196             return;
197         }
198 
199         String action = intent.getAction();
200         String number = PhoneNumberUtils.getNumberFromIntent(intent, this);
201         if (number != null) {
202             number = PhoneNumberUtils.convertKeypadLettersToDigits(number);
203             number = PhoneNumberUtils.stripSeparators(number);
204         }
205         final boolean emergencyNumber =
206                 (number != null) && PhoneNumberUtils.isEmergencyNumber(number);
207 
208         boolean callNow;
209 
210         if (getClass().getName().equals(intent.getComponent().getClassName())) {
211             // If we were launched directly from the OutgoingCallBroadcaster,
212             // not one of its more privileged aliases, then make sure that
213             // only the non-privileged actions are allowed.
214             if (!Intent.ACTION_CALL.equals(intent.getAction())) {
215                 Log.w(TAG, "Attempt to deliver non-CALL action; forcing to CALL");
216                 intent.setAction(Intent.ACTION_CALL);
217             }
218         }
219 
220         /* Change CALL_PRIVILEGED into CALL or CALL_EMERGENCY as needed. */
221         // TODO: This code is redundant with some code in InCallScreen: refactor.
222         if (Intent.ACTION_CALL_PRIVILEGED.equals(action)) {
223             action = emergencyNumber
224                     ? Intent.ACTION_CALL_EMERGENCY
225                     : Intent.ACTION_CALL;
226             intent.setAction(action);
227         }
228 
229         if (Intent.ACTION_CALL.equals(action)) {
230             if (emergencyNumber) {
231                 Log.w(TAG, "Cannot call emergency number " + number
232                         + " with CALL Intent " + intent + ".");
233 
234                 Intent invokeFrameworkDialer = new Intent();
235 
236                 // TwelveKeyDialer is in a tab so we really want
237                 // DialtactsActivity.  Build the intent 'manually' to
238                 // use the java resolver to find the dialer class (as
239                 // opposed to a Context which look up known android
240                 // packages only)
241                 invokeFrameworkDialer.setClassName("com.android.contacts",
242                                                    "com.android.contacts.DialtactsActivity");
243                 invokeFrameworkDialer.setAction(Intent.ACTION_DIAL);
244                 invokeFrameworkDialer.setData(intent.getData());
245 
246                 if (DBG) Log.v(TAG, "onCreate(): calling startActivity for Dialer: "
247                                + invokeFrameworkDialer);
248                 startActivity(invokeFrameworkDialer);
249                 finish();
250                 return;
251             }
252             callNow = false;
253         } else if (Intent.ACTION_CALL_EMERGENCY.equals(action)) {
254             // ACTION_CALL_EMERGENCY case: this is either a CALL_PRIVILEGED
255             // intent that we just turned into a CALL_EMERGENCY intent (see
256             // above), or else it really is an CALL_EMERGENCY intent that
257             // came directly from some other app (e.g. the EmergencyDialer
258             // activity built in to the Phone app.)
259             if (!emergencyNumber) {
260                 Log.w(TAG, "Cannot call non-emergency number " + number
261                         + " with EMERGENCY_CALL Intent " + intent + ".");
262                 finish();
263                 return;
264             }
265             callNow = true;
266         } else {
267             Log.e(TAG, "Unhandled Intent " + intent + ".");
268             finish();
269             return;
270         }
271 
272         // Make sure the screen is turned on.  This is probably the right
273         // thing to do, and more importantly it works around an issue in the
274         // activity manager where we will not launch activities consistently
275         // when the screen is off (since it is trying to keep them paused
276         // and has...  issues).
277         //
278         // Also, this ensures the device stays awake while doing the following
279         // broadcast; technically we should be holding a wake lock here
280         // as well.
281         PhoneApp.getInstance().wakeUpScreen();
282 
283         /* If number is null, we're probably trying to call a non-existent voicemail number,
284          * send an empty flash or something else is fishy.  Whatever the problem, there's no
285          * number, so there's no point in allowing apps to modify the number. */
286         if (number == null || TextUtils.isEmpty(number)) {
287             if (intent.getBooleanExtra(EXTRA_SEND_EMPTY_FLASH, false)) {
288                 Log.i(TAG, "onCreate: SEND_EMPTY_FLASH...");
289                 PhoneUtils.sendEmptyFlash(PhoneApp.getInstance().phone);
290                 finish();
291                 return;
292             } else {
293                 Log.i(TAG, "onCreate: null or empty number, setting callNow=true...");
294                 callNow = true;
295             }
296         }
297 
298         if (callNow) {
299             intent.setClass(this, InCallScreen.class);
300             if (DBG) Log.v(TAG, "onCreate(): callNow case, calling startActivity: " + intent);
301             startActivity(intent);
302         }
303 
304         Intent broadcastIntent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL);
305         if (number != null) {
306             broadcastIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
307         }
308         PhoneUtils.checkAndCopyPhoneProviderExtras(intent, broadcastIntent);
309         broadcastIntent.putExtra(EXTRA_ALREADY_CALLED, callNow);
310         broadcastIntent.putExtra(EXTRA_ORIGINAL_URI, intent.getData().toString());
311 
312         if (DBG) Log.v(TAG, "Broadcasting intent " + broadcastIntent + ".");
313         sendOrderedBroadcast(broadcastIntent, PERMISSION,
314                 new OutgoingCallReceiver(), null, Activity.RESULT_OK, number, null);
315         // The receiver will finish our activity when it finally runs.
316     }
317 
318     // Implement onConfigurationChanged() purely for debugging purposes,
319     // to make sure that the android:configChanges element in our manifest
320     // is working properly.
321     @Override
onConfigurationChanged(Configuration newConfig)322     public void onConfigurationChanged(Configuration newConfig) {
323         super.onConfigurationChanged(newConfig);
324         if (DBG) Log.v(TAG, "onConfigurationChanged: newConfig = " + newConfig);
325     }
326 }
327