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