• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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