Home | History | Annotate | Download | only in phone
      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 
     79         public void onReceive(Context context, Intent intent) {
     80             doReceive(context, intent);
     81             finish();
     82         }
     83 
     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 
    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
    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
    361     public void onConfigurationChanged(Configuration newConfig) {
    362         super.onConfigurationChanged(newConfig);
    363         if (DBG) Log.v(TAG, "onConfigurationChanged: newConfig = " + newConfig);
    364     }
    365 }
    366