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