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