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