• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 HIMSA II K/S - www.himsa.com.
3  * Represented by EHIMA - www.ehima.com
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 package com.android.bluetooth.vc;
19 
20 import static android.Manifest.permission.BLUETOOTH_CONNECT;
21 
22 import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;
23 
24 import android.annotation.RequiresPermission;
25 import android.bluetooth.BluetoothDevice;
26 import android.bluetooth.BluetoothProfile;
27 import android.bluetooth.BluetoothUuid;
28 import android.bluetooth.BluetoothVolumeControl;
29 import android.bluetooth.IBluetoothCsipSetCoordinator;
30 import android.bluetooth.IBluetoothLeAudio;
31 import android.bluetooth.IBluetoothVolumeControl;
32 import android.bluetooth.IBluetoothVolumeControlCallback;
33 import android.content.AttributionSource;
34 import android.content.BroadcastReceiver;
35 import android.content.Context;
36 import android.content.Intent;
37 import android.content.IntentFilter;
38 import android.media.AudioManager;
39 import android.os.HandlerThread;
40 import android.os.ParcelUuid;
41 import android.os.RemoteCallbackList;
42 import android.os.RemoteException;
43 import android.sysprop.BluetoothProperties;
44 import android.util.Log;
45 
46 import com.android.bluetooth.Utils;
47 import com.android.bluetooth.btservice.AdapterService;
48 import com.android.bluetooth.btservice.ProfileService;
49 import com.android.bluetooth.btservice.ServiceFactory;
50 import com.android.bluetooth.btservice.storage.DatabaseManager;
51 import com.android.bluetooth.csip.CsipSetCoordinatorService;
52 import com.android.bluetooth.le_audio.LeAudioService;
53 import com.android.internal.annotations.VisibleForTesting;
54 import com.android.modules.utils.SynchronousResultReceiver;
55 
56 import java.util.ArrayList;
57 import java.util.HashMap;
58 import java.util.List;
59 import java.util.Map;
60 import java.util.Objects;
61 
62 public class VolumeControlService extends ProfileService {
63     private static final boolean DBG = false;
64     private static final String TAG = "VolumeControlService";
65 
66     // Timeout for state machine thread join, to prevent potential ANR.
67     private static final int SM_THREAD_JOIN_TIMEOUT_MS = 1000;
68 
69     // Upper limit of all VolumeControl devices: Bonded or Connected
70     private static final int MAX_VC_STATE_MACHINES = 10;
71     private static final int LE_AUDIO_MAX_VOL = 255;
72     private static final int LE_AUDIO_MIN_VOL = 0;
73 
74     private static VolumeControlService sVolumeControlService;
75 
76     private AdapterService mAdapterService;
77     private DatabaseManager mDatabaseManager;
78     private HandlerThread mStateMachinesThread;
79     private BluetoothDevice mPreviousAudioDevice;
80 
81     @VisibleForTesting
82     RemoteCallbackList<IBluetoothVolumeControlCallback> mCallbacks;
83 
84     @VisibleForTesting
85     static class VolumeControlOffsetDescriptor {
86         Map<Integer, Descriptor> mVolumeOffsets;
87 
88         private class Descriptor {
Descriptor()89             Descriptor() {
90                 mValue = 0;
91                 mLocation = 0;
92                 mDescription = null;
93             }
94             int mValue;
95             int mLocation;
96             String mDescription;
97         };
98 
VolumeControlOffsetDescriptor()99         VolumeControlOffsetDescriptor() {
100             mVolumeOffsets = new HashMap<>();
101         }
102 
size()103         int size() {
104             return mVolumeOffsets.size();
105         }
106 
add(int id)107         void add(int id) {
108             Descriptor d = mVolumeOffsets.get(id);
109             if (d == null) {
110                 mVolumeOffsets.put(id, new Descriptor());
111             }
112         }
113 
setValue(int id, int value)114         boolean setValue(int id, int value) {
115             Descriptor d = mVolumeOffsets.get(id);
116             if (d == null) {
117                 return false;
118             }
119             d.mValue = value;
120             return true;
121         }
122 
getValue(int id)123         int getValue(int id) {
124             Descriptor d = mVolumeOffsets.get(id);
125             if (d == null) {
126                 return 0;
127             }
128             return d.mValue;
129         }
130 
setDescription(int id, String desc)131         boolean setDescription(int id, String desc) {
132             Descriptor d = mVolumeOffsets.get(id);
133             if (d == null) {
134                 return false;
135             }
136             d.mDescription = desc;
137             return true;
138         }
139 
getDescription(int id)140         String getDescription(int id) {
141             Descriptor d = mVolumeOffsets.get(id);
142             if (d == null) {
143                 return null;
144             }
145             return d.mDescription;
146         }
147 
setLocation(int id, int location)148         boolean setLocation(int id, int location) {
149             Descriptor d = mVolumeOffsets.get(id);
150             if (d == null) {
151                 return false;
152             }
153             d.mLocation = location;
154             return true;
155         }
156 
getLocation(int id)157         int getLocation(int id) {
158             Descriptor d = mVolumeOffsets.get(id);
159             if (d == null) {
160                 return 0;
161             }
162             return d.mLocation;
163         }
164 
remove(int id)165         void remove(int id) {
166             mVolumeOffsets.remove(id);
167         }
168 
clear()169         void clear() {
170             mVolumeOffsets.clear();
171         }
172 
dump(StringBuilder sb)173         void dump(StringBuilder sb) {
174             for (Map.Entry<Integer, Descriptor> entry : mVolumeOffsets.entrySet()) {
175                 Descriptor descriptor = entry.getValue();
176                 Integer id = entry.getKey();
177                 ProfileService.println(sb, "        Id: " + id);
178                 ProfileService.println(sb, "        value: " + descriptor.mValue);
179                 ProfileService.println(sb, "        location: " + descriptor.mLocation);
180                 ProfileService.println(sb, "        description: " + descriptor.mDescription);
181             }
182         }
183     }
184 
185     @VisibleForTesting
186     VolumeControlNativeInterface mVolumeControlNativeInterface;
187     @VisibleForTesting
188     AudioManager mAudioManager;
189 
190     private final Map<BluetoothDevice, VolumeControlStateMachine> mStateMachines = new HashMap<>();
191     private final Map<BluetoothDevice, VolumeControlOffsetDescriptor> mAudioOffsets =
192                                                                             new HashMap<>();
193     private final Map<Integer, Integer> mGroupVolumeCache = new HashMap<>();
194     private final Map<Integer, Boolean> mGroupMuteCache = new HashMap<>();
195 
196     private BroadcastReceiver mBondStateChangedReceiver;
197     private BroadcastReceiver mConnectionStateChangedReceiver;
198 
199     @VisibleForTesting
200     ServiceFactory mFactory = new ServiceFactory();
201 
isEnabled()202     public static boolean isEnabled() {
203         return BluetoothProperties.isProfileVcpControllerEnabled().orElse(false);
204     }
205 
206     @Override
initBinder()207     protected IProfileServiceBinder initBinder() {
208         return new BluetoothVolumeControlBinder(this);
209     }
210 
211     @Override
create()212     protected void create() {
213         if (DBG) {
214             Log.d(TAG, "create()");
215         }
216     }
217 
218     @Override
start()219     protected boolean start() {
220         if (DBG) {
221             Log.d(TAG, "start()");
222         }
223         if (sVolumeControlService != null) {
224             throw new IllegalStateException("start() called twice");
225         }
226 
227         // Get AdapterService, VolumeControlNativeInterface, DatabaseManager, AudioManager.
228         // None of them can be null.
229         mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
230                 "AdapterService cannot be null when VolumeControlService starts");
231         mDatabaseManager = Objects.requireNonNull(mAdapterService.getDatabase(),
232                 "DatabaseManager cannot be null when VolumeControlService starts");
233         mVolumeControlNativeInterface = Objects.requireNonNull(
234                 VolumeControlNativeInterface.getInstance(),
235                 "VolumeControlNativeInterface cannot be null when VolumeControlService starts");
236         mAudioManager =  getSystemService(AudioManager.class);
237         Objects.requireNonNull(mAudioManager,
238                 "AudioManager cannot be null when VolumeControlService starts");
239 
240         // Start handler thread for state machines
241         mStateMachines.clear();
242         mStateMachinesThread = new HandlerThread("VolumeControlService.StateMachines");
243         mStateMachinesThread.start();
244 
245         // Setup broadcast receivers
246         IntentFilter filter = new IntentFilter();
247         filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
248         filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
249         mBondStateChangedReceiver = new BondStateChangedReceiver();
250         registerReceiver(mBondStateChangedReceiver, filter);
251         filter = new IntentFilter();
252         filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
253         filter.addAction(BluetoothVolumeControl.ACTION_CONNECTION_STATE_CHANGED);
254         mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver();
255         registerReceiver(mConnectionStateChangedReceiver, filter);
256 
257         mAudioOffsets.clear();
258         mGroupVolumeCache.clear();
259         mGroupMuteCache.clear();
260         mCallbacks = new RemoteCallbackList<IBluetoothVolumeControlCallback>();
261 
262         // Mark service as started
263         setVolumeControlService(this);
264 
265         // Initialize native interface
266         mVolumeControlNativeInterface.init();
267 
268         return true;
269     }
270 
271     @Override
stop()272     protected boolean stop() {
273         if (DBG) {
274             Log.d(TAG, "stop()");
275         }
276         if (sVolumeControlService == null) {
277             Log.w(TAG, "stop() called before start()");
278             return true;
279         }
280 
281         // Mark service as stopped
282         setVolumeControlService(null);
283 
284         // Unregister broadcast receivers
285         unregisterReceiver(mBondStateChangedReceiver);
286         mBondStateChangedReceiver = null;
287         unregisterReceiver(mConnectionStateChangedReceiver);
288         mConnectionStateChangedReceiver = null;
289 
290         // Destroy state machines and stop handler thread
291         synchronized (mStateMachines) {
292             for (VolumeControlStateMachine sm : mStateMachines.values()) {
293                 sm.doQuit();
294                 sm.cleanup();
295             }
296             mStateMachines.clear();
297         }
298 
299         if (mStateMachinesThread != null) {
300             try {
301                 mStateMachinesThread.quitSafely();
302                 mStateMachinesThread.join(SM_THREAD_JOIN_TIMEOUT_MS);
303                 mStateMachinesThread = null;
304             } catch (InterruptedException e) {
305                 // Do not rethrow as we are shutting down anyway
306             }
307         }
308 
309         // Cleanup native interface
310         mVolumeControlNativeInterface.cleanup();
311         mVolumeControlNativeInterface = null;
312 
313         mAudioOffsets.clear();
314         mGroupVolumeCache.clear();
315         mGroupMuteCache.clear();
316 
317         // Clear AdapterService, VolumeControlNativeInterface
318         mAudioManager = null;
319         mVolumeControlNativeInterface = null;
320         mAdapterService = null;
321 
322         if (mCallbacks != null) {
323             mCallbacks.kill();
324         }
325 
326         return true;
327     }
328 
329     @Override
cleanup()330     protected void cleanup() {
331         if (DBG) {
332             Log.d(TAG, "cleanup()");
333         }
334     }
335 
336     /**
337      * Get the VolumeControlService instance
338      * @return VolumeControlService instance
339      */
getVolumeControlService()340     public static synchronized VolumeControlService getVolumeControlService() {
341         if (sVolumeControlService == null) {
342             Log.w(TAG, "getVolumeControlService(): service is NULL");
343             return null;
344         }
345 
346         if (!sVolumeControlService.isAvailable()) {
347             Log.w(TAG, "getVolumeControlService(): service is not available");
348             return null;
349         }
350         return sVolumeControlService;
351     }
352 
353     @VisibleForTesting
setVolumeControlService(VolumeControlService instance)354     static synchronized void setVolumeControlService(VolumeControlService instance) {
355         if (DBG) {
356             Log.d(TAG, "setVolumeControlService(): set to: " + instance);
357         }
358         sVolumeControlService = instance;
359     }
360 
361     @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
connect(BluetoothDevice device)362     public boolean connect(BluetoothDevice device) {
363         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
364                 "Need BLUETOOTH_PRIVILEGED permission");
365         if (DBG) {
366             Log.d(TAG, "connect(): " + device);
367         }
368         if (device == null) {
369             return false;
370         }
371 
372         if (getConnectionPolicy(device) == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
373             return false;
374         }
375         ParcelUuid[] featureUuids = mAdapterService.getRemoteUuids(device);
376         if (!Utils.arrayContains(featureUuids, BluetoothUuid.VOLUME_CONTROL)) {
377             Log.e(TAG, "Cannot connect to " + device
378                     + " : Remote does not have Volume Control UUID");
379             return false;
380         }
381 
382 
383         synchronized (mStateMachines) {
384             VolumeControlStateMachine smConnect = getOrCreateStateMachine(device);
385             if (smConnect == null) {
386                 Log.e(TAG, "Cannot connect to " + device + " : no state machine");
387             }
388             smConnect.sendMessage(VolumeControlStateMachine.CONNECT);
389         }
390 
391         return true;
392     }
393 
394     @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
disconnect(BluetoothDevice device)395     public boolean disconnect(BluetoothDevice device) {
396         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
397                 "Need BLUETOOTH_PRIVILEGED permission");
398         if (DBG) {
399             Log.d(TAG, "disconnect(): " + device);
400         }
401         if (device == null) {
402             return false;
403         }
404         synchronized (mStateMachines) {
405             VolumeControlStateMachine sm = getOrCreateStateMachine(device);
406             if (sm != null) {
407                 sm.sendMessage(VolumeControlStateMachine.DISCONNECT);
408             }
409         }
410 
411         return true;
412     }
413 
414     @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
getConnectedDevices()415     public List<BluetoothDevice> getConnectedDevices() {
416         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
417                 "Need BLUETOOTH_PRIVILEGED permission");
418         synchronized (mStateMachines) {
419             List<BluetoothDevice> devices = new ArrayList<>();
420             for (VolumeControlStateMachine sm : mStateMachines.values()) {
421                 if (sm.isConnected()) {
422                     devices.add(sm.getDevice());
423                 }
424             }
425             return devices;
426         }
427     }
428 
429     /**
430      * Check whether can connect to a peer device.
431      * The check considers a number of factors during the evaluation.
432      *
433      * @param device the peer device to connect to
434      * @return true if connection is allowed, otherwise false
435      */
436     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
okToConnect(BluetoothDevice device)437     public boolean okToConnect(BluetoothDevice device) {
438         /* Make sure device is valid */
439         if (device == null) {
440             Log.e(TAG, "okToConnect: Invalid device");
441             return false;
442         }
443         // Check if this is an incoming connection in Quiet mode.
444         if (mAdapterService.isQuietModeEnabled()) {
445             Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled");
446             return false;
447         }
448         // Check connectionPolicy and accept or reject the connection.
449         int connectionPolicy = getConnectionPolicy(device);
450         int bondState = mAdapterService.getBondState(device);
451         // Allow this connection only if the device is bonded. Any attempt to connect while
452         // bonding would potentially lead to an unauthorized connection.
453         if (bondState != BluetoothDevice.BOND_BONDED) {
454             Log.w(TAG, "okToConnect: return false, bondState=" + bondState);
455             return false;
456         } else if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_UNKNOWN
457                 && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
458             // Otherwise, reject the connection if connectionPolicy is not valid.
459             Log.w(TAG, "okToConnect: return false, connectionPolicy=" + connectionPolicy);
460             return false;
461         }
462         return true;
463     }
464 
465     @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
getDevicesMatchingConnectionStates(int[] states)466     List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
467         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
468                 "Need BLUETOOTH_PRIVILEGED permission");
469         ArrayList<BluetoothDevice> devices = new ArrayList<>();
470         if (states == null) {
471             return devices;
472         }
473         final BluetoothDevice[] bondedDevices = mAdapterService.getBondedDevices();
474         if (bondedDevices == null) {
475             return devices;
476         }
477         synchronized (mStateMachines) {
478             for (BluetoothDevice device : bondedDevices) {
479                 final ParcelUuid[] featureUuids = device.getUuids();
480                 if (!Utils.arrayContains(featureUuids, BluetoothUuid.VOLUME_CONTROL)) {
481                     continue;
482                 }
483                 int connectionState = BluetoothProfile.STATE_DISCONNECTED;
484                 VolumeControlStateMachine sm = mStateMachines.get(device);
485                 if (sm != null) {
486                     connectionState = sm.getConnectionState();
487                 }
488                 for (int state : states) {
489                     if (connectionState == state) {
490                         devices.add(device);
491                         break;
492                     }
493                 }
494             }
495             return devices;
496         }
497     }
498 
499     /**
500      * Get the list of devices that have state machines.
501      *
502      * @return the list of devices that have state machines
503      */
504     @VisibleForTesting
getDevices()505     List<BluetoothDevice> getDevices() {
506         List<BluetoothDevice> devices = new ArrayList<>();
507         synchronized (mStateMachines) {
508             for (VolumeControlStateMachine sm : mStateMachines.values()) {
509                 devices.add(sm.getDevice());
510             }
511             return devices;
512         }
513     }
514 
515     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getConnectionState(BluetoothDevice device)516     public int getConnectionState(BluetoothDevice device) {
517         enforceCallingOrSelfPermission(BLUETOOTH_CONNECT,
518                 "Need BLUETOOTH_CONNECT permission");
519         synchronized (mStateMachines) {
520             VolumeControlStateMachine sm = mStateMachines.get(device);
521             if (sm == null) {
522                 return BluetoothProfile.STATE_DISCONNECTED;
523             }
524             return sm.getConnectionState();
525         }
526     }
527 
528     /**
529      * Set connection policy of the profile and connects it if connectionPolicy is
530      * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
531      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
532      *
533      * <p> The device should already be paired.
534      * Connection policy can be one of:
535      * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
536      * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
537      * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
538      *
539      * @param device the remote device
540      * @param connectionPolicy is the connection policy to set to for this profile
541      * @return true on success, otherwise false
542      */
543     @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
setConnectionPolicy(BluetoothDevice device, int connectionPolicy)544     public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
545         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
546                 "Need BLUETOOTH_PRIVILEGED permission");
547         if (DBG) {
548             Log.d(TAG, "Saved connectionPolicy " + device + " = " + connectionPolicy);
549         }
550         mDatabaseManager.setProfileConnectionPolicy(device, BluetoothProfile.VOLUME_CONTROL,
551                         connectionPolicy);
552         if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
553             connect(device);
554         } else if (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
555             disconnect(device);
556         }
557         return true;
558     }
559 
560     @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
getConnectionPolicy(BluetoothDevice device)561     public int getConnectionPolicy(BluetoothDevice device) {
562         enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED,
563                 "Need BLUETOOTH_PRIVILEGED permission");
564         return mDatabaseManager.getProfileConnectionPolicy(device, BluetoothProfile.VOLUME_CONTROL);
565     }
566 
isVolumeOffsetAvailable(BluetoothDevice device)567     boolean isVolumeOffsetAvailable(BluetoothDevice device) {
568         VolumeControlOffsetDescriptor offsets = mAudioOffsets.get(device);
569         if (offsets == null) {
570             Log.i(TAG, " There is no offset service for device: " + device);
571             return false;
572         }
573         Log.i(TAG, " Offset service available for device: " + device);
574         return true;
575     }
576 
setVolumeOffset(BluetoothDevice device, int volumeOffset)577     void setVolumeOffset(BluetoothDevice device, int volumeOffset) {
578         VolumeControlOffsetDescriptor offsets = mAudioOffsets.get(device);
579         if (offsets == null) {
580             Log.e(TAG, " There is no offset service for device: " + device);
581             return;
582         }
583 
584         /* Use first offset always */
585         int value = offsets.getValue(1);
586         if (value == volumeOffset) {
587             /* Nothing to do - offset already applied */
588             return;
589         }
590 
591         mVolumeControlNativeInterface.setExtAudioOutVolumeOffset(device, 1, volumeOffset);
592     }
593 
594     /**
595      * {@hide}
596      */
setGroupVolume(int groupId, int volume)597     public void setGroupVolume(int groupId, int volume) {
598         if (volume < 0) {
599             Log.w(TAG, "Tried to set invalid volume " + volume + ". Ignored.");
600             return;
601         }
602 
603         mGroupVolumeCache.put(groupId, volume);
604         mVolumeControlNativeInterface.setGroupVolume(groupId, volume);
605 
606         // We only receive the volume change and mute state needs to be acquired manually
607         Boolean isGroupMute = mGroupMuteCache.getOrDefault(groupId, false);
608         Boolean isStreamMute = mAudioManager.isStreamMute(getBluetoothContextualVolumeStream());
609 
610         /* Note: AudioService keeps volume levels for each stream and for each device type,
611          * however it stores the mute state only for the stream type but not for each individual
612          * device type. When active device changes, it's volume level gets aplied, but mute state
613          * is not, but can be either derived from the volume level or just unmuted like for A2DP.
614          * Also setting volume level > 0 to audio system will implicitly unmute the stream.
615          * However LeAudio devices can keep their volume level high, while keeping it mute so we
616          * have to explicitly unmute the remote device.
617          */
618         if (!isGroupMute.equals(isStreamMute)) {
619             Log.w(TAG, "Mute state mismatch, stream mute: " + isStreamMute
620                     + ", device group mute: " + isGroupMute
621                     + ", new volume: " + volume);
622             if (isStreamMute) {
623                 Log.i(TAG, "Mute the group " + groupId);
624                 muteGroup(groupId);
625             }
626             if (!isStreamMute && (volume > 0)) {
627                 Log.i(TAG, "Unmute the group " + groupId);
628                 unmuteGroup(groupId);
629             }
630         }
631     }
632 
633     /**
634      * {@hide}
635      * @param groupId
636      */
getGroupVolume(int groupId)637     public int getGroupVolume(int groupId) {
638         return mGroupVolumeCache.getOrDefault(groupId,
639                         IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME);
640     }
641 
642     /**
643      * @param groupId the group identifier
644      */
getGroupMute(int groupId)645     public Boolean getGroupMute(int groupId) {
646         return mGroupMuteCache.getOrDefault(groupId, false);
647     }
648 
649     /**
650      * {@hide}
651      */
mute(BluetoothDevice device)652     public void mute(BluetoothDevice device) {
653         mVolumeControlNativeInterface.mute(device);
654     }
655 
656     /**
657      * {@hide}
658      */
muteGroup(int groupId)659     public void muteGroup(int groupId) {
660         mGroupMuteCache.put(groupId, true);
661         mVolumeControlNativeInterface.muteGroup(groupId);
662     }
663 
664     /**
665      * {@hide}
666      */
unmute(BluetoothDevice device)667     public void unmute(BluetoothDevice device) {
668         mVolumeControlNativeInterface.unmute(device);
669     }
670 
671     /**
672      * {@hide}
673      */
unmuteGroup(int groupId)674     public void unmuteGroup(int groupId) {
675         mGroupMuteCache.put(groupId, false);
676         mVolumeControlNativeInterface.unmuteGroup(groupId);
677     }
678 
679     /**
680      * {@hide}
681      */
handleGroupNodeAdded(int groupId, BluetoothDevice device)682     public void handleGroupNodeAdded(int groupId, BluetoothDevice device) {
683         // Ignore disconnected device, its volume will be set once it connects
684         synchronized (mStateMachines) {
685             VolumeControlStateMachine sm = mStateMachines.get(device);
686             if (sm == null) {
687                 return;
688             }
689             if (sm.getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
690                 return;
691             }
692         }
693 
694         // Correct the volume level only if device was already reported as connected.
695         boolean can_change_volume = false;
696         synchronized (mStateMachines) {
697             VolumeControlStateMachine sm = mStateMachines.get(device);
698             if (sm != null) {
699                 can_change_volume =
700                         (sm.getConnectionState() == BluetoothProfile.STATE_CONNECTED);
701             }
702         }
703 
704         // If group volume has already changed, the new group member should set it
705         if (can_change_volume) {
706             Integer groupVolume = mGroupVolumeCache.getOrDefault(groupId,
707                     IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME);
708             if (groupVolume != IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME) {
709                 Log.i(TAG, "Setting value:" + groupVolume + " to " + device);
710                 mVolumeControlNativeInterface.setVolume(device, groupVolume);
711             }
712 
713             Boolean isGroupMuted = mGroupMuteCache.getOrDefault(groupId, false);
714             Log.i(TAG, "Setting mute:" + isGroupMuted + " to " + device);
715             if (isGroupMuted) {
716                 mVolumeControlNativeInterface.mute(device);
717             } else {
718                 mVolumeControlNativeInterface.unmute(device);
719             }
720         }
721     }
722 
handleVolumeControlChanged(BluetoothDevice device, int groupId, int volume, boolean mute, boolean isAutonomous)723     void handleVolumeControlChanged(BluetoothDevice device, int groupId,
724                                     int volume, boolean mute, boolean isAutonomous) {
725 
726         if (isAutonomous && device != null) {
727             Log.e(TAG, "We expect only group notification for autonomous updates");
728             return;
729         }
730 
731         if (groupId == IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID) {
732             LeAudioService leAudioService = mFactory.getLeAudioService();
733             if (leAudioService == null) {
734                 Log.e(TAG, "leAudioService not available");
735                 return;
736             }
737             groupId = leAudioService.getGroupId(device);
738         }
739 
740         if (groupId == IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID) {
741             Log.e(TAG, "Device not a part of the group");
742             return;
743         }
744 
745         int groupVolume = getGroupVolume(groupId);
746         Boolean groupMute = getGroupMute(groupId);
747 
748         if (!isAutonomous) {
749             /* If the change is triggered by Android device, the stream is already changed.
750              * However it might be called with isAutonomous, one the first read of after
751              * reconnection. Make sure device has group volume. Also it might happen that
752              * remote side send us wrong value - lets check it.
753              */
754 
755             if ((groupVolume == volume) && (groupMute == mute)) {
756                 Log.i(TAG, " Volume:" + volume + ", mute:" + mute + " confirmed by remote side.");
757                 return;
758             }
759 
760             if (device != null) {
761                 // Correct the volume level only if device was already reported as connected.
762                 boolean can_change_volume = false;
763                 synchronized (mStateMachines) {
764                     VolumeControlStateMachine sm = mStateMachines.get(device);
765                     if (sm != null) {
766                         can_change_volume =
767                                 (sm.getConnectionState() == BluetoothProfile.STATE_CONNECTED);
768                     }
769                 }
770 
771                 if (can_change_volume && (groupVolume != volume) && (groupVolume
772                             != IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME)) {
773                     Log.i(TAG, "Setting value:" + groupVolume + " to " + device);
774                     mVolumeControlNativeInterface.setVolume(device, groupVolume);
775                 }
776                 if (can_change_volume && (groupMute != mute)) {
777                     Log.i(TAG, "Setting mute:" + groupMute + " to " + device);
778                     if (groupMute) {
779                         mVolumeControlNativeInterface.mute(device);
780                     } else {
781                         mVolumeControlNativeInterface.unmute(device);
782                     }
783                 }
784             } else {
785                 Log.e(TAG, "Volume changed did not succeed. Volume: " + volume
786                                 + " expected volume: " + groupVolume);
787             }
788         } else {
789             /* Received group notification for autonomous change. Update cache and audio system. */
790             mGroupVolumeCache.put(groupId, volume);
791             mGroupMuteCache.put(groupId, mute);
792 
793             int streamType = getBluetoothContextualVolumeStream();
794             int flags = AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME;
795             mAudioManager.setStreamVolume(streamType, getDeviceVolume(streamType, volume), flags);
796 
797             if (mAudioManager.isStreamMute(streamType) != mute) {
798                 int adjustment = mute ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE;
799                 mAudioManager.adjustStreamVolume(streamType, adjustment, flags);
800             }
801         }
802     }
803 
804     /**
805      * {@hide}
806      */
getAudioDeviceGroupVolume(int groupId)807     public int getAudioDeviceGroupVolume(int groupId) {
808         int volume = getGroupVolume(groupId);
809         if (getGroupMute(groupId)) {
810             Log.w(TAG, "Volume level is " + volume
811                     + ", but muted. Will report 0 for the audio device.");
812             volume = 0;
813         }
814 
815         if (volume == IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME) return -1;
816         return getDeviceVolume(getBluetoothContextualVolumeStream(), volume);
817     }
818 
getDeviceVolume(int streamType, int bleVolume)819     int getDeviceVolume(int streamType, int bleVolume) {
820         int deviceMaxVolume = mAudioManager.getStreamMaxVolume(streamType);
821 
822         // TODO: Investigate what happens in classic BT when BT volume is changed to zero.
823         double deviceVolume = (double) (bleVolume * deviceMaxVolume) / LE_AUDIO_MAX_VOL;
824         return (int) Math.round(deviceVolume);
825     }
826 
827     // Copied from AudioService.getBluetoothContextualVolumeStream() and modified it.
getBluetoothContextualVolumeStream()828     int getBluetoothContextualVolumeStream() {
829         int mode = mAudioManager.getMode();
830         switch (mode) {
831             case AudioManager.MODE_IN_COMMUNICATION:
832             case AudioManager.MODE_IN_CALL:
833                 return AudioManager.STREAM_VOICE_CALL;
834             case AudioManager.MODE_NORMAL:
835             default:
836                 // other conditions will influence the stream type choice, read on...
837                 break;
838         }
839         return AudioManager.STREAM_MUSIC;
840     }
841 
handleDeviceAvailable(BluetoothDevice device, int numberOfExternalOutputs)842     void handleDeviceAvailable(BluetoothDevice device, int numberOfExternalOutputs) {
843         if (numberOfExternalOutputs == 0) {
844             Log.i(TAG, "Volume offset not available");
845             return;
846         }
847 
848         VolumeControlOffsetDescriptor offsets = mAudioOffsets.get(device);
849         if (offsets == null) {
850             offsets = new VolumeControlOffsetDescriptor();
851             mAudioOffsets.put(device, offsets);
852         } else if (offsets.size() != numberOfExternalOutputs) {
853             Log.i(TAG, "Number of offset changed: ");
854             offsets.clear();
855         }
856 
857         /* Stack delivers us number of audio outputs.
858          * Offset ids a countinous from 1 to number_of_ext_outputs*/
859         for (int i = 1; i <= numberOfExternalOutputs; i++) {
860             offsets.add(i);
861             mVolumeControlNativeInterface.getExtAudioOutVolumeOffset(device, i);
862             mVolumeControlNativeInterface.getExtAudioOutDescription(device, i);
863         }
864     }
865 
handleDeviceExtAudioOffsetChanged(BluetoothDevice device, int id, int value)866     void handleDeviceExtAudioOffsetChanged(BluetoothDevice device, int id, int value) {
867         if (DBG) {
868             Log.d(TAG, " device: " + device + " offset_id: " +  id + " value: " + value);
869         }
870         VolumeControlOffsetDescriptor offsets = mAudioOffsets.get(device);
871         if (offsets == null) {
872             Log.e(TAG, " Offsets not found for device: " + device);
873             return;
874         }
875         offsets.setValue(id, value);
876 
877         if (mCallbacks == null) {
878             return;
879         }
880 
881         int n = mCallbacks.beginBroadcast();
882         for (int i = 0; i < n; i++) {
883             try {
884                 mCallbacks.getBroadcastItem(i).onVolumeOffsetChanged(device, value);
885             } catch (RemoteException e) {
886                 continue;
887             }
888         }
889         mCallbacks.finishBroadcast();
890     }
891 
handleDeviceExtAudioLocationChanged(BluetoothDevice device, int id, int location)892     void handleDeviceExtAudioLocationChanged(BluetoothDevice device, int id, int location) {
893         if (DBG) {
894             Log.d(TAG, " device: " + device + " offset_id: "
895                     + id + " location: " + location);
896         }
897 
898         VolumeControlOffsetDescriptor offsets = mAudioOffsets.get(device);
899         if (offsets == null) {
900             Log.e(TAG, " Offsets not found for device: " + device);
901             return;
902         }
903         offsets.setLocation(id, location);
904     }
905 
handleDeviceExtAudioDescriptionChanged(BluetoothDevice device, int id, String description)906     void handleDeviceExtAudioDescriptionChanged(BluetoothDevice device, int id,
907                                                 String description) {
908         if (DBG) {
909             Log.d(TAG, " device: " + device + " offset_id: "
910                     + id + " description: " + description);
911         }
912 
913         VolumeControlOffsetDescriptor offsets = mAudioOffsets.get(device);
914         if (offsets == null) {
915             Log.e(TAG, " Offsets not found for device: " + device);
916             return;
917         }
918         offsets.setDescription(id, description);
919     }
920 
messageFromNative(VolumeControlStackEvent stackEvent)921     void messageFromNative(VolumeControlStackEvent stackEvent) {
922 
923         if (stackEvent.type == VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED) {
924             handleVolumeControlChanged(stackEvent.device, stackEvent.valueInt1,
925                                        stackEvent.valueInt2, stackEvent.valueBool1,
926                                        stackEvent.valueBool2);
927           return;
928         }
929 
930         Objects.requireNonNull(stackEvent.device,
931                 "Device should never be null, event: " + stackEvent);
932 
933         Intent intent = null;
934 
935         if (intent != null) {
936             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
937                     | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
938             sendBroadcast(intent, BLUETOOTH_CONNECT);
939             return;
940         }
941 
942         BluetoothDevice device = stackEvent.device;
943         if (stackEvent.type == VolumeControlStackEvent.EVENT_TYPE_DEVICE_AVAILABLE) {
944             handleDeviceAvailable(device, stackEvent.valueInt1);
945             return;
946         }
947 
948         if (stackEvent.type
949                 == VolumeControlStackEvent.EVENT_TYPE_EXT_AUDIO_OUT_VOL_OFFSET_CHANGED) {
950             handleDeviceExtAudioOffsetChanged(device, stackEvent.valueInt1, stackEvent.valueInt2);
951             return;
952         }
953 
954         if (stackEvent.type
955                 == VolumeControlStackEvent.EVENT_TYPE_EXT_AUDIO_OUT_LOCATION_CHANGED) {
956             handleDeviceExtAudioLocationChanged(device, stackEvent.valueInt1,
957                                                     stackEvent.valueInt2);
958             return;
959         }
960 
961         if (stackEvent.type
962                 == VolumeControlStackEvent.EVENT_TYPE_EXT_AUDIO_OUT_DESCRIPTION_CHANGED) {
963             handleDeviceExtAudioDescriptionChanged(device, stackEvent.valueInt1,
964                                                     stackEvent.valueString1);
965             return;
966         }
967 
968         synchronized (mStateMachines) {
969             VolumeControlStateMachine sm = mStateMachines.get(device);
970             if (sm == null) {
971                 if (stackEvent.type
972                         == VolumeControlStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) {
973                     switch (stackEvent.valueInt1) {
974                         case VolumeControlStackEvent.CONNECTION_STATE_CONNECTED:
975                         case VolumeControlStackEvent.CONNECTION_STATE_CONNECTING:
976                             sm = getOrCreateStateMachine(device);
977                             break;
978                         default:
979                             break;
980                     }
981                 }
982             }
983             if (sm == null) {
984                 Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent);
985                 return;
986             }
987             sm.sendMessage(VolumeControlStateMachine.STACK_EVENT, stackEvent);
988         }
989     }
990 
getOrCreateStateMachine(BluetoothDevice device)991     private VolumeControlStateMachine getOrCreateStateMachine(BluetoothDevice device) {
992         if (device == null) {
993             Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null");
994             return null;
995         }
996         synchronized (mStateMachines) {
997             VolumeControlStateMachine sm = mStateMachines.get(device);
998             if (sm != null) {
999                 return sm;
1000             }
1001             // Limit the maximum number of state machines to avoid DoS attack
1002             if (mStateMachines.size() >= MAX_VC_STATE_MACHINES) {
1003                 Log.e(TAG, "Maximum number of VolumeControl state machines reached: "
1004                         + MAX_VC_STATE_MACHINES);
1005                 return null;
1006             }
1007             if (DBG) {
1008                 Log.d(TAG, "Creating a new state machine for " + device);
1009             }
1010             sm = VolumeControlStateMachine.make(device, this,
1011                     mVolumeControlNativeInterface, mStateMachinesThread.getLooper());
1012             mStateMachines.put(device, sm);
1013             return sm;
1014         }
1015     }
1016 
1017     // Remove state machine if the bonding for a device is removed
1018     private class BondStateChangedReceiver extends BroadcastReceiver {
1019         @Override
onReceive(Context context, Intent intent)1020         public void onReceive(Context context, Intent intent) {
1021             if (!BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) {
1022                 return;
1023             }
1024             int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
1025                     BluetoothDevice.ERROR);
1026             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
1027             Objects.requireNonNull(device, "ACTION_BOND_STATE_CHANGED with no EXTRA_DEVICE");
1028             bondStateChanged(device, state);
1029         }
1030     }
1031 
1032     /**
1033      * Process a change in the bonding state for a device.
1034      *
1035      * @param device the device whose bonding state has changed
1036      * @param bondState the new bond state for the device. Possible values are:
1037      * {@link BluetoothDevice#BOND_NONE},
1038      * {@link BluetoothDevice#BOND_BONDING},
1039      * {@link BluetoothDevice#BOND_BONDED}.
1040      */
1041     @VisibleForTesting
bondStateChanged(BluetoothDevice device, int bondState)1042     void bondStateChanged(BluetoothDevice device, int bondState) {
1043         if (DBG) {
1044             Log.d(TAG, "Bond state changed for device: " + device + " state: " + bondState);
1045         }
1046         // Remove state machine if the bonding for a device is removed
1047         if (bondState != BluetoothDevice.BOND_NONE) {
1048             return;
1049         }
1050 
1051         synchronized (mStateMachines) {
1052             VolumeControlStateMachine sm = mStateMachines.get(device);
1053             if (sm == null) {
1054                 return;
1055             }
1056             if (sm.getConnectionState() != BluetoothProfile.STATE_DISCONNECTED) {
1057                 Log.i(TAG, "Disconnecting device because it was unbonded.");
1058                 disconnect(device);
1059                 return;
1060             }
1061             removeStateMachine(device);
1062         }
1063     }
1064 
removeStateMachine(BluetoothDevice device)1065     private void removeStateMachine(BluetoothDevice device) {
1066         synchronized (mStateMachines) {
1067             VolumeControlStateMachine sm = mStateMachines.get(device);
1068             if (sm == null) {
1069                 Log.w(TAG, "removeStateMachine: device " + device
1070                         + " does not have a state machine");
1071                 return;
1072             }
1073             Log.i(TAG, "removeStateMachine: removing state machine for device: " + device);
1074             sm.doQuit();
1075             sm.cleanup();
1076             mStateMachines.remove(device);
1077         }
1078     }
1079 
1080     @VisibleForTesting
connectionStateChanged(BluetoothDevice device, int fromState, int toState)1081     synchronized void connectionStateChanged(BluetoothDevice device, int fromState,
1082                                              int toState) {
1083         if ((device == null) || (fromState == toState)) {
1084             Log.e(TAG, "connectionStateChanged: unexpected invocation. device=" + device
1085                     + " fromState=" + fromState + " toState=" + toState);
1086             return;
1087         }
1088 
1089         // Check if the device is disconnected - if unbond, remove the state machine
1090         if (toState == BluetoothProfile.STATE_DISCONNECTED) {
1091             int bondState = mAdapterService.getBondState(device);
1092             if (bondState == BluetoothDevice.BOND_NONE) {
1093                 if (DBG) {
1094                     Log.d(TAG, device + " is unbond. Remove state machine");
1095                 }
1096                 removeStateMachine(device);
1097             }
1098         } else if (toState == BluetoothProfile.STATE_CONNECTED) {
1099             // Restore the group volume if it was changed while the device was not yet connected.
1100             CsipSetCoordinatorService csipClient = mFactory.getCsipSetCoordinatorService();
1101             Integer groupId = csipClient.getGroupId(device, BluetoothUuid.CAP);
1102             if (groupId != IBluetoothCsipSetCoordinator.CSIS_GROUP_ID_INVALID) {
1103                 Integer groupVolume = mGroupVolumeCache.getOrDefault(groupId,
1104                         IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME);
1105                 if (groupVolume != IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME) {
1106                     mVolumeControlNativeInterface.setVolume(device, groupVolume);
1107                 }
1108 
1109                 Boolean groupMute = mGroupMuteCache.getOrDefault(groupId, false);
1110                 if (groupMute) {
1111                     mVolumeControlNativeInterface.mute(device);
1112                 } else {
1113                     mVolumeControlNativeInterface.unmute(device);
1114                 }
1115             }
1116         }
1117     }
1118 
1119     private class ConnectionStateChangedReceiver extends BroadcastReceiver {
1120         @Override
onReceive(Context context, Intent intent)1121         public void onReceive(Context context, Intent intent) {
1122             if (!BluetoothVolumeControl.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
1123                 return;
1124             }
1125             BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
1126             int toState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
1127             int fromState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
1128             connectionStateChanged(device, fromState, toState);
1129         }
1130     }
1131 
1132     /**
1133      * Binder object: must be a static class or memory leak may occur
1134      */
1135     @VisibleForTesting
1136     static class BluetoothVolumeControlBinder extends IBluetoothVolumeControl.Stub
1137             implements IProfileServiceBinder {
1138         @VisibleForTesting
1139         boolean mIsTesting = false;
1140         private VolumeControlService mService;
1141 
1142         @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
getService(AttributionSource source)1143         private VolumeControlService getService(AttributionSource source) {
1144             if (mIsTesting) {
1145                 return mService;
1146             }
1147             if (!Utils.checkServiceAvailable(mService, TAG)
1148                     || !Utils.checkCallerIsSystemOrActiveOrManagedUser(mService, TAG)
1149                     || !Utils.checkConnectPermissionForDataDelivery(mService, source, TAG)) {
1150                 return null;
1151             }
1152             return mService;
1153         }
1154 
BluetoothVolumeControlBinder(VolumeControlService svc)1155         BluetoothVolumeControlBinder(VolumeControlService svc) {
1156             mService = svc;
1157         }
1158 
1159         @Override
cleanup()1160         public void cleanup() {
1161             mService = null;
1162         }
1163 
1164         @Override
connect(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1165         public void connect(BluetoothDevice device, AttributionSource source,
1166                 SynchronousResultReceiver receiver) {
1167             try {
1168                 Objects.requireNonNull(device, "device cannot be null");
1169                 Objects.requireNonNull(source, "source cannot be null");
1170                 Objects.requireNonNull(receiver, "receiver cannot be null");
1171 
1172                 VolumeControlService service = getService(source);
1173                 boolean defaultValue = false;
1174                 if (service != null) {
1175                     defaultValue = service.connect(device);
1176                 }
1177                 receiver.send(defaultValue);
1178             } catch (RuntimeException e) {
1179                 receiver.propagateException(e);
1180             }
1181         }
1182 
1183         @Override
disconnect(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1184         public void disconnect(BluetoothDevice device, AttributionSource source,
1185                 SynchronousResultReceiver receiver) {
1186             try {
1187                 Objects.requireNonNull(device, "device cannot be null");
1188                 Objects.requireNonNull(source, "source cannot be null");
1189                 Objects.requireNonNull(receiver, "receiver cannot be null");
1190 
1191                 VolumeControlService service = getService(source);
1192                 boolean defaultValue = false;
1193                 if (service != null) {
1194                     defaultValue = service.disconnect(device);
1195                 }
1196                 receiver.send(defaultValue);
1197             } catch (RuntimeException e) {
1198                 receiver.propagateException(e);
1199             }
1200         }
1201 
1202         @Override
getConnectedDevices(AttributionSource source, SynchronousResultReceiver receiver)1203         public void getConnectedDevices(AttributionSource source,
1204                 SynchronousResultReceiver receiver) {
1205             try {
1206                 Objects.requireNonNull(source, "source cannot be null");
1207                 Objects.requireNonNull(receiver, "receiver cannot be null");
1208 
1209                 VolumeControlService service = getService(source);
1210                 List<BluetoothDevice> defaultValue = new ArrayList<>();
1211                 if (service != null) {
1212                     enforceBluetoothPrivilegedPermission(service);
1213                     defaultValue = service.getConnectedDevices();
1214                 }
1215                 receiver.send(defaultValue);
1216             } catch (RuntimeException e) {
1217                 receiver.propagateException(e);
1218             }
1219         }
1220 
1221         @Override
getDevicesMatchingConnectionStates(int[] states, AttributionSource source, SynchronousResultReceiver receiver)1222         public void getDevicesMatchingConnectionStates(int[] states,
1223                 AttributionSource source, SynchronousResultReceiver receiver) {
1224             try {
1225                 Objects.requireNonNull(source, "source cannot be null");
1226                 Objects.requireNonNull(receiver, "receiver cannot be null");
1227 
1228                 VolumeControlService service = getService(source);
1229                 List<BluetoothDevice> defaultValue = new ArrayList<>();
1230                 if (service != null) {
1231                     defaultValue = service.getDevicesMatchingConnectionStates(states);
1232                 }
1233                 receiver.send(defaultValue);
1234             } catch (RuntimeException e) {
1235                 receiver.propagateException(e);
1236             }
1237         }
1238 
1239         @Override
getConnectionState(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1240         public void getConnectionState(BluetoothDevice device, AttributionSource source,
1241                 SynchronousResultReceiver receiver) {
1242             try {
1243                 Objects.requireNonNull(device, "device cannot be null");
1244                 Objects.requireNonNull(source, "source cannot be null");
1245                 Objects.requireNonNull(receiver, "receiver cannot be null");
1246 
1247                 VolumeControlService service = getService(source);
1248                 int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
1249                 if (service != null) {
1250                     defaultValue = service.getConnectionState(device);
1251                 }
1252                 receiver.send(defaultValue);
1253             } catch (RuntimeException e) {
1254                 receiver.propagateException(e);
1255             }
1256         }
1257 
1258         @Override
setConnectionPolicy(BluetoothDevice device, int connectionPolicy, AttributionSource source, SynchronousResultReceiver receiver)1259         public void setConnectionPolicy(BluetoothDevice device, int connectionPolicy,
1260                 AttributionSource source, SynchronousResultReceiver receiver) {
1261             try {
1262                 Objects.requireNonNull(device, "device cannot be null");
1263                 Objects.requireNonNull(source, "source cannot be null");
1264                 Objects.requireNonNull(receiver, "receiver cannot be null");
1265 
1266                 VolumeControlService service = getService(source);
1267                 boolean defaultValue = false;
1268                 if (service != null) {
1269                     defaultValue = service.setConnectionPolicy(device, connectionPolicy);
1270                 }
1271                 receiver.send(defaultValue);
1272             } catch (RuntimeException e) {
1273                 receiver.propagateException(e);
1274             }
1275         }
1276 
1277         @Override
getConnectionPolicy(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1278         public void getConnectionPolicy(BluetoothDevice device, AttributionSource source,
1279                 SynchronousResultReceiver receiver) {
1280             try {
1281                 Objects.requireNonNull(device, "device cannot be null");
1282                 Objects.requireNonNull(source, "source cannot be null");
1283                 Objects.requireNonNull(receiver, "receiver cannot be null");
1284 
1285                 VolumeControlService service = getService(source);
1286                 int defaultValue = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
1287                 if (service != null) {
1288                     defaultValue = service.getConnectionPolicy(device);
1289                 }
1290                 receiver.send(defaultValue);
1291             } catch (RuntimeException e) {
1292                 receiver.propagateException(e);
1293             }
1294         }
1295 
1296         @Override
isVolumeOffsetAvailable(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1297         public void isVolumeOffsetAvailable(BluetoothDevice device,
1298                 AttributionSource source, SynchronousResultReceiver receiver) {
1299             try {
1300                 Objects.requireNonNull(device, "device cannot be null");
1301                 Objects.requireNonNull(source, "source cannot be null");
1302                 Objects.requireNonNull(receiver, "receiver cannot be null");
1303 
1304                 boolean defaultValue = false;
1305                 VolumeControlService service = getService(source);
1306                 if (service != null) {
1307                     defaultValue = service.isVolumeOffsetAvailable(device);
1308                 }
1309                 receiver.send(defaultValue);
1310             } catch (RuntimeException e) {
1311                 receiver.propagateException(e);
1312             }
1313         }
1314 
1315         @Override
setVolumeOffset(BluetoothDevice device, int volumeOffset, AttributionSource source, SynchronousResultReceiver receiver)1316         public void setVolumeOffset(BluetoothDevice device, int volumeOffset,
1317                 AttributionSource source, SynchronousResultReceiver receiver) {
1318             try {
1319                 Objects.requireNonNull(device, "device cannot be null");
1320                 Objects.requireNonNull(source, "source cannot be null");
1321                 Objects.requireNonNull(receiver, "receiver cannot be null");
1322 
1323                 VolumeControlService service = getService(source);
1324                 if (service != null) {
1325                     service.setVolumeOffset(device, volumeOffset);
1326                 }
1327                 receiver.send(null);
1328             } catch (RuntimeException e) {
1329                 receiver.propagateException(e);
1330             }
1331         }
1332 
1333         @Override
setGroupVolume(int groupId, int volume, AttributionSource source, SynchronousResultReceiver receiver)1334         public void setGroupVolume(int groupId, int volume, AttributionSource source,
1335                 SynchronousResultReceiver receiver) {
1336             try {
1337                 Objects.requireNonNull(source, "source cannot be null");
1338                 Objects.requireNonNull(receiver, "receiver cannot be null");
1339 
1340                 VolumeControlService service = getService(source);
1341                 if (service != null) {
1342                     service.setGroupVolume(groupId, volume);
1343                 }
1344                 receiver.send(null);
1345             } catch (RuntimeException e) {
1346                 receiver.propagateException(e);
1347             }
1348         }
1349 
1350         @Override
getGroupVolume(int groupId, AttributionSource source, SynchronousResultReceiver receiver)1351         public void getGroupVolume(int groupId, AttributionSource source,
1352                 SynchronousResultReceiver receiver) {
1353             try {
1354                 Objects.requireNonNull(source, "source cannot be null");
1355                 Objects.requireNonNull(receiver, "receiver cannot be null");
1356 
1357                 int groupVolume = 0;
1358                 VolumeControlService service = getService(source);
1359                 if (service != null) {
1360                     groupVolume = service.getGroupVolume(groupId);
1361                 }
1362                 receiver.send(groupVolume);
1363             } catch (RuntimeException e) {
1364                 receiver.propagateException(e);
1365             }
1366         }
1367 
1368 
1369         @Override
mute(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1370         public void mute(BluetoothDevice device,  AttributionSource source,
1371                 SynchronousResultReceiver receiver) {
1372             try {
1373                 Objects.requireNonNull(device, "device cannot be null");
1374                 Objects.requireNonNull(source, "source cannot be null");
1375                 Objects.requireNonNull(receiver, "receiver cannot be null");
1376 
1377                 VolumeControlService service = getService(source);
1378                 if (service != null) {
1379                     service.mute(device);
1380                 }
1381                 receiver.send(null);
1382             } catch (RuntimeException e) {
1383                 receiver.propagateException(e);
1384             }
1385         }
1386 
1387         @Override
muteGroup(int groupId, AttributionSource source, SynchronousResultReceiver receiver)1388         public void muteGroup(int groupId, AttributionSource source,
1389                 SynchronousResultReceiver receiver) {
1390             try {
1391                 Objects.requireNonNull(source, "source cannot be null");
1392                 Objects.requireNonNull(receiver, "receiver cannot be null");
1393 
1394                 VolumeControlService service = getService(source);
1395                 if (service != null) {
1396                     service.muteGroup(groupId);
1397                 }
1398                 receiver.send(null);
1399             } catch (RuntimeException e) {
1400                 receiver.propagateException(e);
1401             }
1402         }
1403 
1404         @Override
unmute(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver)1405         public void unmute(BluetoothDevice device,  AttributionSource source,
1406                 SynchronousResultReceiver receiver) {
1407             try {
1408                 Objects.requireNonNull(device, "device cannot be null");
1409                 Objects.requireNonNull(source, "source cannot be null");
1410                 Objects.requireNonNull(receiver, "receiver cannot be null");
1411 
1412                 VolumeControlService service = getService(source);
1413                 if (service != null) {
1414                     service.unmute(device);
1415                 }
1416                 receiver.send(null);
1417             } catch (RuntimeException e) {
1418                 receiver.propagateException(e);
1419             }
1420         }
1421 
1422         @Override
unmuteGroup(int groupId, AttributionSource source, SynchronousResultReceiver receiver)1423         public void unmuteGroup(int groupId,  AttributionSource source,
1424                 SynchronousResultReceiver receiver) {
1425             try {
1426                 Objects.requireNonNull(source, "source cannot be null");
1427                 Objects.requireNonNull(receiver, "receiver cannot be null");
1428 
1429                 VolumeControlService service = getService(source);
1430                 if (service != null) {
1431                     service.unmuteGroup(groupId);
1432                 }
1433                 receiver.send(null);
1434             } catch (RuntimeException e) {
1435                 receiver.propagateException(e);
1436             }
1437         }
1438 
1439         @Override
registerCallback(IBluetoothVolumeControlCallback callback, AttributionSource source, SynchronousResultReceiver receiver)1440         public void registerCallback(IBluetoothVolumeControlCallback callback,
1441                 AttributionSource source, SynchronousResultReceiver receiver) {
1442             try {
1443                 Objects.requireNonNull(callback, "callback cannot be null");
1444                 Objects.requireNonNull(source, "source cannot be null");
1445                 Objects.requireNonNull(receiver, "receiver cannot be null");
1446 
1447                 VolumeControlService service = getService(source);
1448                 if (service == null) {
1449                     throw new IllegalStateException("Service is unavailable");
1450                 }
1451 
1452                 enforceBluetoothPrivilegedPermission(service);
1453 
1454                 service.mCallbacks.register(callback);
1455                 receiver.send(null);
1456             } catch (RuntimeException e) {
1457                 receiver.propagateException(e);
1458             }
1459         }
1460 
1461         @Override
unregisterCallback(IBluetoothVolumeControlCallback callback, AttributionSource source, SynchronousResultReceiver receiver)1462         public void unregisterCallback(IBluetoothVolumeControlCallback callback,
1463                 AttributionSource source, SynchronousResultReceiver receiver) {
1464             try {
1465                 Objects.requireNonNull(callback, "callback cannot be null");
1466                 Objects.requireNonNull(source, "source cannot be null");
1467                 Objects.requireNonNull(receiver, "receiver cannot be null");
1468 
1469                 VolumeControlService service = getService(source);
1470                 if (service == null) {
1471                     throw new IllegalStateException("Service is unavailable");
1472                 }
1473 
1474                 enforceBluetoothPrivilegedPermission(service);
1475 
1476                 service.mCallbacks.unregister(callback);
1477                 receiver.send(null);
1478             } catch (RuntimeException e) {
1479                 receiver.propagateException(e);
1480             }
1481         }
1482     }
1483 
1484     @Override
dump(StringBuilder sb)1485     public void dump(StringBuilder sb) {
1486         super.dump(sb);
1487         for (VolumeControlStateMachine sm : mStateMachines.values()) {
1488             sm.dump(sb);
1489         }
1490 
1491         for (Map.Entry<BluetoothDevice, VolumeControlOffsetDescriptor> entry :
1492                                                             mAudioOffsets.entrySet()) {
1493             VolumeControlOffsetDescriptor descriptor = entry.getValue();
1494             BluetoothDevice device = entry.getKey();
1495             ProfileService.println(sb, "    Device: " + device);
1496             ProfileService.println(sb, "    Volume offset cnt: " + descriptor.size());
1497             descriptor.dump(sb);
1498         }
1499         for (Map.Entry<Integer, Integer> entry : mGroupVolumeCache.entrySet()) {
1500             Boolean isMute = mGroupMuteCache.getOrDefault(entry.getKey(), false);
1501             ProfileService.println(sb, "    GroupId: " + entry.getKey() + " volume: "
1502                             + entry.getValue() + ", mute: " + isMute);
1503         }
1504     }
1505 }
1506