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 && mTelephonyManager != null) {
121 
122             int subId = SubscriptionManager.getDefaultSubscriptionId();
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 && mTelephonyManager != null) {
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         if (mRoam != roam) {
181             mRoam = roam;
182             sendDeviceStateChanged();
183         }
184     }
185 
setBatteryCharge(int batteryLevel)186     void setBatteryCharge(int batteryLevel) {
187         if (mBatteryCharge != batteryLevel) {
188             mBatteryCharge = batteryLevel;
189             sendDeviceStateChanged();
190         }
191     }
192 
getBatteryCharge()193     int getBatteryCharge() {
194         return mBatteryCharge;
195     }
196 
setSpeakerVolume(int volume)197     void setSpeakerVolume(int volume) {
198         mSpeakerVolume = volume;
199     }
200 
getSpeakerVolume()201     int getSpeakerVolume() {
202         return mSpeakerVolume;
203     }
204 
setMicVolume(int volume)205     void setMicVolume(int volume) {
206         mMicVolume = volume;
207     }
208 
getMicVolume()209     int getMicVolume() {
210         return mMicVolume;
211     }
212 
isInCall()213     boolean isInCall() {
214         return (mNumActive >= 1);
215     }
216 
sendDeviceStateChanged()217     void sendDeviceStateChanged()
218     {
219         // When out of service, send signal strength as 0. Some devices don't
220         // use the service indicator, but only the signal indicator
221         int signal = mService == HeadsetHalConstants.NETWORK_STATE_AVAILABLE ? mSignal : 0;
222 
223         Log.d(TAG, "sendDeviceStateChanged. mService="+ mService +
224                    " mSignal=" + signal +" mRoam="+ mRoam +
225                    " mBatteryCharge=" + mBatteryCharge);
226         HeadsetStateMachine sm = mStateMachine;
227         if (sm != null) {
228             sm.sendMessage(HeadsetStateMachine.DEVICE_STATE_CHANGED,
229                 new HeadsetDeviceState(mService, mRoam, signal, mBatteryCharge));
230         }
231     }
232 
getPhoneStateListener(int subId)233     private PhoneStateListener getPhoneStateListener(int subId) {
234         PhoneStateListener mPhoneStateListener = new PhoneStateListener(subId) {
235             @Override
236             public void onServiceStateChanged(ServiceState serviceState) {
237 
238                 mServiceState = serviceState;
239                 mService = (serviceState.getState() == ServiceState.STATE_IN_SERVICE) ?
240                     HeadsetHalConstants.NETWORK_STATE_AVAILABLE :
241                     HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
242                 setRoam(serviceState.getRoaming() ? HeadsetHalConstants.SERVICE_TYPE_ROAMING
243                                                   : HeadsetHalConstants.SERVICE_TYPE_HOME);
244 
245                 sendDeviceStateChanged();
246             }
247 
248             @Override
249             public void onSignalStrengthsChanged(SignalStrength signalStrength) {
250 
251                 int prevSignal = mSignal;
252                 if (mService == HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE) {
253                     mSignal = 0;
254                 } else if (signalStrength.isGsm()) {
255                     mSignal = signalStrength.getLteLevel();
256                     if (mSignal == SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) {
257                         mSignal = gsmAsuToSignal(signalStrength);
258                     } else {
259                         // SignalStrength#getLteLevel returns the scale from 0-4
260                         // Bluetooth signal scales at 0-5
261                         // Let's match up the larger side
262                         mSignal++;
263                     }
264                 } else {
265                     mSignal = cdmaDbmEcioToSignal(signalStrength);
266                 }
267 
268                 // network signal strength is scaled to BT 1-5 levels.
269                 // This results in a lot of duplicate messages, hence this check
270                 if (prevSignal != mSignal) {
271                     sendDeviceStateChanged();
272                 }
273             }
274 
275             /* convert [0,31] ASU signal strength to the [0,5] expected by
276              * bluetooth devices. Scale is similar to status bar policy
277              */
278             private int gsmAsuToSignal(SignalStrength signalStrength) {
279                 int asu = signalStrength.getGsmSignalStrength();
280                 if      (asu >= 16) return 5;
281                 else if (asu >= 8)  return 4;
282                 else if (asu >= 4)  return 3;
283                 else if (asu >= 2)  return 2;
284                 else if (asu >= 1)  return 1;
285                 else                return 0;
286             }
287 
288             /**
289              * Convert the cdma / evdo db levels to appropriate icon level.
290              * The scale is similar to the one used in status bar policy.
291              *
292              * @param signalStrength
293              * @return the icon level
294              */
295             private int cdmaDbmEcioToSignal(SignalStrength signalStrength) {
296                 int levelDbm = 0;
297                 int levelEcio = 0;
298                 int cdmaIconLevel = 0;
299                 int evdoIconLevel = 0;
300                 int cdmaDbm = signalStrength.getCdmaDbm();
301                 int cdmaEcio = signalStrength.getCdmaEcio();
302 
303                 if (cdmaDbm >= -75) levelDbm = 4;
304                 else if (cdmaDbm >= -85) levelDbm = 3;
305                 else if (cdmaDbm >= -95) levelDbm = 2;
306                 else if (cdmaDbm >= -100) levelDbm = 1;
307                 else levelDbm = 0;
308 
309                 // Ec/Io are in dB*10
310                 if (cdmaEcio >= -90) levelEcio = 4;
311                 else if (cdmaEcio >= -110) levelEcio = 3;
312                 else if (cdmaEcio >= -130) levelEcio = 2;
313                 else if (cdmaEcio >= -150) levelEcio = 1;
314                 else levelEcio = 0;
315 
316                 cdmaIconLevel = (levelDbm < levelEcio) ? levelDbm : levelEcio;
317 
318                 // STOPSHIP: Change back to getRilVoiceRadioTechnology
319                 if (mServiceState != null &&
320                       (mServiceState.getRadioTechnology() ==
321                           ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0 ||
322                        mServiceState.getRadioTechnology() ==
323                            ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A)) {
324                       int evdoEcio = signalStrength.getEvdoEcio();
325                       int evdoSnr = signalStrength.getEvdoSnr();
326                       int levelEvdoEcio = 0;
327                       int levelEvdoSnr = 0;
328 
329                       // Ec/Io are in dB*10
330                       if (evdoEcio >= -650) levelEvdoEcio = 4;
331                       else if (evdoEcio >= -750) levelEvdoEcio = 3;
332                       else if (evdoEcio >= -900) levelEvdoEcio = 2;
333                       else if (evdoEcio >= -1050) levelEvdoEcio = 1;
334                       else levelEvdoEcio = 0;
335 
336                       if (evdoSnr > 7) levelEvdoSnr = 4;
337                       else if (evdoSnr > 5) levelEvdoSnr = 3;
338                       else if (evdoSnr > 3) levelEvdoSnr = 2;
339                       else if (evdoSnr > 1) levelEvdoSnr = 1;
340                       else levelEvdoSnr = 0;
341 
342                       evdoIconLevel = (levelEvdoEcio < levelEvdoSnr) ? levelEvdoEcio : levelEvdoSnr;
343                 }
344                 // TODO(): There is a bug open regarding what should be sent.
345                 return (cdmaIconLevel > evdoIconLevel) ?  cdmaIconLevel : evdoIconLevel;
346             }
347         };
348         return mPhoneStateListener;
349     }
350 
351 }
352 
353 class HeadsetDeviceState {
354     int mService;
355     int mRoam;
356     int mSignal;
357     int mBatteryCharge;
358 
HeadsetDeviceState(int service, int roam, int signal, int batteryCharge)359     HeadsetDeviceState(int service, int roam, int signal, int batteryCharge) {
360         mService = service;
361         mRoam = roam;
362         mSignal = signal;
363         mBatteryCharge = batteryCharge;
364     }
365 }
366 
367 class HeadsetCallState {
368     int mNumActive;
369     int mNumHeld;
370     int mCallState;
371     String mNumber;
372     int mType;
373 
HeadsetCallState(int numActive, int numHeld, int callState, String number, int type)374     public HeadsetCallState(int numActive, int numHeld, int callState, String number, int type) {
375         mNumActive = numActive;
376         mNumHeld = numHeld;
377         mCallState = callState;
378         mNumber = number;
379         mType = type;
380     }
381 }
382 
383 class HeadsetClccResponse {
384     int mIndex;
385     int mDirection;
386     int mStatus;
387     int mMode;
388     boolean mMpty;
389     String mNumber;
390     int mType;
391 
HeadsetClccResponse(int index, int direction, int status, int mode, boolean mpty, String number, int type)392     public HeadsetClccResponse(int index, int direction, int status, int mode, boolean mpty,
393                                String number, int type) {
394         mIndex = index;
395         mDirection = direction;
396         mStatus = status;
397         mMode = mode;
398         mMpty = mpty;
399         mNumber = number;
400         mType = type;
401     }
402 }
403 
404 class HeadsetVendorSpecificResultCode {
405     BluetoothDevice mDevice;
406     String mCommand;
407     String mArg;
408 
HeadsetVendorSpecificResultCode(BluetoothDevice device, String command, String arg)409     public HeadsetVendorSpecificResultCode(BluetoothDevice device, String command, String arg) {
410         mDevice = device;
411         mCommand = command;
412         mArg = arg;
413     }
414 }
415