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