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 com.android.internal.telephony.Call; 20 import com.android.internal.telephony.CallManager; 21 import com.android.internal.telephony.CallerInfo; 22 import com.android.internal.telephony.CallerInfoAsyncQuery; 23 import com.android.internal.telephony.Connection; 24 import com.android.internal.telephony.Phone; 25 import com.android.internal.telephony.PhoneConstants; 26 import com.android.internal.telephony.PhoneBase; 27 import com.android.internal.telephony.TelephonyCapabilities; 28 import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaDisplayInfoRec; 29 import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaSignalInfoRec; 30 import com.android.internal.telephony.cdma.SignalToneUtil; 31 32 import android.app.ActivityManagerNative; 33 import android.bluetooth.BluetoothAdapter; 34 import android.bluetooth.BluetoothHeadset; 35 import android.bluetooth.BluetoothProfile; 36 import android.content.Context; 37 import android.media.AudioAttributes; 38 import android.media.AudioManager; 39 import android.media.ToneGenerator; 40 import android.net.Uri; 41 import android.os.AsyncResult; 42 import android.os.Handler; 43 import android.os.Message; 44 import android.os.SystemProperties; 45 import android.os.SystemVibrator; 46 import android.os.Vibrator; 47 import android.provider.CallLog.Calls; 48 import android.provider.Settings; 49 import android.telephony.DisconnectCause; 50 import android.telephony.PhoneNumberUtils; 51 import android.telephony.PhoneStateListener; 52 import android.telephony.TelephonyManager; 53 import android.util.EventLog; 54 import android.util.Log; 55 56 /** 57 * Phone app module that listens for phone state changes and various other 58 * events from the telephony layer, and triggers any resulting UI behavior 59 * (like starting the Incoming Call UI, playing in-call tones, 60 * updating notifications, writing call log entries, etc.) 61 */ 62 public class CallNotifier extends Handler { 63 private static final String LOG_TAG = "CallNotifier"; 64 private static final boolean DBG = 65 (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1); 66 private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2); 67 68 // Time to display the DisplayInfo Record sent by CDMA network 69 private static final int DISPLAYINFO_NOTIFICATION_TIME = 2000; // msec 70 71 private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder() 72 .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) 73 .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) 74 .build(); 75 76 /** The singleton instance. */ 77 private static CallNotifier sInstance; 78 79 // values used to track the query state 80 private static final int CALLERINFO_QUERY_READY = 0; 81 private static final int CALLERINFO_QUERYING = -1; 82 83 // the state of the CallerInfo Query. 84 private int mCallerInfoQueryState; 85 86 // object used to synchronize access to mCallerInfoQueryState 87 private Object mCallerInfoQueryStateGuard = new Object(); 88 89 // Events generated internally: 90 private static final int PHONE_MWI_CHANGED = 21; 91 private static final int DISPLAYINFO_NOTIFICATION_DONE = 24; 92 private static final int UPDATE_IN_CALL_NOTIFICATION = 27; 93 94 95 private PhoneGlobals mApplication; 96 private CallManager mCM; 97 private BluetoothHeadset mBluetoothHeadset; 98 private CallLogger mCallLogger; 99 100 // ToneGenerator instance for playing SignalInfo tones 101 private ToneGenerator mSignalInfoToneGenerator; 102 103 // The tone volume relative to other sounds in the stream SignalInfo 104 private static final int TONE_RELATIVE_VOLUME_SIGNALINFO = 80; 105 106 private Call.State mPreviousCdmaCallState; 107 private boolean mVoicePrivacyState = false; 108 private boolean mIsCdmaRedialCall = false; 109 110 // Cached AudioManager 111 private AudioManager mAudioManager; 112 113 private final BluetoothManager mBluetoothManager; 114 115 /** 116 * Initialize the singleton CallNotifier instance. 117 * This is only done once, at startup, from PhoneApp.onCreate(). 118 */ init(PhoneGlobals app, Phone phone, CallLogger callLogger, CallStateMonitor callStateMonitor, BluetoothManager bluetoothManager)119 /* package */ static CallNotifier init(PhoneGlobals app, Phone phone, 120 CallLogger callLogger, CallStateMonitor callStateMonitor, 121 BluetoothManager bluetoothManager) { 122 synchronized (CallNotifier.class) { 123 if (sInstance == null) { 124 sInstance = new CallNotifier(app, phone, callLogger, callStateMonitor, 125 bluetoothManager); 126 } else { 127 Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance); 128 } 129 return sInstance; 130 } 131 } 132 133 /** Private constructor; @see init() */ CallNotifier(PhoneGlobals app, Phone phone, CallLogger callLogger, CallStateMonitor callStateMonitor, BluetoothManager bluetoothManager)134 private CallNotifier(PhoneGlobals app, Phone phone, CallLogger callLogger, 135 CallStateMonitor callStateMonitor, BluetoothManager bluetoothManager) { 136 mApplication = app; 137 mCM = app.mCM; 138 mCallLogger = callLogger; 139 mBluetoothManager = bluetoothManager; 140 141 mAudioManager = (AudioManager) mApplication.getSystemService(Context.AUDIO_SERVICE); 142 143 callStateMonitor.addListener(this); 144 145 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 146 if (adapter != null) { 147 adapter.getProfileProxy(mApplication.getApplicationContext(), 148 mBluetoothProfileServiceListener, 149 BluetoothProfile.HEADSET); 150 } 151 152 TelephonyManager telephonyManager = (TelephonyManager)app.getSystemService( 153 Context.TELEPHONY_SERVICE); 154 telephonyManager.listen(mPhoneStateListener, 155 PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR 156 | PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR); 157 } 158 createSignalInfoToneGenerator()159 private void createSignalInfoToneGenerator() { 160 // Instantiate the ToneGenerator for SignalInfo and CallWaiting 161 // TODO: We probably don't need the mSignalInfoToneGenerator instance 162 // around forever. Need to change it so as to create a ToneGenerator instance only 163 // when a tone is being played and releases it after its done playing. 164 if (mSignalInfoToneGenerator == null) { 165 try { 166 mSignalInfoToneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL, 167 TONE_RELATIVE_VOLUME_SIGNALINFO); 168 Log.d(LOG_TAG, "CallNotifier: mSignalInfoToneGenerator created when toneplay"); 169 } catch (RuntimeException e) { 170 Log.w(LOG_TAG, "CallNotifier: Exception caught while creating " + 171 "mSignalInfoToneGenerator: " + e); 172 mSignalInfoToneGenerator = null; 173 } 174 } else { 175 Log.d(LOG_TAG, "mSignalInfoToneGenerator created already, hence skipping"); 176 } 177 } 178 179 @Override handleMessage(Message msg)180 public void handleMessage(Message msg) { 181 switch (msg.what) { 182 case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION: 183 log("RINGING... (new)"); 184 onNewRingingConnection((AsyncResult) msg.obj); 185 break; 186 187 case CallStateMonitor.PHONE_STATE_CHANGED: 188 onPhoneStateChanged((AsyncResult) msg.obj); 189 break; 190 191 case CallStateMonitor.PHONE_DISCONNECT: 192 if (DBG) log("DISCONNECT"); 193 onDisconnect((AsyncResult) msg.obj); 194 break; 195 196 case CallStateMonitor.PHONE_UNKNOWN_CONNECTION_APPEARED: 197 onUnknownConnectionAppeared((AsyncResult) msg.obj); 198 break; 199 200 case PHONE_MWI_CHANGED: 201 onMwiChanged(mApplication.phone.getMessageWaitingIndicator()); 202 break; 203 204 case CallStateMonitor.PHONE_STATE_DISPLAYINFO: 205 if (DBG) log("Received PHONE_STATE_DISPLAYINFO event"); 206 onDisplayInfo((AsyncResult) msg.obj); 207 break; 208 209 case CallStateMonitor.PHONE_STATE_SIGNALINFO: 210 if (DBG) log("Received PHONE_STATE_SIGNALINFO event"); 211 onSignalInfo((AsyncResult) msg.obj); 212 break; 213 214 case DISPLAYINFO_NOTIFICATION_DONE: 215 if (DBG) log("Received Display Info notification done event ..."); 216 CdmaDisplayInfo.dismissDisplayInfoRecord(); 217 break; 218 219 case CallStateMonitor.EVENT_OTA_PROVISION_CHANGE: 220 if (DBG) log("EVENT_OTA_PROVISION_CHANGE..."); 221 mApplication.handleOtaspEvent(msg); 222 break; 223 224 case CallStateMonitor.PHONE_ENHANCED_VP_ON: 225 if (DBG) log("PHONE_ENHANCED_VP_ON..."); 226 if (!mVoicePrivacyState) { 227 int toneToPlay = InCallTonePlayer.TONE_VOICE_PRIVACY; 228 new InCallTonePlayer(toneToPlay).start(); 229 mVoicePrivacyState = true; 230 } 231 break; 232 233 case CallStateMonitor.PHONE_ENHANCED_VP_OFF: 234 if (DBG) log("PHONE_ENHANCED_VP_OFF..."); 235 if (mVoicePrivacyState) { 236 int toneToPlay = InCallTonePlayer.TONE_VOICE_PRIVACY; 237 new InCallTonePlayer(toneToPlay).start(); 238 mVoicePrivacyState = false; 239 } 240 break; 241 242 default: 243 // super.handleMessage(msg); 244 } 245 } 246 247 PhoneStateListener mPhoneStateListener = new PhoneStateListener() { 248 @Override 249 public void onMessageWaitingIndicatorChanged(boolean mwi) { 250 onMwiChanged(mwi); 251 } 252 253 @Override 254 public void onCallForwardingIndicatorChanged(boolean cfi) { 255 onCfiChanged(cfi); 256 } 257 }; 258 259 /** 260 * Handles a "new ringing connection" event from the telephony layer. 261 */ onNewRingingConnection(AsyncResult r)262 private void onNewRingingConnection(AsyncResult r) { 263 Connection c = (Connection) r.result; 264 log("onNewRingingConnection(): state = " + mCM.getState() + ", conn = { " + c + " }"); 265 Call ringing = c.getCall(); 266 Phone phone = ringing.getPhone(); 267 268 // Check for a few cases where we totally ignore incoming calls. 269 if (ignoreAllIncomingCalls(phone)) { 270 // Immediately reject the call, without even indicating to the user 271 // that an incoming call occurred. (This will generally send the 272 // caller straight to voicemail, just as if we *had* shown the 273 // incoming-call UI and the user had declined the call.) 274 PhoneUtils.hangupRingingCall(ringing); 275 return; 276 } 277 278 if (!c.isRinging()) { 279 Log.i(LOG_TAG, "CallNotifier.onNewRingingConnection(): connection not ringing!"); 280 // This is a very strange case: an incoming call that stopped 281 // ringing almost instantly after the onNewRingingConnection() 282 // event. There's nothing we can do here, so just bail out 283 // without doing anything. (But presumably we'll log it in 284 // the call log when the disconnect event comes in...) 285 return; 286 } 287 288 // Stop any signalInfo tone being played on receiving a Call 289 stopSignalInfoTone(); 290 291 Call.State state = c.getState(); 292 // State will be either INCOMING or WAITING. 293 if (VDBG) log("- connection is ringing! state = " + state); 294 // if (DBG) PhoneUtils.dumpCallState(mPhone); 295 296 // No need to do any service state checks here (like for 297 // "emergency mode"), since in those states the SIM won't let 298 // us get incoming connections in the first place. 299 300 // TODO: Consider sending out a serialized broadcast Intent here 301 // (maybe "ACTION_NEW_INCOMING_CALL"), *before* starting the 302 // ringer and going to the in-call UI. The intent should contain 303 // the caller-id info for the current connection, and say whether 304 // it would be a "call waiting" call or a regular ringing call. 305 // If anybody consumed the broadcast, we'd bail out without 306 // ringing or bringing up the in-call UI. 307 // 308 // This would give 3rd party apps a chance to listen for (and 309 // intercept) new ringing connections. An app could reject the 310 // incoming call by consuming the broadcast and doing nothing, or 311 // it could "pick up" the call (without any action by the user!) 312 // via some future TelephonyManager API. 313 // 314 // See bug 1312336 for more details. 315 // We'd need to protect this with a new "intercept incoming calls" 316 // system permission. 317 318 // Obtain a partial wake lock to make sure the CPU doesn't go to 319 // sleep before we finish bringing up the InCallScreen. 320 // (This will be upgraded soon to a full wake lock; see 321 // showIncomingCall().) 322 if (VDBG) log("Holding wake lock on new incoming connection."); 323 mApplication.requestWakeState(PhoneGlobals.WakeState.PARTIAL); 324 325 // Note we *don't* post a status bar notification here, since 326 // we're not necessarily ready to actually show the incoming call 327 // to the user. (For calls in the INCOMING state, at least, we 328 // still need to run a caller-id query, and we may not even ring 329 // at all if the "send directly to voicemail" flag is set.) 330 // 331 // Instead, we update the notification (and potentially launch the 332 // InCallScreen) from the showIncomingCall() method, which runs 333 // when the caller-id query completes or times out. 334 335 if (VDBG) log("- onNewRingingConnection() done."); 336 } 337 338 /** 339 * Determines whether or not we're allowed to present incoming calls to the 340 * user, based on the capabilities and/or current state of the device. 341 * 342 * If this method returns true, that means we should immediately reject the 343 * current incoming call, without even indicating to the user that an 344 * incoming call occurred. 345 * 346 * (We only reject incoming calls in a few cases, like during an OTASP call 347 * when we can't interrupt the user, or if the device hasn't completed the 348 * SetupWizard yet. We also don't allow incoming calls on non-voice-capable 349 * devices. But note that we *always* allow incoming calls while in ECM.) 350 * 351 * @return true if we're *not* allowed to present an incoming call to 352 * the user. 353 */ ignoreAllIncomingCalls(Phone phone)354 private boolean ignoreAllIncomingCalls(Phone phone) { 355 // Incoming calls are totally ignored on non-voice-capable devices. 356 if (!PhoneGlobals.sVoiceCapable) { 357 // ...but still log a warning, since we shouldn't have gotten this 358 // event in the first place! (Incoming calls *should* be blocked at 359 // the telephony layer on non-voice-capable capable devices.) 360 Log.w(LOG_TAG, "Got onNewRingingConnection() on non-voice-capable device! Ignoring..."); 361 return true; 362 } 363 364 // In ECM (emergency callback mode), we ALWAYS allow incoming calls 365 // to get through to the user. (Note that ECM is applicable only to 366 // voice-capable CDMA devices). 367 if (PhoneUtils.isPhoneInEcm(phone)) { 368 if (DBG) log("Incoming call while in ECM: always allow..."); 369 return false; 370 } 371 372 // Incoming calls are totally ignored if the device isn't provisioned yet. 373 boolean provisioned = Settings.Global.getInt(mApplication.getContentResolver(), 374 Settings.Global.DEVICE_PROVISIONED, 0) != 0; 375 if (!provisioned) { 376 Log.i(LOG_TAG, "Ignoring incoming call: not provisioned"); 377 return true; 378 } 379 380 // Incoming calls are totally ignored if an OTASP call is active. 381 if (TelephonyCapabilities.supportsOtasp(phone)) { 382 boolean activateState = (mApplication.cdmaOtaScreenState.otaScreenState 383 == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION); 384 boolean dialogState = (mApplication.cdmaOtaScreenState.otaScreenState 385 == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_SUCCESS_FAILURE_DLG); 386 boolean spcState = mApplication.cdmaOtaProvisionData.inOtaSpcState; 387 388 if (spcState) { 389 Log.i(LOG_TAG, "Ignoring incoming call: OTA call is active"); 390 return true; 391 } else if (activateState || dialogState) { 392 // We *are* allowed to receive incoming calls at this point. 393 // But clear out any residual OTASP UI first. 394 // TODO: It's an MVC violation to twiddle the OTA UI state here; 395 // we should instead provide a higher-level API via OtaUtils. 396 if (dialogState) mApplication.dismissOtaDialogs(); 397 mApplication.clearOtaState(); 398 return false; 399 } 400 } 401 402 // Normal case: allow this call to be presented to the user. 403 return false; 404 } 405 onUnknownConnectionAppeared(AsyncResult r)406 private void onUnknownConnectionAppeared(AsyncResult r) { 407 PhoneConstants.State state = mCM.getState(); 408 409 if (state == PhoneConstants.State.OFFHOOK) { 410 if (DBG) log("unknown connection appeared..."); 411 412 onPhoneStateChanged(r); 413 } 414 } 415 416 /** 417 * Updates the phone UI in response to phone state changes. 418 * 419 * Watch out: certain state changes are actually handled by their own 420 * specific methods: 421 * - see onNewRingingConnection() for new incoming calls 422 * - see onDisconnect() for calls being hung up or disconnected 423 */ onPhoneStateChanged(AsyncResult r)424 private void onPhoneStateChanged(AsyncResult r) { 425 PhoneConstants.State state = mCM.getState(); 426 if (VDBG) log("onPhoneStateChanged: state = " + state); 427 428 // Turn status bar notifications on or off depending upon the state 429 // of the phone. Notification Alerts (audible or vibrating) should 430 // be on if and only if the phone is IDLE. 431 mApplication.notificationMgr.statusBarHelper 432 .enableNotificationAlerts(state == PhoneConstants.State.IDLE); 433 434 Phone fgPhone = mCM.getFgPhone(); 435 if (fgPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) { 436 if ((fgPhone.getForegroundCall().getState() == Call.State.ACTIVE) 437 && ((mPreviousCdmaCallState == Call.State.DIALING) 438 || (mPreviousCdmaCallState == Call.State.ALERTING))) { 439 if (mIsCdmaRedialCall) { 440 int toneToPlay = InCallTonePlayer.TONE_REDIAL; 441 new InCallTonePlayer(toneToPlay).start(); 442 } 443 // Stop any signal info tone when call moves to ACTIVE state 444 stopSignalInfoTone(); 445 } 446 mPreviousCdmaCallState = fgPhone.getForegroundCall().getState(); 447 } 448 449 // Have the PhoneApp recompute its mShowBluetoothIndication 450 // flag based on the (new) telephony state. 451 // There's no need to force a UI update since we update the 452 // in-call notification ourselves (below), and the InCallScreen 453 // listens for phone state changes itself. 454 mBluetoothManager.updateBluetoothIndication(); 455 456 // Update the phone state and other sensor/lock. 457 mApplication.updatePhoneState(state); 458 459 if (state == PhoneConstants.State.OFFHOOK) { 460 461 if (VDBG) log("onPhoneStateChanged: OFF HOOK"); 462 // make sure audio is in in-call mode now 463 PhoneUtils.setAudioMode(mCM); 464 } 465 } 466 updateCallNotifierRegistrationsAfterRadioTechnologyChange()467 void updateCallNotifierRegistrationsAfterRadioTechnologyChange() { 468 if (DBG) Log.d(LOG_TAG, "updateCallNotifierRegistrationsAfterRadioTechnologyChange..."); 469 470 // Instantiate mSignalInfoToneGenerator 471 createSignalInfoToneGenerator(); 472 } 473 onDisconnect(AsyncResult r)474 private void onDisconnect(AsyncResult r) { 475 if (VDBG) log("onDisconnect()... CallManager state: " + mCM.getState()); 476 477 mVoicePrivacyState = false; 478 Connection c = (Connection) r.result; 479 if (c != null) { 480 log("onDisconnect: cause = " + DisconnectCause.toString(c.getDisconnectCause()) 481 + ", incoming = " + c.isIncoming() 482 + ", date = " + c.getCreateTime()); 483 } else { 484 Log.w(LOG_TAG, "onDisconnect: null connection"); 485 } 486 487 int autoretrySetting = 0; 488 if ((c != null) && (c.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA)) { 489 autoretrySetting = android.provider.Settings.Global.getInt(mApplication. 490 getContentResolver(),android.provider.Settings.Global.CALL_AUTO_RETRY, 0); 491 } 492 493 // Stop any signalInfo tone being played when a call gets ended 494 stopSignalInfoTone(); 495 496 if ((c != null) && (c.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA)) { 497 // Resetting the CdmaPhoneCallState members 498 mApplication.cdmaPhoneCallState.resetCdmaPhoneCallState(); 499 } 500 501 // If this is the end of an OTASP call, pass it on to the PhoneApp. 502 if (c != null && TelephonyCapabilities.supportsOtasp(c.getCall().getPhone())) { 503 final String number = c.getAddress(); 504 if (c.getCall().getPhone().isOtaSpNumber(number)) { 505 if (DBG) log("onDisconnect: this was an OTASP call!"); 506 mApplication.handleOtaspDisconnect(); 507 } 508 } 509 510 // Check for the various tones we might need to play (thru the 511 // earpiece) after a call disconnects. 512 int toneToPlay = InCallTonePlayer.TONE_NONE; 513 514 // If we don't need to play BUSY or CONGESTION, then play the 515 // "call ended" tone if this was a "regular disconnect" (i.e. a 516 // normal call where one end or the other hung up) *and* this 517 // disconnect event caused the phone to become idle. (In other 518 // words, we *don't* play the sound if one call hangs up but 519 // there's still an active call on the other line.) 520 // TODO: We may eventually want to disable this via a preference. 521 if ((toneToPlay == InCallTonePlayer.TONE_NONE) 522 && (mCM.getState() == PhoneConstants.State.IDLE) 523 && (c != null)) { 524 int cause = c.getDisconnectCause(); 525 if ((cause == DisconnectCause.NORMAL) // remote hangup 526 || (cause == DisconnectCause.LOCAL)) { // local hangup 527 if (VDBG) log("- need to play CALL_ENDED tone!"); 528 toneToPlay = InCallTonePlayer.TONE_CALL_ENDED; 529 mIsCdmaRedialCall = false; 530 } 531 } 532 533 // All phone calls are disconnected. 534 if (mCM.getState() == PhoneConstants.State.IDLE) { 535 // Don't reset the audio mode or bluetooth/speakerphone state 536 // if we still need to let the user hear a tone through the earpiece. 537 if (toneToPlay == InCallTonePlayer.TONE_NONE) { 538 resetAudioStateAfterDisconnect(); 539 } 540 } 541 542 if (c != null) { 543 mCallLogger.logCall(c); 544 545 final String number = c.getAddress(); 546 final Phone phone = c.getCall().getPhone(); 547 final boolean isEmergencyNumber = 548 PhoneNumberUtils.isLocalEmergencyNumber(mApplication, number); 549 550 // Possibly play a "post-disconnect tone" thru the earpiece. 551 // We do this here, rather than from the InCallScreen 552 // activity, since we need to do this even if you're not in 553 // the Phone UI at the moment the connection ends. 554 if (toneToPlay != InCallTonePlayer.TONE_NONE) { 555 if (VDBG) log("- starting post-disconnect tone (" + toneToPlay + ")..."); 556 new InCallTonePlayer(toneToPlay).start(); 557 558 // TODO: alternatively, we could start an InCallTonePlayer 559 // here with an "unlimited" tone length, 560 // and manually stop it later when this connection truly goes 561 // away. (The real connection over the network was closed as soon 562 // as we got the BUSY message. But our telephony layer keeps the 563 // connection open for a few extra seconds so we can show the 564 // "busy" indication to the user. We could stop the busy tone 565 // when *that* connection's "disconnect" event comes in.) 566 } 567 568 final int cause = c.getDisconnectCause(); 569 if (((mPreviousCdmaCallState == Call.State.DIALING) 570 || (mPreviousCdmaCallState == Call.State.ALERTING)) 571 && (!isEmergencyNumber) 572 && (cause != DisconnectCause.INCOMING_MISSED ) 573 && (cause != DisconnectCause.NORMAL) 574 && (cause != DisconnectCause.LOCAL) 575 && (cause != DisconnectCause.INCOMING_REJECTED)) { 576 if (!mIsCdmaRedialCall) { 577 if (autoretrySetting == InCallScreen.AUTO_RETRY_ON) { 578 // TODO: (Moto): The contact reference data may need to be stored and use 579 // here when redialing a call. For now, pass in NULL as the URI parameter. 580 final int status = 581 PhoneUtils.placeCall(mApplication, phone, number, null, false); 582 if (status != PhoneUtils.CALL_STATUS_FAILED) { 583 mIsCdmaRedialCall = true; 584 } 585 } else { 586 mIsCdmaRedialCall = false; 587 } 588 } else { 589 mIsCdmaRedialCall = false; 590 } 591 } 592 } 593 } 594 595 /** 596 * Resets the audio mode and speaker state when a call ends. 597 */ resetAudioStateAfterDisconnect()598 private void resetAudioStateAfterDisconnect() { 599 if (VDBG) log("resetAudioStateAfterDisconnect()..."); 600 601 if (mBluetoothHeadset != null) { 602 mBluetoothHeadset.disconnectAudio(); 603 } 604 605 // call turnOnSpeaker() with state=false and store=true even if speaker 606 // is already off to reset user requested speaker state. 607 PhoneUtils.turnOnSpeaker(mApplication, false, true); 608 609 PhoneUtils.setAudioMode(mCM); 610 } 611 onMwiChanged(boolean visible)612 private void onMwiChanged(boolean visible) { 613 if (VDBG) log("onMwiChanged(): " + visible); 614 615 // "Voicemail" is meaningless on non-voice-capable devices, 616 // so ignore MWI events. 617 if (!PhoneGlobals.sVoiceCapable) { 618 // ...but still log a warning, since we shouldn't have gotten this 619 // event in the first place! 620 // (PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR events 621 // *should* be blocked at the telephony layer on non-voice-capable 622 // capable devices.) 623 Log.w(LOG_TAG, "Got onMwiChanged() on non-voice-capable device! Ignoring..."); 624 return; 625 } 626 627 mApplication.notificationMgr.updateMwi(visible); 628 } 629 630 /** 631 * Posts a delayed PHONE_MWI_CHANGED event, to schedule a "retry" for a 632 * failed NotificationMgr.updateMwi() call. 633 */ sendMwiChangedDelayed(long delayMillis)634 /* package */ void sendMwiChangedDelayed(long delayMillis) { 635 Message message = Message.obtain(this, PHONE_MWI_CHANGED); 636 sendMessageDelayed(message, delayMillis); 637 } 638 onCfiChanged(boolean visible)639 private void onCfiChanged(boolean visible) { 640 if (VDBG) log("onCfiChanged(): " + visible); 641 mApplication.notificationMgr.updateCfi(visible); 642 } 643 644 /** 645 * Helper class to play tones through the earpiece (or speaker / BT) 646 * during a call, using the ToneGenerator. 647 * 648 * To use, just instantiate a new InCallTonePlayer 649 * (passing in the TONE_* constant for the tone you want) 650 * and start() it. 651 * 652 * When we're done playing the tone, if the phone is idle at that 653 * point, we'll reset the audio routing and speaker state. 654 * (That means that for tones that get played *after* a call 655 * disconnects, like "busy" or "congestion" or "call ended", you 656 * should NOT call resetAudioStateAfterDisconnect() yourself. 657 * Instead, just start the InCallTonePlayer, which will automatically 658 * defer the resetAudioStateAfterDisconnect() call until the tone 659 * finishes playing.) 660 */ 661 private class InCallTonePlayer extends Thread { 662 private int mToneId; 663 private int mState; 664 // The possible tones we can play. 665 public static final int TONE_NONE = 0; 666 public static final int TONE_CALL_WAITING = 1; 667 public static final int TONE_BUSY = 2; 668 public static final int TONE_CONGESTION = 3; 669 public static final int TONE_CALL_ENDED = 4; 670 public static final int TONE_VOICE_PRIVACY = 5; 671 public static final int TONE_REORDER = 6; 672 public static final int TONE_INTERCEPT = 7; 673 public static final int TONE_CDMA_DROP = 8; 674 public static final int TONE_OUT_OF_SERVICE = 9; 675 public static final int TONE_REDIAL = 10; 676 public static final int TONE_OTA_CALL_END = 11; 677 public static final int TONE_UNOBTAINABLE_NUMBER = 13; 678 679 // The tone volume relative to other sounds in the stream 680 static final int TONE_RELATIVE_VOLUME_EMERGENCY = 100; 681 static final int TONE_RELATIVE_VOLUME_HIPRI = 80; 682 static final int TONE_RELATIVE_VOLUME_LOPRI = 50; 683 684 // Buffer time (in msec) to add on to tone timeout value. 685 // Needed mainly when the timeout value for a tone is the 686 // exact duration of the tone itself. 687 static final int TONE_TIMEOUT_BUFFER = 20; 688 689 // The tone state 690 static final int TONE_OFF = 0; 691 static final int TONE_ON = 1; 692 static final int TONE_STOPPED = 2; 693 InCallTonePlayer(int toneId)694 InCallTonePlayer(int toneId) { 695 super(); 696 mToneId = toneId; 697 mState = TONE_OFF; 698 } 699 700 @Override run()701 public void run() { 702 log("InCallTonePlayer.run(toneId = " + mToneId + ")..."); 703 704 int toneType = 0; // passed to ToneGenerator.startTone() 705 int toneVolume; // passed to the ToneGenerator constructor 706 int toneLengthMillis; 707 int phoneType = mCM.getFgPhone().getPhoneType(); 708 709 switch (mToneId) { 710 case TONE_CALL_WAITING: 711 toneType = ToneGenerator.TONE_SUP_CALL_WAITING; 712 toneVolume = TONE_RELATIVE_VOLUME_HIPRI; 713 // Call waiting tone is stopped by stopTone() method 714 toneLengthMillis = Integer.MAX_VALUE - TONE_TIMEOUT_BUFFER; 715 break; 716 case TONE_BUSY: 717 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) { 718 toneType = ToneGenerator.TONE_CDMA_NETWORK_BUSY_ONE_SHOT; 719 toneVolume = TONE_RELATIVE_VOLUME_LOPRI; 720 toneLengthMillis = 1000; 721 } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM 722 || phoneType == PhoneConstants.PHONE_TYPE_SIP 723 || phoneType == PhoneConstants.PHONE_TYPE_IMS 724 || phoneType == PhoneConstants.PHONE_TYPE_THIRD_PARTY) { 725 toneType = ToneGenerator.TONE_SUP_BUSY; 726 toneVolume = TONE_RELATIVE_VOLUME_HIPRI; 727 toneLengthMillis = 4000; 728 } else { 729 throw new IllegalStateException("Unexpected phone type: " + phoneType); 730 } 731 break; 732 case TONE_CONGESTION: 733 toneType = ToneGenerator.TONE_SUP_CONGESTION; 734 toneVolume = TONE_RELATIVE_VOLUME_HIPRI; 735 toneLengthMillis = 4000; 736 break; 737 738 case TONE_CALL_ENDED: 739 toneType = ToneGenerator.TONE_PROP_PROMPT; 740 toneVolume = TONE_RELATIVE_VOLUME_HIPRI; 741 toneLengthMillis = 200; 742 break; 743 case TONE_OTA_CALL_END: 744 if (mApplication.cdmaOtaConfigData.otaPlaySuccessFailureTone == 745 OtaUtils.OTA_PLAY_SUCCESS_FAILURE_TONE_ON) { 746 toneType = ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD; 747 toneVolume = TONE_RELATIVE_VOLUME_HIPRI; 748 toneLengthMillis = 750; 749 } else { 750 toneType = ToneGenerator.TONE_PROP_PROMPT; 751 toneVolume = TONE_RELATIVE_VOLUME_HIPRI; 752 toneLengthMillis = 200; 753 } 754 break; 755 case TONE_VOICE_PRIVACY: 756 toneType = ToneGenerator.TONE_CDMA_ALERT_NETWORK_LITE; 757 toneVolume = TONE_RELATIVE_VOLUME_HIPRI; 758 toneLengthMillis = 5000; 759 break; 760 case TONE_REORDER: 761 toneType = ToneGenerator.TONE_CDMA_REORDER; 762 toneVolume = TONE_RELATIVE_VOLUME_HIPRI; 763 toneLengthMillis = 4000; 764 break; 765 case TONE_INTERCEPT: 766 toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT; 767 toneVolume = TONE_RELATIVE_VOLUME_LOPRI; 768 toneLengthMillis = 500; 769 break; 770 case TONE_CDMA_DROP: 771 case TONE_OUT_OF_SERVICE: 772 toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE; 773 toneVolume = TONE_RELATIVE_VOLUME_LOPRI; 774 toneLengthMillis = 375; 775 break; 776 case TONE_REDIAL: 777 toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE; 778 toneVolume = TONE_RELATIVE_VOLUME_LOPRI; 779 toneLengthMillis = 5000; 780 break; 781 case TONE_UNOBTAINABLE_NUMBER: 782 toneType = ToneGenerator.TONE_SUP_ERROR; 783 toneVolume = TONE_RELATIVE_VOLUME_HIPRI; 784 toneLengthMillis = 4000; 785 break; 786 default: 787 throw new IllegalArgumentException("Bad toneId: " + mToneId); 788 } 789 790 // If the mToneGenerator creation fails, just continue without it. It is 791 // a local audio signal, and is not as important. 792 ToneGenerator toneGenerator; 793 try { 794 int stream; 795 if (mBluetoothHeadset != null) { 796 stream = mBluetoothHeadset.isAudioOn() ? AudioManager.STREAM_BLUETOOTH_SCO: 797 AudioManager.STREAM_VOICE_CALL; 798 } else { 799 stream = AudioManager.STREAM_VOICE_CALL; 800 } 801 toneGenerator = new ToneGenerator(stream, toneVolume); 802 // if (DBG) log("- created toneGenerator: " + toneGenerator); 803 } catch (RuntimeException e) { 804 Log.w(LOG_TAG, 805 "InCallTonePlayer: Exception caught while creating ToneGenerator: " + e); 806 toneGenerator = null; 807 } 808 809 // Using the ToneGenerator (with the CALL_WAITING / BUSY / 810 // CONGESTION tones at least), the ToneGenerator itself knows 811 // the right pattern of tones to play; we do NOT need to 812 // manually start/stop each individual tone, or manually 813 // insert the correct delay between tones. (We just start it 814 // and let it run for however long we want the tone pattern to 815 // continue.) 816 // 817 // TODO: When we stop the ToneGenerator in the middle of a 818 // "tone pattern", it sounds bad if we cut if off while the 819 // tone is actually playing. Consider adding API to the 820 // ToneGenerator to say "stop at the next silent part of the 821 // pattern", or simply "play the pattern N times and then 822 // stop." 823 boolean needToStopTone = true; 824 boolean okToPlayTone = false; 825 826 if (toneGenerator != null) { 827 int ringerMode = mAudioManager.getRingerMode(); 828 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) { 829 if (toneType == ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD) { 830 if ((ringerMode != AudioManager.RINGER_MODE_SILENT) && 831 (ringerMode != AudioManager.RINGER_MODE_VIBRATE)) { 832 if (DBG) log("- InCallTonePlayer: start playing call tone=" + toneType); 833 okToPlayTone = true; 834 needToStopTone = false; 835 } 836 } else if ((toneType == ToneGenerator.TONE_CDMA_NETWORK_BUSY_ONE_SHOT) || 837 (toneType == ToneGenerator.TONE_CDMA_REORDER) || 838 (toneType == ToneGenerator.TONE_CDMA_ABBR_REORDER) || 839 (toneType == ToneGenerator.TONE_CDMA_ABBR_INTERCEPT) || 840 (toneType == ToneGenerator.TONE_CDMA_CALLDROP_LITE)) { 841 if (ringerMode != AudioManager.RINGER_MODE_SILENT) { 842 if (DBG) log("InCallTonePlayer:playing call fail tone:" + toneType); 843 okToPlayTone = true; 844 needToStopTone = false; 845 } 846 } else if ((toneType == ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE) || 847 (toneType == ToneGenerator.TONE_CDMA_ALERT_NETWORK_LITE)) { 848 if ((ringerMode != AudioManager.RINGER_MODE_SILENT) && 849 (ringerMode != AudioManager.RINGER_MODE_VIBRATE)) { 850 if (DBG) log("InCallTonePlayer:playing tone for toneType=" + toneType); 851 okToPlayTone = true; 852 needToStopTone = false; 853 } 854 } else { // For the rest of the tones, always OK to play. 855 okToPlayTone = true; 856 } 857 } else { // Not "CDMA" 858 okToPlayTone = true; 859 } 860 861 synchronized (this) { 862 if (okToPlayTone && mState != TONE_STOPPED) { 863 mState = TONE_ON; 864 toneGenerator.startTone(toneType); 865 try { 866 wait(toneLengthMillis + TONE_TIMEOUT_BUFFER); 867 } catch (InterruptedException e) { 868 Log.w(LOG_TAG, 869 "InCallTonePlayer stopped: " + e); 870 } 871 if (needToStopTone) { 872 toneGenerator.stopTone(); 873 } 874 } 875 // if (DBG) log("- InCallTonePlayer: done playing."); 876 toneGenerator.release(); 877 mState = TONE_OFF; 878 } 879 } 880 881 // Finally, do the same cleanup we otherwise would have done 882 // in onDisconnect(). 883 // 884 // (But watch out: do NOT do this if the phone is in use, 885 // since some of our tones get played *during* a call (like 886 // CALL_WAITING) and we definitely *don't* 887 // want to reset the audio mode / speaker / bluetooth after 888 // playing those! 889 // This call is really here for use with tones that get played 890 // *after* a call disconnects, like "busy" or "congestion" or 891 // "call ended", where the phone has already become idle but 892 // we need to defer the resetAudioStateAfterDisconnect() call 893 // till the tone finishes playing.) 894 if (mCM.getState() == PhoneConstants.State.IDLE) { 895 resetAudioStateAfterDisconnect(); 896 } 897 } 898 stopTone()899 public void stopTone() { 900 synchronized (this) { 901 if (mState == TONE_ON) { 902 notify(); 903 } 904 mState = TONE_STOPPED; 905 } 906 } 907 } 908 909 /** 910 * Displays a notification when the phone receives a DisplayInfo record. 911 */ onDisplayInfo(AsyncResult r)912 private void onDisplayInfo(AsyncResult r) { 913 // Extract the DisplayInfo String from the message 914 CdmaDisplayInfoRec displayInfoRec = (CdmaDisplayInfoRec)(r.result); 915 916 if (displayInfoRec != null) { 917 String displayInfo = displayInfoRec.alpha; 918 if (DBG) log("onDisplayInfo: displayInfo=" + displayInfo); 919 CdmaDisplayInfo.displayInfoRecord(mApplication, displayInfo); 920 921 // start a 2 second timer 922 sendEmptyMessageDelayed(DISPLAYINFO_NOTIFICATION_DONE, 923 DISPLAYINFO_NOTIFICATION_TIME); 924 } 925 } 926 927 /** 928 * Helper class to play SignalInfo tones using the ToneGenerator. 929 * 930 * To use, just instantiate a new SignalInfoTonePlayer 931 * (passing in the ToneID constant for the tone you want) 932 * and start() it. 933 */ 934 private class SignalInfoTonePlayer extends Thread { 935 private int mToneId; 936 SignalInfoTonePlayer(int toneId)937 SignalInfoTonePlayer(int toneId) { 938 super(); 939 mToneId = toneId; 940 } 941 942 @Override run()943 public void run() { 944 log("SignalInfoTonePlayer.run(toneId = " + mToneId + ")..."); 945 createSignalInfoToneGenerator(); 946 if (mSignalInfoToneGenerator != null) { 947 //First stop any ongoing SignalInfo tone 948 mSignalInfoToneGenerator.stopTone(); 949 950 //Start playing the new tone if its a valid tone 951 mSignalInfoToneGenerator.startTone(mToneId); 952 } 953 } 954 } 955 956 /** 957 * Plays a tone when the phone receives a SignalInfo record. 958 */ onSignalInfo(AsyncResult r)959 private void onSignalInfo(AsyncResult r) { 960 // Signal Info are totally ignored on non-voice-capable devices. 961 if (!PhoneGlobals.sVoiceCapable) { 962 Log.w(LOG_TAG, "Got onSignalInfo() on non-voice-capable device! Ignoring..."); 963 return; 964 } 965 966 if (PhoneUtils.isRealIncomingCall(mCM.getFirstActiveRingingCall().getState())) { 967 // Do not start any new SignalInfo tone when Call state is INCOMING 968 // and stop any previous SignalInfo tone which is being played 969 stopSignalInfoTone(); 970 } else { 971 // Extract the SignalInfo String from the message 972 CdmaSignalInfoRec signalInfoRec = (CdmaSignalInfoRec)(r.result); 973 // Only proceed if a Signal info is present. 974 if (signalInfoRec != null) { 975 boolean isPresent = signalInfoRec.isPresent; 976 if (DBG) log("onSignalInfo: isPresent=" + isPresent); 977 if (isPresent) {// if tone is valid 978 int uSignalType = signalInfoRec.signalType; 979 int uAlertPitch = signalInfoRec.alertPitch; 980 int uSignal = signalInfoRec.signal; 981 982 if (DBG) log("onSignalInfo: uSignalType=" + uSignalType + ", uAlertPitch=" + 983 uAlertPitch + ", uSignal=" + uSignal); 984 //Map the Signal to a ToneGenerator ToneID only if Signal info is present 985 int toneID = SignalToneUtil.getAudioToneFromSignalInfo 986 (uSignalType, uAlertPitch, uSignal); 987 988 //Create the SignalInfo tone player and pass the ToneID 989 new SignalInfoTonePlayer(toneID).start(); 990 } 991 } 992 } 993 } 994 995 /** 996 * Stops a SignalInfo tone in the following condition 997 * 1 - On receiving a New Ringing Call 998 * 2 - On disconnecting a call 999 * 3 - On answering a Call Waiting Call 1000 */ stopSignalInfoTone()1001 /* package */ void stopSignalInfoTone() { 1002 if (DBG) log("stopSignalInfoTone: Stopping SignalInfo tone player"); 1003 new SignalInfoTonePlayer(ToneGenerator.TONE_CDMA_SIGNAL_OFF).start(); 1004 } 1005 1006 /** 1007 * Return the private variable mPreviousCdmaCallState. 1008 */ getPreviousCdmaCallState()1009 /* package */ Call.State getPreviousCdmaCallState() { 1010 return mPreviousCdmaCallState; 1011 } 1012 1013 /** 1014 * Return the private variable mVoicePrivacyState. 1015 */ getVoicePrivacyState()1016 /* package */ boolean getVoicePrivacyState() { 1017 return mVoicePrivacyState; 1018 } 1019 1020 /** 1021 * Return the private variable mIsCdmaRedialCall. 1022 */ getIsCdmaRedialCall()1023 /* package */ boolean getIsCdmaRedialCall() { 1024 return mIsCdmaRedialCall; 1025 } 1026 1027 private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = 1028 new BluetoothProfile.ServiceListener() { 1029 public void onServiceConnected(int profile, BluetoothProfile proxy) { 1030 mBluetoothHeadset = (BluetoothHeadset) proxy; 1031 if (VDBG) log("- Got BluetoothHeadset: " + mBluetoothHeadset); 1032 } 1033 1034 public void onServiceDisconnected(int profile) { 1035 mBluetoothHeadset = null; 1036 } 1037 }; 1038 log(String msg)1039 private void log(String msg) { 1040 Log.d(LOG_TAG, msg); 1041 } 1042 } 1043