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