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