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