1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.bluetooth.a2dpsink;
18 
19 import android.bluetooth.BluetoothDevice;
20 import android.util.Log;
21 
22 import com.android.bluetooth.Utils;
23 import com.android.bluetooth.btservice.AdapterService;
24 import com.android.bluetooth.flags.Flags;
25 import com.android.internal.annotations.GuardedBy;
26 import com.android.internal.annotations.VisibleForTesting;
27 
28 import java.util.Objects;
29 
30 /** A2DP Sink Native Interface to/from JNI. */
31 public class A2dpSinkNativeInterface {
32     private static final String TAG = A2dpSinkNativeInterface.class.getSimpleName();
33     private AdapterService mAdapterService;
34 
35     @GuardedBy("INSTANCE_LOCK")
36     private static A2dpSinkNativeInterface sInstance;
37 
38     private static final Object INSTANCE_LOCK = new Object();
39 
A2dpSinkNativeInterface()40     private A2dpSinkNativeInterface() {
41         mAdapterService =
42                 Objects.requireNonNull(
43                         AdapterService.getAdapterService(),
44                         "AdapterService cannot be null when A2dpSinkNativeInterface init");
45     }
46 
47     /** Get singleton instance. */
getInstance()48     public static A2dpSinkNativeInterface getInstance() {
49         synchronized (INSTANCE_LOCK) {
50             if (sInstance == null) {
51                 sInstance = new A2dpSinkNativeInterface();
52             }
53             return sInstance;
54         }
55     }
56 
57     /** Set singleton instance. */
58     @VisibleForTesting
setInstance(A2dpSinkNativeInterface instance)59     public static void setInstance(A2dpSinkNativeInterface instance) {
60         synchronized (INSTANCE_LOCK) {
61             sInstance = instance;
62         }
63     }
64 
65     /**
66      * Initializes the native interface and sets the max number of connected devices
67      *
68      * @param maxConnectedAudioDevices The maximum number of devices that can be connected at once
69      */
init(int maxConnectedAudioDevices)70     public void init(int maxConnectedAudioDevices) {
71         initNative(maxConnectedAudioDevices);
72     }
73 
74     /** Cleanup the native interface. */
cleanup()75     public void cleanup() {
76         cleanupNative();
77     }
78 
getDevice(byte[] address)79     private BluetoothDevice getDevice(byte[] address) {
80         return mAdapterService.getDeviceFromByte(address);
81     }
82 
getByteAddress(BluetoothDevice device)83     private byte[] getByteAddress(BluetoothDevice device) {
84         if (Flags.identityAddressNullIfUnknown()) {
85             return Utils.getByteBrEdrAddress(device);
86         } else {
87             return mAdapterService.getByteIdentityAddress(device);
88         }
89     }
90 
91     /**
92      * Initiates an A2DP connection to a remote device.
93      *
94      * @param device the remote device
95      * @return true on success, otherwise false.
96      */
connectA2dpSink(BluetoothDevice device)97     public boolean connectA2dpSink(BluetoothDevice device) {
98         return connectA2dpNative(getByteAddress(device));
99     }
100 
101     /**
102      * Disconnects A2DP from a remote device.
103      *
104      * @param device the remote device
105      * @return true on success, otherwise false.
106      */
disconnectA2dpSink(BluetoothDevice device)107     public boolean disconnectA2dpSink(BluetoothDevice device) {
108         return disconnectA2dpNative(getByteAddress(device));
109     }
110 
111     /**
112      * Set a BluetoothDevice as the active device
113      *
114      * <p>The active device is the only one that will receive passthrough commands and the only one
115      * that will have its audio decoded.
116      *
117      * <p>Sending null for the active device will make no device active.
118      *
119      * @return True if the active device request has been scheduled
120      */
setActiveDevice(BluetoothDevice device)121     public boolean setActiveDevice(BluetoothDevice device) {
122         // Translate to byte address for JNI. Use an all 0 MAC for no active device
123         byte[] address = null;
124         if (device != null) {
125             address = getByteAddress(device);
126         } else {
127             address = Utils.getBytesFromAddress("00:00:00:00:00:00");
128         }
129         return setActiveDeviceNative(address);
130     }
131 
132     /** Inform A2DP decoder of the current audio focus */
informAudioFocusState(int focusGranted)133     public void informAudioFocusState(int focusGranted) {
134         informAudioFocusStateNative(focusGranted);
135     }
136 
137     /** Inform A2DP decoder the desired audio gain */
informAudioTrackGain(float gain)138     public void informAudioTrackGain(float gain) {
139         informAudioTrackGainNative(gain);
140     }
141 
142     /** Send a stack event up to the A2DP Sink Service */
sendMessageToService(StackEvent event)143     private void sendMessageToService(StackEvent event) {
144         A2dpSinkService service = A2dpSinkService.getA2dpSinkService();
145         if (service != null) {
146             service.messageFromNative(event);
147         } else {
148             Log.e(TAG, "Event ignored, service not available: " + event);
149         }
150     }
151 
152     /** For the JNI to send messages about connection state changes */
onConnectionStateChanged(byte[] address, int state)153     public void onConnectionStateChanged(byte[] address, int state) {
154         StackEvent event = StackEvent.connectionStateChanged(getDevice(address), state);
155         Log.d(TAG, "onConnectionStateChanged: " + event);
156         sendMessageToService(event);
157     }
158 
159     /** For the JNI to send messages about audio stream state changes */
onAudioStateChanged(byte[] address, int state)160     public void onAudioStateChanged(byte[] address, int state) {
161         StackEvent event = StackEvent.audioStateChanged(getDevice(address), state);
162         Log.d(TAG, "onAudioStateChanged: " + event);
163         sendMessageToService(event);
164     }
165 
166     /** For the JNI to send messages about audio configuration changes */
onAudioConfigChanged(byte[] address, int sampleRate, int channelCount)167     public void onAudioConfigChanged(byte[] address, int sampleRate, int channelCount) {
168         StackEvent event =
169                 StackEvent.audioConfigChanged(getDevice(address), sampleRate, channelCount);
170         Log.d(TAG, "onAudioConfigChanged: " + event);
171         sendMessageToService(event);
172     }
173 
174     // Native methods that call into the JNI interface
initNative(int maxConnectedAudioDevices)175     private native void initNative(int maxConnectedAudioDevices);
176 
cleanupNative()177     private native void cleanupNative();
178 
connectA2dpNative(byte[] address)179     private native boolean connectA2dpNative(byte[] address);
180 
disconnectA2dpNative(byte[] address)181     private native boolean disconnectA2dpNative(byte[] address);
182 
setActiveDeviceNative(byte[] address)183     private native boolean setActiveDeviceNative(byte[] address);
184 
informAudioFocusStateNative(int focusGranted)185     private native void informAudioFocusStateNative(int focusGranted);
186 
informAudioTrackGainNative(float gain)187     private native void informAudioTrackGainNative(float gain);
188 }
189