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; 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 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 */ 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. */ 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 */ 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 */ 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 */ 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 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 354 static boolean hangupActiveCall(Call foreground) { 355 if (DBG) log("hangup active call"); 356 return hangup(foreground); 357 } 358 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 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 */ 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 728 static void mergeCalls() { 729 mergeCalls(PhoneGlobals.getInstance().mCM); 730 } 731 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 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 */ 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 */ 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 */ 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 { 1103 VoiceMailNumberMissingException() { 1104 super(); 1105 } 1106 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 1739 static boolean isSpeakerOn(Context context) { 1740 AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 1741 return audioManager.isSpeakerphoneOn(); 1742 } 1743 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 */ 1762 static boolean getMute() { 1763 return false; 1764 } 1765 1766 /* package */ static void setAudioMode() { 1767 } 1768 1769 /** 1770 * Sets the audio mode per current phone state. 1771 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 2118 public static boolean isRealIncomingCall(Call.State state) { 2119 return (state == Call.State.INCOMING && !PhoneGlobals.getInstance().mCM.hasActiveFgCall()); 2120 } 2121 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 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 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 2205 private static void log(String msg) { 2206 Log.d(LOG_TAG, msg); 2207 } 2208 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 */ 2277 public static boolean isLandscape(Context context) { 2278 return context.getResources().getConfiguration().orientation 2279 == Configuration.ORIENTATION_LANDSCAPE; 2280 } 2281 2282 public static PhoneAccountHandle makePstnPhoneAccountHandle(String id) { 2283 return makePstnPhoneAccountHandleWithPrefix(id, "", false); 2284 } 2285 2286 public static PhoneAccountHandle makePstnPhoneAccountHandle(int phoneId) { 2287 return makePstnPhoneAccountHandle(PhoneFactory.getPhone(phoneId)); 2288 } 2289 2290 public static PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) { 2291 return makePstnPhoneAccountHandleWithPrefix(phone, "", false); 2292 } 2293 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 2303 public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix( 2304 String id, String prefix, boolean isEmergency) { 2305 ComponentName pstnConnectionServiceName = getPstnConnectionServiceName(); 2306 return new PhoneAccountHandle(pstnConnectionServiceName, id); 2307 } 2308 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 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 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 */ 2342 public static boolean isPhoneAccountActive(SubscriptionManager sm, PhoneAccountHandle handle) { 2343 return sm.getActiveSubscriptionInfoForIccIndex(handle.getId()) != null; 2344 } 2345 2346 private static ComponentName getPstnConnectionServiceName() { 2347 return PSTN_CONNECTION_SERVICE_COMPONENT; 2348 } 2349 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 */ 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 */ 2380 static final void setRadioPower(boolean enabled) { 2381 for (Phone phone : PhoneFactory.getPhones()) { 2382 phone.setRadioPower(enabled); 2383 } 2384 } 2385 } 2386