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