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