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.Context;
21 import android.os.Handler;
22 import android.telephony.PhoneStateListener;
23 import android.telephony.ServiceState;
24 import android.telephony.SignalStrength;
25 import android.telephony.SubscriptionManager;
26 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
27 import android.telephony.TelephonyManager;
28 import android.util.Log;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 
32 import java.util.HashMap;
33 import java.util.Objects;
34 import java.util.concurrent.Executor;
35 
36 
37 /**
38  * Class that manages Telephony states
39  *
40  * Note:
41  * The methods in this class are not thread safe, don't call them from
42  * multiple threads. Call them from the HeadsetPhoneStateMachine message
43  * handler only.
44  */
45 public class HeadsetPhoneState {
46     private static final String TAG = "HeadsetPhoneState";
47 
48     private final HeadsetService mHeadsetService;
49     private final TelephonyManager mTelephonyManager;
50     private final SubscriptionManager mSubscriptionManager;
51     private final Handler mHandler;
52 
53     private ServiceState mServiceState;
54 
55     // HFP 1.6 CIND service value
56     private int mCindService = HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
57     // Number of active (foreground) calls
58     private int mNumActive;
59     // Current Call Setup State
60     private int mCallState = HeadsetHalConstants.CALL_STATE_IDLE;
61     // Number of held (background) calls
62     private int mNumHeld;
63     // HFP 1.6 CIND signal value
64     private int mCindSignal;
65     // HFP 1.6 CIND roam value
66     private int mCindRoam = HeadsetHalConstants.SERVICE_TYPE_HOME;
67     // HFP 1.6 CIND battchg value
68     private int mCindBatteryCharge;
69 
70     private final HashMap<BluetoothDevice, Integer> mDeviceEventMap = new HashMap<>();
71     private PhoneStateListener mPhoneStateListener;
72     private final OnSubscriptionsChangedListener mOnSubscriptionsChangedListener;
73 
HeadsetPhoneState(HeadsetService headsetService)74     HeadsetPhoneState(HeadsetService headsetService) {
75         Objects.requireNonNull(headsetService, "headsetService is null");
76         mHeadsetService = headsetService;
77         mTelephonyManager =
78                 (TelephonyManager) mHeadsetService.getSystemService(Context.TELEPHONY_SERVICE);
79         Objects.requireNonNull(mTelephonyManager, "TELEPHONY_SERVICE is null");
80         // Register for SubscriptionInfo list changes which is guaranteed to invoke
81         // onSubscriptionInfoChanged and which in turns calls loadInBackgroud.
82         mSubscriptionManager = SubscriptionManager.from(mHeadsetService);
83         Objects.requireNonNull(mSubscriptionManager, "TELEPHONY_SUBSCRIPTION_SERVICE is null");
84         // Initialize subscription on the handler thread
85         mHandler = new Handler(headsetService.getStateMachinesThreadLooper());
86         mOnSubscriptionsChangedListener = new HeadsetPhoneStateOnSubscriptionChangedListener();
87         mSubscriptionManager.addOnSubscriptionsChangedListener(command -> mHandler.post(command),
88                 mOnSubscriptionsChangedListener);
89     }
90 
91     /**
92      * Cleanup this instance. Instance can no longer be used after calling this method.
93      */
cleanup()94     public void cleanup() {
95         synchronized (mDeviceEventMap) {
96             mDeviceEventMap.clear();
97             stopListenForPhoneState();
98         }
99         mSubscriptionManager.removeOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
100     }
101 
102     @Override
toString()103     public String toString() {
104         return "HeadsetPhoneState [mTelephonyServiceAvailability=" + mCindService + ", mNumActive="
105                 + mNumActive + ", mCallState=" + mCallState + ", mNumHeld=" + mNumHeld
106                 + ", mSignal=" + mCindSignal + ", mRoam=" + mCindRoam + ", mBatteryCharge="
107                 + mCindBatteryCharge + ", TelephonyEvents=" + getTelephonyEventsToListen() + "]";
108     }
109 
getTelephonyEventsToListen()110     private int getTelephonyEventsToListen() {
111         synchronized (mDeviceEventMap) {
112             return mDeviceEventMap.values()
113                     .stream()
114                     .reduce(PhoneStateListener.LISTEN_NONE, (a, b) -> a | b);
115         }
116     }
117 
118     /**
119      * Start or stop listening for phone state change
120      *
121      * @param device remote device that subscribes to this phone state update
122      * @param events events in {@link PhoneStateListener} to listen to
123      */
124     @VisibleForTesting
listenForPhoneState(BluetoothDevice device, int events)125     public void listenForPhoneState(BluetoothDevice device, int events) {
126         synchronized (mDeviceEventMap) {
127             int prevEvents = getTelephonyEventsToListen();
128             if (events == PhoneStateListener.LISTEN_NONE) {
129                 mDeviceEventMap.remove(device);
130             } else {
131                 mDeviceEventMap.put(device, events);
132             }
133             int updatedEvents = getTelephonyEventsToListen();
134             if (prevEvents != updatedEvents) {
135                 stopListenForPhoneState();
136                 startListenForPhoneState();
137             }
138         }
139     }
140 
startListenForPhoneState()141     private void startListenForPhoneState() {
142         if (mPhoneStateListener != null) {
143             Log.w(TAG, "startListenForPhoneState, already listening");
144             return;
145         }
146         int events = getTelephonyEventsToListen();
147         if (events == PhoneStateListener.LISTEN_NONE) {
148             Log.w(TAG, "startListenForPhoneState, no event to listen");
149             return;
150         }
151         int subId = SubscriptionManager.getDefaultSubscriptionId();
152         if (!SubscriptionManager.isValidSubscriptionId(subId)) {
153             // Will retry listening for phone state in onSubscriptionsChanged() callback
154             Log.w(TAG, "startListenForPhoneState, invalid subscription ID " + subId);
155             return;
156         }
157         Log.i(TAG, "startListenForPhoneState(), subId=" + subId + ", enabled_events=" + events);
158         mPhoneStateListener = new HeadsetPhoneStateListener(command -> mHandler.post(command));
159         mTelephonyManager.listen(mPhoneStateListener, events);
160     }
161 
stopListenForPhoneState()162     private void stopListenForPhoneState() {
163         if (mPhoneStateListener == null) {
164             Log.i(TAG, "stopListenForPhoneState(), no listener indicates nothing is listening");
165             return;
166         }
167         Log.i(TAG, "stopListenForPhoneState(), stopping listener, enabled_events="
168                 + getTelephonyEventsToListen());
169         mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
170         mPhoneStateListener = null;
171     }
172 
getCindService()173     int getCindService() {
174         return mCindService;
175     }
176 
getNumActiveCall()177     int getNumActiveCall() {
178         return mNumActive;
179     }
180 
181     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
setNumActiveCall(int numActive)182     public void setNumActiveCall(int numActive) {
183         mNumActive = numActive;
184     }
185 
getCallState()186     int getCallState() {
187         return mCallState;
188     }
189 
190     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
setCallState(int callState)191     public void setCallState(int callState) {
192         mCallState = callState;
193     }
194 
getNumHeldCall()195     int getNumHeldCall() {
196         return mNumHeld;
197     }
198 
199     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
setNumHeldCall(int numHeldCall)200     public void setNumHeldCall(int numHeldCall) {
201         mNumHeld = numHeldCall;
202     }
203 
getServiceState()204     ServiceState getServiceState() {
205         return mServiceState;
206     }
207 
getCindSignal()208     int getCindSignal() {
209         return mCindSignal;
210     }
211 
getCindRoam()212     int getCindRoam() {
213         return mCindRoam;
214     }
215 
216     /**
217      * Set battery level value used for +CIND result
218      *
219      * @param batteryLevel battery level value
220      */
221     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
setCindBatteryCharge(int batteryLevel)222     public void setCindBatteryCharge(int batteryLevel) {
223         if (mCindBatteryCharge != batteryLevel) {
224             mCindBatteryCharge = batteryLevel;
225             sendDeviceStateChanged();
226         }
227     }
228 
getCindBatteryCharge()229     int getCindBatteryCharge() {
230         return mCindBatteryCharge;
231     }
232 
isInCall()233     boolean isInCall() {
234         return (mNumActive >= 1);
235     }
236 
sendDeviceStateChanged()237     private synchronized void sendDeviceStateChanged() {
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 = mCindService == HeadsetHalConstants.NETWORK_STATE_AVAILABLE ? mCindSignal : 0;
241 
242         Log.d(TAG, "sendDeviceStateChanged. mService=" + mCindService
243                 + " mSignal=" + mCindSignal + " mRoam=" + mCindRoam
244                 + " mBatteryCharge=" + mCindBatteryCharge);
245         mHeadsetService.onDeviceStateChanged(
246                 new HeadsetDeviceState(mCindService, mCindRoam, signal, mCindBatteryCharge));
247     }
248 
249     private class HeadsetPhoneStateOnSubscriptionChangedListener
250             extends OnSubscriptionsChangedListener {
HeadsetPhoneStateOnSubscriptionChangedListener()251         HeadsetPhoneStateOnSubscriptionChangedListener() {
252             super();
253         }
254 
255         @Override
onSubscriptionsChanged()256         public void onSubscriptionsChanged() {
257             synchronized (mDeviceEventMap) {
258                 int simState = mTelephonyManager.getSimState();
259                 if (simState != TelephonyManager.SIM_STATE_READY) {
260                     mServiceState = null;
261                     mCindSignal = 0;
262                     mCindService = HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
263                     sendDeviceStateChanged();
264                 }
265                 stopListenForPhoneState();
266                 startListenForPhoneState();
267             }
268         }
269     }
270 
271     private class HeadsetPhoneStateListener extends PhoneStateListener {
HeadsetPhoneStateListener(Executor executor)272         HeadsetPhoneStateListener(Executor executor) {
273             super(executor);
274         }
275 
276         @Override
onServiceStateChanged(ServiceState serviceState)277         public synchronized void onServiceStateChanged(ServiceState serviceState) {
278             mServiceState = serviceState;
279             int cindService = (serviceState.getState() == ServiceState.STATE_IN_SERVICE)
280                     ? HeadsetHalConstants.NETWORK_STATE_AVAILABLE
281                     : HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE;
282             int newRoam = serviceState.getRoaming() ? HeadsetHalConstants.SERVICE_TYPE_ROAMING
283                     : HeadsetHalConstants.SERVICE_TYPE_HOME;
284 
285             if (cindService == mCindService && newRoam == mCindRoam) {
286                 // De-bounce the state change
287                 return;
288             }
289             mCindService = cindService;
290             mCindRoam = newRoam;
291             sendDeviceStateChanged();
292         }
293 
294         @Override
onSignalStrengthsChanged(SignalStrength signalStrength)295         public void onSignalStrengthsChanged(SignalStrength signalStrength) {
296             int prevSignal = mCindSignal;
297             if (mCindService == HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE) {
298                 mCindSignal = 0;
299             } else {
300                 mCindSignal = signalStrength.getLevel() + 1;
301             }
302             // +CIND "signal" indicator is always between 0 to 5
303             mCindSignal = Integer.max(Integer.min(mCindSignal, 5), 0);
304             // This results in a lot of duplicate messages, hence this check
305             if (prevSignal != mCindSignal) {
306                 sendDeviceStateChanged();
307             }
308         }
309     }
310 }
311