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