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