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