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