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