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