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