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