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