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 static final int CALL_STATUS_DIALED = 0; // The number was successfully dialed 90 static final int CALL_STATUS_DIALED_MMI = 1; // The specified number was an MMI code 91 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 if (DBG) log("displayMMIInitiate: " + mmiCode); 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 if (DBG) log("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 if (DBG) log("running USSD code, displaying indeterminate 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 if (DBG) log("displayMMIComplete: state=" + state); 866 867 switch (state) { 868 case PENDING: 869 // USSD code asking for feedback from user. 870 text = mmiCode.getMessage(); 871 if (DBG) log("- 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 if (DBG) log("- 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 if (DBG) log("MMI code has finished running."); 933 934 if (DBG) log("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_KEYGUARD_DIALOG); 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 if (DBG) log("USSD code has requested user input. Constructing input dialog."); 970 971 // USSD MMI code that is interacting with the user. The 972 // basic set of steps is this: 973 // 1. User enters a USSD request 974 // 2. We recognize the request and displayMMIInitiate 975 // (above) creates a progress dialog. 976 // 3. Request returns and we get a PENDING or COMPLETE 977 // message. 978 // 4. These MMI messages are caught in the PhoneApp 979 // (onMMIComplete) and the InCallScreen 980 // (mHandler.handleMessage) which bring up this dialog 981 // and closes the original progress dialog, 982 // respectively. 983 // 5. If the message is anything other than PENDING, 984 // we are done, and the alert dialog (directly above) 985 // displays the outcome. 986 // 6. If the network is requesting more information from 987 // the user, the MMI will be in a PENDING state, and 988 // we display this dialog with the message. 989 // 7. User input, or cancel requests result in a return 990 // to step 1. Keep in mind that this is the only 991 // time that a USSD should be canceled. 992 993 // inflate the layout with the scrolling text area for the dialog. 994 ContextThemeWrapper contextThemeWrapper = 995 new ContextThemeWrapper(context, R.style.DialerAlertDialogTheme); 996 LayoutInflater inflater = (LayoutInflater) contextThemeWrapper.getSystemService( 997 Context.LAYOUT_INFLATER_SERVICE); 998 View dialogView = inflater.inflate(R.layout.dialog_ussd_response, null); 999 1000 // get the input field. 1001 final EditText inputText = (EditText) dialogView.findViewById(R.id.input_field); 1002 1003 // specify the dialog's click listener, with SEND and CANCEL logic. 1004 final DialogInterface.OnClickListener mUSSDDialogListener = 1005 new DialogInterface.OnClickListener() { 1006 public void onClick(DialogInterface dialog, int whichButton) { 1007 switch (whichButton) { 1008 case DialogInterface.BUTTON_POSITIVE: 1009 // As per spec 24.080, valid length of ussd string 1010 // is 1 - 160. If length is out of the range then 1011 // display toast message & Cancel MMI operation. 1012 if (inputText.length() < MIN_USSD_LEN 1013 || inputText.length() > MAX_USSD_LEN) { 1014 Toast.makeText(app, 1015 app.getResources().getString(R.string.enter_input, 1016 MIN_USSD_LEN, MAX_USSD_LEN), 1017 Toast.LENGTH_LONG).show(); 1018 if (mmiCode.isCancelable()) { 1019 mmiCode.cancel(); 1020 } 1021 } else { 1022 phone.sendUssdResponse(inputText.getText().toString()); 1023 } 1024 break; 1025 case DialogInterface.BUTTON_NEGATIVE: 1026 if (mmiCode.isCancelable()) { 1027 mmiCode.cancel(); 1028 } 1029 break; 1030 } 1031 } 1032 }; 1033 1034 // build the dialog 1035 final AlertDialog newDialog = new AlertDialog.Builder(contextThemeWrapper) 1036 .setMessage(text) 1037 .setView(dialogView) 1038 .setPositiveButton(R.string.send_button, mUSSDDialogListener) 1039 .setNegativeButton(R.string.cancel, mUSSDDialogListener) 1040 .setCancelable(false) 1041 .create(); 1042 1043 // attach the key listener to the dialog's input field and make 1044 // sure focus is set. 1045 final View.OnKeyListener mUSSDDialogInputListener = 1046 new View.OnKeyListener() { 1047 public boolean onKey(View v, int keyCode, KeyEvent event) { 1048 switch (keyCode) { 1049 case KeyEvent.KEYCODE_CALL: 1050 case KeyEvent.KEYCODE_ENTER: 1051 if(event.getAction() == KeyEvent.ACTION_DOWN) { 1052 phone.sendUssdResponse(inputText.getText().toString()); 1053 newDialog.dismiss(); 1054 } 1055 return true; 1056 } 1057 return false; 1058 } 1059 }; 1060 inputText.setOnKeyListener(mUSSDDialogInputListener); 1061 inputText.requestFocus(); 1062 1063 // set the window properties of the dialog 1064 newDialog.getWindow().setType( 1065 WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); 1066 newDialog.getWindow().addFlags( 1067 WindowManager.LayoutParams.FLAG_DIM_BEHIND); 1068 1069 // now show the dialog! 1070 newDialog.show(); 1071 1072 newDialog.getButton(DialogInterface.BUTTON_POSITIVE) 1073 .setTextColor(context.getResources().getColor(R.color.dialer_theme_color)); 1074 newDialog.getButton(DialogInterface.BUTTON_NEGATIVE) 1075 .setTextColor(context.getResources().getColor(R.color.dialer_theme_color)); 1076 } 1077 } 1078 } 1079 1080 /** 1081 * Cancels the current pending MMI operation, if applicable. 1082 * @return true if we canceled an MMI operation, or false 1083 * if the current pending MMI wasn't cancelable 1084 * or if there was no current pending MMI at all. 1085 * 1086 * @see displayMMIInitiate 1087 */ cancelMmiCode(Phone phone)1088 static boolean cancelMmiCode(Phone phone) { 1089 List<? extends MmiCode> pendingMmis = phone.getPendingMmiCodes(); 1090 int count = pendingMmis.size(); 1091 if (DBG) log("cancelMmiCode: num pending MMIs = " + count); 1092 1093 boolean canceled = false; 1094 if (count > 0) { 1095 // assume that we only have one pending MMI operation active at a time. 1096 // I don't think it's possible to enter multiple MMI codes concurrently 1097 // in the phone UI, because during the MMI operation, an Alert panel 1098 // is displayed, which prevents more MMI code from being entered. 1099 MmiCode mmiCode = pendingMmis.get(0); 1100 if (mmiCode.isCancelable()) { 1101 mmiCode.cancel(); 1102 canceled = true; 1103 } 1104 } 1105 return canceled; 1106 } 1107 1108 public static class VoiceMailNumberMissingException extends Exception { VoiceMailNumberMissingException()1109 VoiceMailNumberMissingException() { 1110 super(); 1111 } 1112 VoiceMailNumberMissingException(String msg)1113 VoiceMailNumberMissingException(String msg) { 1114 super(msg); 1115 } 1116 } 1117 1118 /** 1119 * Given an Intent (which is presumably the ACTION_CALL intent that 1120 * initiated this outgoing call), figure out the actual phone number we 1121 * should dial. 1122 * 1123 * Note that the returned "number" may actually be a SIP address, 1124 * if the specified intent contains a sip: URI. 1125 * 1126 * This method is basically a wrapper around PhoneUtils.getNumberFromIntent(), 1127 * except it's also aware of the EXTRA_ACTUAL_NUMBER_TO_DIAL extra. 1128 * (That extra, if present, tells us the exact string to pass down to the 1129 * telephony layer. It's guaranteed to be safe to dial: it's either a PSTN 1130 * phone number with separators and keypad letters stripped out, or a raw 1131 * unencoded SIP address.) 1132 * 1133 * @return the phone number corresponding to the specified Intent, or null 1134 * if the Intent has no action or if the intent's data is malformed or 1135 * missing. 1136 * 1137 * @throws VoiceMailNumberMissingException if the intent 1138 * contains a "voicemail" URI, but there's no voicemail 1139 * number configured on the device. 1140 */ getInitialNumber(Intent intent)1141 public static String getInitialNumber(Intent intent) 1142 throws PhoneUtils.VoiceMailNumberMissingException { 1143 if (DBG) log("getInitialNumber(): " + intent); 1144 1145 String action = intent.getAction(); 1146 if (TextUtils.isEmpty(action)) { 1147 return null; 1148 } 1149 1150 // If the EXTRA_ACTUAL_NUMBER_TO_DIAL extra is present, get the phone 1151 // number from there. (That extra takes precedence over the actual data 1152 // included in the intent.) 1153 if (intent.hasExtra(OutgoingCallBroadcaster.EXTRA_ACTUAL_NUMBER_TO_DIAL)) { 1154 String actualNumberToDial = 1155 intent.getStringExtra(OutgoingCallBroadcaster.EXTRA_ACTUAL_NUMBER_TO_DIAL); 1156 if (DBG) { 1157 log("==> got EXTRA_ACTUAL_NUMBER_TO_DIAL; returning '" 1158 + toLogSafePhoneNumber(actualNumberToDial) + "'"); 1159 } 1160 return actualNumberToDial; 1161 } 1162 1163 return getNumberFromIntent(PhoneGlobals.getInstance(), intent); 1164 } 1165 1166 /** 1167 * Gets the phone number to be called from an intent. Requires a Context 1168 * to access the contacts database, and a Phone to access the voicemail 1169 * number. 1170 * 1171 * <p>If <code>phone</code> is <code>null</code>, the function will return 1172 * <code>null</code> for <code>voicemail:</code> URIs; 1173 * if <code>context</code> is <code>null</code>, the function will return 1174 * <code>null</code> for person/phone URIs.</p> 1175 * 1176 * <p>If the intent contains a <code>sip:</code> URI, the returned 1177 * "number" is actually the SIP address. 1178 * 1179 * @param context a context to use (or 1180 * @param intent the intent 1181 * 1182 * @throws VoiceMailNumberMissingException if <code>intent</code> contains 1183 * a <code>voicemail:</code> URI, but <code>phone</code> does not 1184 * have a voicemail number set. 1185 * 1186 * @return the phone number (or SIP address) that would be called by the intent, 1187 * or <code>null</code> if the number cannot be found. 1188 */ getNumberFromIntent(Context context, Intent intent)1189 private static String getNumberFromIntent(Context context, Intent intent) 1190 throws VoiceMailNumberMissingException { 1191 Uri uri = intent.getData(); 1192 String scheme = uri.getScheme(); 1193 1194 // The sip: scheme is simple: just treat the rest of the URI as a 1195 // SIP address. 1196 if (PhoneAccount.SCHEME_SIP.equals(scheme)) { 1197 return uri.getSchemeSpecificPart(); 1198 } 1199 1200 // Otherwise, let PhoneNumberUtils.getNumberFromIntent() handle 1201 // the other cases (i.e. tel: and voicemail: and contact: URIs.) 1202 1203 final String number = PhoneNumberUtils.getNumberFromIntent(intent, context); 1204 1205 // Check for a voicemail-dialing request. If the voicemail number is 1206 // empty, throw a VoiceMailNumberMissingException. 1207 if (PhoneAccount.SCHEME_VOICEMAIL.equals(scheme) && 1208 (number == null || TextUtils.isEmpty(number))) 1209 throw new VoiceMailNumberMissingException(); 1210 1211 return number; 1212 } 1213 1214 /** 1215 * Returns the caller-id info corresponding to the specified Connection. 1216 * (This is just a simple wrapper around CallerInfo.getCallerInfo(): we 1217 * extract a phone number from the specified Connection, and feed that 1218 * number into CallerInfo.getCallerInfo().) 1219 * 1220 * The returned CallerInfo may be null in certain error cases, like if the 1221 * specified Connection was null, or if we weren't able to get a valid 1222 * phone number from the Connection. 1223 * 1224 * Finally, if the getCallerInfo() call did succeed, we save the resulting 1225 * CallerInfo object in the "userData" field of the Connection. 1226 * 1227 * NOTE: This API should be avoided, with preference given to the 1228 * asynchronous startGetCallerInfo API. 1229 */ getCallerInfo(Context context, Connection c)1230 static CallerInfo getCallerInfo(Context context, Connection c) { 1231 CallerInfo info = null; 1232 1233 if (c != null) { 1234 //See if there is a URI attached. If there is, this means 1235 //that there is no CallerInfo queried yet, so we'll need to 1236 //replace the URI with a full CallerInfo object. 1237 Object userDataObject = c.getUserData(); 1238 if (userDataObject instanceof Uri) { 1239 info = CallerInfo.getCallerInfo(context, (Uri) userDataObject); 1240 if (info != null) { 1241 c.setUserData(info); 1242 } 1243 } else { 1244 if (userDataObject instanceof CallerInfoToken) { 1245 //temporary result, while query is running 1246 info = ((CallerInfoToken) userDataObject).currentInfo; 1247 } else { 1248 //final query result 1249 info = (CallerInfo) userDataObject; 1250 } 1251 if (info == null) { 1252 // No URI, or Existing CallerInfo, so we'll have to make do with 1253 // querying a new CallerInfo using the connection's phone number. 1254 String number = c.getAddress(); 1255 1256 if (DBG) log("getCallerInfo: number = " + toLogSafePhoneNumber(number)); 1257 1258 if (!TextUtils.isEmpty(number)) { 1259 info = CallerInfo.getCallerInfo(context, number); 1260 if (info != null) { 1261 c.setUserData(info); 1262 } 1263 } 1264 } 1265 } 1266 } 1267 return info; 1268 } 1269 1270 /** 1271 * Class returned by the startGetCallerInfo call to package a temporary 1272 * CallerInfo Object, to be superceded by the CallerInfo Object passed 1273 * into the listener when the query with token mAsyncQueryToken is complete. 1274 */ 1275 public static class CallerInfoToken { 1276 /**indicates that there will no longer be updates to this request.*/ 1277 public boolean isFinal; 1278 1279 public CallerInfo currentInfo; 1280 public CallerInfoAsyncQuery asyncQuery; 1281 } 1282 1283 /** 1284 * Start a CallerInfo Query based on the earliest connection in the call. 1285 */ startGetCallerInfo(Context context, Call call, CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie)1286 static CallerInfoToken startGetCallerInfo(Context context, Call call, 1287 CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie) { 1288 Connection conn = null; 1289 int phoneType = call.getPhone().getPhoneType(); 1290 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) { 1291 conn = call.getLatestConnection(); 1292 } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM) 1293 || (phoneType == PhoneConstants.PHONE_TYPE_SIP) 1294 || (phoneType == PhoneConstants.PHONE_TYPE_IMS) 1295 || (phoneType == PhoneConstants.PHONE_TYPE_THIRD_PARTY)) { 1296 conn = call.getEarliestConnection(); 1297 } else { 1298 throw new IllegalStateException("Unexpected phone type: " + phoneType); 1299 } 1300 1301 return startGetCallerInfo(context, conn, listener, cookie); 1302 } 1303 startGetCallerInfo(Context context, Connection c, CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie)1304 static CallerInfoToken startGetCallerInfo(Context context, Connection c, 1305 CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie) { 1306 return startGetCallerInfo(context, c, listener, cookie, null); 1307 } 1308 1309 /** 1310 * place a temporary callerinfo object in the hands of the caller and notify 1311 * caller when the actual query is done. 1312 */ startGetCallerInfo(Context context, Connection c, CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie, RawGatewayInfo info)1313 static CallerInfoToken startGetCallerInfo(Context context, Connection c, 1314 CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie, 1315 RawGatewayInfo info) { 1316 CallerInfoToken cit; 1317 1318 if (c == null) { 1319 //TODO: perhaps throw an exception here. 1320 cit = new CallerInfoToken(); 1321 cit.asyncQuery = null; 1322 return cit; 1323 } 1324 1325 Object userDataObject = c.getUserData(); 1326 1327 // There are now 3 states for the Connection's userData object: 1328 // 1329 // (1) Uri - query has not been executed yet 1330 // 1331 // (2) CallerInfoToken - query is executing, but has not completed. 1332 // 1333 // (3) CallerInfo - query has executed. 1334 // 1335 // In each case we have slightly different behaviour: 1336 // 1. If the query has not been executed yet (Uri or null), we start 1337 // query execution asynchronously, and note it by attaching a 1338 // CallerInfoToken as the userData. 1339 // 2. If the query is executing (CallerInfoToken), we've essentially 1340 // reached a state where we've received multiple requests for the 1341 // same callerInfo. That means that once the query is complete, 1342 // we'll need to execute the additional listener requested. 1343 // 3. If the query has already been executed (CallerInfo), we just 1344 // return the CallerInfo object as expected. 1345 // 4. Regarding isFinal - there are cases where the CallerInfo object 1346 // will not be attached, like when the number is empty (caller id 1347 // blocking). This flag is used to indicate that the 1348 // CallerInfoToken object is going to be permanent since no 1349 // query results will be returned. In the case where a query 1350 // has been completed, this flag is used to indicate to the caller 1351 // that the data will not be updated since it is valid. 1352 // 1353 // Note: For the case where a number is NOT retrievable, we leave 1354 // the CallerInfo as null in the CallerInfoToken. This is 1355 // something of a departure from the original code, since the old 1356 // code manufactured a CallerInfo object regardless of the query 1357 // outcome. From now on, we will append an empty CallerInfo 1358 // object, to mirror previous behaviour, and to avoid Null Pointer 1359 // Exceptions. 1360 1361 if (userDataObject instanceof Uri) { 1362 // State (1): query has not been executed yet 1363 1364 //create a dummy callerinfo, populate with what we know from URI. 1365 cit = new CallerInfoToken(); 1366 cit.currentInfo = new CallerInfo(); 1367 cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, 1368 (Uri) userDataObject, sCallerInfoQueryListener, c); 1369 cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie); 1370 cit.isFinal = false; 1371 1372 c.setUserData(cit); 1373 1374 if (DBG) log("startGetCallerInfo: query based on Uri: " + userDataObject); 1375 1376 } else if (userDataObject == null) { 1377 // No URI, or Existing CallerInfo, so we'll have to make do with 1378 // querying a new CallerInfo using the connection's phone number. 1379 String number = c.getAddress(); 1380 1381 if (info != null && info != CallGatewayManager.EMPTY_INFO) { 1382 // Gateway number, the connection number is actually the gateway number. 1383 // need to lookup via dialed number. 1384 number = info.trueNumber; 1385 } 1386 1387 if (DBG) { 1388 log("PhoneUtils.startGetCallerInfo: new query for phone number..."); 1389 log("- number (address): " + toLogSafePhoneNumber(number)); 1390 log("- c: " + c); 1391 log("- phone: " + c.getCall().getPhone()); 1392 int phoneType = c.getCall().getPhone().getPhoneType(); 1393 log("- phoneType: " + phoneType); 1394 switch (phoneType) { 1395 case PhoneConstants.PHONE_TYPE_NONE: log(" ==> PHONE_TYPE_NONE"); break; 1396 case PhoneConstants.PHONE_TYPE_GSM: log(" ==> PHONE_TYPE_GSM"); break; 1397 case PhoneConstants.PHONE_TYPE_IMS: log(" ==> PHONE_TYPE_IMS"); break; 1398 case PhoneConstants.PHONE_TYPE_CDMA: log(" ==> PHONE_TYPE_CDMA"); break; 1399 case PhoneConstants.PHONE_TYPE_SIP: log(" ==> PHONE_TYPE_SIP"); break; 1400 case PhoneConstants.PHONE_TYPE_THIRD_PARTY: 1401 log(" ==> PHONE_TYPE_THIRD_PARTY"); 1402 break; 1403 default: log(" ==> Unknown phone type"); break; 1404 } 1405 } 1406 1407 cit = new CallerInfoToken(); 1408 cit.currentInfo = new CallerInfo(); 1409 1410 // Store CNAP information retrieved from the Connection (we want to do this 1411 // here regardless of whether the number is empty or not). 1412 cit.currentInfo.cnapName = c.getCnapName(); 1413 cit.currentInfo.name = cit.currentInfo.cnapName; // This can still get overwritten 1414 // by ContactInfo later 1415 cit.currentInfo.numberPresentation = c.getNumberPresentation(); 1416 cit.currentInfo.namePresentation = c.getCnapNamePresentation(); 1417 1418 if (VDBG) { 1419 log("startGetCallerInfo: number = " + number); 1420 log("startGetCallerInfo: CNAP Info from FW(1): name=" 1421 + cit.currentInfo.cnapName 1422 + ", Name/Number Pres=" + cit.currentInfo.numberPresentation); 1423 } 1424 1425 // handling case where number is null (caller id hidden) as well. 1426 if (!TextUtils.isEmpty(number)) { 1427 // Check for special CNAP cases and modify the CallerInfo accordingly 1428 // to be sure we keep the right information to display/log later 1429 number = modifyForSpecialCnapCases(context, cit.currentInfo, number, 1430 cit.currentInfo.numberPresentation); 1431 1432 cit.currentInfo.phoneNumber = number; 1433 // For scenarios where we may receive a valid number from the network but a 1434 // restricted/unavailable presentation, we do not want to perform a contact query 1435 // (see note on isFinal above). So we set isFinal to true here as well. 1436 if (cit.currentInfo.numberPresentation != PhoneConstants.PRESENTATION_ALLOWED) { 1437 cit.isFinal = true; 1438 } else { 1439 if (DBG) log("==> Actually starting CallerInfoAsyncQuery.startQuery()..."); 1440 cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, 1441 number, sCallerInfoQueryListener, c); 1442 cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie); 1443 cit.isFinal = false; 1444 } 1445 } else { 1446 // This is the case where we are querying on a number that 1447 // is null or empty, like a caller whose caller id is 1448 // blocked or empty (CLIR). The previous behaviour was to 1449 // throw a null CallerInfo object back to the user, but 1450 // this departure is somewhat cleaner. 1451 if (DBG) log("startGetCallerInfo: No query to start, send trivial reply."); 1452 cit.isFinal = true; // please see note on isFinal, above. 1453 } 1454 1455 c.setUserData(cit); 1456 1457 if (DBG) { 1458 log("startGetCallerInfo: query based on number: " + toLogSafePhoneNumber(number)); 1459 } 1460 1461 } else if (userDataObject instanceof CallerInfoToken) { 1462 // State (2): query is executing, but has not completed. 1463 1464 // just tack on this listener to the queue. 1465 cit = (CallerInfoToken) userDataObject; 1466 1467 // handling case where number is null (caller id hidden) as well. 1468 if (cit.asyncQuery != null) { 1469 cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie); 1470 1471 if (DBG) log("startGetCallerInfo: query already running, adding listener: " + 1472 listener.getClass().toString()); 1473 } else { 1474 // handling case where number/name gets updated later on by the network 1475 String updatedNumber = c.getAddress(); 1476 1477 if (info != null) { 1478 // Gateway number, the connection number is actually the gateway number. 1479 // need to lookup via dialed number. 1480 updatedNumber = info.trueNumber; 1481 } 1482 1483 if (DBG) { 1484 log("startGetCallerInfo: updatedNumber initially = " 1485 + toLogSafePhoneNumber(updatedNumber)); 1486 } 1487 if (!TextUtils.isEmpty(updatedNumber)) { 1488 // Store CNAP information retrieved from the Connection 1489 cit.currentInfo.cnapName = c.getCnapName(); 1490 // This can still get overwritten by ContactInfo 1491 cit.currentInfo.name = cit.currentInfo.cnapName; 1492 cit.currentInfo.numberPresentation = c.getNumberPresentation(); 1493 cit.currentInfo.namePresentation = c.getCnapNamePresentation(); 1494 1495 updatedNumber = modifyForSpecialCnapCases(context, cit.currentInfo, 1496 updatedNumber, cit.currentInfo.numberPresentation); 1497 1498 cit.currentInfo.phoneNumber = updatedNumber; 1499 if (DBG) { 1500 log("startGetCallerInfo: updatedNumber=" 1501 + toLogSafePhoneNumber(updatedNumber)); 1502 } 1503 if (VDBG) { 1504 log("startGetCallerInfo: CNAP Info from FW(2): name=" 1505 + cit.currentInfo.cnapName 1506 + ", Name/Number Pres=" + cit.currentInfo.numberPresentation); 1507 } else if (DBG) { 1508 log("startGetCallerInfo: CNAP Info from FW(2)"); 1509 } 1510 // For scenarios where we may receive a valid number from the network but a 1511 // restricted/unavailable presentation, we do not want to perform a contact query 1512 // (see note on isFinal above). So we set isFinal to true here as well. 1513 if (cit.currentInfo.numberPresentation != PhoneConstants.PRESENTATION_ALLOWED) { 1514 cit.isFinal = true; 1515 } else { 1516 cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, 1517 updatedNumber, sCallerInfoQueryListener, c); 1518 cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie); 1519 cit.isFinal = false; 1520 } 1521 } else { 1522 if (DBG) log("startGetCallerInfo: No query to attach to, send trivial reply."); 1523 if (cit.currentInfo == null) { 1524 cit.currentInfo = new CallerInfo(); 1525 } 1526 // Store CNAP information retrieved from the Connection 1527 cit.currentInfo.cnapName = c.getCnapName(); // This can still get 1528 // overwritten by ContactInfo 1529 cit.currentInfo.name = cit.currentInfo.cnapName; 1530 cit.currentInfo.numberPresentation = c.getNumberPresentation(); 1531 cit.currentInfo.namePresentation = c.getCnapNamePresentation(); 1532 1533 if (VDBG) { 1534 log("startGetCallerInfo: CNAP Info from FW(3): name=" 1535 + cit.currentInfo.cnapName 1536 + ", Name/Number Pres=" + cit.currentInfo.numberPresentation); 1537 } else if (DBG) { 1538 log("startGetCallerInfo: CNAP Info from FW(3)"); 1539 } 1540 cit.isFinal = true; // please see note on isFinal, above. 1541 } 1542 } 1543 } else { 1544 // State (3): query is complete. 1545 1546 // The connection's userDataObject is a full-fledged 1547 // CallerInfo instance. Wrap it in a CallerInfoToken and 1548 // return it to the user. 1549 1550 cit = new CallerInfoToken(); 1551 cit.currentInfo = (CallerInfo) userDataObject; 1552 cit.asyncQuery = null; 1553 cit.isFinal = true; 1554 // since the query is already done, call the listener. 1555 if (DBG) log("startGetCallerInfo: query already done, returning CallerInfo"); 1556 if (DBG) log("==> cit.currentInfo = " + cit.currentInfo); 1557 } 1558 return cit; 1559 } 1560 1561 /** 1562 * Static CallerInfoAsyncQuery.OnQueryCompleteListener instance that 1563 * we use with all our CallerInfoAsyncQuery.startQuery() requests. 1564 */ 1565 private static final int QUERY_TOKEN = -1; 1566 static CallerInfoAsyncQuery.OnQueryCompleteListener sCallerInfoQueryListener = 1567 new CallerInfoAsyncQuery.OnQueryCompleteListener () { 1568 /** 1569 * When the query completes, we stash the resulting CallerInfo 1570 * object away in the Connection's "userData" (where it will 1571 * later be retrieved by the in-call UI.) 1572 */ 1573 public void onQueryComplete(int token, Object cookie, CallerInfo ci) { 1574 if (DBG) log("query complete, updating connection.userdata"); 1575 Connection conn = (Connection) cookie; 1576 1577 // Added a check if CallerInfo is coming from ContactInfo or from Connection. 1578 // If no ContactInfo, then we want to use CNAP information coming from network 1579 if (DBG) log("- onQueryComplete: CallerInfo:" + ci); 1580 if (ci.contactExists || ci.isEmergencyNumber() || ci.isVoiceMailNumber()) { 1581 // If the number presentation has not been set by 1582 // the ContactInfo, use the one from the 1583 // connection. 1584 1585 // TODO: Need a new util method to merge the info 1586 // from the Connection in a CallerInfo object. 1587 // Here 'ci' is a new CallerInfo instance read 1588 // from the DB. It has lost all the connection 1589 // info preset before the query (see PhoneUtils 1590 // line 1334). We should have a method to merge 1591 // back into this new instance the info from the 1592 // connection object not set by the DB. If the 1593 // Connection already has a CallerInfo instance in 1594 // userData, then we could use this instance to 1595 // fill 'ci' in. The same routine could be used in 1596 // PhoneUtils. 1597 if (0 == ci.numberPresentation) { 1598 ci.numberPresentation = conn.getNumberPresentation(); 1599 } 1600 } else { 1601 // No matching contact was found for this number. 1602 // Return a new CallerInfo based solely on the CNAP 1603 // information from the network. 1604 1605 CallerInfo newCi = getCallerInfo(null, conn); 1606 1607 // ...but copy over the (few) things we care about 1608 // from the original CallerInfo object: 1609 if (newCi != null) { 1610 newCi.phoneNumber = ci.phoneNumber; // To get formatted phone number 1611 newCi.geoDescription = ci.geoDescription; // To get geo description string 1612 ci = newCi; 1613 } 1614 } 1615 1616 if (DBG) log("==> Stashing CallerInfo " + ci + " into the connection..."); 1617 conn.setUserData(ci); 1618 } 1619 }; 1620 1621 1622 /** 1623 * Returns a single "name" for the specified given a CallerInfo object. 1624 * If the name is null, return defaultString as the default value, usually 1625 * context.getString(R.string.unknown). 1626 */ getCompactNameFromCallerInfo(CallerInfo ci, Context context)1627 static String getCompactNameFromCallerInfo(CallerInfo ci, Context context) { 1628 if (DBG) log("getCompactNameFromCallerInfo: info = " + ci); 1629 1630 String compactName = null; 1631 if (ci != null) { 1632 if (TextUtils.isEmpty(ci.name)) { 1633 // Perform any modifications for special CNAP cases to 1634 // the phone number being displayed, if applicable. 1635 compactName = modifyForSpecialCnapCases(context, ci, ci.phoneNumber, 1636 ci.numberPresentation); 1637 } else { 1638 // Don't call modifyForSpecialCnapCases on regular name. See b/2160795. 1639 compactName = ci.name; 1640 } 1641 } 1642 1643 if ((compactName == null) || (TextUtils.isEmpty(compactName))) { 1644 // If we're still null/empty here, then check if we have a presentation 1645 // string that takes precedence that we could return, otherwise display 1646 // "unknown" string. 1647 if (ci != null && ci.numberPresentation == PhoneConstants.PRESENTATION_RESTRICTED) { 1648 compactName = context.getString(R.string.private_num); 1649 } else if (ci != null && ci.numberPresentation == PhoneConstants.PRESENTATION_PAYPHONE) { 1650 compactName = context.getString(R.string.payphone); 1651 } else { 1652 compactName = context.getString(R.string.unknown); 1653 } 1654 } 1655 if (VDBG) log("getCompactNameFromCallerInfo: compactName=" + compactName); 1656 return compactName; 1657 } 1658 1659 /** 1660 * Returns true if the specified Call is a "conference call", meaning 1661 * that it owns more than one Connection object. This information is 1662 * used to trigger certain UI changes that appear when a conference 1663 * call is active (like displaying the label "Conference call", and 1664 * enabling the "Manage conference" UI.) 1665 * 1666 * Watch out: This method simply checks the number of Connections, 1667 * *not* their states. So if a Call has (for example) one ACTIVE 1668 * connection and one DISCONNECTED connection, this method will return 1669 * true (which is unintuitive, since the Call isn't *really* a 1670 * conference call any more.) 1671 * 1672 * @return true if the specified call has more than one connection (in any state.) 1673 */ isConferenceCall(Call call)1674 static boolean isConferenceCall(Call call) { 1675 // CDMA phones don't have the same concept of "conference call" as 1676 // GSM phones do; there's no special "conference call" state of 1677 // the UI or a "manage conference" function. (Instead, when 1678 // you're in a 3-way call, all we can do is display the "generic" 1679 // state of the UI.) So as far as the in-call UI is concerned, 1680 // Conference corresponds to generic display. 1681 final PhoneGlobals app = PhoneGlobals.getInstance(); 1682 int phoneType = call.getPhone().getPhoneType(); 1683 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) { 1684 CdmaPhoneCallState.PhoneCallState state = app.cdmaPhoneCallState.getCurrentCallState(); 1685 if ((state == CdmaPhoneCallState.PhoneCallState.CONF_CALL) 1686 || ((state == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) 1687 && !app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing())) { 1688 return true; 1689 } 1690 } else { 1691 List<Connection> connections = call.getConnections(); 1692 if (connections != null && connections.size() > 1) { 1693 return true; 1694 } 1695 } 1696 return false; 1697 1698 // TODO: We may still want to change the semantics of this method 1699 // to say that a given call is only really a conference call if 1700 // the number of ACTIVE connections, not the total number of 1701 // connections, is greater than one. (See warning comment in the 1702 // javadoc above.) 1703 // Here's an implementation of that: 1704 // if (connections == null) { 1705 // return false; 1706 // } 1707 // int numActiveConnections = 0; 1708 // for (Connection conn : connections) { 1709 // if (DBG) log(" - CONN: " + conn + ", state = " + conn.getState()); 1710 // if (conn.getState() == Call.State.ACTIVE) numActiveConnections++; 1711 // if (numActiveConnections > 1) { 1712 // return true; 1713 // } 1714 // } 1715 // return false; 1716 } 1717 1718 /** 1719 * Launch the Dialer to start a new call. 1720 * This is just a wrapper around the ACTION_DIAL intent. 1721 */ startNewCall(final CallManager cm)1722 /* package */ static boolean startNewCall(final CallManager cm) { 1723 final PhoneGlobals app = PhoneGlobals.getInstance(); 1724 1725 // Sanity-check that this is OK given the current state of the phone. 1726 if (!okToAddCall(cm)) { 1727 Log.w(LOG_TAG, "startNewCall: can't add a new call in the current state"); 1728 dumpCallManager(); 1729 return false; 1730 } 1731 1732 Intent intent = new Intent(Intent.ACTION_DIAL); 1733 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1734 1735 // when we request the dialer come up, we also want to inform 1736 // it that we're going through the "add call" option from the 1737 // InCallScreen / PhoneUtils. 1738 intent.putExtra(ADD_CALL_MODE_KEY, true); 1739 try { 1740 app.startActivity(intent); 1741 } catch (ActivityNotFoundException e) { 1742 // This is rather rare but possible. 1743 // Note: this method is used even when the phone is encrypted. At that moment 1744 // the system may not find any Activity which can accept this Intent. 1745 Log.e(LOG_TAG, "Activity for adding calls isn't found."); 1746 return false; 1747 } 1748 1749 return true; 1750 } 1751 1752 /** 1753 * Turns on/off speaker. 1754 * 1755 * @param context Context 1756 * @param flag True when speaker should be on. False otherwise. 1757 * @param store True when the settings should be stored in the device. 1758 */ turnOnSpeaker(Context context, boolean flag, boolean store)1759 /* package */ static void turnOnSpeaker(Context context, boolean flag, boolean store) { 1760 if (DBG) log("turnOnSpeaker(flag=" + flag + ", store=" + store + ")..."); 1761 final PhoneGlobals app = PhoneGlobals.getInstance(); 1762 1763 AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 1764 audioManager.setSpeakerphoneOn(flag); 1765 1766 // record the speaker-enable value 1767 if (store) { 1768 sIsSpeakerEnabled = flag; 1769 } 1770 1771 // We also need to make a fresh call to PhoneApp.updateWakeState() 1772 // any time the speaker state changes, since the screen timeout is 1773 // sometimes different depending on whether or not the speaker is 1774 // in use. 1775 app.updateWakeState(); 1776 1777 app.mCM.setEchoSuppressionEnabled(); 1778 } 1779 1780 /** 1781 * Restore the speaker mode, called after a wired headset disconnect 1782 * event. 1783 */ restoreSpeakerMode(Context context)1784 static void restoreSpeakerMode(Context context) { 1785 if (DBG) log("restoreSpeakerMode, restoring to: " + sIsSpeakerEnabled); 1786 1787 // change the mode if needed. 1788 if (isSpeakerOn(context) != sIsSpeakerEnabled) { 1789 turnOnSpeaker(context, sIsSpeakerEnabled, false); 1790 } 1791 } 1792 isSpeakerOn(Context context)1793 static boolean isSpeakerOn(Context context) { 1794 AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 1795 return audioManager.isSpeakerphoneOn(); 1796 } 1797 1798 turnOnNoiseSuppression(Context context, boolean flag, boolean store)1799 static void turnOnNoiseSuppression(Context context, boolean flag, boolean store) { 1800 if (DBG) log("turnOnNoiseSuppression: " + flag); 1801 AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 1802 1803 PersistableBundle b = PhoneGlobals.getInstance().getCarrierConfig(); 1804 if (!b.getBoolean(CarrierConfigManager.KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL)) { 1805 return; 1806 } 1807 1808 if (flag) { 1809 audioManager.setParameters("noise_suppression=auto"); 1810 } else { 1811 audioManager.setParameters("noise_suppression=off"); 1812 } 1813 1814 // record the speaker-enable value 1815 if (store) { 1816 sIsNoiseSuppressionEnabled = flag; 1817 } 1818 1819 // TODO: implement and manage ICON 1820 1821 } 1822 restoreNoiseSuppression(Context context)1823 static void restoreNoiseSuppression(Context context) { 1824 if (DBG) log("restoreNoiseSuppression, restoring to: " + sIsNoiseSuppressionEnabled); 1825 1826 PersistableBundle b = PhoneGlobals.getInstance().getCarrierConfig(); 1827 if (!b.getBoolean(CarrierConfigManager.KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL)) { 1828 return; 1829 } 1830 1831 // change the mode if needed. 1832 if (isNoiseSuppressionOn(context) != sIsNoiseSuppressionEnabled) { 1833 turnOnNoiseSuppression(context, sIsNoiseSuppressionEnabled, false); 1834 } 1835 } 1836 isNoiseSuppressionOn(Context context)1837 static boolean isNoiseSuppressionOn(Context context) { 1838 1839 PersistableBundle b = PhoneGlobals.getInstance().getCarrierConfig(); 1840 if (!b.getBoolean(CarrierConfigManager.KEY_HAS_IN_CALL_NOISE_SUPPRESSION_BOOL)) { 1841 return false; 1842 } 1843 1844 AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 1845 String noiseSuppression = audioManager.getParameters("noise_suppression"); 1846 if (DBG) log("isNoiseSuppressionOn: " + noiseSuppression); 1847 if (noiseSuppression.contains("off")) { 1848 return false; 1849 } else { 1850 return true; 1851 } 1852 } 1853 isInEmergencyCall(CallManager cm)1854 static boolean isInEmergencyCall(CallManager cm) { 1855 Call fgCall = cm.getActiveFgCall(); 1856 // isIdle includes checks for the DISCONNECTING/DISCONNECTED state. 1857 if(!fgCall.isIdle()) { 1858 for (Connection cn : fgCall.getConnections()) { 1859 if (PhoneNumberUtils.isLocalEmergencyNumber(PhoneGlobals.getInstance(), 1860 cn.getAddress())) { 1861 return true; 1862 } 1863 } 1864 } 1865 return false; 1866 } 1867 1868 /** 1869 * Get the mute state of foreground phone, which has the current 1870 * foreground call 1871 */ getMute()1872 static boolean getMute() { 1873 return false; 1874 } 1875 setAudioMode()1876 /* package */ static void setAudioMode() { 1877 } 1878 1879 /** 1880 * Sets the audio mode per current phone state. 1881 */ setAudioMode(CallManager cm)1882 /* package */ static void setAudioMode(CallManager cm) { 1883 } 1884 1885 /** 1886 * Look for ANY connections on the phone that qualify as being 1887 * disconnected. 1888 * 1889 * @return true if we find a connection that is disconnected over 1890 * all the phone's call objects. 1891 */ hasDisconnectedConnections(Phone phone)1892 /* package */ static boolean hasDisconnectedConnections(Phone phone) { 1893 return hasDisconnectedConnections(phone.getForegroundCall()) || 1894 hasDisconnectedConnections(phone.getBackgroundCall()) || 1895 hasDisconnectedConnections(phone.getRingingCall()); 1896 } 1897 1898 /** 1899 * Iterate over all connections in a call to see if there are any 1900 * that are not alive (disconnected or idle). 1901 * 1902 * @return true if we find a connection that is disconnected, and 1903 * pending removal via 1904 * {@link com.android.internal.telephony.Call#clearDisconnected()}. 1905 */ hasDisconnectedConnections(Call call)1906 private static final boolean hasDisconnectedConnections(Call call) { 1907 // look through all connections for non-active ones. 1908 for (Connection c : call.getConnections()) { 1909 if (!c.isAlive()) { 1910 return true; 1911 } 1912 } 1913 return false; 1914 } 1915 1916 // 1917 // Misc UI policy helper functions 1918 // 1919 1920 /** 1921 * @return true if we're allowed to hold calls, given the current 1922 * state of the Phone. 1923 */ okToHoldCall(CallManager cm)1924 /* package */ static boolean okToHoldCall(CallManager cm) { 1925 final Call fgCall = cm.getActiveFgCall(); 1926 final boolean hasHoldingCall = cm.hasActiveBgCall(); 1927 final Call.State fgCallState = fgCall.getState(); 1928 1929 // The "Hold" control is disabled entirely if there's 1930 // no way to either hold or unhold in the current state. 1931 final boolean okToHold = (fgCallState == Call.State.ACTIVE) && !hasHoldingCall; 1932 final boolean okToUnhold = cm.hasActiveBgCall() && (fgCallState == Call.State.IDLE); 1933 final boolean canHold = okToHold || okToUnhold; 1934 1935 return canHold; 1936 } 1937 1938 /** 1939 * @return true if we support holding calls, given the current 1940 * state of the Phone. 1941 */ okToSupportHold(CallManager cm)1942 /* package */ static boolean okToSupportHold(CallManager cm) { 1943 boolean supportsHold = false; 1944 1945 final Call fgCall = cm.getActiveFgCall(); 1946 final boolean hasHoldingCall = cm.hasActiveBgCall(); 1947 final Call.State fgCallState = fgCall.getState(); 1948 1949 if (TelephonyCapabilities.supportsHoldAndUnhold(fgCall.getPhone())) { 1950 // This phone has the concept of explicit "Hold" and "Unhold" actions. 1951 supportsHold = true; 1952 } else if (hasHoldingCall && (fgCallState == Call.State.IDLE)) { 1953 // Even when foreground phone device doesn't support hold/unhold, phone devices 1954 // for background holding calls may do. 1955 final Call bgCall = cm.getFirstActiveBgCall(); 1956 if (bgCall != null && 1957 TelephonyCapabilities.supportsHoldAndUnhold(bgCall.getPhone())) { 1958 supportsHold = true; 1959 } 1960 } 1961 return supportsHold; 1962 } 1963 1964 /** 1965 * @return true if we're allowed to swap calls, given the current 1966 * state of the Phone. 1967 */ okToSwapCalls(CallManager cm)1968 /* package */ static boolean okToSwapCalls(CallManager cm) { 1969 int phoneType = cm.getDefaultPhone().getPhoneType(); 1970 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) { 1971 // CDMA: "Swap" is enabled only when the phone reaches a *generic*. 1972 // state by either accepting a Call Waiting or by merging two calls 1973 PhoneGlobals app = PhoneGlobals.getInstance(); 1974 return (app.cdmaPhoneCallState.getCurrentCallState() 1975 == CdmaPhoneCallState.PhoneCallState.CONF_CALL); 1976 } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM) 1977 || (phoneType == PhoneConstants.PHONE_TYPE_SIP) 1978 || (phoneType == PhoneConstants.PHONE_TYPE_IMS) 1979 || (phoneType == PhoneConstants.PHONE_TYPE_THIRD_PARTY)) { 1980 // GSM: "Swap" is available if both lines are in use and there's no 1981 // incoming call. (Actually we need to verify that the active 1982 // call really is in the ACTIVE state and the holding call really 1983 // is in the HOLDING state, since you *can't* actually swap calls 1984 // when the foreground call is DIALING or ALERTING.) 1985 return !cm.hasActiveRingingCall() 1986 && (cm.getActiveFgCall().getState() == Call.State.ACTIVE) 1987 && (cm.getFirstActiveBgCall().getState() == Call.State.HOLDING); 1988 } else { 1989 throw new IllegalStateException("Unexpected phone type: " + phoneType); 1990 } 1991 } 1992 1993 /** 1994 * @return true if we're allowed to merge calls, given the current 1995 * state of the Phone. 1996 */ okToMergeCalls(CallManager cm)1997 /* package */ static boolean okToMergeCalls(CallManager cm) { 1998 int phoneType = cm.getFgPhone().getPhoneType(); 1999 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) { 2000 // CDMA: "Merge" is enabled only when the user is in a 3Way call. 2001 PhoneGlobals app = PhoneGlobals.getInstance(); 2002 return ((app.cdmaPhoneCallState.getCurrentCallState() 2003 == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) 2004 && !app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing()); 2005 } else { 2006 // GSM: "Merge" is available if both lines are in use and there's no 2007 // incoming call, *and* the current conference isn't already 2008 // "full". 2009 // TODO: shall move all okToMerge logic to CallManager 2010 return !cm.hasActiveRingingCall() && cm.hasActiveFgCall() 2011 && cm.hasActiveBgCall() 2012 && cm.canConference(cm.getFirstActiveBgCall()); 2013 } 2014 } 2015 2016 /** 2017 * @return true if the UI should let you add a new call, given the current 2018 * state of the Phone. 2019 */ okToAddCall(CallManager cm)2020 /* package */ static boolean okToAddCall(CallManager cm) { 2021 Phone phone = cm.getActiveFgCall().getPhone(); 2022 2023 // "Add call" is never allowed in emergency callback mode (ECM). 2024 if (isPhoneInEcm(phone)) { 2025 return false; 2026 } 2027 2028 int phoneType = phone.getPhoneType(); 2029 final Call.State fgCallState = cm.getActiveFgCall().getState(); 2030 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) { 2031 // CDMA: "Add call" button is only enabled when: 2032 // - ForegroundCall is in ACTIVE state 2033 // - After 30 seconds of user Ignoring/Missing a Call Waiting call. 2034 PhoneGlobals app = PhoneGlobals.getInstance(); 2035 return ((fgCallState == Call.State.ACTIVE) 2036 && (app.cdmaPhoneCallState.getAddCallMenuStateAfterCallWaiting())); 2037 } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM) 2038 || (phoneType == PhoneConstants.PHONE_TYPE_SIP) 2039 || (phoneType == PhoneConstants.PHONE_TYPE_IMS) 2040 || (phoneType == PhoneConstants.PHONE_TYPE_THIRD_PARTY)) { 2041 // GSM: "Add call" is available only if ALL of the following are true: 2042 // - There's no incoming ringing call 2043 // - There's < 2 lines in use 2044 // - The foreground call is ACTIVE or IDLE or DISCONNECTED. 2045 // (We mainly need to make sure it *isn't* DIALING or ALERTING.) 2046 final boolean hasRingingCall = cm.hasActiveRingingCall(); 2047 final boolean hasActiveCall = cm.hasActiveFgCall(); 2048 final boolean hasHoldingCall = cm.hasActiveBgCall(); 2049 final boolean allLinesTaken = hasActiveCall && hasHoldingCall; 2050 2051 return !hasRingingCall 2052 && !allLinesTaken 2053 && ((fgCallState == Call.State.ACTIVE) 2054 || (fgCallState == Call.State.IDLE) 2055 || (fgCallState == Call.State.DISCONNECTED)); 2056 } else { 2057 throw new IllegalStateException("Unexpected phone type: " + phoneType); 2058 } 2059 } 2060 2061 /** 2062 * Based on the input CNAP number string, 2063 * @return _RESTRICTED or _UNKNOWN for all the special CNAP strings. 2064 * Otherwise, return CNAP_SPECIAL_CASE_NO. 2065 */ checkCnapSpecialCases(String n)2066 private static int checkCnapSpecialCases(String n) { 2067 if (n.equals("PRIVATE") || 2068 n.equals("P") || 2069 n.equals("RES")) { 2070 if (DBG) log("checkCnapSpecialCases, PRIVATE string: " + n); 2071 return PhoneConstants.PRESENTATION_RESTRICTED; 2072 } else if (n.equals("UNAVAILABLE") || 2073 n.equals("UNKNOWN") || 2074 n.equals("UNA") || 2075 n.equals("U")) { 2076 if (DBG) log("checkCnapSpecialCases, UNKNOWN string: " + n); 2077 return PhoneConstants.PRESENTATION_UNKNOWN; 2078 } else { 2079 if (DBG) log("checkCnapSpecialCases, normal str. number: " + n); 2080 return CNAP_SPECIAL_CASE_NO; 2081 } 2082 } 2083 2084 /** 2085 * Handles certain "corner cases" for CNAP. When we receive weird phone numbers 2086 * from the network to indicate different number presentations, convert them to 2087 * expected number and presentation values within the CallerInfo object. 2088 * @param number number we use to verify if we are in a corner case 2089 * @param presentation presentation value used to verify if we are in a corner case 2090 * @return the new String that should be used for the phone number 2091 */ modifyForSpecialCnapCases(Context context, CallerInfo ci, String number, int presentation)2092 /* package */ static String modifyForSpecialCnapCases(Context context, CallerInfo ci, 2093 String number, int presentation) { 2094 // Obviously we return number if ci == null, but still return number if 2095 // number == null, because in these cases the correct string will still be 2096 // displayed/logged after this function returns based on the presentation value. 2097 if (ci == null || number == null) return number; 2098 2099 if (DBG) { 2100 log("modifyForSpecialCnapCases: initially, number=" 2101 + toLogSafePhoneNumber(number) 2102 + ", presentation=" + presentation + " ci " + ci); 2103 } 2104 2105 // "ABSENT NUMBER" is a possible value we could get from the network as the 2106 // phone number, so if this happens, change it to "Unknown" in the CallerInfo 2107 // and fix the presentation to be the same. 2108 final String[] absentNumberValues = 2109 context.getResources().getStringArray(R.array.absent_num); 2110 if (Arrays.asList(absentNumberValues).contains(number) 2111 && presentation == PhoneConstants.PRESENTATION_ALLOWED) { 2112 number = context.getString(R.string.unknown); 2113 ci.numberPresentation = PhoneConstants.PRESENTATION_UNKNOWN; 2114 } 2115 2116 // Check for other special "corner cases" for CNAP and fix them similarly. Corner 2117 // cases only apply if we received an allowed presentation from the network, so check 2118 // if we think we have an allowed presentation, or if the CallerInfo presentation doesn't 2119 // match the presentation passed in for verification (meaning we changed it previously 2120 // because it's a corner case and we're being called from a different entry point). 2121 if (ci.numberPresentation == PhoneConstants.PRESENTATION_ALLOWED 2122 || (ci.numberPresentation != presentation 2123 && presentation == PhoneConstants.PRESENTATION_ALLOWED)) { 2124 int cnapSpecialCase = checkCnapSpecialCases(number); 2125 if (cnapSpecialCase != CNAP_SPECIAL_CASE_NO) { 2126 // For all special strings, change number & numberPresentation. 2127 if (cnapSpecialCase == PhoneConstants.PRESENTATION_RESTRICTED) { 2128 number = context.getString(R.string.private_num); 2129 } else if (cnapSpecialCase == PhoneConstants.PRESENTATION_UNKNOWN) { 2130 number = context.getString(R.string.unknown); 2131 } 2132 if (DBG) { 2133 log("SpecialCnap: number=" + toLogSafePhoneNumber(number) 2134 + "; presentation now=" + cnapSpecialCase); 2135 } 2136 ci.numberPresentation = cnapSpecialCase; 2137 } 2138 } 2139 if (DBG) { 2140 log("modifyForSpecialCnapCases: returning number string=" 2141 + toLogSafePhoneNumber(number)); 2142 } 2143 return number; 2144 } 2145 2146 // 2147 // Support for 3rd party phone service providers. 2148 // 2149 2150 /** 2151 * Check if a phone number can be route through a 3rd party 2152 * gateway. The number must be a global phone number in numerical 2153 * form (1-800-666-SEXY won't work). 2154 * 2155 * MMI codes and the like cannot be used as a dial number for the 2156 * gateway either. 2157 * 2158 * @param number To be dialed via a 3rd party gateway. 2159 * @return true If the number can be routed through the 3rd party network. 2160 */ isRoutableViaGateway(String number)2161 private static boolean isRoutableViaGateway(String number) { 2162 if (TextUtils.isEmpty(number)) { 2163 return false; 2164 } 2165 number = PhoneNumberUtils.stripSeparators(number); 2166 if (!number.equals(PhoneNumberUtils.convertKeypadLettersToDigits(number))) { 2167 return false; 2168 } 2169 number = PhoneNumberUtils.extractNetworkPortion(number); 2170 return PhoneNumberUtils.isGlobalPhoneNumber(number); 2171 } 2172 2173 /** 2174 * Returns whether the phone is in ECM ("Emergency Callback Mode") or not. 2175 */ isPhoneInEcm(Phone phone)2176 /* package */ static boolean isPhoneInEcm(Phone phone) { 2177 if ((phone != null) && TelephonyCapabilities.supportsEcm(phone)) { 2178 // For phones that support ECM, return true iff PROPERTY_INECM_MODE == "true". 2179 // TODO: There ought to be a better API for this than just 2180 // exposing a system property all the way up to the app layer, 2181 // probably a method like "inEcm()" provided by the telephony 2182 // layer. 2183 String ecmMode = 2184 SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE); 2185 if (ecmMode != null) { 2186 return ecmMode.equals("true"); 2187 } 2188 } 2189 return false; 2190 } 2191 2192 /** 2193 * Returns the most appropriate Phone object to handle a call 2194 * to the specified number. 2195 * 2196 * @param cm the CallManager. 2197 * @param scheme the scheme from the data URI that the number originally came from. 2198 * @param number the phone number, or SIP address. 2199 */ pickPhoneBasedOnNumber(CallManager cm, String scheme, String number, String primarySipUri, ComponentName thirdPartyCallComponent)2200 public static Phone pickPhoneBasedOnNumber(CallManager cm, String scheme, String number, 2201 String primarySipUri, ComponentName thirdPartyCallComponent) { 2202 if (DBG) { 2203 log("pickPhoneBasedOnNumber: scheme " + scheme 2204 + ", number " + toLogSafePhoneNumber(number) 2205 + ", sipUri " 2206 + (primarySipUri != null ? Uri.parse(primarySipUri).toSafeString() : "null") 2207 + ", thirdPartyCallComponent: " + thirdPartyCallComponent); 2208 } 2209 2210 if (primarySipUri != null) { 2211 Phone phone = getSipPhoneFromUri(cm, primarySipUri); 2212 if (phone != null) return phone; 2213 } 2214 2215 return cm.getDefaultPhone(); 2216 } 2217 getSipPhoneFromUri(CallManager cm, String target)2218 public static Phone getSipPhoneFromUri(CallManager cm, String target) { 2219 for (Phone phone : cm.getAllPhones()) { 2220 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_SIP) { 2221 String sipUri = ((SipPhone) phone).getSipUri(); 2222 if (target.equals(sipUri)) { 2223 if (DBG) log("- pickPhoneBasedOnNumber:" + 2224 "found SipPhone! obj = " + phone + ", " 2225 + phone.getClass()); 2226 return phone; 2227 } 2228 } 2229 } 2230 return null; 2231 } 2232 2233 /** 2234 * Returns true when the given call is in INCOMING state and there's no foreground phone call, 2235 * meaning the call is the first real incoming call the phone is having. 2236 */ isRealIncomingCall(Call.State state)2237 public static boolean isRealIncomingCall(Call.State state) { 2238 return (state == Call.State.INCOMING && !PhoneGlobals.getInstance().mCM.hasActiveFgCall()); 2239 } 2240 getPresentationString(Context context, int presentation)2241 public static String getPresentationString(Context context, int presentation) { 2242 String name = context.getString(R.string.unknown); 2243 if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) { 2244 name = context.getString(R.string.private_num); 2245 } else if (presentation == PhoneConstants.PRESENTATION_PAYPHONE) { 2246 name = context.getString(R.string.payphone); 2247 } 2248 return name; 2249 } 2250 sendViewNotificationAsync(Context context, Uri contactUri)2251 public static void sendViewNotificationAsync(Context context, Uri contactUri) { 2252 if (DBG) Log.d(LOG_TAG, "Send view notification to Contacts (uri: " + contactUri + ")"); 2253 Intent intent = new Intent("com.android.contacts.VIEW_NOTIFICATION", contactUri); 2254 intent.setClassName("com.android.contacts", 2255 "com.android.contacts.ViewNotificationService"); 2256 context.startService(intent); 2257 } 2258 2259 // 2260 // General phone and call state debugging/testing code 2261 // 2262 dumpCallState(Phone phone)2263 /* package */ static void dumpCallState(Phone phone) { 2264 PhoneGlobals app = PhoneGlobals.getInstance(); 2265 Log.d(LOG_TAG, "dumpCallState():"); 2266 Log.d(LOG_TAG, "- Phone: " + phone + ", name = " + phone.getPhoneName() 2267 + ", state = " + phone.getState()); 2268 2269 StringBuilder b = new StringBuilder(128); 2270 2271 Call call = phone.getForegroundCall(); 2272 b.setLength(0); 2273 b.append(" - FG call: ").append(call.getState()); 2274 b.append(" isAlive ").append(call.getState().isAlive()); 2275 b.append(" isRinging ").append(call.getState().isRinging()); 2276 b.append(" isDialing ").append(call.getState().isDialing()); 2277 b.append(" isIdle ").append(call.isIdle()); 2278 b.append(" hasConnections ").append(call.hasConnections()); 2279 Log.d(LOG_TAG, b.toString()); 2280 2281 call = phone.getBackgroundCall(); 2282 b.setLength(0); 2283 b.append(" - BG call: ").append(call.getState()); 2284 b.append(" isAlive ").append(call.getState().isAlive()); 2285 b.append(" isRinging ").append(call.getState().isRinging()); 2286 b.append(" isDialing ").append(call.getState().isDialing()); 2287 b.append(" isIdle ").append(call.isIdle()); 2288 b.append(" hasConnections ").append(call.hasConnections()); 2289 Log.d(LOG_TAG, b.toString()); 2290 2291 call = phone.getRingingCall(); 2292 b.setLength(0); 2293 b.append(" - RINGING call: ").append(call.getState()); 2294 b.append(" isAlive ").append(call.getState().isAlive()); 2295 b.append(" isRinging ").append(call.getState().isRinging()); 2296 b.append(" isDialing ").append(call.getState().isDialing()); 2297 b.append(" isIdle ").append(call.isIdle()); 2298 b.append(" hasConnections ").append(call.hasConnections()); 2299 Log.d(LOG_TAG, b.toString()); 2300 2301 2302 final boolean hasRingingCall = !phone.getRingingCall().isIdle(); 2303 final boolean hasActiveCall = !phone.getForegroundCall().isIdle(); 2304 final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle(); 2305 final boolean allLinesTaken = hasActiveCall && hasHoldingCall; 2306 b.setLength(0); 2307 b.append(" - hasRingingCall ").append(hasRingingCall); 2308 b.append(" hasActiveCall ").append(hasActiveCall); 2309 b.append(" hasHoldingCall ").append(hasHoldingCall); 2310 b.append(" allLinesTaken ").append(allLinesTaken); 2311 Log.d(LOG_TAG, b.toString()); 2312 2313 // On CDMA phones, dump out the CdmaPhoneCallState too: 2314 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) { 2315 if (app.cdmaPhoneCallState != null) { 2316 Log.d(LOG_TAG, " - CDMA call state: " 2317 + app.cdmaPhoneCallState.getCurrentCallState()); 2318 } else { 2319 Log.d(LOG_TAG, " - CDMA device, but null cdmaPhoneCallState!"); 2320 } 2321 } 2322 } 2323 log(String msg)2324 private static void log(String msg) { 2325 Log.d(LOG_TAG, msg); 2326 } 2327 dumpCallManager()2328 static void dumpCallManager() { 2329 Call call; 2330 CallManager cm = PhoneGlobals.getInstance().mCM; 2331 StringBuilder b = new StringBuilder(128); 2332 2333 2334 2335 Log.d(LOG_TAG, "############### dumpCallManager() ##############"); 2336 // TODO: Don't log "cm" itself, since CallManager.toString() 2337 // already spews out almost all this same information. 2338 // We should fix CallManager.toString() to be more minimal, and 2339 // use an explicit dumpState() method for the verbose dump. 2340 // Log.d(LOG_TAG, "CallManager: " + cm 2341 // + ", state = " + cm.getState()); 2342 Log.d(LOG_TAG, "CallManager: state = " + cm.getState()); 2343 b.setLength(0); 2344 call = cm.getActiveFgCall(); 2345 b.append(" - FG call: ").append(cm.hasActiveFgCall()? "YES ": "NO "); 2346 b.append(call); 2347 b.append( " State: ").append(cm.getActiveFgCallState()); 2348 b.append( " Conn: ").append(cm.getFgCallConnections()); 2349 Log.d(LOG_TAG, b.toString()); 2350 b.setLength(0); 2351 call = cm.getFirstActiveBgCall(); 2352 b.append(" - BG call: ").append(cm.hasActiveBgCall()? "YES ": "NO "); 2353 b.append(call); 2354 b.append( " State: ").append(cm.getFirstActiveBgCall().getState()); 2355 b.append( " Conn: ").append(cm.getBgCallConnections()); 2356 Log.d(LOG_TAG, b.toString()); 2357 b.setLength(0); 2358 call = cm.getFirstActiveRingingCall(); 2359 b.append(" - RINGING call: ").append(cm.hasActiveRingingCall()? "YES ": "NO "); 2360 b.append(call); 2361 b.append( " State: ").append(cm.getFirstActiveRingingCall().getState()); 2362 Log.d(LOG_TAG, b.toString()); 2363 2364 2365 2366 for (Phone phone : CallManager.getInstance().getAllPhones()) { 2367 if (phone != null) { 2368 Log.d(LOG_TAG, "Phone: " + phone + ", name = " + phone.getPhoneName() 2369 + ", state = " + phone.getState()); 2370 b.setLength(0); 2371 call = phone.getForegroundCall(); 2372 b.append(" - FG call: ").append(call); 2373 b.append( " State: ").append(call.getState()); 2374 b.append( " Conn: ").append(call.hasConnections()); 2375 Log.d(LOG_TAG, b.toString()); 2376 b.setLength(0); 2377 call = phone.getBackgroundCall(); 2378 b.append(" - BG call: ").append(call); 2379 b.append( " State: ").append(call.getState()); 2380 b.append( " Conn: ").append(call.hasConnections()); 2381 Log.d(LOG_TAG, b.toString());b.setLength(0); 2382 call = phone.getRingingCall(); 2383 b.append(" - RINGING call: ").append(call); 2384 b.append( " State: ").append(call.getState()); 2385 b.append( " Conn: ").append(call.hasConnections()); 2386 Log.d(LOG_TAG, b.toString()); 2387 } 2388 } 2389 2390 Log.d(LOG_TAG, "############## END dumpCallManager() ###############"); 2391 } 2392 2393 /** 2394 * @return if the context is in landscape orientation. 2395 */ isLandscape(Context context)2396 public static boolean isLandscape(Context context) { 2397 return context.getResources().getConfiguration().orientation 2398 == Configuration.ORIENTATION_LANDSCAPE; 2399 } 2400 makePstnPhoneAccountHandle(String id)2401 public static PhoneAccountHandle makePstnPhoneAccountHandle(String id) { 2402 return makePstnPhoneAccountHandleWithPrefix(id, "", false); 2403 } 2404 makePstnPhoneAccountHandle(int phoneId)2405 public static PhoneAccountHandle makePstnPhoneAccountHandle(int phoneId) { 2406 return makePstnPhoneAccountHandle(PhoneFactory.getPhone(phoneId)); 2407 } 2408 makePstnPhoneAccountHandle(Phone phone)2409 public static PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) { 2410 return makePstnPhoneAccountHandleWithPrefix(phone, "", false); 2411 } 2412 makePstnPhoneAccountHandleWithPrefix( Phone phone, String prefix, boolean isEmergency)2413 public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix( 2414 Phone phone, String prefix, boolean isEmergency) { 2415 // TODO: Should use some sort of special hidden flag to decorate this account as 2416 // an emergency-only account 2417 String id = isEmergency ? EMERGENCY_ACCOUNT_HANDLE_ID : prefix + 2418 String.valueOf(phone.getFullIccSerialNumber()); 2419 return makePstnPhoneAccountHandleWithPrefix(id, prefix, isEmergency); 2420 } 2421 makePstnPhoneAccountHandleWithPrefix( String id, String prefix, boolean isEmergency)2422 public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix( 2423 String id, String prefix, boolean isEmergency) { 2424 ComponentName pstnConnectionServiceName = getPstnConnectionServiceName(); 2425 return new PhoneAccountHandle(pstnConnectionServiceName, id); 2426 } 2427 getSubIdForPhoneAccount(PhoneAccount phoneAccount)2428 public static int getSubIdForPhoneAccount(PhoneAccount phoneAccount) { 2429 if (phoneAccount != null 2430 && phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { 2431 return getSubIdForPhoneAccountHandle(phoneAccount.getAccountHandle()); 2432 } 2433 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 2434 } 2435 getSubIdForPhoneAccountHandle(PhoneAccountHandle handle)2436 public static int getSubIdForPhoneAccountHandle(PhoneAccountHandle handle) { 2437 Phone phone = getPhoneForPhoneAccountHandle(handle); 2438 if (phone != null) { 2439 return phone.getSubId(); 2440 } 2441 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 2442 } 2443 getPhoneForPhoneAccountHandle(PhoneAccountHandle handle)2444 public static Phone getPhoneForPhoneAccountHandle(PhoneAccountHandle handle) { 2445 if (handle != null && handle.getComponentName().equals(getPstnConnectionServiceName())) { 2446 return getPhoneFromIccId(handle.getId()); 2447 } 2448 return null; 2449 } 2450 2451 2452 /** 2453 * Determine if a given phone account corresponds to an active SIM 2454 * 2455 * @param sm An instance of the subscription manager so it is not recreated for each calling of 2456 * this method. 2457 * @param handle The handle for the phone account to check 2458 * @return {@code true} If there is an active SIM for this phone account, 2459 * {@code false} otherwise. 2460 */ isPhoneAccountActive(SubscriptionManager sm, PhoneAccountHandle handle)2461 public static boolean isPhoneAccountActive(SubscriptionManager sm, PhoneAccountHandle handle) { 2462 return sm.getActiveSubscriptionInfoForIccIndex(handle.getId()) != null; 2463 } 2464 getPstnConnectionServiceName()2465 private static ComponentName getPstnConnectionServiceName() { 2466 return PSTN_CONNECTION_SERVICE_COMPONENT; 2467 } 2468 getPhoneFromIccId(String iccId)2469 private static Phone getPhoneFromIccId(String iccId) { 2470 if (!TextUtils.isEmpty(iccId)) { 2471 for (Phone phone : PhoneFactory.getPhones()) { 2472 String phoneIccId = phone.getFullIccSerialNumber(); 2473 if (iccId.equals(phoneIccId)) { 2474 return phone; 2475 } 2476 } 2477 } 2478 return null; 2479 } 2480 2481 /** 2482 * Register ICC status for all phones. 2483 */ registerIccStatus(Handler handler, int event)2484 static final void registerIccStatus(Handler handler, int event) { 2485 for (Phone phone : PhoneFactory.getPhones()) { 2486 IccCard sim = phone.getIccCard(); 2487 if (sim != null) { 2488 if (VDBG) Log.v(LOG_TAG, "register for ICC status, phone " + phone.getPhoneId()); 2489 sim.registerForNetworkLocked(handler, event, phone); 2490 } 2491 } 2492 } 2493 2494 /** 2495 * Set the radio power on/off state for all phones. 2496 * 2497 * @param enabled true means on, false means off. 2498 */ setRadioPower(boolean enabled)2499 static final void setRadioPower(boolean enabled) { 2500 for (Phone phone : PhoneFactory.getPhones()) { 2501 phone.setRadioPower(enabled); 2502 } 2503 } 2504 } 2505