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