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