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 android.bluetooth.BluetoothAdapter;
21 import android.bluetooth.BluetoothDevice;
22 import android.util.Log;
23 
24 import com.android.bluetooth.Utils;
25 import com.android.internal.annotations.GuardedBy;
26 import com.android.internal.annotations.VisibleForTesting;
27 
28 public class VolumeControlNativeInterface {
29     private static final String TAG = "VolumeControlNativeInterface";
30     private BluetoothAdapter mAdapter;
31 
32     @GuardedBy("INSTANCE_LOCK")
33     private static VolumeControlNativeInterface sInstance;
34 
35     private static final Object INSTANCE_LOCK = new Object();
36 
VolumeControlNativeInterface()37     private VolumeControlNativeInterface() {
38         mAdapter = BluetoothAdapter.getDefaultAdapter();
39         if (mAdapter == null) {
40             Log.wtf(TAG, "No Bluetooth Adapter Available");
41         }
42     }
43 
44     /** Get singleton instance. */
getInstance()45     public static VolumeControlNativeInterface getInstance() {
46         synchronized (INSTANCE_LOCK) {
47             if (sInstance == null) {
48                 sInstance = new VolumeControlNativeInterface();
49             }
50             return sInstance;
51         }
52     }
53 
54     /** Set singleton instance. */
55     @VisibleForTesting
setInstance(VolumeControlNativeInterface instance)56     public static void setInstance(VolumeControlNativeInterface instance) {
57         synchronized (INSTANCE_LOCK) {
58             sInstance = instance;
59         }
60     }
61 
62     /**
63      * Initializes the native interface.
64      *
65      * <p>priorities to configure.
66      */
67     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
init()68     public void init() {
69         initNative();
70     }
71 
72     /** Cleanup the native interface. */
73     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
cleanup()74     public void cleanup() {
75         cleanupNative();
76     }
77 
78     /**
79      * Initiates VolumeControl connection to a remote device.
80      *
81      * @param device the remote device
82      * @return true on success, otherwise false.
83      */
84     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
connectVolumeControl(BluetoothDevice device)85     public boolean connectVolumeControl(BluetoothDevice device) {
86         return connectVolumeControlNative(getByteAddress(device));
87     }
88 
89     /**
90      * Disconnects VolumeControl from a remote device.
91      *
92      * @param device the remote device
93      * @return true on success, otherwise false.
94      */
95     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
disconnectVolumeControl(BluetoothDevice device)96     public boolean disconnectVolumeControl(BluetoothDevice device) {
97         return disconnectVolumeControlNative(getByteAddress(device));
98     }
99 
100     /** Sets the VolumeControl volume */
101     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
setVolume(BluetoothDevice device, int volume)102     public void setVolume(BluetoothDevice device, int volume) {
103         setVolumeNative(getByteAddress(device), volume);
104     }
105 
106     /** Sets the VolumeControl volume for the group */
107     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
setGroupVolume(int groupId, int volume)108     public void setGroupVolume(int groupId, int volume) {
109         setGroupVolumeNative(groupId, volume);
110     }
111 
112     /** Mute the VolumeControl volume */
113     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
mute(BluetoothDevice device)114     public void mute(BluetoothDevice device) {
115         muteNative(getByteAddress(device));
116     }
117 
118     /** Mute the VolumeControl volume in the group */
119     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
muteGroup(int groupId)120     public void muteGroup(int groupId) {
121         muteGroupNative(groupId);
122     }
123 
124     /** Unmute the VolumeControl volume */
125     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
unmute(BluetoothDevice device)126     public void unmute(BluetoothDevice device) {
127         unmuteNative(getByteAddress(device));
128     }
129 
130     /** Unmute the VolumeControl volume group */
131     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
unmuteGroup(int groupId)132     public void unmuteGroup(int groupId) {
133         unmuteGroupNative(groupId);
134     }
135 
136     /**
137      * Gets external audio output volume offset from a remote device.
138      *
139      * @param device the remote device
140      * @param externalOutputId external audio output id
141      * @return true on success, otherwise false.
142      */
143     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
getExtAudioOutVolumeOffset(BluetoothDevice device, int externalOutputId)144     public boolean getExtAudioOutVolumeOffset(BluetoothDevice device, int externalOutputId) {
145         return getExtAudioOutVolumeOffsetNative(getByteAddress(device), externalOutputId);
146     }
147 
148     /**
149      * Sets external audio output volume offset to a remote device.
150      *
151      * @param device the remote device
152      * @param externalOutputId external audio output id
153      * @param offset requested offset
154      * @return true on success, otherwise false.
155      */
156     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
setExtAudioOutVolumeOffset( BluetoothDevice device, int externalOutputId, int offset)157     public boolean setExtAudioOutVolumeOffset(
158             BluetoothDevice device, int externalOutputId, int offset) {
159         if (Utils.isPtsTestMode()) {
160             setVolumeNative(getByteAddress(device), offset);
161             return true;
162         }
163         return setExtAudioOutVolumeOffsetNative(getByteAddress(device), externalOutputId, offset);
164     }
165 
166     /**
167      * Gets external audio output location from a remote device.
168      *
169      * @param device the remote device
170      * @param externalOutputId external audio output id
171      * @return true on success, otherwise false.
172      */
173     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
getExtAudioOutLocation(BluetoothDevice device, int externalOutputId)174     public boolean getExtAudioOutLocation(BluetoothDevice device, int externalOutputId) {
175         return getExtAudioOutLocationNative(getByteAddress(device), externalOutputId);
176     }
177 
178     /**
179      * Sets external audio volume offset to a remote device.
180      *
181      * @param device the remote device
182      * @param externalOutputId external audio output id
183      * @param location requested location
184      * @return true on success, otherwise false.
185      */
186     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
setExtAudioOutLocation( BluetoothDevice device, int externalOutputId, int location)187     public boolean setExtAudioOutLocation(
188             BluetoothDevice device, int externalOutputId, int location) {
189         return setExtAudioOutLocationNative(getByteAddress(device), externalOutputId, location);
190     }
191 
192     /**
193      * Gets external audio output description from a remote device.
194      *
195      * @param device the remote device
196      * @param externalOutputId external audio output id
197      * @return true on success, otherwise false.
198      */
199     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
getExtAudioOutDescription(BluetoothDevice device, int externalOutputId)200     public boolean getExtAudioOutDescription(BluetoothDevice device, int externalOutputId) {
201         return getExtAudioOutDescriptionNative(getByteAddress(device), externalOutputId);
202     }
203 
204     /**
205      * Sets external audio volume description to a remote device.
206      *
207      * @param device the remote device
208      * @param externalOutputId external audio output id
209      * @param descr requested description
210      * @return true on success, otherwise false.
211      */
212     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
setExtAudioOutDescription( BluetoothDevice device, int externalOutputId, String descr)213     public boolean setExtAudioOutDescription(
214             BluetoothDevice device, int externalOutputId, String descr) {
215         return setExtAudioOutDescriptionNative(getByteAddress(device), externalOutputId, descr);
216     }
217 
getDevice(byte[] address)218     private BluetoothDevice getDevice(byte[] address) {
219         return mAdapter.getRemoteDevice(address);
220     }
221 
getByteAddress(BluetoothDevice device)222     private byte[] getByteAddress(BluetoothDevice device) {
223         if (device == null) {
224             return Utils.getBytesFromAddress("00:00:00:00:00:00");
225         }
226         return Utils.getBytesFromAddress(device.getAddress());
227     }
228 
sendMessageToService(VolumeControlStackEvent event)229     private void sendMessageToService(VolumeControlStackEvent event) {
230         VolumeControlService service = VolumeControlService.getVolumeControlService();
231         if (service != null) {
232             service.messageFromNative(event);
233         } else {
234             Log.e(TAG, "Event ignored, service not available: " + event);
235         }
236     }
237 
238     // Callbacks from the native stack back into the Java framework.
239     // All callbacks are routed via the Service which will disambiguate which
240     // state machine the message should be routed to.
241     @VisibleForTesting
onConnectionStateChanged(int state, byte[] address)242     void onConnectionStateChanged(int state, byte[] address) {
243         VolumeControlStackEvent event =
244                 new VolumeControlStackEvent(
245                         VolumeControlStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
246         event.device = getDevice(address);
247         event.valueInt1 = state;
248 
249         Log.d(TAG, "onConnectionStateChanged: " + event);
250         sendMessageToService(event);
251     }
252 
253     @VisibleForTesting
onVolumeStateChanged(int volume, boolean mute, byte[] address, boolean isAutonomous)254     void onVolumeStateChanged(int volume, boolean mute, byte[] address, boolean isAutonomous) {
255         VolumeControlStackEvent event =
256                 new VolumeControlStackEvent(
257                         VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED);
258         event.device = getDevice(address);
259         event.valueInt1 = -1;
260         event.valueInt2 = volume;
261         event.valueBool1 = mute;
262         event.valueBool2 = isAutonomous;
263 
264         Log.d(TAG, "onVolumeStateChanged: " + event);
265         sendMessageToService(event);
266     }
267 
268     @VisibleForTesting
onGroupVolumeStateChanged(int volume, boolean mute, int groupId, boolean isAutonomous)269     void onGroupVolumeStateChanged(int volume, boolean mute, int groupId, boolean isAutonomous) {
270         VolumeControlStackEvent event =
271                 new VolumeControlStackEvent(
272                         VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED);
273         event.device = null;
274         event.valueInt1 = groupId;
275         event.valueInt2 = volume;
276         event.valueBool1 = mute;
277         event.valueBool2 = isAutonomous;
278 
279         Log.d(TAG, "onGroupVolumeStateChanged: " + event);
280         sendMessageToService(event);
281     }
282 
283     @VisibleForTesting
onDeviceAvailable(int numOfExternalOutputs, byte[] address)284     void onDeviceAvailable(int numOfExternalOutputs, byte[] address) {
285         VolumeControlStackEvent event =
286                 new VolumeControlStackEvent(VolumeControlStackEvent.EVENT_TYPE_DEVICE_AVAILABLE);
287         event.device = getDevice(address);
288         event.valueInt1 = numOfExternalOutputs;
289 
290         Log.d(TAG, "onDeviceAvailable: " + event);
291         sendMessageToService(event);
292     }
293 
294     @VisibleForTesting
onExtAudioOutVolumeOffsetChanged(int externalOutputId, int offset, byte[] address)295     void onExtAudioOutVolumeOffsetChanged(int externalOutputId, int offset, byte[] address) {
296         VolumeControlStackEvent event =
297                 new VolumeControlStackEvent(
298                         VolumeControlStackEvent.EVENT_TYPE_EXT_AUDIO_OUT_VOL_OFFSET_CHANGED);
299         event.device = getDevice(address);
300         event.valueInt1 = externalOutputId;
301         event.valueInt2 = offset;
302 
303         Log.d(TAG, "onExtAudioOutVolumeOffsetChanged: " + event);
304         sendMessageToService(event);
305     }
306 
307     @VisibleForTesting
onExtAudioOutLocationChanged(int externalOutputId, int location, byte[] address)308     void onExtAudioOutLocationChanged(int externalOutputId, int location, byte[] address) {
309         VolumeControlStackEvent event =
310                 new VolumeControlStackEvent(
311                         VolumeControlStackEvent.EVENT_TYPE_EXT_AUDIO_OUT_LOCATION_CHANGED);
312         event.device = getDevice(address);
313         event.valueInt1 = externalOutputId;
314         event.valueInt2 = location;
315 
316         Log.d(TAG, "onExtAudioOutLocationChanged: " + event);
317         sendMessageToService(event);
318     }
319 
320     @VisibleForTesting
onExtAudioOutDescriptionChanged(int externalOutputId, String descr, byte[] address)321     void onExtAudioOutDescriptionChanged(int externalOutputId, String descr, byte[] address) {
322         VolumeControlStackEvent event =
323                 new VolumeControlStackEvent(
324                         VolumeControlStackEvent.EVENT_TYPE_EXT_AUDIO_OUT_DESCRIPTION_CHANGED);
325         event.device = getDevice(address);
326         event.valueInt1 = externalOutputId;
327         event.valueString1 = descr;
328 
329         Log.d(TAG, "onExtAudioOutLocationChanged: " + event);
330         sendMessageToService(event);
331     }
332 
333     // Native methods that call into the JNI interface
initNative()334     private native void initNative();
335 
cleanupNative()336     private native void cleanupNative();
337 
connectVolumeControlNative(byte[] address)338     private native boolean connectVolumeControlNative(byte[] address);
339 
disconnectVolumeControlNative(byte[] address)340     private native boolean disconnectVolumeControlNative(byte[] address);
341 
setVolumeNative(byte[] address, int volume)342     private native void setVolumeNative(byte[] address, int volume);
343 
setGroupVolumeNative(int groupId, int volume)344     private native void setGroupVolumeNative(int groupId, int volume);
345 
muteNative(byte[] address)346     private native void muteNative(byte[] address);
347 
muteGroupNative(int groupId)348     private native void muteGroupNative(int groupId);
349 
unmuteNative(byte[] address)350     private native void unmuteNative(byte[] address);
351 
unmuteGroupNative(int groupId)352     private native void unmuteGroupNative(int groupId);
353 
getExtAudioOutVolumeOffsetNative(byte[] address, int externalOutputId)354     private native boolean getExtAudioOutVolumeOffsetNative(byte[] address, int externalOutputId);
355 
setExtAudioOutVolumeOffsetNative( byte[] address, int externalOutputId, int offset)356     private native boolean setExtAudioOutVolumeOffsetNative(
357             byte[] address, int externalOutputId, int offset);
358 
getExtAudioOutLocationNative(byte[] address, int externalOutputId)359     private native boolean getExtAudioOutLocationNative(byte[] address, int externalOutputId);
360 
setExtAudioOutLocationNative( byte[] address, int externalOutputId, int location)361     private native boolean setExtAudioOutLocationNative(
362             byte[] address, int externalOutputId, int location);
363 
getExtAudioOutDescriptionNative(byte[] address, int externalOutputId)364     private native boolean getExtAudioOutDescriptionNative(byte[] address, int externalOutputId);
365 
setExtAudioOutDescriptionNative( byte[] address, int externalOutputId, String descr)366     private native boolean setExtAudioOutDescriptionNative(
367             byte[] address, int externalOutputId, String descr);
368 }
369