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