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.Connection; 22 import com.android.internal.telephony.Phone; 23 import com.android.internal.telephony.PhoneConstants; 24 import com.android.internal.telephony.TelephonyCapabilities; 25 import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaDisplayInfoRec; 26 import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaSignalInfoRec; 27 import com.android.internal.telephony.cdma.SignalToneUtil; 28 29 import android.bluetooth.BluetoothAdapter; 30 import android.bluetooth.BluetoothHeadset; 31 import android.bluetooth.BluetoothProfile; 32 import android.content.Context; 33 import android.media.AudioAttributes; 34 import android.media.AudioManager; 35 import android.media.ToneGenerator; 36 import android.os.AsyncResult; 37 import android.os.Handler; 38 import android.os.Message; 39 import android.os.SystemProperties; 40 import android.provider.Settings; 41 import android.telecom.TelecomManager; 42 import android.telephony.DisconnectCause; 43 import android.telephony.PhoneNumberUtils; 44 import android.telephony.PhoneStateListener; 45 import android.telephony.SubscriptionInfo; 46 import android.telephony.SubscriptionManager; 47 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener; 48 import android.telephony.TelephonyManager; 49 import android.util.ArrayMap; 50 import android.util.Log; 51 52 import java.util.Iterator; 53 import java.util.List; 54 import java.util.Map; 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 message from the underlying phone layers. 69 private static final int SHOW_MESSAGE_NOTIFICATION_TIME = 3000; // msec 70 71 /** The singleton instance. */ 72 private static CallNotifier sInstance; 73 74 private Map<Integer, CallNotifierPhoneStateListener> mPhoneStateListeners = 75 new ArrayMap<Integer, CallNotifierPhoneStateListener>(); 76 77 private PhoneGlobals mApplication; 78 private CallManager mCM; 79 private BluetoothHeadset mBluetoothHeadset; 80 81 // ToneGenerator instance for playing SignalInfo tones 82 private ToneGenerator mSignalInfoToneGenerator; 83 84 // The tone volume relative to other sounds in the stream SignalInfo 85 private static final int TONE_RELATIVE_VOLUME_SIGNALINFO = 80; 86 87 private boolean mVoicePrivacyState = false; 88 89 // Cached AudioManager 90 private AudioManager mAudioManager; 91 private SubscriptionManager mSubscriptionManager; 92 private TelephonyManager mTelephonyManager; 93 94 // Events from the Phone object: 95 public static final int PHONE_DISCONNECT = 3; 96 public static final int PHONE_STATE_DISPLAYINFO = 6; 97 public static final int PHONE_STATE_SIGNALINFO = 7; 98 public static final int PHONE_ENHANCED_VP_ON = 9; 99 public static final int PHONE_ENHANCED_VP_OFF = 10; 100 public static final int PHONE_SUPP_SERVICE_FAILED = 14; 101 public static final int PHONE_TTY_MODE_RECEIVED = 15; 102 // Events generated internally. 103 // We should store all the possible event type values in one place to make sure that 104 // they don't step on each others' toes. 105 public static final int INTERNAL_SHOW_MESSAGE_NOTIFICATION_DONE = 22; 106 // Other events from call manager 107 public static final int EVENT_OTA_PROVISION_CHANGE = 20; 108 109 /** 110 * Initialize the singleton CallNotifier instance. 111 * This is only done once, at startup, from PhoneApp.onCreate(). 112 */ init( PhoneGlobals app)113 /* package */ static CallNotifier init( 114 PhoneGlobals app) { 115 synchronized (CallNotifier.class) { 116 if (sInstance == null) { 117 sInstance = new CallNotifier(app); 118 } else { 119 Log.wtf(LOG_TAG, "init() called multiple times! sInstance = " + sInstance); 120 } 121 return sInstance; 122 } 123 } 124 125 /** Private constructor; @see init() */ CallNotifier( PhoneGlobals app)126 private CallNotifier( 127 PhoneGlobals app) { 128 mApplication = app; 129 mCM = app.mCM; 130 131 mAudioManager = (AudioManager) mApplication.getSystemService(Context.AUDIO_SERVICE); 132 mTelephonyManager = 133 (TelephonyManager) mApplication.getSystemService(Context.TELEPHONY_SERVICE); 134 mSubscriptionManager = (SubscriptionManager) mApplication.getSystemService( 135 Context.TELEPHONY_SUBSCRIPTION_SERVICE); 136 137 registerForNotifications(); 138 139 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 140 if (adapter != null) { 141 adapter.getProfileProxy(mApplication.getApplicationContext(), 142 mBluetoothProfileServiceListener, 143 BluetoothProfile.HEADSET); 144 } 145 146 mSubscriptionManager.addOnSubscriptionsChangedListener( 147 new OnSubscriptionsChangedListener() { 148 @Override 149 public void onSubscriptionsChanged() { 150 updatePhoneStateListeners(); 151 } 152 }); 153 } 154 createSignalInfoToneGenerator()155 private void createSignalInfoToneGenerator() { 156 // Instantiate the ToneGenerator for SignalInfo and CallWaiting 157 // TODO: We probably don't need the mSignalInfoToneGenerator instance 158 // around forever. Need to change it so as to create a ToneGenerator instance only 159 // when a tone is being played and releases it after its done playing. 160 if (mSignalInfoToneGenerator == null) { 161 try { 162 mSignalInfoToneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL, 163 TONE_RELATIVE_VOLUME_SIGNALINFO); 164 Log.d(LOG_TAG, "CallNotifier: mSignalInfoToneGenerator created when toneplay"); 165 } catch (RuntimeException e) { 166 Log.w(LOG_TAG, "CallNotifier: Exception caught while creating " + 167 "mSignalInfoToneGenerator: " + e); 168 mSignalInfoToneGenerator = null; 169 } 170 } else { 171 Log.d(LOG_TAG, "mSignalInfoToneGenerator created already, hence skipping"); 172 } 173 } 174 175 /** 176 * Register for call state notifications with the CallManager. 177 */ registerForNotifications()178 private void registerForNotifications() { 179 mCM.registerForDisconnect(this, PHONE_DISCONNECT, null); 180 mCM.registerForCdmaOtaStatusChange(this, EVENT_OTA_PROVISION_CHANGE, null); 181 mCM.registerForDisplayInfo(this, PHONE_STATE_DISPLAYINFO, null); 182 mCM.registerForSignalInfo(this, PHONE_STATE_SIGNALINFO, null); 183 mCM.registerForInCallVoicePrivacyOn(this, PHONE_ENHANCED_VP_ON, null); 184 mCM.registerForInCallVoicePrivacyOff(this, PHONE_ENHANCED_VP_OFF, null); 185 mCM.registerForSuppServiceFailed(this, PHONE_SUPP_SERVICE_FAILED, null); 186 mCM.registerForTtyModeReceived(this, PHONE_TTY_MODE_RECEIVED, null); 187 } 188 189 @Override handleMessage(Message msg)190 public void handleMessage(Message msg) { 191 if (DBG) { 192 Log.d(LOG_TAG, "handleMessage(" + msg.what + ")"); 193 } 194 switch (msg.what) { 195 case PHONE_DISCONNECT: 196 if (DBG) log("DISCONNECT"); 197 // Stop any signalInfo tone being played when a call gets ended, the rest of the 198 // disconnect functionality in onDisconnect() is handled in ConnectionService. 199 stopSignalInfoTone(); 200 break; 201 202 case PHONE_STATE_DISPLAYINFO: 203 if (DBG) log("Received PHONE_STATE_DISPLAYINFO event"); 204 onDisplayInfo((AsyncResult) msg.obj); 205 break; 206 207 case PHONE_STATE_SIGNALINFO: 208 if (DBG) log("Received PHONE_STATE_SIGNALINFO event"); 209 onSignalInfo((AsyncResult) msg.obj); 210 break; 211 212 case INTERNAL_SHOW_MESSAGE_NOTIFICATION_DONE: 213 if (DBG) log("Received Display Info notification done event ..."); 214 PhoneDisplayMessage.dismissMessage(); 215 break; 216 217 case EVENT_OTA_PROVISION_CHANGE: 218 if (DBG) log("EVENT_OTA_PROVISION_CHANGE..."); 219 mApplication.handleOtaspEvent(msg); 220 break; 221 222 case PHONE_ENHANCED_VP_ON: 223 if (DBG) log("PHONE_ENHANCED_VP_ON..."); 224 if (!mVoicePrivacyState) { 225 int toneToPlay = InCallTonePlayer.TONE_VOICE_PRIVACY; 226 new InCallTonePlayer(toneToPlay).start(); 227 mVoicePrivacyState = true; 228 } 229 break; 230 231 case PHONE_ENHANCED_VP_OFF: 232 if (DBG) log("PHONE_ENHANCED_VP_OFF..."); 233 if (mVoicePrivacyState) { 234 int toneToPlay = InCallTonePlayer.TONE_VOICE_PRIVACY; 235 new InCallTonePlayer(toneToPlay).start(); 236 mVoicePrivacyState = false; 237 } 238 break; 239 240 case PHONE_SUPP_SERVICE_FAILED: 241 if (DBG) log("PHONE_SUPP_SERVICE_FAILED..."); 242 onSuppServiceFailed((AsyncResult) msg.obj); 243 break; 244 245 case PHONE_TTY_MODE_RECEIVED: 246 if (DBG) log("Received PHONE_TTY_MODE_RECEIVED event"); 247 onTtyModeReceived((AsyncResult) msg.obj); 248 break; 249 250 default: 251 // super.handleMessage(msg); 252 } 253 } 254 updateCallNotifierRegistrationsAfterRadioTechnologyChange()255 void updateCallNotifierRegistrationsAfterRadioTechnologyChange() { 256 if (DBG) Log.d(LOG_TAG, "updateCallNotifierRegistrationsAfterRadioTechnologyChange..."); 257 258 // Instantiate mSignalInfoToneGenerator 259 createSignalInfoToneGenerator(); 260 } 261 262 /** 263 * Resets the audio mode and speaker state when a call ends. 264 */ resetAudioStateAfterDisconnect()265 private void resetAudioStateAfterDisconnect() { 266 if (VDBG) log("resetAudioStateAfterDisconnect()..."); 267 268 if (mBluetoothHeadset != null) { 269 mBluetoothHeadset.disconnectAudio(); 270 } 271 272 // call turnOnSpeaker() with state=false and store=true even if speaker 273 // is already off to reset user requested speaker state. 274 PhoneUtils.turnOnSpeaker(mApplication, false, true); 275 276 PhoneUtils.setAudioMode(mCM); 277 } 278 279 /** 280 * Helper class to play tones through the earpiece (or speaker / BT) 281 * during a call, using the ToneGenerator. 282 * 283 * To use, just instantiate a new InCallTonePlayer 284 * (passing in the TONE_* constant for the tone you want) 285 * and start() it. 286 * 287 * When we're done playing the tone, if the phone is idle at that 288 * point, we'll reset the audio routing and speaker state. 289 * (That means that for tones that get played *after* a call 290 * disconnects, like "busy" or "congestion" or "call ended", you 291 * should NOT call resetAudioStateAfterDisconnect() yourself. 292 * Instead, just start the InCallTonePlayer, which will automatically 293 * defer the resetAudioStateAfterDisconnect() call until the tone 294 * finishes playing.) 295 */ 296 private class InCallTonePlayer extends Thread { 297 private int mToneId; 298 private int mState; 299 // The possible tones we can play. 300 public static final int TONE_NONE = 0; 301 public static final int TONE_CALL_WAITING = 1; 302 public static final int TONE_BUSY = 2; 303 public static final int TONE_CONGESTION = 3; 304 public static final int TONE_CALL_ENDED = 4; 305 public static final int TONE_VOICE_PRIVACY = 5; 306 public static final int TONE_REORDER = 6; 307 public static final int TONE_INTERCEPT = 7; 308 public static final int TONE_CDMA_DROP = 8; 309 public static final int TONE_OUT_OF_SERVICE = 9; 310 public static final int TONE_REDIAL = 10; 311 public static final int TONE_OTA_CALL_END = 11; 312 public static final int TONE_UNOBTAINABLE_NUMBER = 13; 313 314 // The tone volume relative to other sounds in the stream 315 static final int TONE_RELATIVE_VOLUME_EMERGENCY = 100; 316 static final int TONE_RELATIVE_VOLUME_HIPRI = 80; 317 static final int TONE_RELATIVE_VOLUME_LOPRI = 50; 318 319 // Buffer time (in msec) to add on to tone timeout value. 320 // Needed mainly when the timeout value for a tone is the 321 // exact duration of the tone itself. 322 static final int TONE_TIMEOUT_BUFFER = 20; 323 324 // The tone state 325 static final int TONE_OFF = 0; 326 static final int TONE_ON = 1; 327 static final int TONE_STOPPED = 2; 328 InCallTonePlayer(int toneId)329 InCallTonePlayer(int toneId) { 330 super(); 331 mToneId = toneId; 332 mState = TONE_OFF; 333 } 334 335 @Override run()336 public void run() { 337 log("InCallTonePlayer.run(toneId = " + mToneId + ")..."); 338 339 int toneType = 0; // passed to ToneGenerator.startTone() 340 int toneVolume; // passed to the ToneGenerator constructor 341 int toneLengthMillis; 342 int phoneType = mCM.getFgPhone().getPhoneType(); 343 344 switch (mToneId) { 345 case TONE_CALL_WAITING: 346 toneType = ToneGenerator.TONE_SUP_CALL_WAITING; 347 toneVolume = TONE_RELATIVE_VOLUME_HIPRI; 348 // Call waiting tone is stopped by stopTone() method 349 toneLengthMillis = Integer.MAX_VALUE - TONE_TIMEOUT_BUFFER; 350 break; 351 case TONE_BUSY: 352 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) { 353 toneType = ToneGenerator.TONE_CDMA_NETWORK_BUSY_ONE_SHOT; 354 toneVolume = TONE_RELATIVE_VOLUME_LOPRI; 355 toneLengthMillis = 1000; 356 } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM 357 || phoneType == PhoneConstants.PHONE_TYPE_SIP 358 || phoneType == PhoneConstants.PHONE_TYPE_IMS 359 || phoneType == PhoneConstants.PHONE_TYPE_THIRD_PARTY) { 360 toneType = ToneGenerator.TONE_SUP_BUSY; 361 toneVolume = TONE_RELATIVE_VOLUME_HIPRI; 362 toneLengthMillis = 4000; 363 } else { 364 throw new IllegalStateException("Unexpected phone type: " + phoneType); 365 } 366 break; 367 case TONE_CONGESTION: 368 toneType = ToneGenerator.TONE_SUP_CONGESTION; 369 toneVolume = TONE_RELATIVE_VOLUME_HIPRI; 370 toneLengthMillis = 4000; 371 break; 372 373 case TONE_CALL_ENDED: 374 toneType = ToneGenerator.TONE_PROP_PROMPT; 375 toneVolume = TONE_RELATIVE_VOLUME_HIPRI; 376 toneLengthMillis = 200; 377 break; 378 case TONE_OTA_CALL_END: 379 if (mApplication.cdmaOtaConfigData.otaPlaySuccessFailureTone == 380 OtaUtils.OTA_PLAY_SUCCESS_FAILURE_TONE_ON) { 381 toneType = ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD; 382 toneVolume = TONE_RELATIVE_VOLUME_HIPRI; 383 toneLengthMillis = 750; 384 } else { 385 toneType = ToneGenerator.TONE_PROP_PROMPT; 386 toneVolume = TONE_RELATIVE_VOLUME_HIPRI; 387 toneLengthMillis = 200; 388 } 389 break; 390 case TONE_VOICE_PRIVACY: 391 toneType = ToneGenerator.TONE_CDMA_ALERT_NETWORK_LITE; 392 toneVolume = TONE_RELATIVE_VOLUME_HIPRI; 393 toneLengthMillis = 5000; 394 break; 395 case TONE_REORDER: 396 toneType = ToneGenerator.TONE_CDMA_REORDER; 397 toneVolume = TONE_RELATIVE_VOLUME_HIPRI; 398 toneLengthMillis = 4000; 399 break; 400 case TONE_INTERCEPT: 401 toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT; 402 toneVolume = TONE_RELATIVE_VOLUME_LOPRI; 403 toneLengthMillis = 500; 404 break; 405 case TONE_CDMA_DROP: 406 case TONE_OUT_OF_SERVICE: 407 toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE; 408 toneVolume = TONE_RELATIVE_VOLUME_LOPRI; 409 toneLengthMillis = 375; 410 break; 411 case TONE_REDIAL: 412 toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE; 413 toneVolume = TONE_RELATIVE_VOLUME_LOPRI; 414 toneLengthMillis = 5000; 415 break; 416 case TONE_UNOBTAINABLE_NUMBER: 417 toneType = ToneGenerator.TONE_SUP_ERROR; 418 toneVolume = TONE_RELATIVE_VOLUME_HIPRI; 419 toneLengthMillis = 4000; 420 break; 421 default: 422 throw new IllegalArgumentException("Bad toneId: " + mToneId); 423 } 424 425 // If the mToneGenerator creation fails, just continue without it. It is 426 // a local audio signal, and is not as important. 427 ToneGenerator toneGenerator; 428 try { 429 int stream; 430 if (mBluetoothHeadset != null) { 431 stream = mBluetoothHeadset.isAudioOn() ? AudioManager.STREAM_BLUETOOTH_SCO: 432 AudioManager.STREAM_VOICE_CALL; 433 } else { 434 stream = AudioManager.STREAM_VOICE_CALL; 435 } 436 toneGenerator = new ToneGenerator(stream, toneVolume); 437 // if (DBG) log("- created toneGenerator: " + toneGenerator); 438 } catch (RuntimeException e) { 439 Log.w(LOG_TAG, 440 "InCallTonePlayer: Exception caught while creating ToneGenerator: " + e); 441 toneGenerator = null; 442 } 443 444 // Using the ToneGenerator (with the CALL_WAITING / BUSY / 445 // CONGESTION tones at least), the ToneGenerator itself knows 446 // the right pattern of tones to play; we do NOT need to 447 // manually start/stop each individual tone, or manually 448 // insert the correct delay between tones. (We just start it 449 // and let it run for however long we want the tone pattern to 450 // continue.) 451 // 452 // TODO: When we stop the ToneGenerator in the middle of a 453 // "tone pattern", it sounds bad if we cut if off while the 454 // tone is actually playing. Consider adding API to the 455 // ToneGenerator to say "stop at the next silent part of the 456 // pattern", or simply "play the pattern N times and then 457 // stop." 458 boolean needToStopTone = true; 459 boolean okToPlayTone = false; 460 461 if (toneGenerator != null) { 462 int ringerMode = mAudioManager.getRingerMode(); 463 if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) { 464 if (toneType == ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD) { 465 if ((ringerMode != AudioManager.RINGER_MODE_SILENT) && 466 (ringerMode != AudioManager.RINGER_MODE_VIBRATE)) { 467 if (DBG) log("- InCallTonePlayer: start playing call tone=" + toneType); 468 okToPlayTone = true; 469 needToStopTone = false; 470 } 471 } else if ((toneType == ToneGenerator.TONE_CDMA_NETWORK_BUSY_ONE_SHOT) || 472 (toneType == ToneGenerator.TONE_CDMA_REORDER) || 473 (toneType == ToneGenerator.TONE_CDMA_ABBR_REORDER) || 474 (toneType == ToneGenerator.TONE_CDMA_ABBR_INTERCEPT) || 475 (toneType == ToneGenerator.TONE_CDMA_CALLDROP_LITE)) { 476 if (ringerMode != AudioManager.RINGER_MODE_SILENT) { 477 if (DBG) log("InCallTonePlayer:playing call fail tone:" + toneType); 478 okToPlayTone = true; 479 needToStopTone = false; 480 } 481 } else if ((toneType == ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE) || 482 (toneType == ToneGenerator.TONE_CDMA_ALERT_NETWORK_LITE)) { 483 if ((ringerMode != AudioManager.RINGER_MODE_SILENT) && 484 (ringerMode != AudioManager.RINGER_MODE_VIBRATE)) { 485 if (DBG) log("InCallTonePlayer:playing tone for toneType=" + toneType); 486 okToPlayTone = true; 487 needToStopTone = false; 488 } 489 } else { // For the rest of the tones, always OK to play. 490 okToPlayTone = true; 491 } 492 } else { // Not "CDMA" 493 okToPlayTone = true; 494 } 495 496 synchronized (this) { 497 if (okToPlayTone && mState != TONE_STOPPED) { 498 mState = TONE_ON; 499 toneGenerator.startTone(toneType); 500 try { 501 wait(toneLengthMillis + TONE_TIMEOUT_BUFFER); 502 } catch (InterruptedException e) { 503 Log.w(LOG_TAG, 504 "InCallTonePlayer stopped: " + e); 505 } 506 if (needToStopTone) { 507 toneGenerator.stopTone(); 508 } 509 } 510 // if (DBG) log("- InCallTonePlayer: done playing."); 511 toneGenerator.release(); 512 mState = TONE_OFF; 513 } 514 } 515 516 // Finally, do the same cleanup we otherwise would have done 517 // in onDisconnect(). 518 // 519 // (But watch out: do NOT do this if the phone is in use, 520 // since some of our tones get played *during* a call (like 521 // CALL_WAITING) and we definitely *don't* 522 // want to reset the audio mode / speaker / bluetooth after 523 // playing those! 524 // This call is really here for use with tones that get played 525 // *after* a call disconnects, like "busy" or "congestion" or 526 // "call ended", where the phone has already become idle but 527 // we need to defer the resetAudioStateAfterDisconnect() call 528 // till the tone finishes playing.) 529 if (mCM.getState() == PhoneConstants.State.IDLE) { 530 resetAudioStateAfterDisconnect(); 531 } 532 } 533 } 534 535 /** 536 * Displays a notification when the phone receives a DisplayInfo record. 537 */ onDisplayInfo(AsyncResult r)538 private void onDisplayInfo(AsyncResult r) { 539 // Extract the DisplayInfo String from the message 540 CdmaDisplayInfoRec displayInfoRec = (CdmaDisplayInfoRec)(r.result); 541 542 if (displayInfoRec != null) { 543 String displayInfo = displayInfoRec.alpha; 544 if (DBG) log("onDisplayInfo: displayInfo=" + displayInfo); 545 PhoneDisplayMessage.displayNetworkMessage(mApplication, displayInfo); 546 547 // start a timer that kills the dialog 548 sendEmptyMessageDelayed(INTERNAL_SHOW_MESSAGE_NOTIFICATION_DONE, 549 SHOW_MESSAGE_NOTIFICATION_TIME); 550 } 551 } 552 553 /** 554 * Displays a notification when the phone receives a notice that a supplemental 555 * service has failed. 556 * TODO: This is a NOOP if it isn't for conferences or resuming call failures right now. 557 */ onSuppServiceFailed(AsyncResult r)558 private void onSuppServiceFailed(AsyncResult r) { 559 if (r.result != Phone.SuppService.CONFERENCE && r.result != Phone.SuppService.RESUME) { 560 if (DBG) log("onSuppServiceFailed: not a merge or resume failure event"); 561 return; 562 } 563 564 String mergeFailedString = ""; 565 if (r.result == Phone.SuppService.CONFERENCE) { 566 if (DBG) log("onSuppServiceFailed: displaying merge failure message"); 567 mergeFailedString = mApplication.getResources().getString( 568 R.string.incall_error_supp_service_conference); 569 } else if (r.result == Phone.SuppService.RESUME) { 570 if (DBG) log("onSuppServiceFailed: displaying merge failure message"); 571 mergeFailedString = mApplication.getResources().getString( 572 R.string.incall_error_supp_service_switch); 573 } else if (r.result == Phone.SuppService.HOLD) { 574 mergeFailedString = mApplication.getResources().getString( 575 R.string.incall_error_supp_service_hold); 576 } 577 PhoneDisplayMessage.displayErrorMessage(mApplication, mergeFailedString); 578 579 // start a timer that kills the dialog 580 sendEmptyMessageDelayed(INTERNAL_SHOW_MESSAGE_NOTIFICATION_DONE, 581 SHOW_MESSAGE_NOTIFICATION_TIME); 582 } 583 updatePhoneStateListeners()584 public void updatePhoneStateListeners() { 585 List<SubscriptionInfo> subInfos = mSubscriptionManager.getActiveSubscriptionInfoList(); 586 587 // Unregister phone listeners for inactive subscriptions. 588 Iterator<Integer> itr = mPhoneStateListeners.keySet().iterator(); 589 while (itr.hasNext()) { 590 int subId = itr.next(); 591 if (subInfos == null || !containsSubId(subInfos, subId)) { 592 // Hide the outstanding notifications. 593 mApplication.notificationMgr.updateMwi(subId, false); 594 mApplication.notificationMgr.updateCfi(subId, false); 595 596 // Listening to LISTEN_NONE removes the listener. 597 mTelephonyManager.listen( 598 mPhoneStateListeners.get(subId), PhoneStateListener.LISTEN_NONE); 599 itr.remove(); 600 } 601 } 602 603 if (subInfos == null) { 604 return; 605 } 606 607 // Register new phone listeners for active subscriptions. 608 for (int i = 0; i < subInfos.size(); i++) { 609 int subId = subInfos.get(i).getSubscriptionId(); 610 if (!mPhoneStateListeners.containsKey(subId)) { 611 CallNotifierPhoneStateListener listener = new CallNotifierPhoneStateListener(subId); 612 mTelephonyManager.listen(listener, 613 PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR 614 | PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR); 615 mPhoneStateListeners.put(subId, listener); 616 } 617 } 618 } 619 620 /** 621 * @return {@code true} if the list contains SubscriptionInfo with the given subscription id. 622 */ containsSubId(List<SubscriptionInfo> subInfos, int subId)623 private boolean containsSubId(List<SubscriptionInfo> subInfos, int subId) { 624 if (subInfos == null) { 625 return false; 626 } 627 628 for (int i = 0; i < subInfos.size(); i++) { 629 if (subInfos.get(i).getSubscriptionId() == subId) { 630 return true; 631 } 632 } 633 return false; 634 } 635 636 /** 637 * Displays a notification when the phone receives a notice that TTY mode 638 * has changed on remote end. 639 */ onTtyModeReceived(AsyncResult r)640 private void onTtyModeReceived(AsyncResult r) { 641 if (DBG) log("TtyModeReceived: displaying notification message"); 642 643 int resId = 0; 644 switch (((Integer)r.result).intValue()) { 645 case TelecomManager.TTY_MODE_FULL: 646 resId = com.android.internal.R.string.peerTtyModeFull; 647 break; 648 case TelecomManager.TTY_MODE_HCO: 649 resId = com.android.internal.R.string.peerTtyModeHco; 650 break; 651 case TelecomManager.TTY_MODE_VCO: 652 resId = com.android.internal.R.string.peerTtyModeVco; 653 break; 654 case TelecomManager.TTY_MODE_OFF: 655 resId = com.android.internal.R.string.peerTtyModeOff; 656 break; 657 default: 658 Log.e(LOG_TAG, "Unsupported TTY mode: " + r.result); 659 break; 660 } 661 if (resId != 0) { 662 PhoneDisplayMessage.displayNetworkMessage(mApplication, 663 mApplication.getResources().getString(resId)); 664 665 // start a timer that kills the dialog 666 sendEmptyMessageDelayed(INTERNAL_SHOW_MESSAGE_NOTIFICATION_DONE, 667 SHOW_MESSAGE_NOTIFICATION_TIME); 668 } 669 } 670 671 /** 672 * Helper class to play SignalInfo tones using the ToneGenerator. 673 * 674 * To use, just instantiate a new SignalInfoTonePlayer 675 * (passing in the ToneID constant for the tone you want) 676 * and start() it. 677 */ 678 private class SignalInfoTonePlayer extends Thread { 679 private int mToneId; 680 SignalInfoTonePlayer(int toneId)681 SignalInfoTonePlayer(int toneId) { 682 super(); 683 mToneId = toneId; 684 } 685 686 @Override run()687 public void run() { 688 log("SignalInfoTonePlayer.run(toneId = " + mToneId + ")..."); 689 createSignalInfoToneGenerator(); 690 if (mSignalInfoToneGenerator != null) { 691 //First stop any ongoing SignalInfo tone 692 mSignalInfoToneGenerator.stopTone(); 693 694 //Start playing the new tone if its a valid tone 695 mSignalInfoToneGenerator.startTone(mToneId); 696 } 697 } 698 } 699 700 /** 701 * Plays a tone when the phone receives a SignalInfo record. 702 */ onSignalInfo(AsyncResult r)703 private void onSignalInfo(AsyncResult r) { 704 // Signal Info are totally ignored on non-voice-capable devices. 705 if (!PhoneGlobals.sVoiceCapable) { 706 Log.w(LOG_TAG, "Got onSignalInfo() on non-voice-capable device! Ignoring..."); 707 return; 708 } 709 710 if (PhoneUtils.isRealIncomingCall(mCM.getFirstActiveRingingCall().getState())) { 711 // Do not start any new SignalInfo tone when Call state is INCOMING 712 // and stop any previous SignalInfo tone which is being played 713 stopSignalInfoTone(); 714 } else { 715 // Extract the SignalInfo String from the message 716 CdmaSignalInfoRec signalInfoRec = (CdmaSignalInfoRec)(r.result); 717 // Only proceed if a Signal info is present. 718 if (signalInfoRec != null) { 719 boolean isPresent = signalInfoRec.isPresent; 720 if (DBG) log("onSignalInfo: isPresent=" + isPresent); 721 if (isPresent) {// if tone is valid 722 int uSignalType = signalInfoRec.signalType; 723 int uAlertPitch = signalInfoRec.alertPitch; 724 int uSignal = signalInfoRec.signal; 725 726 if (DBG) log("onSignalInfo: uSignalType=" + uSignalType + ", uAlertPitch=" + 727 uAlertPitch + ", uSignal=" + uSignal); 728 //Map the Signal to a ToneGenerator ToneID only if Signal info is present 729 int toneID = SignalToneUtil.getAudioToneFromSignalInfo 730 (uSignalType, uAlertPitch, uSignal); 731 732 //Create the SignalInfo tone player and pass the ToneID 733 new SignalInfoTonePlayer(toneID).start(); 734 } 735 } 736 } 737 } 738 739 /** 740 * Stops a SignalInfo tone in the following condition 741 * 1 - On receiving a New Ringing Call 742 * 2 - On disconnecting a call 743 * 3 - On answering a Call Waiting Call 744 */ stopSignalInfoTone()745 /* package */ void stopSignalInfoTone() { 746 if (DBG) log("stopSignalInfoTone: Stopping SignalInfo tone player"); 747 new SignalInfoTonePlayer(ToneGenerator.TONE_CDMA_SIGNAL_OFF).start(); 748 } 749 750 private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = 751 new BluetoothProfile.ServiceListener() { 752 public void onServiceConnected(int profile, BluetoothProfile proxy) { 753 mBluetoothHeadset = (BluetoothHeadset) proxy; 754 if (VDBG) log("- Got BluetoothHeadset: " + mBluetoothHeadset); 755 } 756 757 public void onServiceDisconnected(int profile) { 758 mBluetoothHeadset = null; 759 } 760 }; 761 762 private class CallNotifierPhoneStateListener extends PhoneStateListener { CallNotifierPhoneStateListener(int subId)763 public CallNotifierPhoneStateListener(int subId) { 764 super(subId); 765 } 766 767 @Override onMessageWaitingIndicatorChanged(boolean visible)768 public void onMessageWaitingIndicatorChanged(boolean visible) { 769 if (VDBG) log("onMessageWaitingIndicatorChanged(): " + this.mSubId + " " + visible); 770 mApplication.notificationMgr.updateMwi(this.mSubId, visible); 771 } 772 773 @Override onCallForwardingIndicatorChanged(boolean visible)774 public void onCallForwardingIndicatorChanged(boolean visible) { 775 if (VDBG) log("onCallForwardingIndicatorChanged(): " + this.mSubId + " " + visible); 776 mApplication.notificationMgr.updateCfi(this.mSubId, visible); 777 } 778 }; 779 log(String msg)780 private void log(String msg) { 781 Log.d(LOG_TAG, msg); 782 } 783 } 784