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