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