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