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