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