• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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