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 if (DBG) Log.v(TAG, "CALL to " + /*number*/ "xxxxxxx" + " proceeding."); 149 150 startSipCallOptionsHandler(context, intent, uri, number); 151 } 152 } 153 154 private void startSipCallOptionsHandler(Context context, Intent intent, 155 Uri uri, String number) { 156 Intent newIntent = new Intent(Intent.ACTION_CALL, uri); 157 newIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number); 158 159 PhoneUtils.checkAndCopyPhoneProviderExtras(intent, newIntent); 160 161 newIntent.setClass(context, InCallScreen.class); 162 newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 163 164 Intent selectPhoneIntent = new Intent(EXTRA_NEW_CALL_INTENT, uri); 165 selectPhoneIntent.setClass(context, SipCallOptionHandler.class); 166 selectPhoneIntent.putExtra(EXTRA_NEW_CALL_INTENT, newIntent); 167 selectPhoneIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 168 if (DBG) Log.v(TAG, "startSipCallOptionsHandler(): " + 169 "calling startActivity: " + selectPhoneIntent); 170 context.startActivity(selectPhoneIntent); 171 } 172 173 @Override 174 protected void onCreate(Bundle icicle) { 175 super.onCreate(icicle); 176 177 Intent intent = getIntent(); 178 final Configuration configuration = getResources().getConfiguration(); 179 180 if (DBG) Log.v(TAG, "onCreate: this = " + this + ", icicle = " + icicle); 181 if (DBG) Log.v(TAG, " - getIntent() = " + intent); 182 if (DBG) Log.v(TAG, " - configuration = " + configuration); 183 184 if (icicle != null) { 185 // A non-null icicle means that this activity is being 186 // re-initialized after previously being shut down. 187 // 188 // In practice this happens very rarely (because the lifetime 189 // of this activity is so short!), but it *can* happen if the 190 // framework detects a configuration change at exactly the 191 // right moment; see bug 2202413. 192 // 193 // In this case, do nothing. Our onCreate() method has already 194 // run once (with icicle==null the first time), which means 195 // that the NEW_OUTGOING_CALL broadcast for this new call has 196 // already been sent. 197 Log.i(TAG, "onCreate: non-null icicle! " 198 + "Bailing out, not sending NEW_OUTGOING_CALL broadcast..."); 199 200 // No need to finish() here, since the OutgoingCallReceiver from 201 // our original instance will do that. (It'll actually call 202 // finish() on our original instance, which apparently works fine 203 // even though the ActivityManager has already shut that instance 204 // down. And note that if we *do* call finish() here, that just 205 // results in an "ActivityManager: Duplicate finish request" 206 // warning when the OutgoingCallReceiver runs.) 207 208 return; 209 } 210 211 String action = intent.getAction(); 212 String number = PhoneNumberUtils.getNumberFromIntent(intent, this); 213 // Check the number, don't convert for sip uri 214 // TODO put uriNumber under PhoneNumberUtils 215 if (number != null) { 216 if (!PhoneNumberUtils.isUriNumber(number)) { 217 number = PhoneNumberUtils.convertKeypadLettersToDigits(number); 218 number = PhoneNumberUtils.stripSeparators(number); 219 } 220 } 221 final boolean emergencyNumber = 222 (number != null) && PhoneNumberUtils.isEmergencyNumber(number); 223 224 boolean callNow; 225 226 if (getClass().getName().equals(intent.getComponent().getClassName())) { 227 // If we were launched directly from the OutgoingCallBroadcaster, 228 // not one of its more privileged aliases, then make sure that 229 // only the non-privileged actions are allowed. 230 if (!Intent.ACTION_CALL.equals(intent.getAction())) { 231 Log.w(TAG, "Attempt to deliver non-CALL action; forcing to CALL"); 232 intent.setAction(Intent.ACTION_CALL); 233 } 234 } 235 236 /* Change CALL_PRIVILEGED into CALL or CALL_EMERGENCY as needed. */ 237 // TODO: This code is redundant with some code in InCallScreen: refactor. 238 if (Intent.ACTION_CALL_PRIVILEGED.equals(action)) { 239 action = emergencyNumber 240 ? Intent.ACTION_CALL_EMERGENCY 241 : Intent.ACTION_CALL; 242 if (DBG) Log.v(TAG, "- updating action from CALL_PRIVILEGED to " + action); 243 intent.setAction(action); 244 } 245 246 if (Intent.ACTION_CALL.equals(action)) { 247 if (emergencyNumber) { 248 Log.w(TAG, "Cannot call emergency number " + number 249 + " with CALL Intent " + intent + "."); 250 251 Intent invokeFrameworkDialer = new Intent(); 252 253 // TwelveKeyDialer is in a tab so we really want 254 // DialtactsActivity. Build the intent 'manually' to 255 // use the java resolver to find the dialer class (as 256 // opposed to a Context which look up known android 257 // packages only) 258 invokeFrameworkDialer.setClassName("com.android.contacts", 259 "com.android.contacts.DialtactsActivity"); 260 invokeFrameworkDialer.setAction(Intent.ACTION_DIAL); 261 invokeFrameworkDialer.setData(intent.getData()); 262 263 if (DBG) Log.v(TAG, "onCreate(): calling startActivity for Dialer: " 264 + invokeFrameworkDialer); 265 startActivity(invokeFrameworkDialer); 266 finish(); 267 return; 268 } 269 callNow = false; 270 } else if (Intent.ACTION_CALL_EMERGENCY.equals(action)) { 271 // ACTION_CALL_EMERGENCY case: this is either a CALL_PRIVILEGED 272 // intent that we just turned into a CALL_EMERGENCY intent (see 273 // above), or else it really is an CALL_EMERGENCY intent that 274 // came directly from some other app (e.g. the EmergencyDialer 275 // activity built in to the Phone app.) 276 if (!emergencyNumber) { 277 Log.w(TAG, "Cannot call non-emergency number " + number 278 + " with EMERGENCY_CALL Intent " + intent + "."); 279 finish(); 280 return; 281 } 282 callNow = true; 283 } else { 284 Log.e(TAG, "Unhandled Intent " + intent + "."); 285 finish(); 286 return; 287 } 288 289 // Make sure the screen is turned on. This is probably the right 290 // thing to do, and more importantly it works around an issue in the 291 // activity manager where we will not launch activities consistently 292 // when the screen is off (since it is trying to keep them paused 293 // and has... issues). 294 // 295 // Also, this ensures the device stays awake while doing the following 296 // broadcast; technically we should be holding a wake lock here 297 // as well. 298 PhoneApp.getInstance().wakeUpScreen(); 299 300 /* If number is null, we're probably trying to call a non-existent voicemail number, 301 * send an empty flash or something else is fishy. Whatever the problem, there's no 302 * number, so there's no point in allowing apps to modify the number. */ 303 if (number == null || TextUtils.isEmpty(number)) { 304 if (intent.getBooleanExtra(EXTRA_SEND_EMPTY_FLASH, false)) { 305 Log.i(TAG, "onCreate: SEND_EMPTY_FLASH..."); 306 PhoneUtils.sendEmptyFlash(PhoneApp.getPhone()); 307 finish(); 308 return; 309 } else { 310 Log.i(TAG, "onCreate: null or empty number, setting callNow=true..."); 311 callNow = true; 312 } 313 } 314 315 if (callNow) { 316 intent.setClass(this, InCallScreen.class); 317 if (DBG) Log.v(TAG, "onCreate(): callNow case, calling startActivity: " + intent); 318 startActivity(intent); 319 } 320 321 // For now, SIP calls will be processed directly without a 322 // NEW_OUTGOING_CALL broadcast. 323 // 324 // TODO: In the future, though, 3rd party apps *should* be allowed to 325 // intercept outgoing calls to SIP addresses as well. To do this, we should 326 // (1) update the NEW_OUTGOING_CALL intent documentation to explain this 327 // case, and (2) pass the outgoing SIP address by *not* overloading the 328 // EXTRA_PHONE_NUMBER extra, but instead using a new separate extra to hold 329 // the outgoing SIP address. (Be sure to document whether it's a URI or just 330 // a plain address, whether it could be a tel: URI, etc.) 331 Uri uri = intent.getData(); 332 String scheme = uri.getScheme(); 333 if ("sip".equals(scheme) || PhoneNumberUtils.isUriNumber(number)) { 334 startSipCallOptionsHandler(this, intent, uri, number); 335 finish(); 336 return; 337 } 338 339 Intent broadcastIntent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL); 340 if (number != null) { 341 broadcastIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number); 342 } 343 PhoneUtils.checkAndCopyPhoneProviderExtras(intent, broadcastIntent); 344 broadcastIntent.putExtra(EXTRA_ALREADY_CALLED, callNow); 345 broadcastIntent.putExtra(EXTRA_ORIGINAL_URI, uri.toString()); 346 347 if (DBG) Log.v(TAG, "Broadcasting intent: " + broadcastIntent + "."); 348 sendOrderedBroadcast(broadcastIntent, PERMISSION, new OutgoingCallReceiver(), 349 null, Activity.RESULT_OK, number, null); 350 } 351 352 // Implement onConfigurationChanged() purely for debugging purposes, 353 // to make sure that the android:configChanges element in our manifest 354 // is working properly. 355 @Override 356 public void onConfigurationChanged(Configuration newConfig) { 357 super.onConfigurationChanged(newConfig); 358 if (DBG) Log.v(TAG, "onConfigurationChanged: newConfig = " + newConfig); 359 } 360 } 361