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.support.annotation.VisibleForTesting; 26 import android.telephony.PhoneStateListener; 27 import android.telephony.ServiceState; 28 import android.telephony.SignalStrength; 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.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 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 */ 99 public void cleanup() { 100 synchronized (mDeviceEventMap) { 101 mDeviceEventMap.clear(); 102 stopListenForPhoneState(); 103 } 104 mSubscriptionManager.removeOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener); 105 } 106 107 @Override 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 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 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 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(subId, 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 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 187 int getCindService() { 188 return mCindService; 189 } 190 191 int getNumActiveCall() { 192 return mNumActive; 193 } 194 195 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) 196 public void setNumActiveCall(int numActive) { 197 mNumActive = numActive; 198 } 199 200 int getCallState() { 201 return mCallState; 202 } 203 204 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) 205 public void setCallState(int callState) { 206 mCallState = callState; 207 } 208 209 int getNumHeldCall() { 210 return mNumHeld; 211 } 212 213 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) 214 public void setNumHeldCall(int numHeldCall) { 215 mNumHeld = numHeldCall; 216 } 217 218 int getCindSignal() { 219 return mCindSignal; 220 } 221 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(otherwise = VisibleForTesting.PACKAGE_PRIVATE) 232 public void setCindBatteryCharge(int batteryLevel) { 233 if (mCindBatteryCharge != batteryLevel) { 234 mCindBatteryCharge = batteryLevel; 235 sendDeviceStateChanged(); 236 } 237 } 238 239 int getCindBatteryCharge() { 240 return mCindBatteryCharge; 241 } 242 243 boolean isInCall() { 244 return (mNumActive >= 1); 245 } 246 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 { 263 HeadsetPhoneStateOnSubscriptionChangedListener(Looper looper) { 264 super(looper); 265 } 266 267 @Override 268 public void onSubscriptionsChanged() { 269 synchronized (mDeviceEventMap) { 270 stopListenForPhoneState(); 271 startListenForPhoneState(); 272 } 273 } 274 } 275 276 private class HeadsetPhoneStateListener extends PhoneStateListener { 277 HeadsetPhoneStateListener(Integer subId, Looper looper) { 278 super(subId, looper); 279 } 280 281 @Override 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 325 public void onSignalStrengthsChanged(SignalStrength signalStrength) { 326 327 int prevSignal = mCindSignal; 328 if (mCindService == HeadsetHalConstants.NETWORK_STATE_NOT_AVAILABLE) { 329 mCindSignal = 0; 330 } else if (signalStrength.isGsm()) { 331 mCindSignal = signalStrength.getLteLevel(); 332 if (mCindSignal == SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN) { 333 mCindSignal = gsmAsuToSignal(signalStrength); 334 } else { 335 // SignalStrength#getLteLevel returns the scale from 0-4 336 // Bluetooth signal scales at 0-5 337 // Let's match up the larger side 338 mCindSignal++; 339 } 340 } else { 341 mCindSignal = cdmaDbmEcioToSignal(signalStrength); 342 } 343 344 // network signal strength is scaled to BT 1-5 levels. 345 // This results in a lot of duplicate messages, hence this check 346 if (prevSignal != mCindSignal) { 347 sendDeviceStateChanged(); 348 } 349 } 350 351 /* convert [0,31] ASU signal strength to the [0,5] expected by 352 * bluetooth devices. Scale is similar to status bar policy 353 */ 354 private int gsmAsuToSignal(SignalStrength signalStrength) { 355 int asu = signalStrength.getGsmSignalStrength(); 356 if (asu == 99) { 357 return 0; 358 } else if (asu >= 16) { 359 return 5; 360 } else if (asu >= 8) { 361 return 4; 362 } else if (asu >= 4) { 363 return 3; 364 } else if (asu >= 2) { 365 return 2; 366 } else if (asu >= 1) { 367 return 1; 368 } else { 369 return 0; 370 } 371 } 372 373 /** 374 * Convert the cdma / evdo db levels to appropriate icon level. 375 * The scale is similar to the one used in status bar policy. 376 * 377 * @param signalStrength signal strength level 378 * @return the icon level for remote device 379 */ 380 private int cdmaDbmEcioToSignal(SignalStrength signalStrength) { 381 int levelDbm = 0; 382 int levelEcio = 0; 383 int cdmaIconLevel = 0; 384 int evdoIconLevel = 0; 385 int cdmaDbm = signalStrength.getCdmaDbm(); 386 int cdmaEcio = signalStrength.getCdmaEcio(); 387 388 if (cdmaDbm >= -75) { 389 levelDbm = 4; 390 } else if (cdmaDbm >= -85) { 391 levelDbm = 3; 392 } else if (cdmaDbm >= -95) { 393 levelDbm = 2; 394 } else if (cdmaDbm >= -100) { 395 levelDbm = 1; 396 } else { 397 levelDbm = 0; 398 } 399 400 // Ec/Io are in dB*10 401 if (cdmaEcio >= -90) { 402 levelEcio = 4; 403 } else if (cdmaEcio >= -110) { 404 levelEcio = 3; 405 } else if (cdmaEcio >= -130) { 406 levelEcio = 2; 407 } else if (cdmaEcio >= -150) { 408 levelEcio = 1; 409 } else { 410 levelEcio = 0; 411 } 412 413 cdmaIconLevel = (levelDbm < levelEcio) ? levelDbm : levelEcio; 414 415 // STOPSHIP: Change back to getRilVoiceRadioTechnology 416 if (mServiceState != null && ( 417 mServiceState.getRadioTechnology() == ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0 418 || mServiceState.getRadioTechnology() 419 == ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A)) { 420 int evdoEcio = signalStrength.getEvdoEcio(); 421 int evdoSnr = signalStrength.getEvdoSnr(); 422 int levelEvdoEcio = 0; 423 int levelEvdoSnr = 0; 424 425 // Ec/Io are in dB*10 426 if (evdoEcio >= -650) { 427 levelEvdoEcio = 4; 428 } else if (evdoEcio >= -750) { 429 levelEvdoEcio = 3; 430 } else if (evdoEcio >= -900) { 431 levelEvdoEcio = 2; 432 } else if (evdoEcio >= -1050) { 433 levelEvdoEcio = 1; 434 } else { 435 levelEvdoEcio = 0; 436 } 437 438 if (evdoSnr > 7) { 439 levelEvdoSnr = 4; 440 } else if (evdoSnr > 5) { 441 levelEvdoSnr = 3; 442 } else if (evdoSnr > 3) { 443 levelEvdoSnr = 2; 444 } else if (evdoSnr > 1) { 445 levelEvdoSnr = 1; 446 } else { 447 levelEvdoSnr = 0; 448 } 449 450 evdoIconLevel = (levelEvdoEcio < levelEvdoSnr) ? levelEvdoEcio : levelEvdoSnr; 451 } 452 // TODO(): There is a bug open regarding what should be sent. 453 return (cdmaIconLevel > evdoIconLevel) ? cdmaIconLevel : evdoIconLevel; 454 } 455 } 456 } 457