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