Home | History | Annotate | Download | only in hfp
      1 /*
      2  * Copyright (C) 2012 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.bluetooth.hfp;
     18 
     19 import android.bluetooth.BluetoothDevice;
     20 import android.content.BroadcastReceiver;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.IntentFilter;
     24 import android.os.Looper;
     25 import android.support.annotation.VisibleForTesting;
     26 import android.telephony.PhoneStateListener;
     27 import android.telephony.ServiceState;
     28 import android.telephony.SignalStrength;
     29 import android.telephony.SubscriptionManager;
     30 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
     31 import android.telephony.TelephonyManager;
     32 import android.util.Log;
     33 
     34 import com.android.internal.telephony.IccCardConstants;
     35 import com.android.internal.telephony.TelephonyIntents;
     36 
     37 import java.util.HashMap;
     38 import java.util.Objects;
     39 
     40 
     41 /**
     42  * Class that manages Telephony states
     43  *
     44  * Note:
     45  * The methods in this class are not thread safe, don't call them from
     46  * multiple threads. Call them from the HeadsetPhoneStateMachine message
     47  * handler only.
     48  */
     49 public class HeadsetPhoneState {
     50     private static final String TAG = "HeadsetPhoneState";
     51 
     52     private final HeadsetService mHeadsetService;
     53     private final TelephonyManager mTelephonyManager;
     54     private final SubscriptionManager mSubscriptionManager;
     55 
     56     private ServiceState mServiceState;
     57 
     58     // HFP 1.6 CIND service value
     59     private int mCindService = HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
     60     // Check this before sending out service state to the device -- if the SIM isn't fully
     61     // loaded, don't expose that the network is available.
     62     private boolean mIsSimStateLoaded;
     63     // Number of active (foreground) calls
     64     private int mNumActive;
     65     // Current Call Setup State
     66     private int mCallState = HeadsetHalConstants.CALL_STATE_IDLE;
     67     // Number of held (background) calls
     68     private int mNumHeld;
     69     // HFP 1.6 CIND signal value
     70     private int mCindSignal;
     71     // HFP 1.6 CIND roam value
     72     private int mCindRoam = HeadsetHalConstants.SERVICE_TYPE_HOME;
     73     // HFP 1.6 CIND battchg value
     74     private int mCindBatteryCharge;
     75 
     76     private final HashMap<BluetoothDevice, Integer> mDeviceEventMap = new HashMap<>();
     77     private PhoneStateListener mPhoneStateListener;
     78     private final OnSubscriptionsChangedListener mOnSubscriptionsChangedListener;
     79 
     80     HeadsetPhoneState(HeadsetService headsetService) {
     81         Objects.requireNonNull(headsetService, "headsetService is null");
     82         mHeadsetService = headsetService;
     83         mTelephonyManager =
     84                 (TelephonyManager) mHeadsetService.getSystemService(Context.TELEPHONY_SERVICE);
     85         Objects.requireNonNull(mTelephonyManager, "TELEPHONY_SERVICE is null");
     86         // Register for SubscriptionInfo list changes which is guaranteed to invoke
     87         // onSubscriptionInfoChanged and which in turns calls loadInBackgroud.
     88         mSubscriptionManager = SubscriptionManager.from(mHeadsetService);
     89         Objects.requireNonNull(mSubscriptionManager, "TELEPHONY_SUBSCRIPTION_SERVICE is null");
     90         // Initialize subscription on the handler thread
     91         mOnSubscriptionsChangedListener = new HeadsetPhoneStateOnSubscriptionChangedListener(
     92                 headsetService.getStateMachinesThreadLooper());
     93         mSubscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
     94     }
     95 
     96     /**
     97      * Cleanup this instance. Instance can no longer be used after calling this method.
     98      */
     99     public void cleanup() {
    100         synchronized (mDeviceEventMap) {
    101             mDeviceEventMap.clear();
    102             stopListenForPhoneState();
    103         }
    104         mSubscriptionManager.removeOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
    105     }
    106 
    107     @Override
    108     public String toString() {
    109         return "HeadsetPhoneState [mTelephonyServiceAvailability=" + mCindService + ", mNumActive="
    110                 + mNumActive + ", mCallState=" + mCallState + ", mNumHeld=" + mNumHeld
    111                 + ", mSignal=" + mCindSignal + ", mRoam=" + mCindRoam + ", mBatteryCharge="
    112                 + mCindBatteryCharge + ", TelephonyEvents=" + getTelephonyEventsToListen() + "]";
    113     }
    114 
    115     private int getTelephonyEventsToListen() {
    116         synchronized (mDeviceEventMap) {
    117             return mDeviceEventMap.values()
    118                     .stream()
    119                     .reduce(PhoneStateListener.LISTEN_NONE, (a, b) -> a | b);
    120         }
    121     }
    122 
    123     /**
    124      * Start or stop listening for phone state change
    125      *
    126      * @param device remote device that subscribes to this phone state update
    127      * @param events events in {@link PhoneStateListener} to listen to
    128      */
    129     @VisibleForTesting
    130     public void listenForPhoneState(BluetoothDevice device, int events) {
    131         synchronized (mDeviceEventMap) {
    132             int prevEvents = getTelephonyEventsToListen();
    133             if (events == PhoneStateListener.LISTEN_NONE) {
    134                 mDeviceEventMap.remove(device);
    135             } else {
    136                 mDeviceEventMap.put(device, events);
    137             }
    138             int updatedEvents = getTelephonyEventsToListen();
    139             if (prevEvents != updatedEvents) {
    140                 stopListenForPhoneState();
    141                 startListenForPhoneState();
    142             }
    143         }
    144     }
    145 
    146     private void startListenForPhoneState() {
    147         if (mPhoneStateListener != null) {
    148             Log.w(TAG, "startListenForPhoneState, already listening");
    149             return;
    150         }
    151         int events = getTelephonyEventsToListen();
    152         if (events == PhoneStateListener.LISTEN_NONE) {
    153             Log.w(TAG, "startListenForPhoneState, no event to listen");
    154             return;
    155         }
    156         int subId = SubscriptionManager.getDefaultSubscriptionId();
    157         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
    158             // Will retry listening for phone state in onSubscriptionsChanged() callback
    159             Log.w(TAG, "startListenForPhoneState, invalid subscription ID " + subId);
    160             return;
    161         }
    162         Log.i(TAG, "startListenForPhoneState(), subId=" + subId + ", enabled_events=" + events);
    163         mPhoneStateListener = new HeadsetPhoneStateListener(subId,
    164                 mHeadsetService.getStateMachinesThreadLooper());
    165         mTelephonyManager.listen(mPhoneStateListener, events);
    166         if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
    167             mTelephonyManager.setRadioIndicationUpdateMode(
    168                     TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
    169                     TelephonyManager.INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF);
    170         }
    171     }
    172 
    173     private void stopListenForPhoneState() {
    174         if (mPhoneStateListener == null) {
    175             Log.i(TAG, "stopListenForPhoneState(), no listener indicates nothing is listening");
    176             return;
    177         }
    178         Log.i(TAG, "stopListenForPhoneState(), stopping listener, enabled_events="
    179                 + getTelephonyEventsToListen());
    180         mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
    181         mTelephonyManager.setRadioIndicationUpdateMode(
    182                 TelephonyManager.INDICATION_FILTER_SIGNAL_STRENGTH,
    183                 TelephonyManager.INDICATION_UPDATE_MODE_NORMAL);
    184         mPhoneStateListener = null;
    185     }
    186 
    187     int getCindService() {
    188         return mCindService;
    189     }
    190 
    191     int getNumActiveCall() {
    192         return mNumActive;
    193     }
    194 
    195     @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
    196     public void setNumActiveCall(int numActive) {
    197         mNumActive = numActive;
    198     }
    199 
    200     int getCallState() {
    201         return mCallState;
    202     }
    203 
    204     @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
    205     public void setCallState(int callState) {
    206         mCallState = callState;
    207     }
    208 
    209     int getNumHeldCall() {
    210         return mNumHeld;
    211     }
    212 
    213     @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
    214     public void setNumHeldCall(int numHeldCall) {
    215         mNumHeld = numHeldCall;
    216     }
    217 
    218     int getCindSignal() {
    219         return mCindSignal;
    220     }
    221 
    222     int getCindRoam() {
    223         return mCindRoam;
    224     }
    225 
    226     /**
    227      * Set battery level value used for +CIND result
    228      *
    229      * @param batteryLevel battery level value
    230      */
    231     @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
    232     public void setCindBatteryCharge(int batteryLevel) {
    233         if (mCindBatteryCharge != batteryLevel) {
    234             mCindBatteryCharge = batteryLevel;
    235             sendDeviceStateChanged();
    236         }
    237     }
    238 
    239     int getCindBatteryCharge() {
    240         return mCindBatteryCharge;
    241     }
    242 
    243     boolean isInCall() {
    244         return (mNumActive >= 1);
    245     }
    246 
    247     private void sendDeviceStateChanged() {
    248         int service =
    249                 mIsSimStateLoaded ? mCindService : HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
    250         // When out of service, send signal strength as 0. Some devices don't
    251         // use the service indicator, but only the signal indicator
    252         int signal = service == HeadsetHalConstants.NETWORK_STATE_AVAILABLE ? mCindSignal : 0;
    253 
    254         Log.d(TAG, "sendDeviceStateChanged. mService=" + mCindService + " mIsSimStateLoaded="
    255                 + mIsSimStateLoaded + " mSignal=" + signal + " mRoam=" + mCindRoam
    256                 + " mBatteryCharge=" + mCindBatteryCharge);
    257         mHeadsetService.onDeviceStateChanged(
    258                 new HeadsetDeviceState(service, mCindRoam, signal, mCindBatteryCharge));
    259     }
    260 
    261     private class HeadsetPhoneStateOnSubscriptionChangedListener
    262             extends OnSubscriptionsChangedListener {
    263         HeadsetPhoneStateOnSubscriptionChangedListener(Looper looper) {
    264             super(looper);
    265         }
    266 
    267         @Override
    268         public void onSubscriptionsChanged() {
    269             synchronized (mDeviceEventMap) {
    270                 stopListenForPhoneState();
    271                 startListenForPhoneState();
    272             }
    273         }
    274     }
    275 
    276     private class HeadsetPhoneStateListener extends PhoneStateListener {
    277         HeadsetPhoneStateListener(Integer subId, Looper looper) {
    278             super(subId, looper);
    279         }
    280 
    281         @Override
    282         public synchronized void onServiceStateChanged(ServiceState serviceState) {
    283             mServiceState = serviceState;
    284             int cindService = (serviceState.getState() == ServiceState.STATE_IN_SERVICE)
    285                     ? HeadsetHalConstants.NETWORK_STATE_AVAILABLE
    286                     : HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
    287             int newRoam = serviceState.getRoaming() ? HeadsetHalConstants.SERVICE_TYPE_ROAMING
    288                     : HeadsetHalConstants.SERVICE_TYPE_HOME;
    289 
    290             if (cindService == mCindService && newRoam == mCindRoam) {
    291                 // De-bounce the state change
    292                 return;
    293             }
    294             mCindService = cindService;
    295             mCindRoam = newRoam;
    296 
    297             // If this is due to a SIM insertion, we want to defer sending device state changed
    298             // until all the SIM config is loaded.
    299             if (cindService == HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE) {
    300                 mIsSimStateLoaded = false;
    301                 sendDeviceStateChanged();
    302                 return;
    303             }
    304             IntentFilter simStateChangedFilter =
    305                     new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
    306             mHeadsetService.registerReceiver(new BroadcastReceiver() {
    307                 @Override
    308                 public void onReceive(Context context, Intent intent) {
    309                     if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(intent.getAction())) {
    310                         // This is a sticky broadcast, so if it's already been loaded,
    311                         // this'll execute immediately.
    312                         if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(
    313                                 intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE))) {
    314                             mIsSimStateLoaded = true;
    315                             sendDeviceStateChanged();
    316                             mHeadsetService.unregisterReceiver(this);
    317                         }
    318                     }
    319                 }
    320             }, simStateChangedFilter);
    321 
    322         }
    323 
    324         @Override
    325         public void onSignalStrengthsChanged(SignalStrength signalStrength) {
    326 
    327             int prevSignal = mCindSignal;
    328             if (mCindService == HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE) {
    329                 mCindSignal = 0;
    330             } else if (signalStrength.isGsm()) {
    331                 mCindSignal = signalStrength.getLteLevel();
    332                 if (mCindSignal == SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
    333                     mCindSignal = gsmAsuToSignal(signalStrength);
    334                 } else {
    335                     // SignalStrength#getLteLevel returns the scale from 0-4
    336                     // Bluetooth signal scales at 0-5
    337                     // Let's match up the larger side
    338                     mCindSignal++;
    339                 }
    340             } else {
    341                 mCindSignal = cdmaDbmEcioToSignal(signalStrength);
    342             }
    343 
    344             // network signal strength is scaled to BT 1-5 levels.
    345             // This results in a lot of duplicate messages, hence this check
    346             if (prevSignal != mCindSignal) {
    347                 sendDeviceStateChanged();
    348             }
    349         }
    350 
    351         /* convert [0,31] ASU signal strength to the [0,5] expected by
    352          * bluetooth devices. Scale is similar to status bar policy
    353          */
    354         private int gsmAsuToSignal(SignalStrength signalStrength) {
    355             int asu = signalStrength.getGsmSignalStrength();
    356             if (asu == 99) {
    357                 return 0;
    358             } else if (asu >= 16) {
    359                 return 5;
    360             } else if (asu >= 8) {
    361                 return 4;
    362             } else if (asu >= 4) {
    363                 return 3;
    364             } else if (asu >= 2) {
    365                 return 2;
    366             } else if (asu >= 1) {
    367                 return 1;
    368             } else {
    369                 return 0;
    370             }
    371         }
    372 
    373         /**
    374          * Convert the cdma / evdo db levels to appropriate icon level.
    375          * The scale is similar to the one used in status bar policy.
    376          *
    377          * @param signalStrength signal strength level
    378          * @return the icon level for remote device
    379          */
    380         private int cdmaDbmEcioToSignal(SignalStrength signalStrength) {
    381             int levelDbm = 0;
    382             int levelEcio = 0;
    383             int cdmaIconLevel = 0;
    384             int evdoIconLevel = 0;
    385             int cdmaDbm = signalStrength.getCdmaDbm();
    386             int cdmaEcio = signalStrength.getCdmaEcio();
    387 
    388             if (cdmaDbm >= -75) {
    389                 levelDbm = 4;
    390             } else if (cdmaDbm >= -85) {
    391                 levelDbm = 3;
    392             } else if (cdmaDbm >= -95) {
    393                 levelDbm = 2;
    394             } else if (cdmaDbm >= -100) {
    395                 levelDbm = 1;
    396             } else {
    397                 levelDbm = 0;
    398             }
    399 
    400             // Ec/Io are in dB*10
    401             if (cdmaEcio >= -90) {
    402                 levelEcio = 4;
    403             } else if (cdmaEcio >= -110) {
    404                 levelEcio = 3;
    405             } else if (cdmaEcio >= -130) {
    406                 levelEcio = 2;
    407             } else if (cdmaEcio >= -150) {
    408                 levelEcio = 1;
    409             } else {
    410                 levelEcio = 0;
    411             }
    412 
    413             cdmaIconLevel = (levelDbm < levelEcio) ? levelDbm : levelEcio;
    414 
    415             // STOPSHIP: Change back to getRilVoiceRadioTechnology
    416             if (mServiceState != null && (
    417                     mServiceState.getRadioTechnology() == ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0
    418                             || mServiceState.getRadioTechnology()
    419                             == ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A)) {
    420                 int evdoEcio = signalStrength.getEvdoEcio();
    421                 int evdoSnr = signalStrength.getEvdoSnr();
    422                 int levelEvdoEcio = 0;
    423                 int levelEvdoSnr = 0;
    424 
    425                 // Ec/Io are in dB*10
    426                 if (evdoEcio >= -650) {
    427                     levelEvdoEcio = 4;
    428                 } else if (evdoEcio >= -750) {
    429                     levelEvdoEcio = 3;
    430                 } else if (evdoEcio >= -900) {
    431                     levelEvdoEcio = 2;
    432                 } else if (evdoEcio >= -1050) {
    433                     levelEvdoEcio = 1;
    434                 } else {
    435                     levelEvdoEcio = 0;
    436                 }
    437 
    438                 if (evdoSnr > 7) {
    439                     levelEvdoSnr = 4;
    440                 } else if (evdoSnr > 5) {
    441                     levelEvdoSnr = 3;
    442                 } else if (evdoSnr > 3) {
    443                     levelEvdoSnr = 2;
    444                 } else if (evdoSnr > 1) {
    445                     levelEvdoSnr = 1;
    446                 } else {
    447                     levelEvdoSnr = 0;
    448                 }
    449 
    450                 evdoIconLevel = (levelEvdoEcio < levelEvdoSnr) ? levelEvdoEcio : levelEvdoSnr;
    451             }
    452             // TODO(): There is a bug open regarding what should be sent.
    453             return (cdmaIconLevel > evdoIconLevel) ? cdmaIconLevel : evdoIconLevel;
    454         }
    455     }
    456 }
    457