1 /* 2 * Copyright (C) 2006 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.AlertDialog; 20 import android.app.Dialog; 21 import android.app.ProgressDialog; 22 import android.bluetooth.IBluetoothHeadsetPhone; 23 import android.content.ActivityNotFoundException; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.DialogInterface; 27 import android.content.Intent; 28 import android.content.res.Configuration; 29 import android.media.AudioManager; 30 import android.net.Uri; 31 import android.os.Handler; 32 import android.os.Message; 33 import android.os.PersistableBundle; 34 import android.os.RemoteException; 35 import android.os.SystemProperties; 36 import android.telecom.PhoneAccount; 37 import android.telecom.PhoneAccountHandle; 38 import android.telecom.VideoProfile; 39 import android.telephony.CarrierConfigManager; 40 import android.telephony.PhoneNumberUtils; 41 import android.telephony.SubscriptionManager; 42 import android.text.TextUtils; 43 import android.util.Log; 44 import android.view.ContextThemeWrapper; 45 import android.view.KeyEvent; 46 import android.view.LayoutInflater; 47 import android.view.View; 48 import android.view.WindowManager; 49 import android.widget.EditText; 50 import android.widget.Toast; 51 52 import com.android.internal.telephony.Call; 53 import com.android.internal.telephony.CallManager; 54 import com.android.internal.telephony.CallStateException; 55 import com.android.internal.telephony.CallerInfo; 56 import com.android.internal.telephony.CallerInfoAsyncQuery; 57 import com.android.internal.telephony.Connection; 58 import com.android.internal.telephony.IccCard; 59 import com.android.internal.telephony.MmiCode; 60 import com.android.internal.telephony.Phone; 61 import com.android.internal.telephony.PhoneConstants; 62 import com.android.internal.telephony.PhoneFactory; 63 import com.android.internal.telephony.TelephonyCapabilities; 64 import com.android.internal.telephony.TelephonyProperties; 65 import com.android.internal.telephony.sip.SipPhone; 66 import com.android.phone.CallGatewayManager.RawGatewayInfo; 67 import com.android.services.telephony.TelephonyConnectionService; 68 69 import java.util.Arrays; 70 import java.util.List; 71 72 /** 73 * Misc utilities for the Phone app. 74 */ 75 public class PhoneUtils { 76 public static final String EMERGENCY_ACCOUNT_HANDLE_ID = "E"; 77 private static final String LOG_TAG = "PhoneUtils"; 78 private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2); 79 80 // Do not check in with VDBG = true, since that may write PII to the system log. 81 private static final boolean VDBG = false; 82 83 /** Control stack trace for Audio Mode settings */ 84 private static final boolean DBG_SETAUDIOMODE_STACK = false; 85 86 /** Identifier for the "Add Call" intent extra. */ 87 static final String ADD_CALL_MODE_KEY = "add_call_mode"; 88 89 // Return codes from placeCall() 90 static final int CALL_STATUS_DIALED = 0; // The number was successfully dialed 91 static final int CALL_STATUS_DIALED_MMI = 1; // The specified number was an MMI code 92 static final int CALL_STATUS_FAILED = 2; // The call failed 93 94 // State of the Phone's audio modes 95 // Each state can move to the other states, but within the state only certain 96 // transitions for AudioManager.setMode() are allowed. 97 static final int AUDIO_IDLE = 0; /** audio behaviour at phone idle */ 98 static final int AUDIO_RINGING = 1; /** audio behaviour while ringing */ 99 static final int AUDIO_OFFHOOK = 2; /** audio behaviour while in call. */ 100 101 // USSD string length for MMI operations 102 static final int MIN_USSD_LEN = 1; 103 static final int MAX_USSD_LEN = 160; 104 105 /** Speaker state, persisting between wired headset connection events */ 106 private static boolean sIsSpeakerEnabled = false; 107 108 /** Static handler for the connection/mute tracking */ 109 private static ConnectionHandler mConnectionHandler; 110 111 /** Phone state changed event*/ 112 private static final int PHONE_STATE_CHANGED = -1; 113 114 /** check status then decide whether answerCall */ 115 private static final int MSG_CHECK_STATUS_ANSWERCALL = 100; 116 117 /** poll phone DISCONNECTING status interval */ 118 private static final int DISCONNECTING_POLLING_INTERVAL_MS = 200; 119 120 /** poll phone DISCONNECTING status times limit */ 121 private static final int DISCONNECTING_POLLING_TIMES_LIMIT = 8; 122 123 /** Define for not a special CNAP string */ 124 private static final int CNAP_SPECIAL_CASE_NO = -1; 125 126 /** Noise suppression status as selected by user */ 127 private static boolean sIsNoiseSuppressionEnabled = true; 128 129 /** 130 * Theme to use for dialogs displayed by utility methods in this class. This is needed 131 * because these dialogs are displayed using the application context, which does not resolve 132 * the dialog theme correctly. 133 */ 134 private static final int THEME = AlertDialog.THEME_DEVICE_DEFAULT_LIGHT; 135 136 private static class FgRingCalls { 137 private Call fgCall; 138 private Call ringing; FgRingCalls(Call fg, Call ring)139 public FgRingCalls(Call fg, Call ring) { 140 fgCall = fg; 141 ringing = ring; 142 } 143 } 144 145 /** USSD information used to aggregate all USSD messages */ 146 private static AlertDialog sUssdDialog = null; 147 private static StringBuilder sUssdMsg = new StringBuilder(); 148 149 /** 150 * Handler that tracks the connections and updates the value of the 151 * Mute settings for each connection as needed. 152 */ 153 private static class ConnectionHandler extends Handler { 154 @Override handleMessage(Message msg)155 public void handleMessage(Message msg) { 156 switch (msg.what) { 157 case MSG_CHECK_STATUS_ANSWERCALL: 158 FgRingCalls frC = (FgRingCalls) msg.obj; 159 // wait for finishing disconnecting 160 // before check the ringing call state 161 if ((frC.fgCall != null) && 162 (frC.fgCall.getState() == Call.State.DISCONNECTING) && 163 (msg.arg1 < DISCONNECTING_POLLING_TIMES_LIMIT)) { 164 Message retryMsg = 165 mConnectionHandler.obtainMessage(MSG_CHECK_STATUS_ANSWERCALL); 166 retryMsg.arg1 = 1 + msg.arg1; 167 retryMsg.obj = msg.obj; 168 mConnectionHandler.sendMessageDelayed(retryMsg, 169 DISCONNECTING_POLLING_INTERVAL_MS); 170 // since hangupActiveCall() also accepts the ringing call 171 // check if the ringing call was already answered or not 172 // only answer it when the call still is ringing 173 } else if (frC.ringing.isRinging()) { 174 if (msg.arg1 == DISCONNECTING_POLLING_TIMES_LIMIT) { 175 Log.e(LOG_TAG, "DISCONNECTING time out"); 176 } 177 answerCall(frC.ringing); 178 } 179 break; 180 } 181 } 182 } 183 184 /** 185 * Register the ConnectionHandler with the phone, to receive connection events 186 */ initializeConnectionHandler(CallManager cm)187 public static void initializeConnectionHandler(CallManager cm) { 188 if (mConnectionHandler == null) { 189 mConnectionHandler = new ConnectionHandler(); 190 } 191 192 // pass over cm as user.obj 193 cm.registerForPreciseCallStateChanged(mConnectionHandler, PHONE_STATE_CHANGED, cm); 194 195 } 196 197 /** This class is never instantiated. */ PhoneUtils()198 private PhoneUtils() { 199 } 200 201 /** 202 * Answer the currently-ringing call. 203 * 204 * @return true if we answered the call, or false if there wasn't 205 * actually a ringing incoming call, or some other error occurred. 206 * 207 * @see #answerAndEndHolding(CallManager, Call) 208 * @see #answerAndEndActive(CallManager, Call) 209 */ answerCall(Call ringingCall)210 /* package */ static boolean answerCall(Call ringingCall) { 211 log("answerCall(" + ringingCall + ")..."); 212 final PhoneGlobals app = PhoneGlobals.getInstance(); 213 final CallNotifier notifier = app.notifier; 214 215 final Phone phone = ringingCall.getPhone(); 216 final boolean phoneIsCdma = (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA); 217 boolean answered = false; 218 IBluetoothHeadsetPhone btPhone = null; 219 220 if (phoneIsCdma) { 221 // Stop any signalInfo tone being played when a Call waiting gets answered 222 if (ringingCall.getState() == Call.State.WAITING) { 223 notifier.stopSignalInfoTone(); 224 } 225 } 226 227 if (ringingCall != null && ringingCall.isRinging()) { 228 if (DBG) log("answerCall: call state = " + ringingCall.getState()); 229 try { 230 if (phoneIsCdma) { 231 if (app.cdmaPhoneCallState.getCurrentCallState() 232 == CdmaPhoneCallState.PhoneCallState.IDLE) { 233 // This is the FIRST incoming call being answered. 234 // Set the Phone Call State to SINGLE_ACTIVE 235 app.cdmaPhoneCallState.setCurrentCallState( 236 CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE); 237 } else { 238 // This is the CALL WAITING call being answered. 239 // Set the Phone Call State to CONF_CALL 240 app.cdmaPhoneCallState.setCurrentCallState( 241 CdmaPhoneCallState.PhoneCallState.CONF_CALL); 242 // Enable "Add Call" option after answering a Call Waiting as the user 243 // should be allowed to add another call in case one of the parties 244 // drops off 245 app.cdmaPhoneCallState.setAddCallMenuStateAfterCallWaiting(true); 246 } 247 } 248 249 final boolean isRealIncomingCall = isRealIncomingCall(ringingCall.getState()); 250 251 //if (DBG) log("sPhone.acceptCall"); 252 app.mCM.acceptCall(ringingCall); 253 answered = true; 254 255 setAudioMode(); 256 257 // Check is phone in any dock, and turn on speaker accordingly 258 final boolean speakerActivated = activateSpeakerIfDocked(phone); 259 260 final BluetoothManager btManager = app.getBluetoothManager(); 261 262 // When answering a phone call, the user will move the phone near to her/his ear 263 // and start conversation, without checking its speaker status. If some other 264 // application turned on the speaker mode before the call and didn't turn it off, 265 // Phone app would need to be responsible for the speaker phone. 266 // Here, we turn off the speaker if 267 // - the phone call is the first in-coming call, 268 // - we did not activate speaker by ourselves during the process above, and 269 // - Bluetooth headset is not in use. 270 if (isRealIncomingCall && !speakerActivated && isSpeakerOn(app) 271 && !btManager.isBluetoothHeadsetAudioOn()) { 272 // This is not an error but might cause users' confusion. Add log just in case. 273 Log.i(LOG_TAG, "Forcing speaker off due to new incoming call..."); 274 turnOnSpeaker(app, false, true); 275 } 276 } catch (CallStateException ex) { 277 Log.w(LOG_TAG, "answerCall: caught " + ex, ex); 278 279 if (phoneIsCdma) { 280 // restore the cdmaPhoneCallState and btPhone.cdmaSetSecondCallState: 281 app.cdmaPhoneCallState.setCurrentCallState( 282 app.cdmaPhoneCallState.getPreviousCallState()); 283 if (btPhone != null) { 284 try { 285 btPhone.cdmaSetSecondCallState(false); 286 } catch (RemoteException e) { 287 Log.e(LOG_TAG, Log.getStackTraceString(new Throwable())); 288 } 289 } 290 } 291 } 292 } 293 return answered; 294 } 295 296 /** 297 * Hangs up all active calls. 298 */ hangupAllCalls(CallManager cm)299 static void hangupAllCalls(CallManager cm) { 300 final Call ringing = cm.getFirstActiveRingingCall(); 301 final Call fg = cm.getActiveFgCall(); 302 final Call bg = cm.getFirstActiveBgCall(); 303 304 // We go in reverse order, BG->FG->RINGING because hanging up a ringing call or an active 305 // call can move a bg call to a fg call which would force us to loop over each call 306 // several times. This ordering works best to ensure we dont have any more calls. 307 if (bg != null && !bg.isIdle()) { 308 hangup(bg); 309 } 310 if (fg != null && !fg.isIdle()) { 311 hangup(fg); 312 } 313 if (ringing != null && !ringing.isIdle()) { 314 hangupRingingCall(fg); 315 } 316 } 317 318 /** 319 * Smart "hang up" helper method which hangs up exactly one connection, 320 * based on the current Phone state, as follows: 321 * <ul> 322 * <li>If there's a ringing call, hang that up. 323 * <li>Else if there's a foreground call, hang that up. 324 * <li>Else if there's a background call, hang that up. 325 * <li>Otherwise do nothing. 326 * </ul> 327 * @return true if we successfully hung up, or false 328 * if there were no active calls at all. 329 */ hangup(CallManager cm)330 static boolean hangup(CallManager cm) { 331 boolean hungup = false; 332 Call ringing = cm.getFirstActiveRingingCall(); 333 Call fg = cm.getActiveFgCall(); 334 Call bg = cm.getFirstActiveBgCall(); 335 336 if (!ringing.isIdle()) { 337 log("hangup(): hanging up ringing call"); 338 hungup = hangupRingingCall(ringing); 339 } else if (!fg.isIdle()) { 340 log("hangup(): hanging up foreground call"); 341 hungup = hangup(fg); 342 } else if (!bg.isIdle()) { 343 log("hangup(): hanging up background call"); 344 hungup = hangup(bg); 345 } else { 346 // No call to hang up! This is unlikely in normal usage, 347 // since the UI shouldn't be providing an "End call" button in 348 // the first place. (But it *can* happen, rarely, if an 349 // active call happens to disconnect on its own right when the 350 // user is trying to hang up..) 351 log("hangup(): no active call to hang up"); 352 } 353 if (DBG) log("==> hungup = " + hungup); 354 355 return hungup; 356 } 357 hangupRingingCall(Call ringing)358 static boolean hangupRingingCall(Call ringing) { 359 if (DBG) log("hangup ringing call"); 360 int phoneType = ringing.getPhone().getPhoneType(); 361 Call.State state = ringing.getState(); 362 363 if (state == Call.State.INCOMING) { 364 // Regular incoming call (with no other active calls) 365 log("hangupRingingCall(): regular incoming call: hangup()"); 366 return hangup(ringing); 367 } else { 368 // Unexpected state: the ringing call isn't INCOMING or 369 // WAITING, so there's no reason to have called 370 // hangupRingingCall() in the first place. 371 // (Presumably the incoming call went away at the exact moment 372 // we got here, so just do nothing.) 373 Log.w(LOG_TAG, "hangupRingingCall: no INCOMING or WAITING call"); 374 return false; 375 } 376 } 377 hangupActiveCall(Call foreground)378 static boolean hangupActiveCall(Call foreground) { 379 if (DBG) log("hangup active call"); 380 return hangup(foreground); 381 } 382 hangupHoldingCall(Call background)383 static boolean hangupHoldingCall(Call background) { 384 if (DBG) log("hangup holding call"); 385 return hangup(background); 386 } 387 388 /** 389 * Used in CDMA phones to end the complete Call session 390 * @param phone the Phone object. 391 * @return true if *any* call was successfully hung up 392 */ hangupRingingAndActive(Phone phone)393 static boolean hangupRingingAndActive(Phone phone) { 394 boolean hungUpRingingCall = false; 395 boolean hungUpFgCall = false; 396 Call ringingCall = phone.getRingingCall(); 397 Call fgCall = phone.getForegroundCall(); 398 399 // Hang up any Ringing Call 400 if (!ringingCall.isIdle()) { 401 log("hangupRingingAndActive: Hang up Ringing Call"); 402 hungUpRingingCall = hangupRingingCall(ringingCall); 403 } 404 405 // Hang up any Active Call 406 if (!fgCall.isIdle()) { 407 log("hangupRingingAndActive: Hang up Foreground Call"); 408 hungUpFgCall = hangupActiveCall(fgCall); 409 } 410 411 return hungUpRingingCall || hungUpFgCall; 412 } 413 414 /** 415 * Trivial wrapper around Call.hangup(), except that we return a 416 * boolean success code rather than throwing CallStateException on 417 * failure. 418 * 419 * @return true if the call was successfully hung up, or false 420 * if the call wasn't actually active. 421 */ hangup(Call call)422 static boolean hangup(Call call) { 423 try { 424 CallManager cm = PhoneGlobals.getInstance().mCM; 425 426 if (call.getState() == Call.State.ACTIVE && cm.hasActiveBgCall()) { 427 // handle foreground call hangup while there is background call 428 log("- hangup(Call): hangupForegroundResumeBackground..."); 429 cm.hangupForegroundResumeBackground(cm.getFirstActiveBgCall()); 430 } else { 431 log("- hangup(Call): regular hangup()..."); 432 call.hangup(); 433 } 434 return true; 435 } catch (CallStateException ex) { 436 Log.e(LOG_TAG, "Call hangup: caught " + ex, ex); 437 } 438 439 return false; 440 } 441 442 /** 443 * Trivial wrapper around Connection.hangup(), except that we silently 444 * do nothing (rather than throwing CallStateException) if the 445 * connection wasn't actually active. 446 */ hangup(Connection c)447 static void hangup(Connection c) { 448 try { 449 if (c != null) { 450 c.hangup(); 451 } 452 } catch (CallStateException ex) { 453 Log.w(LOG_TAG, "Connection hangup: caught " + ex, ex); 454 } 455 } 456 answerAndEndHolding(CallManager cm, Call ringing)457 static boolean answerAndEndHolding(CallManager cm, Call ringing) { 458 if (DBG) log("end holding & answer waiting: 1"); 459 if (!hangupHoldingCall(cm.getFirstActiveBgCall())) { 460 Log.e(LOG_TAG, "end holding failed!"); 461 return false; 462 } 463 464 if (DBG) log("end holding & answer waiting: 2"); 465 return answerCall(ringing); 466 467 } 468 469 /** 470 * Answers the incoming call specified by "ringing", and ends the currently active phone call. 471 * 472 * This method is useful when's there's an incoming call which we cannot manage with the 473 * current call. e.g. when you are having a phone call with CDMA network and has received 474 * a SIP call, then we won't expect our telephony can manage those phone calls simultaneously. 475 * Note that some types of network may allow multiple phone calls at once; GSM allows to hold 476 * an ongoing phone call, so we don't need to end the active call. The caller of this method 477 * needs to check if the network allows multiple phone calls or not. 478 * 479 * @see #answerCall(Call) 480 * @see InCallScreen#internalAnswerCall() 481 */ answerAndEndActive(CallManager cm, Call ringing)482 /* package */ static boolean answerAndEndActive(CallManager cm, Call ringing) { 483 if (DBG) log("answerAndEndActive()..."); 484 485 // Unlike the answerCall() method, we *don't* need to stop the 486 // ringer or change audio modes here since the user is already 487 // in-call, which means that the audio mode is already set 488 // correctly, and that we wouldn't have started the ringer in the 489 // first place. 490 491 // hanging up the active call also accepts the waiting call 492 // while active call and waiting call are from the same phone 493 // i.e. both from GSM phone 494 Call fgCall = cm.getActiveFgCall(); 495 if (!hangupActiveCall(fgCall)) { 496 Log.w(LOG_TAG, "end active call failed!"); 497 return false; 498 } 499 500 mConnectionHandler.removeMessages(MSG_CHECK_STATUS_ANSWERCALL); 501 Message msg = mConnectionHandler.obtainMessage(MSG_CHECK_STATUS_ANSWERCALL); 502 msg.arg1 = 1; 503 msg.obj = new FgRingCalls(fgCall, ringing); 504 mConnectionHandler.sendMessage(msg); 505 506 return true; 507 } 508 509 /** 510 * For a CDMA phone, advance the call state upon making a new 511 * outgoing call. 512 * 513 * <pre> 514 * IDLE -> SINGLE_ACTIVE 515 * or 516 * SINGLE_ACTIVE -> THRWAY_ACTIVE 517 * </pre> 518 * @param app The phone instance. 519 */ updateCdmaCallStateOnNewOutgoingCall(PhoneGlobals app, Connection connection)520 private static void updateCdmaCallStateOnNewOutgoingCall(PhoneGlobals app, 521 Connection connection) { 522 if (app.cdmaPhoneCallState.getCurrentCallState() == 523 CdmaPhoneCallState.PhoneCallState.IDLE) { 524 // This is the first outgoing call. Set the Phone Call State to ACTIVE 525 app.cdmaPhoneCallState.setCurrentCallState( 526 CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE); 527 } else { 528 // This is the second outgoing call. Set the Phone Call State to 3WAY 529 app.cdmaPhoneCallState.setCurrentCallState( 530 CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE); 531 532 // TODO: Remove this code. 533 //app.getCallModeler().setCdmaOutgoing3WayCall(connection); 534 } 535 } 536 537 /** 538 * @see placeCall below 539 */ placeCall(Context context, Phone phone, String number, Uri contactRef, boolean isEmergencyCall)540 public static int placeCall(Context context, Phone phone, String number, Uri contactRef, 541 boolean isEmergencyCall) { 542 return placeCall(context, phone, number, contactRef, isEmergencyCall, 543 CallGatewayManager.EMPTY_INFO, null); 544 } 545 546 /** 547 * Dial the number using the phone passed in. 548 * 549 * If the connection is establised, this method issues a sync call 550 * that may block to query the caller info. 551 * TODO: Change the logic to use the async query. 552 * 553 * @param context To perform the CallerInfo query. 554 * @param phone the Phone object. 555 * @param number to be dialed as requested by the user. This is 556 * NOT the phone number to connect to. It is used only to build the 557 * call card and to update the call log. See above for restrictions. 558 * @param contactRef that triggered the call. Typically a 'tel:' 559 * uri but can also be a 'content://contacts' one. 560 * @param isEmergencyCall indicates that whether or not this is an 561 * emergency call 562 * @param gatewayUri Is the address used to setup the connection, null 563 * if not using a gateway 564 * @param callGateway Class for setting gateway data on a successful call. 565 * 566 * @return either CALL_STATUS_DIALED or CALL_STATUS_FAILED 567 */ placeCall(Context context, Phone phone, String number, Uri contactRef, boolean isEmergencyCall, RawGatewayInfo gatewayInfo, CallGatewayManager callGateway)568 public static int placeCall(Context context, Phone phone, String number, Uri contactRef, 569 boolean isEmergencyCall, RawGatewayInfo gatewayInfo, CallGatewayManager callGateway) { 570 final Uri gatewayUri = gatewayInfo.gatewayUri; 571 572 if (VDBG) { 573 log("placeCall()... number: '" + number + "'" 574 + ", GW:'" + gatewayUri + "'" 575 + ", contactRef:" + contactRef 576 + ", isEmergencyCall: " + isEmergencyCall); 577 } else { 578 log("placeCall()... number: " + toLogSafePhoneNumber(number) 579 + ", GW: " + (gatewayUri != null ? "non-null" : "null") 580 + ", emergency? " + isEmergencyCall); 581 } 582 final PhoneGlobals app = PhoneGlobals.getInstance(); 583 584 boolean useGateway = false; 585 if (null != gatewayUri && 586 !isEmergencyCall && 587 PhoneUtils.isRoutableViaGateway(number)) { // Filter out MMI, OTA and other codes. 588 useGateway = true; 589 } 590 591 int status = CALL_STATUS_DIALED; 592 Connection connection; 593 String numberToDial; 594 if (useGateway) { 595 // TODO: 'tel' should be a constant defined in framework base 596 // somewhere (it is in webkit.) 597 if (null == gatewayUri || !PhoneAccount.SCHEME_TEL.equals(gatewayUri.getScheme())) { 598 Log.e(LOG_TAG, "Unsupported URL:" + gatewayUri); 599 return CALL_STATUS_FAILED; 600 } 601 602 // We can use getSchemeSpecificPart because we don't allow # 603 // in the gateway numbers (treated a fragment delim.) However 604 // if we allow more complex gateway numbers sequence (with 605 // passwords or whatnot) that use #, this may break. 606 // TODO: Need to support MMI codes. 607 numberToDial = gatewayUri.getSchemeSpecificPart(); 608 } else { 609 numberToDial = number; 610 } 611 612 // Remember if the phone state was in IDLE state before this call. 613 // After calling CallManager#dial(), getState() will return different state. 614 final boolean initiallyIdle = app.mCM.getState() == PhoneConstants.State.IDLE; 615 616 try { 617 connection = app.mCM.dial(phone, numberToDial, VideoProfile.STATE_AUDIO_ONLY); 618 } catch (CallStateException ex) { 619 // CallStateException means a new outgoing call is not currently 620 // possible: either no more call slots exist, or there's another 621 // call already in the process of dialing or ringing. 622 Log.w(LOG_TAG, "Exception from app.mCM.dial()", ex); 623 return CALL_STATUS_FAILED; 624 625 // Note that it's possible for CallManager.dial() to return 626 // null *without* throwing an exception; that indicates that 627 // we dialed an MMI (see below). 628 } 629 630 int phoneType = phone.getPhoneType(); 631 632 // On GSM phones, null is returned for MMI codes 633 if (null == connection) { 634 status = CALL_STATUS_FAILED; 635 } else { 636 // Now that the call is successful, we can save the gateway info for the call 637 if (callGateway != null) { 638 callGateway.setGatewayInfoForConnection(connection, gatewayInfo); 639 } 640 641 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) { 642 updateCdmaCallStateOnNewOutgoingCall(app, connection); 643 } 644 645 if (gatewayUri == null) { 646 // phone.dial() succeeded: we're now in a normal phone call. 647 // attach the URI to the CallerInfo Object if it is there, 648 // otherwise just attach the Uri Reference. 649 // if the uri does not have a "content" scheme, then we treat 650 // it as if it does NOT have a unique reference. 651 String content = context.getContentResolver().SCHEME_CONTENT; 652 if ((contactRef != null) && (contactRef.getScheme().equals(content))) { 653 Object userDataObject = connection.getUserData(); 654 if (userDataObject == null) { 655 connection.setUserData(contactRef); 656 } else { 657 // TODO: This branch is dead code, we have 658 // just created the connection which has 659 // no user data (null) by default. 660 if (userDataObject instanceof CallerInfo) { 661 ((CallerInfo) userDataObject).contactRefUri = contactRef; 662 } else { 663 ((CallerInfoToken) userDataObject).currentInfo.contactRefUri = 664 contactRef; 665 } 666 } 667 } 668 } 669 670 startGetCallerInfo(context, connection, null, null, gatewayInfo); 671 672 setAudioMode(); 673 674 if (DBG) log("about to activate speaker"); 675 // Check is phone in any dock, and turn on speaker accordingly 676 final boolean speakerActivated = activateSpeakerIfDocked(phone); 677 678 final BluetoothManager btManager = app.getBluetoothManager(); 679 680 // See also similar logic in answerCall(). 681 if (initiallyIdle && !speakerActivated && isSpeakerOn(app) 682 && !btManager.isBluetoothHeadsetAudioOn()) { 683 // This is not an error but might cause users' confusion. Add log just in case. 684 Log.i(LOG_TAG, "Forcing speaker off when initiating a new outgoing call..."); 685 PhoneUtils.turnOnSpeaker(app, false, true); 686 } 687 } 688 689 return status; 690 } 691 toLogSafePhoneNumber(String number)692 /* package */ static String toLogSafePhoneNumber(String number) { 693 // For unknown number, log empty string. 694 if (number == null) { 695 return ""; 696 } 697 698 if (VDBG) { 699 // When VDBG is true we emit PII. 700 return number; 701 } 702 703 // Do exactly same thing as Uri#toSafeString() does, which will enable us to compare 704 // sanitized phone numbers. 705 StringBuilder builder = new StringBuilder(); 706 for (int i = 0; i < number.length(); i++) { 707 char c = number.charAt(i); 708 if (c == '-' || c == '@' || c == '.') { 709 builder.append(c); 710 } else { 711 builder.append('x'); 712 } 713 } 714 return builder.toString(); 715 } 716 717 /** 718 * Wrapper function to control when to send an empty Flash command to the network. 719 * Mainly needed for CDMA networks, such as scenarios when we need to send a blank flash 720 * to the network prior to placing a 3-way call for it to be successful. 721 */ sendEmptyFlash(Phone phone)722 static void sendEmptyFlash(Phone phone) { 723 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) { 724 Call fgCall = phone.getForegroundCall(); 725 if (fgCall.getState() == Call.State.ACTIVE) { 726 // Send the empty flash 727 if (DBG) Log.d(LOG_TAG, "onReceive: (CDMA) sending empty flash to network"); 728 switchHoldingAndActive(phone.getBackgroundCall()); 729 } 730 } 731 } 732 swap()733 static void swap() { 734 final PhoneGlobals mApp = PhoneGlobals.getInstance(); 735 if (!okToSwapCalls(mApp.mCM)) { 736 // TODO: throw an error instead? 737 return; 738 } 739 740 // Swap the fg and bg calls. 741 // In the future we may provide some way for user to choose among 742 // multiple background calls, for now, always act on the first background call. 743 PhoneUtils.switchHoldingAndActive(mApp.mCM.getFirstActiveBgCall()); 744 } 745 746 /** 747 * @param heldCall is the background call want to be swapped 748 */ switchHoldingAndActive(Call heldCall)749 static void switchHoldingAndActive(Call heldCall) { 750 log("switchHoldingAndActive()..."); 751 try { 752 CallManager cm = PhoneGlobals.getInstance().mCM; 753 if (heldCall.isIdle()) { 754 // no heldCall, so it is to hold active call 755 cm.switchHoldingAndActive(cm.getFgPhone().getBackgroundCall()); 756 } else { 757 // has particular heldCall, so to switch 758 cm.switchHoldingAndActive(heldCall); 759 } 760 setAudioMode(cm); 761 } catch (CallStateException ex) { 762 Log.w(LOG_TAG, "switchHoldingAndActive: caught " + ex, ex); 763 } 764 } 765 mergeCalls()766 static void mergeCalls() { 767 mergeCalls(PhoneGlobals.getInstance().mCM); 768 } 769 mergeCalls(CallManager cm)770 static void mergeCalls(CallManager cm) { 771 int phoneType = cm.getFgPhone().getPhoneType(); 772 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) { 773 log("mergeCalls(): CDMA..."); 774 PhoneGlobals app = PhoneGlobals.getInstance(); 775 if (app.cdmaPhoneCallState.getCurrentCallState() 776 == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) { 777 // Set the Phone Call State to conference 778 app.cdmaPhoneCallState.setCurrentCallState( 779 CdmaPhoneCallState.PhoneCallState.CONF_CALL); 780 781 // Send flash cmd 782 // TODO: Need to change the call from switchHoldingAndActive to 783 // something meaningful as we are not actually trying to swap calls but 784 // instead are merging two calls by sending a Flash command. 785 log("- sending flash..."); 786 switchHoldingAndActive(cm.getFirstActiveBgCall()); 787 } 788 } else { 789 try { 790 log("mergeCalls(): calling cm.conference()..."); 791 cm.conference(cm.getFirstActiveBgCall()); 792 } catch (CallStateException ex) { 793 Log.w(LOG_TAG, "mergeCalls: caught " + ex, ex); 794 } 795 } 796 } 797 separateCall(Connection c)798 static void separateCall(Connection c) { 799 try { 800 if (DBG) log("separateCall: " + toLogSafePhoneNumber(c.getAddress())); 801 c.separate(); 802 } catch (CallStateException ex) { 803 Log.w(LOG_TAG, "separateCall: caught " + ex, ex); 804 } 805 } 806 807 /** 808 * Handle the MMIInitiate message and put up an alert that lets 809 * the user cancel the operation, if applicable. 810 * 811 * @param context context to get strings. 812 * @param mmiCode the MmiCode object being started. 813 * @param buttonCallbackMessage message to post when button is clicked. 814 * @param previousAlert a previous alert used in this activity. 815 * @return the dialog handle 816 */ displayMMIInitiate(Context context, MmiCode mmiCode, Message buttonCallbackMessage, Dialog previousAlert)817 static Dialog displayMMIInitiate(Context context, 818 MmiCode mmiCode, 819 Message buttonCallbackMessage, 820 Dialog previousAlert) { 821 if (DBG) log("displayMMIInitiate: " + mmiCode); 822 if (previousAlert != null) { 823 previousAlert.dismiss(); 824 } 825 826 // The UI paradigm we are using now requests that all dialogs have 827 // user interaction, and that any other messages to the user should 828 // be by way of Toasts. 829 // 830 // In adhering to this request, all MMI initiating "OK" dialogs 831 // (non-cancelable MMIs) that end up being closed when the MMI 832 // completes (thereby showing a completion dialog) are being 833 // replaced with Toasts. 834 // 835 // As a side effect, moving to Toasts for the non-cancelable MMIs 836 // also means that buttonCallbackMessage (which was tied into "OK") 837 // is no longer invokable for these dialogs. This is not a problem 838 // since the only callback messages we supported were for cancelable 839 // MMIs anyway. 840 // 841 // A cancelable MMI is really just a USSD request. The term 842 // "cancelable" here means that we can cancel the request when the 843 // system prompts us for a response, NOT while the network is 844 // processing the MMI request. Any request to cancel a USSD while 845 // the network is NOT ready for a response may be ignored. 846 // 847 // With this in mind, we replace the cancelable alert dialog with 848 // a progress dialog, displayed until we receive a request from 849 // the the network. For more information, please see the comments 850 // in the displayMMIComplete() method below. 851 // 852 // Anything that is NOT a USSD request is a normal MMI request, 853 // which will bring up a toast (desribed above). 854 855 boolean isCancelable = (mmiCode != null) && mmiCode.isCancelable(); 856 857 if (!isCancelable) { 858 if (DBG) log("not a USSD code, displaying status toast."); 859 CharSequence text = context.getText(R.string.mmiStarted); 860 Toast.makeText(context, text, Toast.LENGTH_SHORT) 861 .show(); 862 return null; 863 } else { 864 if (DBG) log("running USSD code, displaying indeterminate progress."); 865 866 // create the indeterminate progress dialog and display it. 867 ProgressDialog pd = new ProgressDialog(context); 868 pd.setMessage(context.getText(R.string.ussdRunning)); 869 pd.setCancelable(false); 870 pd.setIndeterminate(true); 871 pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); 872 873 pd.show(); 874 875 return pd; 876 } 877 878 } 879 880 /** 881 * Handle the MMIComplete message and fire off an intent to display 882 * the message. 883 * 884 * @param context context to get strings. 885 * @param mmiCode MMI result. 886 * @param previousAlert a previous alert used in this activity. 887 */ displayMMIComplete(final Phone phone, Context context, final MmiCode mmiCode, Message dismissCallbackMessage, AlertDialog previousAlert)888 static void displayMMIComplete(final Phone phone, Context context, final MmiCode mmiCode, 889 Message dismissCallbackMessage, 890 AlertDialog previousAlert) { 891 final PhoneGlobals app = PhoneGlobals.getInstance(); 892 CharSequence text; 893 int title = 0; // title for the progress dialog, if needed. 894 MmiCode.State state = mmiCode.getState(); 895 896 if (DBG) log("displayMMIComplete: state=" + state); 897 898 switch (state) { 899 case PENDING: 900 // USSD code asking for feedback from user. 901 text = mmiCode.getMessage(); 902 if (DBG) log("- using text from PENDING MMI message: '" + text + "'"); 903 break; 904 case CANCELLED: 905 text = null; 906 break; 907 case COMPLETE: 908 if (app.getPUKEntryActivity() != null) { 909 // if an attempt to unPUK the device was made, we specify 910 // the title and the message here. 911 title = com.android.internal.R.string.PinMmi; 912 text = context.getText(R.string.puk_unlocked); 913 break; 914 } 915 // All other conditions for the COMPLETE mmi state will cause 916 // the case to fall through to message logic in common with 917 // the FAILED case. 918 919 case FAILED: 920 text = mmiCode.getMessage(); 921 if (DBG) log("- using text from MMI message: '" + text + "'"); 922 break; 923 default: 924 throw new IllegalStateException("Unexpected MmiCode state: " + state); 925 } 926 927 if (previousAlert != null) { 928 previousAlert.dismiss(); 929 } 930 931 // Check to see if a UI exists for the PUK activation. If it does 932 // exist, then it indicates that we're trying to unblock the PUK. 933 if ((app.getPUKEntryActivity() != null) && (state == MmiCode.State.COMPLETE)) { 934 if (DBG) log("displaying PUK unblocking progress dialog."); 935 936 // create the progress dialog, make sure the flags and type are 937 // set correctly. 938 ProgressDialog pd = new ProgressDialog(app); 939 pd.setTitle(title); 940 pd.setMessage(text); 941 pd.setCancelable(false); 942 pd.setIndeterminate(true); 943 pd.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); 944 pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); 945 946 // display the dialog 947 pd.show(); 948 949 // indicate to the Phone app that the progress dialog has 950 // been assigned for the PUK unlock / SIM READY process. 951 app.setPukEntryProgressDialog(pd); 952 953 } else { 954 // In case of failure to unlock, we'll need to reset the 955 // PUK unlock activity, so that the user may try again. 956 if (app.getPUKEntryActivity() != null) { 957 app.setPukEntryActivity(null); 958 } 959 960 // A USSD in a pending state means that it is still 961 // interacting with the user. 962 if (state != MmiCode.State.PENDING) { 963 if (DBG) log("MMI code has finished running."); 964 965 if (DBG) log("Extended NW displayMMIInitiate (" + text + ")"); 966 if (text == null || text.length() == 0) 967 return; 968 969 // displaying system alert dialog on the screen instead of 970 // using another activity to display the message. This 971 // places the message at the forefront of the UI. 972 973 if (sUssdDialog == null) { 974 sUssdDialog = new AlertDialog.Builder(context, THEME) 975 .setPositiveButton(R.string.ok, null) 976 .setCancelable(true) 977 .setOnDismissListener(new DialogInterface.OnDismissListener() { 978 @Override 979 public void onDismiss(DialogInterface dialog) { 980 sUssdMsg.setLength(0); 981 } 982 }) 983 .create(); 984 985 sUssdDialog.getWindow().setType( 986 WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); 987 sUssdDialog.getWindow().addFlags( 988 WindowManager.LayoutParams.FLAG_DIM_BEHIND); 989 } 990 if (sUssdMsg.length() != 0) { 991 sUssdMsg 992 .insert(0, "\n") 993 .insert(0, app.getResources().getString(R.string.ussd_dialog_sep)) 994 .insert(0, "\n"); 995 } 996 sUssdMsg.insert(0, text); 997 sUssdDialog.setMessage(sUssdMsg.toString()); 998 sUssdDialog.show(); 999 } else { 1000 if (DBG) log("USSD code has requested user input. Constructing input dialog."); 1001 1002 // USSD MMI code that is interacting with the user. The 1003 // basic set of steps is this: 1004 // 1. User enters a USSD request 1005 // 2. We recognize the request and displayMMIInitiate 1006 // (above) creates a progress dialog. 1007 // 3. Request returns and we get a PENDING or COMPLETE 1008 // message. 1009 // 4. These MMI messages are caught in the PhoneApp 1010 // (onMMIComplete) and the InCallScreen 1011 // (mHandler.handleMessage) which bring up this dialog 1012 // and closes the original progress dialog, 1013 // respectively. 1014 // 5. If the message is anything other than PENDING, 1015 // we are done, and the alert dialog (directly above) 1016 // displays the outcome. 1017 // 6. If the network is requesting more information from 1018 // the user, the MMI will be in a PENDING state, and 1019 // we display this dialog with the message. 1020 // 7. User input, or cancel requests result in a return 1021 // to step 1. Keep in mind that this is the only 1022 // time that a USSD should be canceled. 1023 1024 // inflate the layout with the scrolling text area for the dialog. 1025 ContextThemeWrapper contextThemeWrapper = 1026 new ContextThemeWrapper(context, R.style.DialerAlertDialogTheme); 1027 LayoutInflater inflater = (LayoutInflater) contextThemeWrapper.getSystemService( 1028 Context.LAYOUT_INFLATER_SERVICE); 1029 View dialogView = inflater.inflate(R.layout.dialog_ussd_response, null); 1030 1031 // get the input field. 1032 final EditText inputText = (EditText) dialogView.findViewById(R.id.input_field); 1033 1034 // specify the dialog's click listener, with SEND and CANCEL logic. 1035 final DialogInterface.OnClickListener mUSSDDialogListener = 1036 new DialogInterface.OnClickListener() { 1037 public void onClick(DialogInterface dialog, int whichButton) { 1038 switch (whichButton) { 1039 case DialogInterface.BUTTON_POSITIVE: 1040 // As per spec 24.080, valid length of ussd string 1041 // is 1 - 160. If length is out of the range then 1042 // display toast message & Cancel MMI operation. 1043 if (inputText.length() < MIN_USSD_LEN 1044 || inputText.length() > MAX_USSD_LEN) { 1045 Toast.makeText(app, 1046 app.getResources().getString(R.string.enter_input, 1047 MIN_USSD_LEN, MAX_USSD_LEN), 1048 Toast.LENGTH_LONG).show(); 1049 if (mmiCode.isCancelable()) { 1050 mmiCode.cancel(); 1051 } 1052 } else { 1053 phone.sendUssdResponse(inputText.getText().toString()); 1054 } 1055 break; 1056 case DialogInterface.BUTTON_NEGATIVE: 1057 if (mmiCode.isCancelable()) { 1058 mmiCode.cancel(); 1059 } 1060 break; 1061 } 1062 } 1063 }; 1064 1065 // build the dialog 1066 final AlertDialog newDialog = new AlertDialog.Builder(contextThemeWrapper) 1067 .setMessage(text) 1068 .setView(dialogView) 1069 .setPositiveButton(R.string.send_button, mUSSDDialogListener) 1070 .setNegativeButton(R.string.cancel, mUSSDDialogListener) 1071 .setCancelable(false) 1072 .create(); 1073 1074 // attach the key listener to the dialog's input field and make 1075 // sure focus is set. 1076 final View.OnKeyListener mUSSDDialogInputListener = 1077 new View.OnKeyListener() { 1078 public boolean onKey(View v, int keyCode, KeyEvent event) { 1079 switch (keyCode) { 1080 case KeyEvent.KEYCODE_CALL: 1081 case KeyEvent.KEYCODE_ENTER: 1082 if(event.getAction() == KeyEvent.ACTION_DOWN) { 1083 phone.sendUssdResponse(inputText.getText().toString()); 1084 newDialog.dismiss(); 1085 } 1086 return true; 1087 } 1088 return false; 1089 } 1090 }; 1091 inputText.setOnKeyListener(mUSSDDialogInputListener); 1092 inputText.requestFocus(); 1093 1094 // set the window properties of the dialog 1095 newDialog.getWindow().setType( 1096 WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); 1097 newDialog.getWindow().addFlags( 1098 WindowManager.LayoutParams.FLAG_DIM_BEHIND); 1099 1100 // now show the dialog! 1101 newDialog.show(); 1102 1103 newDialog.getButton(DialogInterface.BUTTON_POSITIVE) 1104 .setTextColor(context.getResources().getColor(R.color.dialer_theme_color)); 1105 newDialog.getButton(DialogInterface.BUTTON_NEGATIVE) 1106 .setTextColor(context.getResources().getColor(R.color.dialer_theme_color)); 1107 } 1108 } 1109 } 1110 1111 /** 1112 * Cancels the current pending MMI operation, if applicable. 1113 * @return true if we canceled an MMI operation, or false 1114 * if the current pending MMI wasn't cancelable 1115 * or if there was no current pending MMI at all. 1116 * 1117 * @see displayMMIInitiate 1118 */ cancelMmiCode(Phone phone)1119 static boolean cancelMmiCode(Phone phone) { 1120 List<? extends MmiCode> pendingMmis = phone.getPendingMmiCodes(); 1121 int count = pendingMmis.size(); 1122 if (DBG) log("cancelMmiCode: num pending MMIs = " + count); 1123 1124 boolean canceled = false; 1125 if (count > 0) { 1126 // assume that we only have one pending MMI operation active at a time. 1127 // I don't think it's possible to enter multiple MMI codes concurrently 1128 // in the phone UI, because during the MMI operation, an Alert panel 1129 // is displayed, which prevents more MMI code from being entered. 1130 MmiCode mmiCode = pendingMmis.get(0); 1131 if (mmiCode.isCancelable()) { 1132 mmiCode.cancel(); 1133 canceled = true; 1134 } 1135 } 1136 return canceled; 1137 } 1138 1139 public static class VoiceMailNumberMissingException extends Exception { VoiceMailNumberMissingException()1140 VoiceMailNumberMissingException() { 1141 super(); 1142 } 1143 VoiceMailNumberMissingException(String msg)1144 VoiceMailNumberMissingException(String msg) { 1145 super(msg); 1146 } 1147 } 1148 1149 /** 1150 * Given an Intent (which is presumably the ACTION_CALL intent that 1151 * initiated this outgoing call), figure out the actual phone number we 1152 * should dial. 1153 * 1154 * Note that the returned "number" may actually be a SIP address, 1155 * if the specified intent contains a sip: URI. 1156 * 1157 * This method is basically a wrapper around PhoneUtils.getNumberFromIntent(), 1158 * except it's also aware of the EXTRA_ACTUAL_NUMBER_TO_DIAL extra. 1159 * (That extra, if present, tells us the exact string to pass down to the 1160 * telephony layer. It's guaranteed to be safe to dial: it's either a PSTN 1161 * phone number with separators and keypad letters stripped out, or a raw 1162 * unencoded SIP address.) 1163 * 1164 * @return the phone number corresponding to the specified Intent, or null 1165 * if the Intent has no action or if the intent's data is malformed or 1166 * missing. 1167 * 1168 * @throws VoiceMailNumberMissingException if the intent 1169 * contains a "voicemail" URI, but there's no voicemail 1170 * number configured on the device. 1171 */ getInitialNumber(Intent intent)1172 public static String getInitialNumber(Intent intent) 1173 throws PhoneUtils.VoiceMailNumberMissingException { 1174 if (DBG) log("getInitialNumber(): " + intent); 1175 1176 String action = intent.getAction(); 1177 if (TextUtils.isEmpty(action)) { 1178 return null; 1179 } 1180 1181 // If the EXTRA_ACTUAL_NUMBER_TO_DIAL extra is present, get the phone 1182 // number from there. (That extra takes precedence over the actual data 1183 // included in the intent.) 1184 if (intent.hasExtra(OutgoingCallBroadcaster.EXTRA_ACTUAL_NUMBER_TO_DIAL)) { 1185 String actualNumberToDial = 1186 intent.getStringExtra(OutgoingCallBroadcaster.EXTRA_ACTUAL_NUMBER_TO_DIAL); 1187 if (DBG) { 1188 log("==> got EXTRA_ACTUAL_NUMBER_TO_DIAL; returning '" 1189 + toLogSafePhoneNumber(actualNumberToDial) + "'"); 1190 } 1191 return actualNumberToDial; 1192 } 1193 1194 return getNumberFromIntent(PhoneGlobals.getInstance(), intent); 1195 } 1196 1197 /** 1198 * Gets the phone number to be called from an intent. Requires a Context 1199 * to access the contacts database, and a Phone to access the voicemail 1200 * number. 1201 * 1202 * <p>If <code>phone</code> is <code>null</code>, the function will return 1203 * <code>null</code> for <code>voicemail:</code> URIs; 1204 * if <code>context</code> is <code>null</code>, the function will return 1205 * <code>null</code> for person/phone URIs.</p> 1206 * 1207 * <p>If the intent contains a <code>sip:</code> URI, the returned 1208 * "number" is actually the SIP address. 1209 * 1210 * @param context a context to use (or 1211 * @param intent the intent 1212 * 1213 * @throws VoiceMailNumberMissingException if <code>intent</code> contains 1214 * a <code>voicemail:</code> URI, but <code>phone</code> does not 1215 * have a voicemail number set. 1216 * 1217 * @return the phone number (or SIP address) that would be called by the intent, 1218 * or <code>null</code> if the number cannot be found. 1219 */ getNumberFromIntent(Context context, Intent intent)1220 private static String getNumberFromIntent(Context context, Intent intent) 1221 throws VoiceMailNumberMissingException { 1222 Uri uri = intent.getData(); 1223 String scheme = uri.getScheme(); 1224 1225 // The sip: scheme is simple: just treat the rest of the URI as a 1226 // SIP address. 1227 if (PhoneAccount.SCHEME_SIP.equals(scheme)) { 1228 return uri.getSchemeSpecificPart(); 1229 } 1230 1231 // Otherwise, let PhoneNumberUtils.getNumberFromIntent() handle 1232 // the other cases (i.e. tel: and voicemail: and contact: URIs.) 1233 1234 final String number = PhoneNumberUtils.getNumberFromIntent(intent, context); 1235 1236 // Check for a voicemail-dialing request. If the voicemail number is 1237 // empty, throw a VoiceMailNumberMissingException. 1238 if (PhoneAccount.SCHEME_VOICEMAIL.equals(scheme) && 1239 (number == null || TextUtils.isEmpty(number))) 1240 throw new VoiceMailNumberMissingException(); 1241 1242 return number; 1243 } 1244 1245 /** 1246 * Returns the caller-id info corresponding to the specified Connection. 1247 * (This is just a simple wrapper around CallerInfo.getCallerInfo(): we 1248 * extract a phone number from the specified Connection, and feed that 1249 * number into CallerInfo.getCallerInfo().) 1250 * 1251 * The returned CallerInfo may be null in certain error cases, like if the 1252 * specified Connection was null, or if we weren't able to get a valid 1253 * phone number from the Connection. 1254 * 1255 * Finally, if the getCallerInfo() call did succeed, we save the resulting 1256 * CallerInfo object in the "userData" field of the Connection. 1257 * 1258 * NOTE: This API should be avoided, with preference given to the 1259 * asynchronous startGetCallerInfo API. 1260 */ getCallerInfo(Context context, Connection c)1261 static CallerInfo getCallerInfo(Context context, Connection c) { 1262 CallerInfo info = null; 1263 1264 if (c != null) { 1265 //See if there is a URI attached. If there is, this means 1266 //that there is no CallerInfo queried yet, so we'll need to 1267 //replace the URI with a full CallerInfo object. 1268 Object userDataObject = c.getUserData(); 1269 if (userDataObject instanceof Uri) { 1270 info = CallerInfo.getCallerInfo(context, (Uri) userDataObject); 1271 if (info != null) { 1272 c.setUserData(info); 1273 } 1274 } else { 1275 if (userDataObject instanceof CallerInfoToken) { 1276 //temporary result, while query is running 1277 info = ((CallerInfoToken) userDataObject).currentInfo; 1278 } else { 1279 //final query result 1280 info = (CallerInfo) userDataObject; 1281 } 1282 if (info == null) { 1283 // No URI, or Existing CallerInfo, so we'll have to make do with 1284 // querying a new CallerInfo using the connection's phone number. 1285 String number = c.getAddress(); 1286 1287 if (DBG) log("getCallerInfo: number = " + toLogSafePhoneNumber(number)); 1288 1289 if (!TextUtils.isEmpty(number)) { 1290 info = CallerInfo.getCallerInfo(context, number); 1291 if (info != null) { 1292 c.setUserData(info); 1293 } 1294 } 1295 } 1296 } 1297 } 1298 return info; 1299 } 1300 1301 /** 1302 * Class returned by the startGetCallerInfo call to package a temporary 1303 * CallerInfo Object, to be superceded by the CallerInfo Object passed 1304 * into the listener when the query with token mAsyncQueryToken is complete. 1305 */ 1306 public static class CallerInfoToken { 1307 /**indicates that there will no longer be updates to this request.*/ 1308 public boolean isFinal; 1309 1310 public CallerInfo currentInfo; 1311 public CallerInfoAsyncQuery asyncQuery; 1312 } 1313 1314 /** 1315 * Start a CallerInfo Query based on the earliest connection in the call. 1316 */ startGetCallerInfo(Context context, Call call, CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie)1317 static CallerInfoToken startGetCallerInfo(Context context, Call call, 1318 CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie) { 1319 Connection conn = null; 1320 int phoneType = call.getPhone().getPhoneType(); 1321 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) { 1322 conn = call.getLatestConnection(); 1323 } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM) 1324 || (phoneType == PhoneConstants.PHONE_TYPE_SIP) 1325 || (phoneType == PhoneConstants.PHONE_TYPE_IMS) 1326 || (phoneType == PhoneConstants.PHONE_TYPE_THIRD_PARTY)) { 1327 conn = call.getEarliestConnection(); 1328 } else { 1329 throw new IllegalStateException("Unexpected phone type: " + phoneType); 1330 } 1331 1332 return startGetCallerInfo(context, conn, listener, cookie); 1333 } 1334 startGetCallerInfo(Context context, Connection c, CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie)1335 static CallerInfoToken startGetCallerInfo(Context context, Connection c, 1336 CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie) { 1337 return startGetCallerInfo(context, c, listener, cookie, null); 1338 } 1339 1340 /** 1341 * place a temporary callerinfo object in the hands of the caller and notify 1342 * caller when the actual query is done. 1343 */ startGetCallerInfo(Context context, Connection c, CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie, RawGatewayInfo info)1344 static CallerInfoToken startGetCallerInfo(Context context, Connection c, 1345 CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie, 1346 RawGatewayInfo info) { 1347 CallerInfoToken cit; 1348 1349 if (c == null) { 1350 //TODO: perhaps throw an exception here. 1351 cit = new CallerInfoToken(); 1352 cit.asyncQuery = null; 1353 return cit; 1354 } 1355 1356 Object userDataObject = c.getUserData(); 1357 1358 // There are now 3 states for the Connection's userData object: 1359 // 1360 // (1) Uri - query has not been executed yet 1361 // 1362 // (2) CallerInfoToken - query is executing, but has not completed. 1363 // 1364 // (3) CallerInfo - query has executed. 1365 // 1366 // In each case we have slightly different behaviour: 1367 // 1. If the query has not been executed yet (Uri or null), we start 1368 // query execution asynchronously, and note it by attaching a 1369 // CallerInfoToken as the userData. 1370 // 2. If the query is executing (CallerInfoToken), we've essentially 1371 // reached a state where we've received multiple requests for the 1372 // same callerInfo. That means that once the query is complete, 1373 // we'll need to execute the additional listener requested. 1374 // 3. If the query has already been executed (CallerInfo), we just 1375 // return the CallerInfo object as expected. 1376 // 4. Regarding isFinal - there are cases where the CallerInfo object 1377 // will not be attached, like when the number is empty (caller id 1378 // blocking). This flag is used to indicate that the 1379 // CallerInfoToken object is going to be permanent since no 1380 // query results will be returned. In the case where a query 1381 // has been completed, this flag is used to indicate to the caller 1382 // that the data will not be updated since it is valid. 1383 // 1384 // Note: For the case where a number is NOT retrievable, we leave 1385 // the CallerInfo as null in the CallerInfoToken. This is 1386 // something of a departure from the original code, since the old 1387 // code manufactured a CallerInfo object regardless of the query 1388 // outcome. From now on, we will append an empty CallerInfo 1389 // object, to mirror previous behaviour, and to avoid Null Pointer 1390 // Exceptions. 1391 1392 if (userDataObject instanceof Uri) { 1393 // State (1): query has not been executed yet 1394 1395 //create a dummy callerinfo, populate with what we know from URI. 1396 cit = new CallerInfoToken(); 1397 cit.currentInfo = new CallerInfo(); 1398 cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, 1399 (Uri) userDataObject, sCallerInfoQueryListener, c); 1400 cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie); 1401 cit.isFinal = false; 1402 1403 c.setUserData(cit); 1404 1405 if (DBG) log("startGetCallerInfo: query based on Uri: " + userDataObject); 1406 1407 } else if (userDataObject == null) { 1408 // No URI, or Existing CallerInfo, so we'll have to make do with 1409 // querying a new CallerInfo using the connection's phone number. 1410 String number = c.getAddress(); 1411 1412 if (info != null && info != CallGatewayManager.EMPTY_INFO) { 1413 // Gateway number, the connection number is actually the gateway number. 1414 // need to lookup via dialed number. 1415 number = info.trueNumber; 1416 } 1417 1418 if (DBG) { 1419 log("PhoneUtils.startGetCallerInfo: new query for phone number..."); 1420 log("- number (address): " + toLogSafePhoneNumber(number)); 1421 log("- c: " + c); 1422 log("- phone: " + c.getCall().getPhone()); 1423 int phoneType = c.getCall().getPhone().getPhoneType(); 1424 log("- phoneType: " + phoneType); 1425 switch (phoneType) { 1426 case PhoneConstants.PHONE_TYPE_NONE: log(" ==> PHONE_TYPE_NONE"); break; 1427 case PhoneConstants.PHONE_TYPE_GSM: log(" ==> PHONE_TYPE_GSM"); break; 1428 case PhoneConstants.PHONE_TYPE_IMS: log(" ==> PHONE_TYPE_IMS"); break; 1429 case PhoneConstants.PHONE_TYPE_CDMA: log(" ==> PHONE_TYPE_CDMA"); break; 1430 case PhoneConstants.PHONE_TYPE_SIP: log(" ==> PHONE_TYPE_SIP"); break; 1431 case PhoneConstants.PHONE_TYPE_THIRD_PARTY: 1432 log(" ==> PHONE_TYPE_THIRD_PARTY"); 1433 break; 1434 default: log(" ==> Unknown phone type"); break; 1435 } 1436 } 1437 1438 cit = new CallerInfoToken(); 1439 cit.currentInfo = new CallerInfo(); 1440 1441 // Store CNAP information retrieved from the Connection (we want to do this 1442 // here regardless of whether the number is empty or not). 1443 cit.currentInfo.cnapName = c.getCnapName(); 1444 cit.currentInfo.name = cit.currentInfo.cnapName; // This can still get overwritten 1445 // by ContactInfo later 1446 cit.currentInfo.numberPresentation = c.getNumberPresentation(); 1447 cit.currentInfo.namePresentation = c.getCnapNamePresentation(); 1448 1449 if (VDBG) { 1450 log("startGetCallerInfo: number = " + number); 1451 log("startGetCallerInfo: CNAP Info from FW(1): name=" 1452 + cit.currentInfo.cnapName 1453 + ", Name/Number Pres=" + cit.currentInfo.numberPresentation); 1454 } 1455 1456 // handling case where number is null (caller id hidden) as well. 1457 if (!TextUtils.isEmpty(number)) { 1458 // Check for special CNAP cases and modify the CallerInfo accordingly 1459 // to be sure we keep the right information to display/log later 1460 number = modifyForSpecialCnapCases(context, cit.currentInfo, number, 1461 cit.currentInfo.numberPresentation); 1462 1463 cit.currentInfo.phoneNumber = number; 1464 // For scenarios where we may receive a valid number from the network but a 1465 // restricted/unavailable presentation, we do not want to perform a contact query 1466 // (see note on isFinal above). So we set isFinal to true here as well. 1467 if (cit.currentInfo.numberPresentation != PhoneConstants.PRESENTATION_ALLOWED) { 1468 cit.isFinal = true; 1469 } else { 1470 if (DBG) log("==> Actually starting CallerInfoAsyncQuery.startQuery()..."); 1471 cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, 1472 number, sCallerInfoQueryListener, c); 1473 cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie); 1474 cit.isFinal = false; 1475 } 1476 } else { 1477 // This is the case where we are querying on a number that 1478 // is null or empty, like a caller whose caller id is 1479 // blocked or empty (CLIR). The previous behaviour was to 1480 // throw a null CallerInfo object back to the user, but 1481 // this departure is somewhat cleaner. 1482 if (DBG) log("startGetCallerInfo: No query to start, send trivial reply."); 1483 cit.isFinal = true; // please see note on isFinal, above. 1484 } 1485 1486 c.setUserData(cit); 1487 1488 if (DBG) { 1489 log("startGetCallerInfo: query based on number: " + toLogSafePhoneNumber(number)); 1490 } 1491 1492 } else if (userDataObject instanceof CallerInfoToken) { 1493 // State (2): query is executing, but has not completed. 1494 1495 // just tack on this listener to the queue. 1496 cit = (CallerInfoToken) userDataObject; 1497 1498 // handling case where number is null (caller id hidden) as well. 1499 if (cit.asyncQuery != null) { 1500 cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie); 1501 1502 if (DBG) log("startGetCallerInfo: query already running, adding listener: " + 1503 listener.getClass().toString()); 1504 } else { 1505 // handling case where number/name gets updated later on by the network 1506 String updatedNumber = c.getAddress(); 1507 1508 if (info != null) { 1509 // Gateway number, the connection number is actually the gateway number. 1510 // need to lookup via dialed number. 1511 updatedNumber = info.trueNumber; 1512 } 1513 1514 if (DBG) { 1515 log("startGetCallerInfo: updatedNumber initially = " 1516 + toLogSafePhoneNumber(updatedNumber)); 1517 } 1518 if (!TextUtils.isEmpty(updatedNumber)) { 1519 // Store CNAP information retrieved from the Connection 1520 cit.currentInfo.cnapName = c.getCnapName(); 1521 // This can still get overwritten by ContactInfo 1522 cit.currentInfo.name = cit.currentInfo.cnapName; 1523 cit.currentInfo.numberPresentation = c.getNumberPresentation(); 1524 cit.currentInfo.namePresentation = c.getCnapNamePresentation(); 1525 1526 updatedNumber = modifyForSpecialCnapCases(context, cit.currentInfo, 1527 updatedNumber, cit.currentInfo.numberPresentation); 1528 1529 cit.currentInfo.phoneNumber = updatedNumber; 1530 if (DBG) { 1531 log("startGetCallerInfo: updatedNumber=" 1532 + toLogSafePhoneNumber(updatedNumber)); 1533 } 1534 if (VDBG) { 1535 log("startGetCallerInfo: CNAP Info from FW(2): name=" 1536 + cit.currentInfo.cnapName 1537 + ", Name/Number Pres=" + cit.currentInfo.numberPresentation); 1538 } else if (DBG) { 1539 log("startGetCallerInfo: CNAP Info from FW(2)"); 1540 } 1541 // For scenarios where we may receive a valid number from the network but a 1542 // restricted/unavailable presentation, we do not want to perform a contact query 1543 // (see note on isFinal above). So we set isFinal to true here as well. 1544 if (cit.currentInfo.numberPresentation != PhoneConstants.PRESENTATION_ALLOWED) { 1545 cit.isFinal = true; 1546 } else { 1547 cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, 1548 updatedNumber, sCallerInfoQueryListener, c); 1549 cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie); 1550 cit.isFinal = false; 1551 } 1552 } else { 1553 if (DBG) log("startGetCallerInfo: No query to attach to, send trivial reply."); 1554 if (cit.currentInfo == null) { 1555 cit.currentInfo = new CallerInfo(); 1556 } 1557 // Store CNAP information retrieved from the Connection 1558 cit.currentInfo.cnapName = c.getCnapName(); // This can still get 1559 // overwritten by ContactInfo 1560 cit.currentInfo.name = cit.currentInfo.cnapName; 1561 cit.currentInfo.numberPresentation = c.getNumberPresentation(); 1562 cit.currentInfo.namePresentation = c.getCnapNamePresentation(); 1563 1564 if (VDBG) { 1565 log("startGetCallerInfo: CNAP Info from FW(3): name=" 1566 + cit.currentInfo.cnapName 1567 + ", Name/Number Pres=" + cit.currentInfo.numberPresentation); 1568 } else if (DBG) { 1569 log("startGetCallerInfo: CNAP Info from FW(3)"); 1570 } 1571 cit.isFinal = true; // please see note on isFinal, above. 1572 } 1573 } 1574 } else { 1575 // State (3): query is complete. 1576 1577 // The connection's userDataObject is a full-fledged 1578 // CallerInfo instance. Wrap it in a CallerInfoToken and 1579 // return it to the user. 1580 1581 cit = new CallerInfoToken(); 1582 cit.currentInfo = (CallerInfo) userDataObject; 1583 cit.asyncQuery = null; 1584 cit.isFinal = true; 1585 // since the query is already done, call the listener. 1586 if (DBG) log("startGetCallerInfo: query already done, returning CallerInfo"); 1587 if (DBG) log("==> cit.currentInfo = " + cit.currentInfo); 1588 } 1589 return cit; 1590 } 1591 1592 /** 1593 * Static CallerInfoAsyncQuery.OnQueryCompleteListener instance that 1594 * we use with all our CallerInfoAsyncQuery.startQuery() requests. 1595 */ 1596 private static final int QUERY_TOKEN = -1; 1597 static CallerInfoAsyncQuery.OnQueryCompleteListener sCallerInfoQueryListener = 1598 new CallerInfoAsyncQuery.OnQueryCompleteListener () { 1599 /** 1600 * When the query completes, we stash the resulting CallerInfo 1601 * object away in the Connection's "userData" (where it will 1602 * later be retrieved by the in-call UI.) 1603 */ 1604 public void onQueryComplete(int token, Object cookie, CallerInfo ci) { 1605 if (DBG) log("query complete, updating connection.userdata"); 1606 Connection conn = (Connection) cookie; 1607 1608 // Added a check if CallerInfo is coming from ContactInfo or from Connection. 1609 // If no ContactInfo, then we want to use CNAP information coming from network 1610 if (DBG) log("- onQueryComplete: CallerInfo:" + ci); 1611 if (ci.contactExists || ci.isEmergencyNumber() || ci.isVoiceMailNumber()) { 1612 // If the number presentation has not been set by 1613 // the ContactInfo, use the one from the 1614 // connection. 1615 1616 // TODO: Need a new util method to merge the info 1617 // from the Connection in a CallerInfo object. 1618 // Here 'ci' is a new CallerInfo instance read 1619 // from the DB. It has lost all the connection 1620 // info preset before the query (see PhoneUtils 1621 // line 1334). We should have a method to merge 1622 // back into this new instance the info from the 1623 // connection object not set by the DB. If the 1624 // Connection already has a CallerInfo instance in 1625 // userData, then we could use this instance to 1626 // fill 'ci' in. The same routine could be used in 1627 // PhoneUtils. 1628 if (0 == ci.numberPresentation) { 1629 ci.numberPresentation = conn.getNumberPresentation(); 1630 } 1631 } else { 1632 // No matching contact was found for this number. 1633 // Return a new CallerInfo based solely on the CNAP 1634 // information from the network. 1635 1636 CallerInfo newCi = getCallerInfo(null, conn); 1637 1638 // ...but copy over the (few) things we care about 1639 // from the original CallerInfo object: 1640 if (newCi != null) { 1641 newCi.phoneNumber = ci.phoneNumber; // To get formatted phone number 1642 newCi.geoDescription = ci.geoDescription; // To get geo description string 1643 ci = newCi; 1644 } 1645 } 1646 1647 if (DBG) log("==> Stashing CallerInfo " + ci + " into the connection..."); 1648 conn.setUserData(ci); 1649 } 1650 }; 1651 1652 1653 /** 1654 * Returns a single "name" for the specified given a CallerInfo object. 1655 * If the name is null, return defaultString as the default value, usually 1656 * context.getString(R.string.unknown). 1657 */ getCompactNameFromCallerInfo(CallerInfo ci, Context context)1658 static String getCompactNameFromCallerInfo(CallerInfo ci, Context context) { 1659 if (DBG) log("getCompactNameFromCallerInfo: info = " + ci); 1660 1661 String compactName = null; 1662 if (ci != null) { 1663 if (TextUtils.isEmpty(ci.name)) { 1664 // Perform any modifications for special CNAP cases to 1665 // the phone number being displayed, if applicable. 1666 compactName = modifyForSpecialCnapCases(context, ci, ci.phoneNumber, 1667 ci.numberPresentation); 1668 } else { 1669 // Don't call modifyForSpecialCnapCases on regular name. See b/2160795. 1670 compactName = ci.name; 1671 } 1672 } 1673 1674 if ((compactName == null) || (TextUtils.isEmpty(compactName))) { 1675 // If we're still null/empty here, then check if we have a presentation 1676 // string that takes precedence that we could return, otherwise display 1677 // "unknown" string. 1678 if (ci != null && ci.numberPresentation == PhoneConstants.PRESENTATION_RESTRICTED) { 1679 compactName = context.getString(R.string.private_num); 1680 } else if (ci != null && ci.numberPresentation == PhoneConstants.PRESENTATION_PAYPHONE) { 1681 compactName = context.getString(R.string.payphone); 1682 } else { 1683 compactName = context.getString(R.string.unknown); 1684 } 1685 } 1686 if (VDBG) log("getCompactNameFromCallerInfo: compactName=" + compactName); 1687 return compactName; 1688 } 1689 1690 /** 1691 * Returns true if the specified Call is a "conference call", meaning 1692 * that it owns more than one Connection object. This information is 1693 * used to trigger certain UI changes that appear when a conference 1694 * call is active (like displaying the label "Conference call", and 1695 * enabling the "Manage conference" UI.) 1696 * 1697 * Watch out: This method simply checks the number of Connections, 1698 * *not* their states. So if a Call has (for example) one ACTIVE 1699 * connection and one DISCONNECTED connection, this method will return 1700 * true (which is unintuitive, since the Call isn't *really* a 1701 * conference call any more.) 1702 * 1703 * @return true if the specified call has more than one connection (in any state.) 1704 */ isConferenceCall(Call call)1705 static boolean isConferenceCall(Call call) { 1706 // CDMA phones don't have the same concept of "conference call" as 1707 // GSM phones do; there's no special "conference call" state of 1708 // the UI or a "manage conference" function. (Instead, when 1709 // you're in a 3-way call, all we can do is display the "generic" 1710 // state of the UI.) So as far as the in-call UI is concerned, 1711 // Conference corresponds to generic display. 1712 final PhoneGlobals app = PhoneGlobals.getInstance(); 1713 int phoneType = call.getPhone().getPhoneType(); 1714 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) { 1715 CdmaPhoneCallState.PhoneCallState state = app.cdmaPhoneCallState.getCurrentCallState(); 1716 if ((state == CdmaPhoneCallState.PhoneCallState.CONF_CALL) 1717 || ((state == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) 1718 && !app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing())) { 1719 return true; 1720 } 1721 } else { 1722 List<Connection> connections = call.getConnections(); 1723 if (connections != null && connections.size() > 1) { 1724 return true; 1725 } 1726 } 1727 return false; 1728 1729 // TODO: We may still want to change the semantics of this method 1730 // to say that a given call is only really a conference call if 1731 // the number of ACTIVE connections, not the total number of 1732 // connections, is greater than one. (See warning comment in the 1733 // javadoc above.) 1734 // Here's an implementation of that: 1735 // if (connections == null) { 1736 // return false; 1737 // } 1738 // int numActiveConnections = 0; 1739 // for (Connection conn : connections) { 1740 // if (DBG) log(" - CONN: " + conn + ", state = " + conn.getState()); 1741 // if (conn.getState() == Call.State.ACTIVE) numActiveConnections++; 1742 // if (numActiveConnections > 1) { 1743 // return true; 1744 // } 1745 // } 1746 // return false; 1747 } 1748 1749 /** 1750 * Launch the Dialer to start a new call. 1751 * This is just a wrapper around the ACTION_DIAL intent. 1752 */ startNewCall(final CallManager cm)1753 /* package */ static boolean startNewCall(final CallManager cm) { 1754 final PhoneGlobals app = PhoneGlobals.getInstance(); 1755 1756 // Sanity-check that this is OK given the current state of the phone. 1757 if (!okToAddCall(cm)) { 1758 Log.w(LOG_TAG, "startNewCall: can't add a new call in the current state"); 1759 dumpCallManager(); 1760 return false; 1761 } 1762 1763 Intent intent = new Intent(Intent.ACTION_DIAL); 1764 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1765 1766 // when we request the dialer come up, we also want to inform 1767 // it that we're going through the "add call" option from the 1768 // InCallScreen / PhoneUtils. 1769 intent.putExtra(ADD_CALL_MODE_KEY, true); 1770 try { 1771 app.startActivity(intent); 1772 } catch (ActivityNotFoundException e) { 1773 // This is rather rare but possible. 1774 // Note: this method is used even when the phone is encrypted. At that moment 1775 // the system may not find any Activity which can accept this Intent. 1776 Log.e(LOG_TAG, "Activity for adding calls isn't found."); 1777 return false; 1778 } 1779 1780 return true; 1781 } 1782 1783 /** 1784 * Turns on/off speaker. 1785 * 1786 * @param context Context 1787 * @param flag True when speaker should be on. False otherwise. 1788 * @param store True when the settings should be stored in the device. 1789 */ turnOnSpeaker(Context context, boolean flag, boolean store)1790 /* package */ static void turnOnSpeaker(Context context, boolean flag, boolean store) { 1791 if (DBG) log("turnOnSpeaker(flag=" + flag + ", store=" + store + ")..."); 1792 final PhoneGlobals app = PhoneGlobals.getInstance(); 1793 1794 AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 1795 audioManager.setSpeakerphoneOn(flag); 1796 1797 // record the speaker-enable value 1798 if (store) { 1799 sIsSpeakerEnabled = flag; 1800 } 1801 1802 // We also need to make a fresh call to PhoneApp.updateWakeState() 1803 // any time the speaker state changes, since the screen timeout is 1804 // sometimes different depending on whether or not the speaker is 1805 // in use. 1806 app.updateWakeState(); 1807 1808 app.mCM.setEchoSuppressionEnabled(); 1809 } 1810 1811 /** 1812 * Restore the speaker mode, called after a wired headset disconnect 1813 * event. 1814 */ restoreSpeakerMode(Context context)1815 static void restoreSpeakerMode(Context context) { 1816 if (DBG) log("restoreSpeakerMode, restoring to: " + sIsSpeakerEnabled); 1817 1818 // change the mode if needed. 1819 if (isSpeakerOn(context) != sIsSpeakerEnabled) { 1820 turnOnSpeaker(context, sIsSpeakerEnabled, false); 1821 } 1822 } 1823 isSpeakerOn(Context context)1824 static boolean isSpeakerOn(Context context) { 1825 AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 1826 return audioManager.isSpeakerphoneOn(); 1827 } 1828 1829 turnOnNoiseSuppression(Context context, boolean flag, boolean store)1830 static void turnOnNoiseSuppression(Context context, boolean flag, boolean store) { 1831 if (DBG) log("turnOnNoiseSuppression: " + flag); 1832 AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 1833 1834 PersistableBundle b = PhoneGlobals.getInstance().getCarrierConfig(); 1835 if (!b.getBoolean(CarrierConfigManager.KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL)) { 1836 return; 1837 } 1838 1839 if (flag) { 1840 audioManager.setParameters("noise_suppression=auto"); 1841 } else { 1842 audioManager.setParameters("noise_suppression=off"); 1843 } 1844 1845 // record the speaker-enable value 1846 if (store) { 1847 sIsNoiseSuppressionEnabled = flag; 1848 } 1849 1850 // TODO: implement and manage ICON 1851 1852 } 1853 restoreNoiseSuppression(Context context)1854 static void restoreNoiseSuppression(Context context) { 1855 if (DBG) log("restoreNoiseSuppression, restoring to: " + sIsNoiseSuppressionEnabled); 1856 1857 PersistableBundle b = PhoneGlobals.getInstance().getCarrierConfig(); 1858 if (!b.getBoolean(CarrierConfigManager.KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL)) { 1859 return; 1860 } 1861 1862 // change the mode if needed. 1863 if (isNoiseSuppressionOn(context) != sIsNoiseSuppressionEnabled) { 1864 turnOnNoiseSuppression(context, sIsNoiseSuppressionEnabled, false); 1865 } 1866 } 1867 isNoiseSuppressionOn(Context context)1868 static boolean isNoiseSuppressionOn(Context context) { 1869 1870 PersistableBundle b = PhoneGlobals.getInstance().getCarrierConfig(); 1871 if (!b.getBoolean(CarrierConfigManager.KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL)) { 1872 return false; 1873 } 1874 1875 AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 1876 String noiseSuppression = audioManager.getParameters("noise_suppression"); 1877 if (DBG) log("isNoiseSuppressionOn: " + noiseSuppression); 1878 if (noiseSuppression.contains("off")) { 1879 return false; 1880 } else { 1881 return true; 1882 } 1883 } 1884 isInEmergencyCall(CallManager cm)1885 static boolean isInEmergencyCall(CallManager cm) { 1886 for (Connection cn : cm.getActiveFgCall().getConnections()) { 1887 if (PhoneNumberUtils.isLocalEmergencyNumber(PhoneGlobals.getInstance(), 1888 cn.getAddress())) { 1889 return true; 1890 } 1891 } 1892 return false; 1893 } 1894 1895 /** 1896 * Get the mute state of foreground phone, which has the current 1897 * foreground call 1898 */ getMute()1899 static boolean getMute() { 1900 return false; 1901 } 1902 setAudioMode()1903 /* package */ static void setAudioMode() { 1904 } 1905 1906 /** 1907 * Sets the audio mode per current phone state. 1908 */ setAudioMode(CallManager cm)1909 /* package */ static void setAudioMode(CallManager cm) { 1910 } 1911 1912 /** 1913 * Look for ANY connections on the phone that qualify as being 1914 * disconnected. 1915 * 1916 * @return true if we find a connection that is disconnected over 1917 * all the phone's call objects. 1918 */ hasDisconnectedConnections(Phone phone)1919 /* package */ static boolean hasDisconnectedConnections(Phone phone) { 1920 return hasDisconnectedConnections(phone.getForegroundCall()) || 1921 hasDisconnectedConnections(phone.getBackgroundCall()) || 1922 hasDisconnectedConnections(phone.getRingingCall()); 1923 } 1924 1925 /** 1926 * Iterate over all connections in a call to see if there are any 1927 * that are not alive (disconnected or idle). 1928 * 1929 * @return true if we find a connection that is disconnected, and 1930 * pending removal via 1931 * {@link com.android.internal.telephony.gsm.GsmCall#clearDisconnected()}. 1932 */ hasDisconnectedConnections(Call call)1933 private static final boolean hasDisconnectedConnections(Call call) { 1934 // look through all connections for non-active ones. 1935 for (Connection c : call.getConnections()) { 1936 if (!c.isAlive()) { 1937 return true; 1938 } 1939 } 1940 return false; 1941 } 1942 1943 // 1944 // Misc UI policy helper functions 1945 // 1946 1947 /** 1948 * @return true if we're allowed to hold calls, given the current 1949 * state of the Phone. 1950 */ okToHoldCall(CallManager cm)1951 /* package */ static boolean okToHoldCall(CallManager cm) { 1952 final Call fgCall = cm.getActiveFgCall(); 1953 final boolean hasHoldingCall = cm.hasActiveBgCall(); 1954 final Call.State fgCallState = fgCall.getState(); 1955 1956 // The "Hold" control is disabled entirely if there's 1957 // no way to either hold or unhold in the current state. 1958 final boolean okToHold = (fgCallState == Call.State.ACTIVE) && !hasHoldingCall; 1959 final boolean okToUnhold = cm.hasActiveBgCall() && (fgCallState == Call.State.IDLE); 1960 final boolean canHold = okToHold || okToUnhold; 1961 1962 return canHold; 1963 } 1964 1965 /** 1966 * @return true if we support holding calls, given the current 1967 * state of the Phone. 1968 */ okToSupportHold(CallManager cm)1969 /* package */ static boolean okToSupportHold(CallManager cm) { 1970 boolean supportsHold = false; 1971 1972 final Call fgCall = cm.getActiveFgCall(); 1973 final boolean hasHoldingCall = cm.hasActiveBgCall(); 1974 final Call.State fgCallState = fgCall.getState(); 1975 1976 if (TelephonyCapabilities.supportsHoldAndUnhold(fgCall.getPhone())) { 1977 // This phone has the concept of explicit "Hold" and "Unhold" actions. 1978 supportsHold = true; 1979 } else if (hasHoldingCall && (fgCallState == Call.State.IDLE)) { 1980 // Even when foreground phone device doesn't support hold/unhold, phone devices 1981 // for background holding calls may do. 1982 final Call bgCall = cm.getFirstActiveBgCall(); 1983 if (bgCall != null && 1984 TelephonyCapabilities.supportsHoldAndUnhold(bgCall.getPhone())) { 1985 supportsHold = true; 1986 } 1987 } 1988 return supportsHold; 1989 } 1990 1991 /** 1992 * @return true if we're allowed to swap calls, given the current 1993 * state of the Phone. 1994 */ okToSwapCalls(CallManager cm)1995 /* package */ static boolean okToSwapCalls(CallManager cm) { 1996 int phoneType = cm.getDefaultPhone().getPhoneType(); 1997 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) { 1998 // CDMA: "Swap" is enabled only when the phone reaches a *generic*. 1999 // state by either accepting a Call Waiting or by merging two calls 2000 PhoneGlobals app = PhoneGlobals.getInstance(); 2001 return (app.cdmaPhoneCallState.getCurrentCallState() 2002 == CdmaPhoneCallState.PhoneCallState.CONF_CALL); 2003 } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM) 2004 || (phoneType == PhoneConstants.PHONE_TYPE_SIP) 2005 || (phoneType == PhoneConstants.PHONE_TYPE_IMS) 2006 || (phoneType == PhoneConstants.PHONE_TYPE_THIRD_PARTY)) { 2007 // GSM: "Swap" is available if both lines are in use and there's no 2008 // incoming call. (Actually we need to verify that the active 2009 // call really is in the ACTIVE state and the holding call really 2010 // is in the HOLDING state, since you *can't* actually swap calls 2011 // when the foreground call is DIALING or ALERTING.) 2012 return !cm.hasActiveRingingCall() 2013 && (cm.getActiveFgCall().getState() == Call.State.ACTIVE) 2014 && (cm.getFirstActiveBgCall().getState() == Call.State.HOLDING); 2015 } else { 2016 throw new IllegalStateException("Unexpected phone type: " + phoneType); 2017 } 2018 } 2019 2020 /** 2021 * @return true if we're allowed to merge calls, given the current 2022 * state of the Phone. 2023 */ okToMergeCalls(CallManager cm)2024 /* package */ static boolean okToMergeCalls(CallManager cm) { 2025 int phoneType = cm.getFgPhone().getPhoneType(); 2026 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) { 2027 // CDMA: "Merge" is enabled only when the user is in a 3Way call. 2028 PhoneGlobals app = PhoneGlobals.getInstance(); 2029 return ((app.cdmaPhoneCallState.getCurrentCallState() 2030 == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) 2031 && !app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing()); 2032 } else { 2033 // GSM: "Merge" is available if both lines are in use and there's no 2034 // incoming call, *and* the current conference isn't already 2035 // "full". 2036 // TODO: shall move all okToMerge logic to CallManager 2037 return !cm.hasActiveRingingCall() && cm.hasActiveFgCall() 2038 && cm.hasActiveBgCall() 2039 && cm.canConference(cm.getFirstActiveBgCall()); 2040 } 2041 } 2042 2043 /** 2044 * @return true if the UI should let you add a new call, given the current 2045 * state of the Phone. 2046 */ okToAddCall(CallManager cm)2047 /* package */ static boolean okToAddCall(CallManager cm) { 2048 Phone phone = cm.getActiveFgCall().getPhone(); 2049 2050 // "Add call" is never allowed in emergency callback mode (ECM). 2051 if (isPhoneInEcm(phone)) { 2052 return false; 2053 } 2054 2055 int phoneType = phone.getPhoneType(); 2056 final Call.State fgCallState = cm.getActiveFgCall().getState(); 2057 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) { 2058 // CDMA: "Add call" button is only enabled when: 2059 // - ForegroundCall is in ACTIVE state 2060 // - After 30 seconds of user Ignoring/Missing a Call Waiting call. 2061 PhoneGlobals app = PhoneGlobals.getInstance(); 2062 return ((fgCallState == Call.State.ACTIVE) 2063 && (app.cdmaPhoneCallState.getAddCallMenuStateAfterCallWaiting())); 2064 } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM) 2065 || (phoneType == PhoneConstants.PHONE_TYPE_SIP) 2066 || (phoneType == PhoneConstants.PHONE_TYPE_IMS) 2067 || (phoneType == PhoneConstants.PHONE_TYPE_THIRD_PARTY)) { 2068 // GSM: "Add call" is available only if ALL of the following are true: 2069 // - There's no incoming ringing call 2070 // - There's < 2 lines in use 2071 // - The foreground call is ACTIVE or IDLE or DISCONNECTED. 2072 // (We mainly need to make sure it *isn't* DIALING or ALERTING.) 2073 final boolean hasRingingCall = cm.hasActiveRingingCall(); 2074 final boolean hasActiveCall = cm.hasActiveFgCall(); 2075 final boolean hasHoldingCall = cm.hasActiveBgCall(); 2076 final boolean allLinesTaken = hasActiveCall && hasHoldingCall; 2077 2078 return !hasRingingCall 2079 && !allLinesTaken 2080 && ((fgCallState == Call.State.ACTIVE) 2081 || (fgCallState == Call.State.IDLE) 2082 || (fgCallState == Call.State.DISCONNECTED)); 2083 } else { 2084 throw new IllegalStateException("Unexpected phone type: " + phoneType); 2085 } 2086 } 2087 2088 /** 2089 * Based on the input CNAP number string, 2090 * @return _RESTRICTED or _UNKNOWN for all the special CNAP strings. 2091 * Otherwise, return CNAP_SPECIAL_CASE_NO. 2092 */ checkCnapSpecialCases(String n)2093 private static int checkCnapSpecialCases(String n) { 2094 if (n.equals("PRIVATE") || 2095 n.equals("P") || 2096 n.equals("RES")) { 2097 if (DBG) log("checkCnapSpecialCases, PRIVATE string: " + n); 2098 return PhoneConstants.PRESENTATION_RESTRICTED; 2099 } else if (n.equals("UNAVAILABLE") || 2100 n.equals("UNKNOWN") || 2101 n.equals("UNA") || 2102 n.equals("U")) { 2103 if (DBG) log("checkCnapSpecialCases, UNKNOWN string: " + n); 2104 return PhoneConstants.PRESENTATION_UNKNOWN; 2105 } else { 2106 if (DBG) log("checkCnapSpecialCases, normal str. number: " + n); 2107 return CNAP_SPECIAL_CASE_NO; 2108 } 2109 } 2110 2111 /** 2112 * Handles certain "corner cases" for CNAP. When we receive weird phone numbers 2113 * from the network to indicate different number presentations, convert them to 2114 * expected number and presentation values within the CallerInfo object. 2115 * @param number number we use to verify if we are in a corner case 2116 * @param presentation presentation value used to verify if we are in a corner case 2117 * @return the new String that should be used for the phone number 2118 */ modifyForSpecialCnapCases(Context context, CallerInfo ci, String number, int presentation)2119 /* package */ static String modifyForSpecialCnapCases(Context context, CallerInfo ci, 2120 String number, int presentation) { 2121 // Obviously we return number if ci == null, but still return number if 2122 // number == null, because in these cases the correct string will still be 2123 // displayed/logged after this function returns based on the presentation value. 2124 if (ci == null || number == null) return number; 2125 2126 if (DBG) { 2127 log("modifyForSpecialCnapCases: initially, number=" 2128 + toLogSafePhoneNumber(number) 2129 + ", presentation=" + presentation + " ci " + ci); 2130 } 2131 2132 // "ABSENT NUMBER" is a possible value we could get from the network as the 2133 // phone number, so if this happens, change it to "Unknown" in the CallerInfo 2134 // and fix the presentation to be the same. 2135 final String[] absentNumberValues = 2136 context.getResources().getStringArray(R.array.absent_num); 2137 if (Arrays.asList(absentNumberValues).contains(number) 2138 && presentation == PhoneConstants.PRESENTATION_ALLOWED) { 2139 number = context.getString(R.string.unknown); 2140 ci.numberPresentation = PhoneConstants.PRESENTATION_UNKNOWN; 2141 } 2142 2143 // Check for other special "corner cases" for CNAP and fix them similarly. Corner 2144 // cases only apply if we received an allowed presentation from the network, so check 2145 // if we think we have an allowed presentation, or if the CallerInfo presentation doesn't 2146 // match the presentation passed in for verification (meaning we changed it previously 2147 // because it's a corner case and we're being called from a different entry point). 2148 if (ci.numberPresentation == PhoneConstants.PRESENTATION_ALLOWED 2149 || (ci.numberPresentation != presentation 2150 && presentation == PhoneConstants.PRESENTATION_ALLOWED)) { 2151 int cnapSpecialCase = checkCnapSpecialCases(number); 2152 if (cnapSpecialCase != CNAP_SPECIAL_CASE_NO) { 2153 // For all special strings, change number & numberPresentation. 2154 if (cnapSpecialCase == PhoneConstants.PRESENTATION_RESTRICTED) { 2155 number = context.getString(R.string.private_num); 2156 } else if (cnapSpecialCase == PhoneConstants.PRESENTATION_UNKNOWN) { 2157 number = context.getString(R.string.unknown); 2158 } 2159 if (DBG) { 2160 log("SpecialCnap: number=" + toLogSafePhoneNumber(number) 2161 + "; presentation now=" + cnapSpecialCase); 2162 } 2163 ci.numberPresentation = cnapSpecialCase; 2164 } 2165 } 2166 if (DBG) { 2167 log("modifyForSpecialCnapCases: returning number string=" 2168 + toLogSafePhoneNumber(number)); 2169 } 2170 return number; 2171 } 2172 2173 // 2174 // Support for 3rd party phone service providers. 2175 // 2176 2177 /** 2178 * Check if a phone number can be route through a 3rd party 2179 * gateway. The number must be a global phone number in numerical 2180 * form (1-800-666-SEXY won't work). 2181 * 2182 * MMI codes and the like cannot be used as a dial number for the 2183 * gateway either. 2184 * 2185 * @param number To be dialed via a 3rd party gateway. 2186 * @return true If the number can be routed through the 3rd party network. 2187 */ isRoutableViaGateway(String number)2188 private static boolean isRoutableViaGateway(String number) { 2189 if (TextUtils.isEmpty(number)) { 2190 return false; 2191 } 2192 number = PhoneNumberUtils.stripSeparators(number); 2193 if (!number.equals(PhoneNumberUtils.convertKeypadLettersToDigits(number))) { 2194 return false; 2195 } 2196 number = PhoneNumberUtils.extractNetworkPortion(number); 2197 return PhoneNumberUtils.isGlobalPhoneNumber(number); 2198 } 2199 2200 /** 2201 * This function is called when phone answers or places a call. 2202 * Check if the phone is in a car dock or desk dock. 2203 * If yes, turn on the speaker, when no wired or BT headsets are connected. 2204 * Otherwise do nothing. 2205 * @return true if activated 2206 */ activateSpeakerIfDocked(Phone phone)2207 private static boolean activateSpeakerIfDocked(Phone phone) { 2208 if (DBG) log("activateSpeakerIfDocked()..."); 2209 2210 boolean activated = false; 2211 if (PhoneGlobals.mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) { 2212 if (DBG) log("activateSpeakerIfDocked(): In a dock -> may need to turn on speaker."); 2213 final PhoneGlobals app = PhoneGlobals.getInstance(); 2214 2215 // TODO: This function should move to AudioRouter 2216 final BluetoothManager btManager = app.getBluetoothManager(); 2217 //final WiredHeadsetManager wiredHeadset = app.getWiredHeadsetManager(); 2218 //final AudioRouter audioRouter = app.getAudioRouter(); 2219 2220 /*if (!wiredHeadset.isHeadsetPlugged() && !btManager.isBluetoothHeadsetAudioOn()) { 2221 //audioRouter.setSpeaker(true); 2222 activated = true; 2223 }*/ 2224 } 2225 return activated; 2226 } 2227 2228 2229 /** 2230 * Returns whether the phone is in ECM ("Emergency Callback Mode") or not. 2231 */ isPhoneInEcm(Phone phone)2232 /* package */ static boolean isPhoneInEcm(Phone phone) { 2233 if ((phone != null) && TelephonyCapabilities.supportsEcm(phone)) { 2234 // For phones that support ECM, return true iff PROPERTY_INECM_MODE == "true". 2235 // TODO: There ought to be a better API for this than just 2236 // exposing a system property all the way up to the app layer, 2237 // probably a method like "inEcm()" provided by the telephony 2238 // layer. 2239 String ecmMode = 2240 SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE); 2241 if (ecmMode != null) { 2242 return ecmMode.equals("true"); 2243 } 2244 } 2245 return false; 2246 } 2247 2248 /** 2249 * Returns the most appropriate Phone object to handle a call 2250 * to the specified number. 2251 * 2252 * @param cm the CallManager. 2253 * @param scheme the scheme from the data URI that the number originally came from. 2254 * @param number the phone number, or SIP address. 2255 */ pickPhoneBasedOnNumber(CallManager cm, String scheme, String number, String primarySipUri, ComponentName thirdPartyCallComponent)2256 public static Phone pickPhoneBasedOnNumber(CallManager cm, String scheme, String number, 2257 String primarySipUri, ComponentName thirdPartyCallComponent) { 2258 if (DBG) { 2259 log("pickPhoneBasedOnNumber: scheme " + scheme 2260 + ", number " + toLogSafePhoneNumber(number) 2261 + ", sipUri " 2262 + (primarySipUri != null ? Uri.parse(primarySipUri).toSafeString() : "null") 2263 + ", thirdPartyCallComponent: " + thirdPartyCallComponent); 2264 } 2265 2266 if (primarySipUri != null) { 2267 Phone phone = getSipPhoneFromUri(cm, primarySipUri); 2268 if (phone != null) return phone; 2269 } 2270 2271 return cm.getDefaultPhone(); 2272 } 2273 getSipPhoneFromUri(CallManager cm, String target)2274 public static Phone getSipPhoneFromUri(CallManager cm, String target) { 2275 for (Phone phone : cm.getAllPhones()) { 2276 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_SIP) { 2277 String sipUri = ((SipPhone) phone).getSipUri(); 2278 if (target.equals(sipUri)) { 2279 if (DBG) log("- pickPhoneBasedOnNumber:" + 2280 "found SipPhone! obj = " + phone + ", " 2281 + phone.getClass()); 2282 return phone; 2283 } 2284 } 2285 } 2286 return null; 2287 } 2288 2289 /** 2290 * Returns true when the given call is in INCOMING state and there's no foreground phone call, 2291 * meaning the call is the first real incoming call the phone is having. 2292 */ isRealIncomingCall(Call.State state)2293 public static boolean isRealIncomingCall(Call.State state) { 2294 return (state == Call.State.INCOMING && !PhoneGlobals.getInstance().mCM.hasActiveFgCall()); 2295 } 2296 getPresentationString(Context context, int presentation)2297 public static String getPresentationString(Context context, int presentation) { 2298 String name = context.getString(R.string.unknown); 2299 if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) { 2300 name = context.getString(R.string.private_num); 2301 } else if (presentation == PhoneConstants.PRESENTATION_PAYPHONE) { 2302 name = context.getString(R.string.payphone); 2303 } 2304 return name; 2305 } 2306 sendViewNotificationAsync(Context context, Uri contactUri)2307 public static void sendViewNotificationAsync(Context context, Uri contactUri) { 2308 if (DBG) Log.d(LOG_TAG, "Send view notification to Contacts (uri: " + contactUri + ")"); 2309 Intent intent = new Intent("com.android.contacts.VIEW_NOTIFICATION", contactUri); 2310 intent.setClassName("com.android.contacts", 2311 "com.android.contacts.ViewNotificationService"); 2312 context.startService(intent); 2313 } 2314 2315 // 2316 // General phone and call state debugging/testing code 2317 // 2318 dumpCallState(Phone phone)2319 /* package */ static void dumpCallState(Phone phone) { 2320 PhoneGlobals app = PhoneGlobals.getInstance(); 2321 Log.d(LOG_TAG, "dumpCallState():"); 2322 Log.d(LOG_TAG, "- Phone: " + phone + ", name = " + phone.getPhoneName() 2323 + ", state = " + phone.getState()); 2324 2325 StringBuilder b = new StringBuilder(128); 2326 2327 Call call = phone.getForegroundCall(); 2328 b.setLength(0); 2329 b.append(" - FG call: ").append(call.getState()); 2330 b.append(" isAlive ").append(call.getState().isAlive()); 2331 b.append(" isRinging ").append(call.getState().isRinging()); 2332 b.append(" isDialing ").append(call.getState().isDialing()); 2333 b.append(" isIdle ").append(call.isIdle()); 2334 b.append(" hasConnections ").append(call.hasConnections()); 2335 Log.d(LOG_TAG, b.toString()); 2336 2337 call = phone.getBackgroundCall(); 2338 b.setLength(0); 2339 b.append(" - BG call: ").append(call.getState()); 2340 b.append(" isAlive ").append(call.getState().isAlive()); 2341 b.append(" isRinging ").append(call.getState().isRinging()); 2342 b.append(" isDialing ").append(call.getState().isDialing()); 2343 b.append(" isIdle ").append(call.isIdle()); 2344 b.append(" hasConnections ").append(call.hasConnections()); 2345 Log.d(LOG_TAG, b.toString()); 2346 2347 call = phone.getRingingCall(); 2348 b.setLength(0); 2349 b.append(" - RINGING call: ").append(call.getState()); 2350 b.append(" isAlive ").append(call.getState().isAlive()); 2351 b.append(" isRinging ").append(call.getState().isRinging()); 2352 b.append(" isDialing ").append(call.getState().isDialing()); 2353 b.append(" isIdle ").append(call.isIdle()); 2354 b.append(" hasConnections ").append(call.hasConnections()); 2355 Log.d(LOG_TAG, b.toString()); 2356 2357 2358 final boolean hasRingingCall = !phone.getRingingCall().isIdle(); 2359 final boolean hasActiveCall = !phone.getForegroundCall().isIdle(); 2360 final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle(); 2361 final boolean allLinesTaken = hasActiveCall && hasHoldingCall; 2362 b.setLength(0); 2363 b.append(" - hasRingingCall ").append(hasRingingCall); 2364 b.append(" hasActiveCall ").append(hasActiveCall); 2365 b.append(" hasHoldingCall ").append(hasHoldingCall); 2366 b.append(" allLinesTaken ").append(allLinesTaken); 2367 Log.d(LOG_TAG, b.toString()); 2368 2369 // On CDMA phones, dump out the CdmaPhoneCallState too: 2370 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) { 2371 if (app.cdmaPhoneCallState != null) { 2372 Log.d(LOG_TAG, " - CDMA call state: " 2373 + app.cdmaPhoneCallState.getCurrentCallState()); 2374 } else { 2375 Log.d(LOG_TAG, " - CDMA device, but null cdmaPhoneCallState!"); 2376 } 2377 } 2378 } 2379 log(String msg)2380 private static void log(String msg) { 2381 Log.d(LOG_TAG, msg); 2382 } 2383 dumpCallManager()2384 static void dumpCallManager() { 2385 Call call; 2386 CallManager cm = PhoneGlobals.getInstance().mCM; 2387 StringBuilder b = new StringBuilder(128); 2388 2389 2390 2391 Log.d(LOG_TAG, "############### dumpCallManager() ##############"); 2392 // TODO: Don't log "cm" itself, since CallManager.toString() 2393 // already spews out almost all this same information. 2394 // We should fix CallManager.toString() to be more minimal, and 2395 // use an explicit dumpState() method for the verbose dump. 2396 // Log.d(LOG_TAG, "CallManager: " + cm 2397 // + ", state = " + cm.getState()); 2398 Log.d(LOG_TAG, "CallManager: state = " + cm.getState()); 2399 b.setLength(0); 2400 call = cm.getActiveFgCall(); 2401 b.append(" - FG call: ").append(cm.hasActiveFgCall()? "YES ": "NO "); 2402 b.append(call); 2403 b.append( " State: ").append(cm.getActiveFgCallState()); 2404 b.append( " Conn: ").append(cm.getFgCallConnections()); 2405 Log.d(LOG_TAG, b.toString()); 2406 b.setLength(0); 2407 call = cm.getFirstActiveBgCall(); 2408 b.append(" - BG call: ").append(cm.hasActiveBgCall()? "YES ": "NO "); 2409 b.append(call); 2410 b.append( " State: ").append(cm.getFirstActiveBgCall().getState()); 2411 b.append( " Conn: ").append(cm.getBgCallConnections()); 2412 Log.d(LOG_TAG, b.toString()); 2413 b.setLength(0); 2414 call = cm.getFirstActiveRingingCall(); 2415 b.append(" - RINGING call: ").append(cm.hasActiveRingingCall()? "YES ": "NO "); 2416 b.append(call); 2417 b.append( " State: ").append(cm.getFirstActiveRingingCall().getState()); 2418 Log.d(LOG_TAG, b.toString()); 2419 2420 2421 2422 for (Phone phone : CallManager.getInstance().getAllPhones()) { 2423 if (phone != null) { 2424 Log.d(LOG_TAG, "Phone: " + phone + ", name = " + phone.getPhoneName() 2425 + ", state = " + phone.getState()); 2426 b.setLength(0); 2427 call = phone.getForegroundCall(); 2428 b.append(" - FG call: ").append(call); 2429 b.append( " State: ").append(call.getState()); 2430 b.append( " Conn: ").append(call.hasConnections()); 2431 Log.d(LOG_TAG, b.toString()); 2432 b.setLength(0); 2433 call = phone.getBackgroundCall(); 2434 b.append(" - BG call: ").append(call); 2435 b.append( " State: ").append(call.getState()); 2436 b.append( " Conn: ").append(call.hasConnections()); 2437 Log.d(LOG_TAG, b.toString());b.setLength(0); 2438 call = phone.getRingingCall(); 2439 b.append(" - RINGING call: ").append(call); 2440 b.append( " State: ").append(call.getState()); 2441 b.append( " Conn: ").append(call.hasConnections()); 2442 Log.d(LOG_TAG, b.toString()); 2443 } 2444 } 2445 2446 Log.d(LOG_TAG, "############## END dumpCallManager() ###############"); 2447 } 2448 2449 /** 2450 * @return if the context is in landscape orientation. 2451 */ isLandscape(Context context)2452 public static boolean isLandscape(Context context) { 2453 return context.getResources().getConfiguration().orientation 2454 == Configuration.ORIENTATION_LANDSCAPE; 2455 } 2456 makePstnPhoneAccountHandle(String id)2457 public static PhoneAccountHandle makePstnPhoneAccountHandle(String id) { 2458 return makePstnPhoneAccountHandleWithPrefix(id, "", false); 2459 } 2460 makePstnPhoneAccountHandle(int phoneId)2461 public static PhoneAccountHandle makePstnPhoneAccountHandle(int phoneId) { 2462 return makePstnPhoneAccountHandle(PhoneFactory.getPhone(phoneId)); 2463 } 2464 makePstnPhoneAccountHandle(Phone phone)2465 public static PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) { 2466 return makePstnPhoneAccountHandleWithPrefix(phone, "", false); 2467 } 2468 makePstnPhoneAccountHandleWithPrefix( Phone phone, String prefix, boolean isEmergency)2469 public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix( 2470 Phone phone, String prefix, boolean isEmergency) { 2471 // TODO: Should use some sort of special hidden flag to decorate this account as 2472 // an emergency-only account 2473 String id = isEmergency ? EMERGENCY_ACCOUNT_HANDLE_ID : prefix + 2474 String.valueOf(phone.getIccSerialNumber()); 2475 return makePstnPhoneAccountHandleWithPrefix(id, prefix, isEmergency); 2476 } 2477 makePstnPhoneAccountHandleWithPrefix( String id, String prefix, boolean isEmergency)2478 public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix( 2479 String id, String prefix, boolean isEmergency) { 2480 ComponentName pstnConnectionServiceName = getPstnConnectionServiceName(); 2481 return new PhoneAccountHandle(pstnConnectionServiceName, id); 2482 } 2483 getSubIdForPhoneAccount(PhoneAccount phoneAccount)2484 public static int getSubIdForPhoneAccount(PhoneAccount phoneAccount) { 2485 if (phoneAccount != null 2486 && phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { 2487 return getSubIdForPhoneAccountHandle(phoneAccount.getAccountHandle()); 2488 } 2489 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 2490 } 2491 getSubIdForPhoneAccountHandle(PhoneAccountHandle handle)2492 public static int getSubIdForPhoneAccountHandle(PhoneAccountHandle handle) { 2493 if (handle != null && handle.getComponentName().equals(getPstnConnectionServiceName())) { 2494 Phone phone = getPhoneFromIccId(handle.getId()); 2495 if (phone != null) { 2496 return phone.getSubId(); 2497 } 2498 } 2499 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 2500 } 2501 2502 /** 2503 * Determine if a given phone account corresponds to an active SIM 2504 * 2505 * @param sm An instance of the subscription manager so it is not recreated for each calling of 2506 * this method. 2507 * @param handle The handle for the phone account to check 2508 * @return {@code true} If there is an active SIM for this phone account, 2509 * {@code false} otherwise. 2510 */ isPhoneAccountActive(SubscriptionManager sm, PhoneAccountHandle handle)2511 public static boolean isPhoneAccountActive(SubscriptionManager sm, PhoneAccountHandle handle) { 2512 return sm.getActiveSubscriptionInfoForIccIndex(handle.getId()) != null; 2513 } 2514 getPstnConnectionServiceName()2515 private static ComponentName getPstnConnectionServiceName() { 2516 return new ComponentName(PhoneGlobals.getInstance(), TelephonyConnectionService.class); 2517 } 2518 getPhoneFromIccId(String iccId)2519 private static Phone getPhoneFromIccId(String iccId) { 2520 if (!TextUtils.isEmpty(iccId)) { 2521 for (Phone phone : PhoneFactory.getPhones()) { 2522 String phoneIccId = phone.getIccSerialNumber(); 2523 if (iccId.equals(phoneIccId)) { 2524 return phone; 2525 } 2526 } 2527 } 2528 return null; 2529 } 2530 2531 /** 2532 * Register ICC status for all phones. 2533 */ registerIccStatus(Handler handler, int event)2534 static final void registerIccStatus(Handler handler, int event) { 2535 for (Phone phone : PhoneFactory.getPhones()) { 2536 IccCard sim = phone.getIccCard(); 2537 if (sim != null) { 2538 if (VDBG) Log.v(LOG_TAG, "register for ICC status, phone " + phone.getPhoneId()); 2539 sim.registerForNetworkLocked(handler, event, phone); 2540 } 2541 } 2542 } 2543 2544 /** 2545 * Set the radio power on/off state for all phones. 2546 * 2547 * @param enabled true means on, false means off. 2548 */ setRadioPower(boolean enabled)2549 static final void setRadioPower(boolean enabled) { 2550 for (Phone phone : PhoneFactory.getPhones()) { 2551 phone.setRadioPower(enabled); 2552 } 2553 } 2554 } 2555