1 /*
2  * Copyright 2017 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 /*
18  * Defines the native inteface that is used by state machine/service to
19  * send or receive messages from the native stack. This file is registered
20  * for the native methods in the corresponding JNI C++ file.
21  */
22 package com.android.bluetooth.a2dp;
23 
24 import android.bluetooth.BluetoothA2dp;
25 import android.bluetooth.BluetoothAdapter;
26 import android.bluetooth.BluetoothCodecConfig;
27 import android.bluetooth.BluetoothCodecStatus;
28 import android.bluetooth.BluetoothCodecType;
29 import android.bluetooth.BluetoothDevice;
30 import android.util.Log;
31 
32 import com.android.bluetooth.Utils;
33 import com.android.bluetooth.btservice.AdapterService;
34 import com.android.bluetooth.flags.Flags;
35 import com.android.internal.annotations.GuardedBy;
36 import com.android.internal.annotations.VisibleForTesting;
37 
38 import java.util.Arrays;
39 import java.util.List;
40 import java.util.Objects;
41 
42 /** A2DP Native Interface to/from JNI. */
43 public class A2dpNativeInterface {
44     private static final String TAG = A2dpNativeInterface.class.getSimpleName();
45     private BluetoothAdapter mAdapter;
46     private AdapterService mAdapterService;
47 
48     @GuardedBy("INSTANCE_LOCK")
49     private static A2dpNativeInterface sInstance;
50 
51     private static BluetoothCodecType[] sSupportedCodecTypes;
52 
53     private static final Object INSTANCE_LOCK = new Object();
54 
55     @VisibleForTesting
A2dpNativeInterface()56     private A2dpNativeInterface() {
57         mAdapter = BluetoothAdapter.getDefaultAdapter();
58         if (mAdapter == null) {
59             Log.wtf(TAG, "No Bluetooth Adapter Available");
60         }
61         mAdapterService =
62                 Objects.requireNonNull(
63                         AdapterService.getAdapterService(),
64                         "AdapterService cannot be null when A2dpNativeInterface init");
65     }
66 
67     /** Get singleton instance. */
getInstance()68     public static A2dpNativeInterface getInstance() {
69         synchronized (INSTANCE_LOCK) {
70             if (sInstance == null) {
71                 sInstance = new A2dpNativeInterface();
72             }
73             return sInstance;
74         }
75     }
76 
77     /** Set singleton instance. */
78     @VisibleForTesting
setInstance(A2dpNativeInterface instance)79     public static void setInstance(A2dpNativeInterface instance) {
80         synchronized (INSTANCE_LOCK) {
81             sInstance = instance;
82         }
83     }
84 
85     /**
86      * Initializes the native interface.
87      *
88      * @param maxConnectedAudioDevices maximum number of A2DP Sink devices that can be connected
89      *     simultaneously
90      * @param codecConfigPriorities an array with the codec configuration priorities to configure.
91      */
init( int maxConnectedAudioDevices, BluetoothCodecConfig[] codecConfigPriorities, BluetoothCodecConfig[] codecConfigOffloading)92     public void init(
93             int maxConnectedAudioDevices,
94             BluetoothCodecConfig[] codecConfigPriorities,
95             BluetoothCodecConfig[] codecConfigOffloading) {
96         initNative(maxConnectedAudioDevices, codecConfigPriorities, codecConfigOffloading);
97     }
98 
99     /** Cleanup the native interface. */
cleanup()100     public void cleanup() {
101         cleanupNative();
102     }
103 
104     /** Returns the list of locally supported codec types. */
getSupportedCodecTypes()105     public List<BluetoothCodecType> getSupportedCodecTypes() {
106         if (sSupportedCodecTypes == null) {
107             sSupportedCodecTypes = getSupportedCodecTypesNative();
108         }
109         return Arrays.asList(sSupportedCodecTypes);
110     }
111 
112     /**
113      * Initiates A2DP connection to a remote device.
114      *
115      * @param device the remote device
116      * @return true on success, otherwise false.
117      */
connectA2dp(BluetoothDevice device)118     public boolean connectA2dp(BluetoothDevice device) {
119         return connectA2dpNative(getByteAddress(device));
120     }
121 
122     /**
123      * Disconnects A2DP from a remote device.
124      *
125      * @param device the remote device
126      * @return true on success, otherwise false.
127      */
disconnectA2dp(BluetoothDevice device)128     public boolean disconnectA2dp(BluetoothDevice device) {
129         return disconnectA2dpNative(getByteAddress(device));
130     }
131 
132     /**
133      * Sets a connected A2DP remote device to silence mode.
134      *
135      * @param device the remote device
136      * @return true on success, otherwise false.
137      */
setSilenceDevice(BluetoothDevice device, boolean silence)138     public boolean setSilenceDevice(BluetoothDevice device, boolean silence) {
139         return setSilenceDeviceNative(getByteAddress(device), silence);
140     }
141 
142     /**
143      * Sets a connected A2DP remote device as active.
144      *
145      * @param device the remote device
146      * @return true on success, otherwise false.
147      */
setActiveDevice(BluetoothDevice device)148     public boolean setActiveDevice(BluetoothDevice device) {
149         return setActiveDeviceNative(getByteAddress(device));
150     }
151 
152     /**
153      * Sets the codec configuration preferences.
154      *
155      * @param device the remote Bluetooth device
156      * @param codecConfigArray an array with the codec configurations to configure.
157      * @return true on success, otherwise false.
158      */
setCodecConfigPreference( BluetoothDevice device, BluetoothCodecConfig[] codecConfigArray)159     public boolean setCodecConfigPreference(
160             BluetoothDevice device, BluetoothCodecConfig[] codecConfigArray) {
161         return setCodecConfigPreferenceNative(getByteAddress(device), codecConfigArray);
162     }
163 
getDevice(byte[] address)164     private BluetoothDevice getDevice(byte[] address) {
165         return mAdapterService.getDeviceFromByte(address);
166     }
167 
getByteAddress(BluetoothDevice device)168     private byte[] getByteAddress(BluetoothDevice device) {
169         if (device == null) {
170             return Utils.getBytesFromAddress("00:00:00:00:00:00");
171         }
172         if (Flags.identityAddressNullIfUnknown()) {
173             return Utils.getByteBrEdrAddress(device);
174         } else {
175             return mAdapterService.getByteIdentityAddress(device);
176         }
177     }
178 
sendMessageToService(A2dpStackEvent event)179     private void sendMessageToService(A2dpStackEvent event) {
180         A2dpService service = A2dpService.getA2dpService();
181         if (service != null) {
182             service.messageFromNative(event);
183         } else {
184             Log.w(TAG, "Event ignored, service not available: " + event);
185         }
186     }
187 
188     // Callbacks from the native stack back into the Java framework.
189     // All callbacks are routed via the Service which will disambiguate which
190     // state machine the message should be routed to.
191 
onConnectionStateChanged(byte[] address, int state)192     private void onConnectionStateChanged(byte[] address, int state) {
193         A2dpStackEvent event =
194                 new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
195         event.device = getDevice(address);
196         event.valueInt = state;
197 
198         Log.d(TAG, "onConnectionStateChanged: " + event);
199         sendMessageToService(event);
200     }
201 
onAudioStateChanged(byte[] address, int state)202     private void onAudioStateChanged(byte[] address, int state) {
203         A2dpStackEvent event = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
204         event.device = getDevice(address);
205         event.valueInt = state;
206 
207         Log.d(TAG, "onAudioStateChanged: " + event);
208         sendMessageToService(event);
209     }
210 
onCodecConfigChanged( byte[] address, BluetoothCodecConfig newCodecConfig, BluetoothCodecConfig[] codecsLocalCapabilities, BluetoothCodecConfig[] codecsSelectableCapabilities)211     private void onCodecConfigChanged(
212             byte[] address,
213             BluetoothCodecConfig newCodecConfig,
214             BluetoothCodecConfig[] codecsLocalCapabilities,
215             BluetoothCodecConfig[] codecsSelectableCapabilities) {
216         A2dpStackEvent event = new A2dpStackEvent(A2dpStackEvent.EVENT_TYPE_CODEC_CONFIG_CHANGED);
217         event.device = getDevice(address);
218         event.codecStatus =
219                 new BluetoothCodecStatus(
220                         newCodecConfig,
221                         Arrays.asList(codecsLocalCapabilities),
222                         Arrays.asList(codecsSelectableCapabilities));
223         Log.d(TAG, "onCodecConfigChanged: " + event);
224         sendMessageToService(event);
225     }
226 
isMandatoryCodecPreferred(byte[] address)227     private boolean isMandatoryCodecPreferred(byte[] address) {
228         A2dpService service = A2dpService.getA2dpService();
229         if (service != null) {
230             int enabled = service.getOptionalCodecsEnabled(getDevice(address));
231             Log.d(TAG, "isMandatoryCodecPreferred: optional preference " + enabled);
232             // Optional codecs are more preferred if possible
233             return enabled == BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED;
234         } else {
235             Log.w(TAG, "isMandatoryCodecPreferred: service not available");
236             return false;
237         }
238     }
239 
240     // Native methods that call into the JNI interface
initNative( int maxConnectedAudioDevices, BluetoothCodecConfig[] codecConfigPriorities, BluetoothCodecConfig[] codecConfigOffloading)241     private native void initNative(
242             int maxConnectedAudioDevices,
243             BluetoothCodecConfig[] codecConfigPriorities,
244             BluetoothCodecConfig[] codecConfigOffloading);
245 
cleanupNative()246     private native void cleanupNative();
247 
getSupportedCodecTypesNative()248     private native BluetoothCodecType[] getSupportedCodecTypesNative();
249 
connectA2dpNative(byte[] address)250     private native boolean connectA2dpNative(byte[] address);
251 
disconnectA2dpNative(byte[] address)252     private native boolean disconnectA2dpNative(byte[] address);
253 
setSilenceDeviceNative(byte[] address, boolean silence)254     private native boolean setSilenceDeviceNative(byte[] address, boolean silence);
255 
setActiveDeviceNative(byte[] address)256     private native boolean setActiveDeviceNative(byte[] address);
257 
setCodecConfigPreferenceNative( byte[] address, BluetoothCodecConfig[] codecConfigArray)258     private native boolean setCodecConfigPreferenceNative(
259             byte[] address, BluetoothCodecConfig[] codecConfigArray);
260 }
261