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