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