1 /* 2 * Copyright 2022 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.bas; 18 19 import static android.Manifest.permission.BLUETOOTH_CONNECT; 20 21 import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission; 22 23 import android.annotation.RequiresPermission; 24 import android.bluetooth.BluetoothDevice; 25 import android.bluetooth.BluetoothProfile; 26 import android.bluetooth.BluetoothUuid; 27 import android.bluetooth.IBluetoothBattery; 28 import android.content.AttributionSource; 29 import android.content.BroadcastReceiver; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.IntentFilter; 33 import android.os.HandlerThread; 34 import android.os.ParcelUuid; 35 import android.sysprop.BluetoothProperties; 36 import android.util.Log; 37 38 import com.android.bluetooth.Utils; 39 import com.android.bluetooth.btservice.AdapterService; 40 import com.android.bluetooth.btservice.ProfileService; 41 import com.android.bluetooth.btservice.storage.DatabaseManager; 42 import com.android.internal.annotations.VisibleForTesting; 43 import com.android.modules.utils.SynchronousResultReceiver; 44 45 import java.lang.ref.WeakReference; 46 import java.util.ArrayList; 47 import java.util.HashMap; 48 import java.util.List; 49 import java.util.Map; 50 import java.util.Objects; 51 52 /** 53 * A profile service that connects to the Battery service (BAS) of BLE devices 54 */ 55 public class BatteryService extends ProfileService { 56 private static final boolean DBG = false; 57 private static final String TAG = "BatteryService"; 58 59 // Timeout for state machine thread join, to prevent potential ANR. 60 private static final int SM_THREAD_JOIN_TIMEOUT_MS = 1_000; 61 62 private static final int MAX_BATTERY_STATE_MACHINES = 10; 63 private static BatteryService sBatteryService; 64 65 private AdapterService mAdapterService; 66 private DatabaseManager mDatabaseManager; 67 private HandlerThread mStateMachinesThread; 68 private final Map<BluetoothDevice, BatteryStateMachine> mStateMachines = new HashMap<>(); 69 70 private BroadcastReceiver mBondStateChangedReceiver; 71 isEnabled()72 public static boolean isEnabled() { 73 return BluetoothProperties.isProfileBasClientEnabled().orElse(false); 74 } 75 76 @Override initBinder()77 protected IProfileServiceBinder initBinder() { 78 return new BluetoothBatteryBinder(this); 79 } 80 81 @Override create()82 protected void create() { 83 if (DBG) { 84 Log.d(TAG, "create()"); 85 } 86 } 87 88 @Override start()89 protected boolean start() { 90 if (DBG) { 91 Log.d(TAG, "start()"); 92 } 93 if (sBatteryService != null) { 94 throw new IllegalStateException("start() called twice"); 95 } 96 97 mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(), 98 "AdapterService cannot be null when BatteryService starts"); 99 mDatabaseManager = Objects.requireNonNull(mAdapterService.getDatabase(), 100 "DatabaseManager cannot be null when BatteryService starts"); 101 102 mStateMachines.clear(); 103 mStateMachinesThread = new HandlerThread("BatteryService.StateMachines"); 104 mStateMachinesThread.start(); 105 106 // Setup broadcast receivers 107 IntentFilter filter = new IntentFilter(); 108 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 109 mBondStateChangedReceiver = new BondStateChangedReceiver(); 110 registerReceiver(mBondStateChangedReceiver, filter); 111 112 setBatteryService(this); 113 114 return true; 115 } 116 117 @Override stop()118 protected boolean stop() { 119 if (DBG) { 120 Log.d(TAG, "stop()"); 121 } 122 if (sBatteryService == null) { 123 Log.w(TAG, "stop() called before start()"); 124 return true; 125 } 126 127 setBatteryService(null); 128 // Unregister broadcast receivers 129 unregisterReceiver(mBondStateChangedReceiver); 130 mBondStateChangedReceiver = null; 131 132 // Destroy state machines and stop handler thread 133 synchronized (mStateMachines) { 134 for (BatteryStateMachine sm : mStateMachines.values()) { 135 sm.doQuit(); 136 sm.cleanup(); 137 } 138 mStateMachines.clear(); 139 } 140 141 142 if (mStateMachinesThread != null) { 143 try { 144 mStateMachinesThread.quitSafely(); 145 mStateMachinesThread.join(SM_THREAD_JOIN_TIMEOUT_MS); 146 mStateMachinesThread = null; 147 } catch (InterruptedException e) { 148 // Do not rethrow as we are shutting down anyway 149 } 150 } 151 152 mAdapterService = null; 153 154 return true; 155 } 156 157 @Override cleanup()158 protected void cleanup() { 159 if (DBG) { 160 Log.d(TAG, "cleanup()"); 161 } 162 } 163 164 /** 165 * Gets the BatteryService instance 166 */ getBatteryService()167 public static synchronized BatteryService getBatteryService() { 168 if (sBatteryService == null) { 169 Log.w(TAG, "getBatteryService(): service is NULL"); 170 return null; 171 } 172 173 if (!sBatteryService.isAvailable()) { 174 Log.w(TAG, "getBatteryService(): service is not available"); 175 return null; 176 } 177 return sBatteryService; 178 } 179 setBatteryService(BatteryService instance)180 private static synchronized void setBatteryService(BatteryService instance) { 181 if (DBG) { 182 Log.d(TAG, "setBatteryService(): set to: " + instance); 183 } 184 sBatteryService = instance; 185 } 186 187 /** 188 * Connects to the battery service of the given device. 189 */ 190 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) connect(BluetoothDevice device)191 public boolean connect(BluetoothDevice device) { 192 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 193 "Need BLUETOOTH_PRIVILEGED permission"); 194 if (DBG) { 195 Log.d(TAG, "connect(): " + device); 196 } 197 if (device == null) { 198 Log.w(TAG, "Ignore connecting to null device"); 199 return false; 200 } 201 202 if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 203 Log.w(TAG, "Cannot connect to " + device + " : policy forbidden"); 204 return false; 205 } 206 ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device); 207 if (!Utils.arrayContains(featureUuids, BluetoothUuid.BATTERY)) { 208 Log.e(TAG, "Cannot connect to " + device 209 + " : Remote does not have Battery UUID"); 210 return false; 211 } 212 213 synchronized (mStateMachines) { 214 BatteryStateMachine sm = getOrCreateStateMachine(device); 215 if (sm == null) { 216 Log.e(TAG, "Cannot connect to " + device + " : no state machine"); 217 return false; 218 } 219 sm.sendMessage(BatteryStateMachine.CONNECT); 220 } 221 222 return true; 223 } 224 225 /** 226 * Disconnects from the battery service of the given device. 227 */ 228 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) disconnect(BluetoothDevice device)229 public boolean disconnect(BluetoothDevice device) { 230 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 231 "Need BLUETOOTH_PRIVILEGED permission"); 232 if (DBG) { 233 Log.d(TAG, "disconnect(): " + device); 234 } 235 if (device == null) { 236 Log.w(TAG, "Ignore disconnecting to null device"); 237 return false; 238 } 239 synchronized (mStateMachines) { 240 BatteryStateMachine sm = getOrCreateStateMachine(device); 241 if (sm != null) { 242 sm.sendMessage(BatteryStateMachine.DISCONNECT); 243 } 244 } 245 246 return true; 247 } 248 249 /** 250 * Gets devices that battery service is connected. 251 * @return 252 */ 253 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) getConnectedDevices()254 public List<BluetoothDevice> getConnectedDevices() { 255 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 256 "Need BLUETOOTH_PRIVILEGED permission"); 257 synchronized (mStateMachines) { 258 List<BluetoothDevice> devices = new ArrayList<>(); 259 for (BatteryStateMachine sm : mStateMachines.values()) { 260 if (sm.isConnected()) { 261 devices.add(sm.getDevice()); 262 } 263 } 264 return devices; 265 } 266 } 267 268 /** 269 * Check whether it can connect to a peer device. 270 * The check considers a number of factors during the evaluation. 271 * 272 * @param device the peer device to connect to 273 * @return true if connection is allowed, otherwise false 274 */ 275 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) canConnect(BluetoothDevice device)276 public boolean canConnect(BluetoothDevice device) { 277 // Check connectionPolicy and accept or reject the connection. 278 int connectionPolicy = getConnectionPolicy(device); 279 int bondState = mAdapterService.getBondState(device); 280 // Allow this connection only if the device is bonded. Any attempt to connect while 281 // bonding would potentially lead to an unauthorized connection. 282 if (bondState != BluetoothDevice.BOND_BONDED) { 283 Log.w(TAG, "canConnect: return false, bondState=" + bondState); 284 return false; 285 } else if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN 286 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 287 // Otherwise, reject the connection if connectionPolicy is not valid. 288 Log.w(TAG, "canConnect: return false, connectionPolicy=" + connectionPolicy); 289 return false; 290 } 291 return true; 292 } 293 294 /** 295 * Called when the connection state of a state machine is changed 296 */ 297 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) handleConnectionStateChanged(BatteryStateMachine sm, int fromState, int toState)298 public void handleConnectionStateChanged(BatteryStateMachine sm, 299 int fromState, int toState) { 300 BluetoothDevice device = sm.getDevice(); 301 if ((sm == null) || (fromState == toState)) { 302 Log.e(TAG, "connectionStateChanged: unexpected invocation. device=" + device 303 + " fromState=" + fromState + " toState=" + toState); 304 return; 305 } 306 307 // Check if the device is disconnected - if unbonded, remove the state machine 308 if (toState == BluetoothProfile.STATE_DISCONNECTED) { 309 int bondState = mAdapterService.getBondState(device); 310 if (bondState == BluetoothDevice.BOND_NONE) { 311 if (DBG) { 312 Log.d(TAG, device + " is unbonded. Remove state machine"); 313 } 314 removeStateMachine(device); 315 } 316 } 317 } 318 319 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) getDevicesMatchingConnectionStates(int[] states)320 List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { 321 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 322 "Need BLUETOOTH_PRIVILEGED permission"); 323 ArrayList<BluetoothDevice> devices = new ArrayList<>(); 324 if (states == null) { 325 return devices; 326 } 327 final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices(); 328 if (bondedDevices == null) { 329 return devices; 330 } 331 synchronized (mStateMachines) { 332 for (BluetoothDevice device : bondedDevices) { 333 int connectionState = BluetoothProfile.STATE_DISCONNECTED; 334 BatteryStateMachine sm = mStateMachines.get(device); 335 if (sm != null) { 336 connectionState = sm.getConnectionState(); 337 } 338 for (int state : states) { 339 if (connectionState == state) { 340 devices.add(device); 341 break; 342 } 343 } 344 } 345 return devices; 346 } 347 } 348 349 /** 350 * Get the list of devices that have state machines. 351 * 352 * @return the list of devices that have state machines 353 */ 354 @VisibleForTesting getDevices()355 List<BluetoothDevice> getDevices() { 356 List<BluetoothDevice> devices = new ArrayList<>(); 357 synchronized (mStateMachines) { 358 for (BatteryStateMachine sm : mStateMachines.values()) { 359 devices.add(sm.getDevice()); 360 } 361 return devices; 362 } 363 } 364 365 /** 366 * Gets the connection state of the given device's battery service 367 */ 368 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getConnectionState(BluetoothDevice device)369 public int getConnectionState(BluetoothDevice device) { 370 enforceCallingOrSelfPermission(BLUETOOTH_CONNECT, 371 "Need BLUETOOTH_CONNECT permission"); 372 synchronized (mStateMachines) { 373 BatteryStateMachine sm = mStateMachines.get(device); 374 if (sm == null) { 375 return BluetoothProfile.STATE_DISCONNECTED; 376 } 377 return sm.getConnectionState(); 378 } 379 } 380 381 /** 382 * Set connection policy of the profile and connects it if connectionPolicy is 383 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is 384 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN} 385 * 386 * <p> The device should already be paired. 387 * Connection policy can be one of: 388 * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}, 389 * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}, 390 * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN} 391 * 392 * @param device the remote device 393 * @param connectionPolicy is the connection policy to set to for this profile 394 * @return true on success, otherwise false 395 */ 396 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) setConnectionPolicy(BluetoothDevice device, int connectionPolicy)397 public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) { 398 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 399 "Need BLUETOOTH_PRIVILEGED permission"); 400 if (DBG) { 401 Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy); 402 } 403 mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.BATTERY, 404 connectionPolicy); 405 if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) { 406 connect(device); 407 } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 408 disconnect(device); 409 } 410 return true; 411 } 412 413 /** 414 * Gets the connection policy for the battery service of the given device. 415 */ 416 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) getConnectionPolicy(BluetoothDevice device)417 public int getConnectionPolicy(BluetoothDevice device) { 418 enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, 419 "Need BLUETOOTH_PRIVILEGED permission"); 420 return mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.BATTERY); 421 } 422 /** 423 * Called when the battery level of the device is notified. 424 */ 425 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) handleBatteryChanged(BluetoothDevice device, int batteryLevel)426 public void handleBatteryChanged(BluetoothDevice device, int batteryLevel) { 427 mAdapterService.setBatteryLevel(device, batteryLevel); 428 } 429 getOrCreateStateMachine(BluetoothDevice device)430 private BatteryStateMachine getOrCreateStateMachine(BluetoothDevice device) { 431 if (device == null) { 432 Log.e(TAG, "getOrCreateGatt failed: device cannot be null"); 433 return null; 434 } 435 synchronized (mStateMachines) { 436 BatteryStateMachine sm = mStateMachines.get(device); 437 if (sm != null) { 438 return sm; 439 } 440 // Limit the maximum number of state machines to avoid DoS attack 441 if (mStateMachines.size() >= MAX_BATTERY_STATE_MACHINES) { 442 Log.e(TAG, "Maximum number of Battery state machines reached: " 443 + MAX_BATTERY_STATE_MACHINES); 444 return null; 445 } 446 if (DBG) { 447 Log.d(TAG, "Creating a new state machine for " + device); 448 } 449 sm = BatteryStateMachine.make(device, this, mStateMachinesThread.getLooper()); 450 mStateMachines.put(device, sm); 451 return sm; 452 } 453 } 454 455 // Remove state machine if the bonding for a device is removed 456 private class BondStateChangedReceiver extends BroadcastReceiver { 457 @Override onReceive(Context context, Intent intent)458 public void onReceive(Context context, Intent intent) { 459 if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { 460 return; 461 } 462 int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, 463 BluetoothDevice.ERROR); 464 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 465 Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE"); 466 handleBondStateChanged(device, state); 467 } 468 } 469 470 /** 471 * Process a change in the bonding state for a device. 472 * 473 * @param device the device whose bonding state has changed 474 * @param bondState the new bond state for the device. Possible values are: 475 * {@link BluetoothDevice#BOND_NONE}, 476 * {@link BluetoothDevice#BOND_BONDING}, 477 * {@link BluetoothDevice#BOND_BONDED}, 478 * {@link BluetoothDevice#ERROR}. 479 */ 480 @VisibleForTesting handleBondStateChanged(BluetoothDevice device, int bondState)481 void handleBondStateChanged(BluetoothDevice device, int bondState) { 482 if (DBG) { 483 Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState); 484 } 485 // Remove state machine if the bonding for a device is removed 486 if (bondState != BluetoothDevice.BOND_NONE) { 487 return; 488 } 489 490 synchronized (mStateMachines) { 491 BatteryStateMachine sm = mStateMachines.get(device); 492 if (sm == null) { 493 return; 494 } 495 if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) { 496 return; 497 } 498 removeStateMachine(device); 499 } 500 } 501 removeStateMachine(BluetoothDevice device)502 private void removeStateMachine(BluetoothDevice device) { 503 if (device == null) { 504 Log.e(TAG, "removeStateMachine failed: device cannot be null"); 505 return; 506 } 507 synchronized (mStateMachines) { 508 BatteryStateMachine sm = mStateMachines.remove(device); 509 if (sm == null) { 510 Log.w(TAG, "removeStateMachine: device " + device 511 + " does not have a state machine"); 512 return; 513 } 514 Log.i(TAG, "removeGatt: removing bluetooth gatt for device: " + device); 515 sm.doQuit(); 516 sm.cleanup(); 517 } 518 } 519 520 /** 521 * Binder object: must be a static class or memory leak may occur 522 */ 523 @VisibleForTesting 524 static class BluetoothBatteryBinder extends IBluetoothBattery.Stub 525 implements IProfileServiceBinder { 526 private final WeakReference<BatteryService> mServiceRef; 527 528 @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) getService(AttributionSource source)529 private BatteryService getService(AttributionSource source) { 530 BatteryService service = mServiceRef.get(); 531 if (Utils.isInstrumentationTestMode()) { 532 return service; 533 } 534 535 if (!Utils.checkServiceAvailable(service, TAG) 536 || !Utils.checkCallerIsSystemOrActiveOrManagedUser(service, TAG) 537 || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) { 538 return null; 539 } 540 return service; 541 } 542 BluetoothBatteryBinder(BatteryService svc)543 BluetoothBatteryBinder(BatteryService svc) { 544 mServiceRef = new WeakReference<>(svc); 545 } 546 547 @Override cleanup()548 public void cleanup() { 549 mServiceRef.clear(); 550 } 551 552 @Override connect(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)553 public void connect(BluetoothDevice device, AttributionSource source, 554 SynchronousResultReceiver receiver) { 555 try { 556 BatteryService service = getService(source); 557 boolean result = false; 558 if (service != null) { 559 result = service.connect(device); 560 } 561 receiver.send(result); 562 } catch (RuntimeException e) { 563 receiver.propagateException(e); 564 } 565 } 566 567 @Override disconnect(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)568 public void disconnect(BluetoothDevice device, AttributionSource source, 569 SynchronousResultReceiver receiver) { 570 try { 571 BatteryService service = getService(source); 572 boolean result = false; 573 if (service != null) { 574 result = service.disconnect(device); 575 } 576 receiver.send(result); 577 } catch (RuntimeException e) { 578 receiver.propagateException(e); 579 } 580 } 581 582 @Override getConnectedDevices(AttributionSource source, SynchronousResultReceiver receiver)583 public void getConnectedDevices(AttributionSource source, 584 SynchronousResultReceiver receiver) { 585 try { 586 BatteryService service = getService(source); 587 List<BluetoothDevice> result = new ArrayList<>(); 588 if (service != null) { 589 enforceBluetoothPrivilegedPermission(service); 590 result = service.getConnectedDevices(); 591 } 592 receiver.send(result); 593 } catch (RuntimeException e) { 594 receiver.propagateException(e); 595 } 596 } 597 598 @Override getDevicesMatchingConnectionStates(int[] states, AttributionSource source, SynchronousResultReceiver receiver)599 public void getDevicesMatchingConnectionStates(int[] states, 600 AttributionSource source, SynchronousResultReceiver receiver) { 601 try { 602 BatteryService service = getService(source); 603 List<BluetoothDevice> result = new ArrayList<>(); 604 if (service != null) { 605 result = service.getDevicesMatchingConnectionStates(states); 606 } 607 receiver.send(result); 608 } catch (RuntimeException e) { 609 receiver.propagateException(e); 610 } 611 } 612 613 @Override getConnectionState(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)614 public void getConnectionState(BluetoothDevice device, AttributionSource source, 615 SynchronousResultReceiver receiver) { 616 try { 617 BatteryService service = getService(source); 618 int result = BluetoothProfile.STATE_DISCONNECTED; 619 if (service != null) { 620 result = service.getConnectionState(device); 621 } 622 receiver.send(result); 623 } catch (RuntimeException e) { 624 receiver.propagateException(e); 625 } 626 } 627 628 @Override setConnectionPolicy(BluetoothDevice device, int connectionPolicy, AttributionSource source, SynchronousResultReceiver receiver)629 public void setConnectionPolicy(BluetoothDevice device, int connectionPolicy, 630 AttributionSource source, SynchronousResultReceiver receiver) { 631 try { 632 BatteryService service = getService(source); 633 boolean result = false; 634 if (service != null) { 635 result = service.setConnectionPolicy(device, connectionPolicy); 636 } 637 receiver.send(result); 638 } catch (RuntimeException e) { 639 receiver.propagateException(e); 640 } 641 } 642 643 @Override getConnectionPolicy(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)644 public void getConnectionPolicy(BluetoothDevice device, AttributionSource source, 645 SynchronousResultReceiver receiver) { 646 try { 647 BatteryService service = getService(source); 648 int result = BluetoothProfile.CONNECTION_POLICY_UNKNOWN; 649 if (service != null) { 650 result = service.getConnectionPolicy(device); 651 } 652 receiver.send(result); 653 } catch (RuntimeException e) { 654 receiver.propagateException(e); 655 } 656 } 657 } 658 659 @Override dump(StringBuilder sb)660 public void dump(StringBuilder sb) { 661 super.dump(sb); 662 for (BatteryStateMachine sm : mStateMachines.values()) { 663 sm.dump(sb); 664 } 665 } 666 } 667