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