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