• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.internal.annotations.GuardedBy;
25 
26 import java.util.Objects;
27 
28 /**
29  * A2DP Sink Native Interface to/from JNI.
30  */
31 public class A2dpSinkNativeInterface {
32     private static final String TAG = "A2dpSinkNativeInterface";
33     private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
34     private AdapterService mAdapterService;
35 
36     @GuardedBy("INSTANCE_LOCK")
37     private static A2dpSinkNativeInterface sInstance;
38     private static final Object INSTANCE_LOCK = new Object();
39 
40     static {
classInitNative()41         classInitNative();
42     }
43 
A2dpSinkNativeInterface()44     private A2dpSinkNativeInterface() {
45         mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
46                 "AdapterService cannot be null when A2dpSinkNativeInterface init");
47     }
48 
49     /**
50      * Get singleton instance.
51      */
getInstance()52     public static A2dpSinkNativeInterface getInstance() {
53         synchronized (INSTANCE_LOCK) {
54             if (sInstance == null) {
55                 sInstance = new A2dpSinkNativeInterface();
56             }
57             return sInstance;
58         }
59     }
60 
61     /**
62      * Initializes the native interface and sets the max number of connected devices
63      *
64      * @param maxConnectedAudioDevices The maximum number of devices that can be connected at once
65      */
init(int maxConnectedAudioDevices)66     public void init(int maxConnectedAudioDevices) {
67         initNative(maxConnectedAudioDevices);
68     }
69 
70     /**
71      * Cleanup the native interface.
72      */
cleanup()73     public void cleanup() {
74         cleanupNative();
75     }
76 
getDevice(byte[] address)77     private BluetoothDevice getDevice(byte[] address) {
78         return mAdapterService.getDeviceFromByte(address);
79     }
80 
getByteAddress(BluetoothDevice device)81     private byte[] getByteAddress(BluetoothDevice device) {
82         return mAdapterService.getByteIdentityAddress(device);
83     }
84 
85     /**
86      * Initiates an A2DP connection to a remote device.
87      *
88      * @param device the remote device
89      * @return true on success, otherwise false.
90      */
connectA2dpSink(BluetoothDevice device)91     public boolean connectA2dpSink(BluetoothDevice device) {
92         return connectA2dpNative(getByteAddress(device));
93     }
94 
95     /**
96      * Disconnects A2DP from a remote device.
97      *
98      * @param device the remote device
99      * @return true on success, otherwise false.
100      */
disconnectA2dpSink(BluetoothDevice device)101     public boolean disconnectA2dpSink(BluetoothDevice device) {
102         return disconnectA2dpNative(getByteAddress(device));
103     }
104 
105     /**
106      * Set a BluetoothDevice as the active device
107      *
108      * The active device is the only one that will receive passthrough commands and the only one
109      * that will have its audio decoded.
110      *
111      * Sending null for the active device will make no device active.
112      *
113      * @param device
114      * @return True if the active device request has been scheduled
115      */
setActiveDevice(BluetoothDevice device)116     public boolean setActiveDevice(BluetoothDevice device) {
117         // Translate to byte address for JNI. Use an all 0 MAC for no active device
118         byte[] address = null;
119         if (device != null) {
120             address = getByteAddress(device);
121         } else {
122             address = Utils.getBytesFromAddress("00:00:00:00:00:00");
123         }
124         return setActiveDeviceNative(address);
125     }
126 
127     /**
128      * Inform A2DP decoder of the current audio focus
129      *
130      * @param focusGranted
131      */
informAudioFocusState(int focusGranted)132     public void informAudioFocusState(int focusGranted) {
133         informAudioFocusStateNative(focusGranted);
134     }
135 
136     /**
137      * Inform A2DP decoder the desired audio gain
138      *
139      * @param gain
140      */
informAudioTrackGain(float gain)141     public void informAudioTrackGain(float gain) {
142         informAudioTrackGainNative(gain);
143     }
144 
145     /**
146      * Send a stack event up to the A2DP Sink Service
147      */
sendMessageToService(StackEvent event)148     private void sendMessageToService(StackEvent event) {
149         A2dpSinkService service = A2dpSinkService.getA2dpSinkService();
150         if (service != null) {
151             service.messageFromNative(event);
152         } else {
153             Log.e(TAG, "Event ignored, service not available: " + event);
154         }
155     }
156 
157     /**
158      * For the JNI to send messages about connection state changes
159      */
onConnectionStateChanged(byte[] address, int state)160     public void onConnectionStateChanged(byte[] address, int state) {
161         StackEvent event =
162                 StackEvent.connectionStateChanged(getDevice(address), state);
163         if (DBG) {
164             Log.d(TAG, "onConnectionStateChanged: " + event);
165         }
166         sendMessageToService(event);
167     }
168 
169     /**
170      * For the JNI to send messages about audio stream state changes
171      */
onAudioStateChanged(byte[] address, int state)172     public void onAudioStateChanged(byte[] address, int state) {
173         StackEvent event = StackEvent.audioStateChanged(getDevice(address), state);
174         if (DBG) {
175             Log.d(TAG, "onAudioStateChanged: " + event);
176         }
177         sendMessageToService(event);
178     }
179 
180     /**
181      * For the JNI to send messages about audio configuration changes
182      */
onAudioConfigChanged(byte[] address, int sampleRate, int channelCount)183     public void onAudioConfigChanged(byte[] address, int sampleRate, int channelCount) {
184         StackEvent event = StackEvent.audioConfigChanged(
185                 getDevice(address), sampleRate, channelCount);
186         if (DBG) {
187             Log.d(TAG, "onAudioConfigChanged: " + event);
188         }
189         sendMessageToService(event);
190     }
191 
192     // Native methods that call into the JNI interface
classInitNative()193     private static native void classInitNative();
initNative(int maxConnectedAudioDevices)194     private native void initNative(int maxConnectedAudioDevices);
cleanupNative()195     private native void cleanupNative();
connectA2dpNative(byte[] address)196     private native boolean connectA2dpNative(byte[] address);
disconnectA2dpNative(byte[] address)197     private native boolean disconnectA2dpNative(byte[] address);
setActiveDeviceNative(byte[] address)198     private native boolean setActiveDeviceNative(byte[] address);
informAudioFocusStateNative(int focusGranted)199     private native void informAudioFocusStateNative(int focusGranted);
informAudioTrackGainNative(float gain)200     private native void informAudioTrackGainNative(float gain);
201 }
202