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