Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d959560e authored by Jakub Tyszkowski's avatar Jakub Tyszkowski
Browse files

le_broadcast: Initial alignment of service with the API

This aligns the .aidl interfaces, adds the callback
registration mechanism, exposes profile in the
Bluetooth Adapter and stitches together basic API
calls with the service implementation.
It does not bring the BluetoothLeBroadcastMetadata
support yet.

Bug: 150670922
Bug: 210985995
Tag: #feature
Test: atest BluetoothInstrumentationTests
Sponsor: jpawlowski@

Change-Id: Ia454badc4f95a4b9953e57709a680f178ebfea45
parent ca502e66
Loading
Loading
Loading
Loading
+430 −90

File changed.

Preview size limit exceeded, changes collapsed.

+28 −57
Original line number Diff line number Diff line
@@ -150,85 +150,56 @@ public class LeAudioBroadcastServiceTest {
    @Test
    public void testCreateBroadcastNative() {
        int broadcast_profile = 0;
        byte[] meta = new byte[]{0x02, 0x01, 0x02};
        byte[] code = {0x00, 0x01, 0x00};
        mService.createBroadcast(meta, broadcast_profile, code);

        verify(mNativeInterface, times(1)).createBroadcast(eq(meta),
                eq(broadcast_profile), eq(code));
    }
        BluetoothLeAudioContentMetadata.Builder meta_builder =
                new BluetoothLeAudioContentMetadata.Builder();
        meta_builder.setLanguage("EN");
        meta_builder.setProgramInfo("Public broadcast info");
        BluetoothLeAudioContentMetadata meta = meta_builder.build();
        mService.createBroadcast(meta, code);

    @Test
    public void testStartBroadcastNative() {
        int broadcast_profile = 0;
        byte[] meta = new byte[]{0x02, 0x01, 0x02};
        byte[] code = {0x00, 0x01, 0x00};
        mService.createBroadcast(meta, broadcast_profile, code);

        int broadcast_id = 243;
        mService.startBroadcast(broadcast_id);
        verify(mNativeInterface, times(1)).startBroadcast(eq(broadcast_id));
        verify(mNativeInterface, times(1)).createBroadcast(eq(meta.getRawMetadata()), eq(1),
                eq(code));
    }

    @Test
    public void testStopBroadcastNative() {
    public void testStartStopBroadcastNative() {
        int broadcast_profile = 0;
        byte[] meta = new byte[]{0x02, 0x01, 0x02};
        byte[] code = {0x00, 0x01, 0x00};
        mService.createBroadcast(meta, broadcast_profile, code);

        int broadcast_id = 243;
        mService.stopBroadcast(broadcast_id);
        verify(mNativeInterface, times(1)).stopBroadcast(eq(broadcast_id));
    }
        BluetoothLeAudioContentMetadata.Builder meta_builder =
        new BluetoothLeAudioContentMetadata.Builder();
        meta_builder.setLanguage("EN");
        meta_builder.setProgramInfo("Public broadcast info");
        BluetoothLeAudioContentMetadata meta = meta_builder.build();
        mService.createBroadcast(meta, code);

    @Test
    public void testPauseBroadcastNative() {
        int broadcast_profile = 0;
        byte[] meta = new byte[]{0x02, 0x01, 0x02};
        byte[] code = {0x00, 0x01, 0x00};
        mService.createBroadcast(meta, broadcast_profile, code);
        int instance_id = 243;
        mService.startBroadcast(instance_id);
        verify(mNativeInterface, times(1)).startBroadcast(eq(instance_id));

        int broadcast_id = 243;
        mService.pauseBroadcast(broadcast_id);
        verify(mNativeInterface, times(1)).pauseBroadcast(eq(broadcast_id));
        mService.stopBroadcast(instance_id);
        verify(mNativeInterface, times(1)).stopBroadcast(eq(instance_id));
    }

    @Test
    public void testDestroyBroadcastNative() {
        int broadcast_profile = 0;
        byte[] meta = new byte[]{0x02, 0x01, 0x02};
        byte[] code = {0x00, 0x01, 0x00};
        mService.createBroadcast(meta, broadcast_profile, code);

        BluetoothLeAudioContentMetadata.Builder meta_builder =
                new BluetoothLeAudioContentMetadata.Builder();
        meta_builder.setLanguage("ENG");
        meta_builder.setProgramInfo("Public broadcast info");
        BluetoothLeAudioContentMetadata meta = meta_builder.build();
        mService.createBroadcast(meta, code);

        int broadcast_id = 243;
        mService.destroyBroadcast(broadcast_id);
        verify(mNativeInterface, times(1)).destroyBroadcast(eq(broadcast_id));
    }

    @Test
    public void testGetBroadcastAddressNative() {
        int broadcast_profile = 0;
        byte[] meta = new byte[]{0x02, 0x01, 0x02};
        byte[] code = {0x00, 0x01, 0x00};
        mService.createBroadcast(meta, broadcast_profile, code);

        int broadcast_id = 243;
        mService.getBroadcastId(broadcast_id);
        verify(mNativeInterface, times(1)).getBroadcastId(eq(broadcast_id));
    }

    @Test
    public void testGetAllBroadcastStates() {
        int broadcast_profile = 0;
        byte[] meta = new byte[]{0x02, 0x01, 0x02};
        byte[] code = {0x00, 0x01, 0x00};
        mService.createBroadcast(meta, broadcast_profile, code);

        int broadcast_id = 243;
        mService.getAllBroadcastStates();
        verify(mNativeInterface, times(1)).getAllBroadcastStates();
    }
    // FIXME: Add the missign API test cases

    private class LeAudioIntentReceiver extends BroadcastReceiver {
        @Override
+7 −0
Original line number Diff line number Diff line
@@ -3499,6 +3499,9 @@ public final class BluetoothAdapter {
        } else if (profile == BluetoothProfile.LE_AUDIO) {
            BluetoothLeAudio leAudio = new BluetoothLeAudio(context, listener, this);
            return true;
        } else if (profile == BluetoothProfile.LE_AUDIO_BROADCAST) {
            BluetoothLeBroadcast leAudio = new BluetoothLeBroadcast(context, listener);
            return true;
        } else if (profile == BluetoothProfile.VOLUME_CONTROL) {
            BluetoothVolumeControl vcs = new BluetoothVolumeControl(context, listener, this);
            return true;
@@ -3606,6 +3609,10 @@ public final class BluetoothAdapter {
                BluetoothLeAudio leAudio = (BluetoothLeAudio) proxy;
                leAudio.close();
                break;
            case BluetoothProfile.LE_AUDIO_BROADCAST:
                BluetoothLeBroadcast leAudioBroadcast = (BluetoothLeBroadcast) proxy;
                leAudioBroadcast.close();
                break;
            case BluetoothProfile.VOLUME_CONTROL:
                BluetoothVolumeControl vcs = (BluetoothVolumeControl) proxy;
                vcs.close();
+344 −10
Original line number Diff line number Diff line
@@ -16,21 +16,33 @@

package android.bluetooth;

import static android.bluetooth.BluetoothUtils.getSyncTimeout;

import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
import android.content.AttributionSource;
import android.content.Context;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.CloseGuard;
import android.util.Log;

import com.android.modules.utils.SynchronousResultReceiver;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;

/**
 * This class provides the public APIs to control the BAP Broadcast Source profile.
@@ -45,6 +57,148 @@ import java.util.concurrent.Executor;
public final class BluetoothLeBroadcast implements AutoCloseable, BluetoothProfile {
    private static final String TAG = "BluetoothLeBroadcast";
    private static final boolean DBG = true;
    private static final boolean VDBG = false;

    private CloseGuard mCloseGuard;

    private final BluetoothAdapter mAdapter;
    private final AttributionSource mAttributionSource;
    private final BluetoothProfileConnector<IBluetoothLeAudio> mProfileConnector =
            new BluetoothProfileConnector(this, BluetoothProfile.LE_AUDIO_BROADCAST,
                    "BluetoothLeAudioBroadcast", IBluetoothLeAudio.class.getName()) {
                @Override
                public IBluetoothLeAudio getServiceInterface(IBinder service) {
                    return IBluetoothLeAudio.Stub.asInterface(service);
                }
            };

    private final Map<Callback, Executor> mCallbackExecutorMap = new HashMap<>();

    @SuppressLint("AndroidFrameworkBluetoothPermission")
    private final IBluetoothLeBroadcastCallback mCallback =
            new IBluetoothLeBroadcastCallback.Stub() {
        @Override
        public void onBroadcastStarted(int reason, int broadcastId) {
            for (Map.Entry<BluetoothLeBroadcast.Callback, Executor> callbackExecutorEntry:
                    mCallbackExecutorMap.entrySet()) {
                BluetoothLeBroadcast.Callback callback = callbackExecutorEntry.getKey();
                Executor executor = callbackExecutorEntry.getValue();
                executor.execute(() -> callback.onBroadcastStarted(reason, broadcastId));
            }
        }

        @Override
        public void onBroadcastStartFailed(int reason) {
            for (Map.Entry<BluetoothLeBroadcast.Callback, Executor> callbackExecutorEntry:
                    mCallbackExecutorMap.entrySet()) {
                BluetoothLeBroadcast.Callback callback = callbackExecutorEntry.getKey();
                Executor executor = callbackExecutorEntry.getValue();
                executor.execute(() -> callback.onBroadcastStartFailed(reason));
            }
        }

        @Override
        public void onBroadcastStopped(int reason, int broadcastId) {
            for (Map.Entry<BluetoothLeBroadcast.Callback, Executor> callbackExecutorEntry:
                    mCallbackExecutorMap.entrySet()) {
                BluetoothLeBroadcast.Callback callback = callbackExecutorEntry.getKey();
                Executor executor = callbackExecutorEntry.getValue();
                executor.execute(() -> callback.onBroadcastStopped(reason, broadcastId));
            }
        }

        @Override
        public void onBroadcastStopFailed(int reason) {
            for (Map.Entry<BluetoothLeBroadcast.Callback, Executor> callbackExecutorEntry:
                    mCallbackExecutorMap.entrySet()) {
                BluetoothLeBroadcast.Callback callback = callbackExecutorEntry.getKey();
                Executor executor = callbackExecutorEntry.getValue();
                executor.execute(() -> callback.onBroadcastStopFailed(reason));
            }
        }

        @Override
        public void onPlaybackStarted(int reason, int broadcastId) {
            for (Map.Entry<BluetoothLeBroadcast.Callback, Executor> callbackExecutorEntry:
                    mCallbackExecutorMap.entrySet()) {
                BluetoothLeBroadcast.Callback callback = callbackExecutorEntry.getKey();
                Executor executor = callbackExecutorEntry.getValue();
                executor.execute(() -> callback.onPlaybackStarted(reason, broadcastId));
            }
        }

        @Override
        public void onPlaybackStopped(int reason, int broadcastId) {
            for (Map.Entry<BluetoothLeBroadcast.Callback, Executor> callbackExecutorEntry:
                    mCallbackExecutorMap.entrySet()) {
                BluetoothLeBroadcast.Callback callback = callbackExecutorEntry.getKey();
                Executor executor = callbackExecutorEntry.getValue();
                executor.execute(() -> callback.onPlaybackStopped(reason, broadcastId));
            }
        }

        @Override
        public void onBroadcastUpdated(int reason, int broadcastId) {
            for (Map.Entry<BluetoothLeBroadcast.Callback, Executor> callbackExecutorEntry:
                    mCallbackExecutorMap.entrySet()) {
                BluetoothLeBroadcast.Callback callback = callbackExecutorEntry.getKey();
                Executor executor = callbackExecutorEntry.getValue();
                executor.execute(() -> callback.onBroadcastUpdated(reason, broadcastId));
            }
        }

        @Override
        public void onBroadcastUpdateFailed(int reason, int broadcastId) {
            for (Map.Entry<BluetoothLeBroadcast.Callback, Executor> callbackExecutorEntry:
                    mCallbackExecutorMap.entrySet()) {
                BluetoothLeBroadcast.Callback callback = callbackExecutorEntry.getKey();
                Executor executor = callbackExecutorEntry.getValue();
                executor.execute(() -> callback.onBroadcastUpdateFailed(reason, broadcastId));
            }
        }

        @Override
        public void onBroadcastMetadataChanged(int broadcastId,
                BluetoothLeBroadcastMetadata metadata) {
            for (Map.Entry<BluetoothLeBroadcast.Callback, Executor> callbackExecutorEntry:
                    mCallbackExecutorMap.entrySet()) {
                BluetoothLeBroadcast.Callback callback = callbackExecutorEntry.getKey();
                Executor executor = callbackExecutorEntry.getValue();
                executor.execute(() -> callback.onBroadcastMetadataChanged(broadcastId, metadata));
            }
        }
    };

    @SuppressLint("AndroidFrameworkBluetoothPermission")
    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
            new IBluetoothStateChangeCallback.Stub() {
                public void onBluetoothStateChange(boolean up) {
                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
                    if (up) {
                        // re-register the service-to-app callback
                        synchronized (mCallbackExecutorMap) {
                            if (!mCallbackExecutorMap.isEmpty()) {
                                try {
                                    final IBluetoothLeAudio service = getService();
                                    if (service != null) {
                                        final SynchronousResultReceiver<Integer> recv =
                                                new SynchronousResultReceiver();
                                        service.registerLeBroadcastCallback(mCallback,
                                                mAttributionSource, recv);
                                        recv.awaitResultNoInterrupt(getSyncTimeout())
                                                .getValue(null);
                                    }
                                } catch (TimeoutException e) {
                                    Log.e(TAG, "onBluetoothServiceUp: Failed to register "
                                            + "Le Broadcaster callback", e);
                                } catch (RemoteException e) {
                                    throw e.rethrowFromSystemServer();
                                }
                            }
                        }
                    }
                }
            };

    /**
     * Interface for receiving events related to Broadcast Source
@@ -171,7 +325,33 @@ public final class BluetoothLeBroadcast implements AutoCloseable, BluetoothProfi
     * @param listener listens for service callbacks across binder
     * @hide
     */
    /*package*/ BluetoothLeBroadcast(Context context, BluetoothProfile.ServiceListener listener) {}
    /*package*/ BluetoothLeBroadcast(Context context, BluetoothProfile.ServiceListener listener) {
        mAdapter = BluetoothAdapter.getDefaultAdapter();
        mAttributionSource = mAdapter.getAttributionSource();
        mProfileConnector.connect(context, listener);

        IBluetoothManager mgr = mAdapter.getBluetoothManager();
        if (mgr != null) {
            try {
                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }

        mCloseGuard = new CloseGuard();
        mCloseGuard.open("close");
    }

    /**
     * @hide
     */
    protected void finalize() {
        if (mCloseGuard != null) {
            mCloseGuard.warnIfOpen();
        }
        close();
    }

    /**
     * Not supported since LE Audio Broadcasts do not establish a connection.
@@ -246,8 +426,36 @@ public final class BluetoothLeBroadcast implements AutoCloseable, BluetoothProfi
        if (callback == null) {
            throw new IllegalArgumentException("callback cannot be null");
        }
        log("registerCallback");
        throw new UnsupportedOperationException("Not Implemented");
        if (!isEnabled()) {
            throw new IllegalStateException("service not enabled");
        }

        if (DBG) log("registerCallback");

        synchronized (mCallbackExecutorMap) {
            // If the callback map is empty, we register the service-to-app callback
            if (mCallbackExecutorMap.isEmpty()) {
                try {
                    final IBluetoothLeAudio service = getService();
                    if (service != null) {
                        final SynchronousResultReceiver<Integer> recv =
                                new SynchronousResultReceiver();
                        service.registerLeBroadcastCallback(mCallback, mAttributionSource, recv);
                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
                    }
                } catch (TimeoutException | IllegalStateException e) {
                    Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }

            // Adds the passed in callback to our map of callbacks to executors
            if (mCallbackExecutorMap.containsKey(callback)) {
                throw new IllegalArgumentException("This callback has already been registered");
            }
            mCallbackExecutorMap.put(callback, executor);
        }
    }

    /**
@@ -271,8 +479,30 @@ public final class BluetoothLeBroadcast implements AutoCloseable, BluetoothProfi
        if (callback == null) {
            throw new IllegalArgumentException("callback cannot be null");
        }
        log("unregisterCallback");
        throw new UnsupportedOperationException("Not Implemented");

        if (DBG) log("unregisterCallback");

        synchronized (mCallbackExecutorMap) {
            if (mCallbackExecutorMap.remove(callback) != null) {
                throw new IllegalArgumentException("This callback has not been registered");
            }
        }

        // If the callback map is empty, we unregister the service-to-app callback
        if (mCallbackExecutorMap.isEmpty()) {
            try {
                final IBluetoothLeAudio service = getService();
                if (service != null) {
                    final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
                    service.unregisterLeBroadcastCallback(mCallback, mAttributionSource, recv);
                    recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
                }
            } catch (TimeoutException | IllegalStateException e) {
                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }

    /**
@@ -323,6 +553,17 @@ public final class BluetoothLeBroadcast implements AutoCloseable, BluetoothProfi
    public void startBroadcast(@NonNull BluetoothLeAudioContentMetadata contentMetadata,
            @Nullable byte[] broadcastCode) {
        if (DBG) log("startBroadcasting");
        final IBluetoothLeAudio service = getService();
        if (service == null) {
            Log.w(TAG, "Proxy not attached to service");
            if (DBG) log(Log.getStackTraceString(new Throwable()));
        } else if (isEnabled()) {
            try {
                service.startBroadcast(contentMetadata, broadcastCode, mAttributionSource);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }

    /**
@@ -346,7 +587,18 @@ public final class BluetoothLeBroadcast implements AutoCloseable, BluetoothProfi
    })
    public void updateBroadcast(int broadcastId,
            @NonNull BluetoothLeAudioContentMetadata contentMetadata) {

        if (DBG) log("updateBroadcast");
        final IBluetoothLeAudio service = getService();
        if (service == null) {
            Log.w(TAG, "Proxy not attached to service");
            if (DBG) log(Log.getStackTraceString(new Throwable()));
        } else if (isEnabled()) {
            try {
                service.updateBroadcast(broadcastId, contentMetadata, mAttributionSource);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }

    /**
@@ -368,6 +620,17 @@ public final class BluetoothLeBroadcast implements AutoCloseable, BluetoothProfi
    })
    public void stopBroadcast(int broadcastId) {
        if (DBG) log("disableBroadcastMode");
        final IBluetoothLeAudio service = getService();
        if (service == null) {
            Log.w(TAG, "Proxy not attached to service");
            if (DBG) log(Log.getStackTraceString(new Throwable()));
        } else if (isEnabled()) {
            try {
                service.stopBroadcast(broadcastId, mAttributionSource);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }

    /**
@@ -385,7 +648,23 @@ public final class BluetoothLeBroadcast implements AutoCloseable, BluetoothProfi
            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
    })
    public boolean isPlaying(int broadcastId) {
        return false;
        final IBluetoothLeAudio service = getService();
        final boolean defaultValue = false;
        if (service == null) {
            Log.w(TAG, "Proxy not attached to service");
            if (DBG) log(Log.getStackTraceString(new Throwable()));
        } else if (isEnabled()) {
            try {
                final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
                service.isPlaying(broadcastId, mAttributionSource, recv);
                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
            } catch (TimeoutException e) {
                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        return defaultValue;
    }

    /**
@@ -402,7 +681,24 @@ public final class BluetoothLeBroadcast implements AutoCloseable, BluetoothProfi
            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
    })
    public @NonNull List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() {
        return Collections.emptyList();
        final IBluetoothLeAudio service = getService();
        final List<BluetoothLeBroadcastMetadata> defaultValue = Collections.emptyList();
        if (service == null) {
            Log.w(TAG, "Proxy not attached to service");
            if (DBG) log(Log.getStackTraceString(new Throwable()));
        } else if (isEnabled()) {
            try {
                final SynchronousResultReceiver<List<BluetoothLeBroadcastMetadata>> recv =
                        new SynchronousResultReceiver();
                service.getAllBroadcastMetadata(mAttributionSource, recv);
                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
            } catch (TimeoutException e) {
                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        return defaultValue;
    }

    /**
@@ -413,7 +709,23 @@ public final class BluetoothLeBroadcast implements AutoCloseable, BluetoothProfi
    @SystemApi
    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
    public int getMaximumNumberOfBroadcast() {
        return 1;
        final IBluetoothLeAudio service = getService();
        final int defaultValue = 1;
        if (service == null) {
            Log.w(TAG, "Proxy not attached to service");
            if (DBG) log(Log.getStackTraceString(new Throwable()));
        } else if (isEnabled()) {
            try {
                final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
                service.getMaximumNumberOfBroadcast(mAttributionSource, recv);
                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
            } catch (TimeoutException e) {
                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        return defaultValue;
    }

    /**
@@ -421,7 +733,29 @@ public final class BluetoothLeBroadcast implements AutoCloseable, BluetoothProfi
     * @hide
     */
    @Override
    public void close() throws Exception {}
    public void close() {
        if (VDBG) log("close()");

        IBluetoothManager mgr = mAdapter.getBluetoothManager();
        if (mgr != null) {
            try {
                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
            } catch (RemoteException e) {
                Log.e(TAG, "", e);
            }
        }

        mProfileConnector.disconnect();
    }

    private boolean isEnabled() {
        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
        return false;
    }

    private IBluetoothLeAudio getService() {
        return mProfileConnector.getService();
    }

    private static void log(String msg) {
        Log.d(TAG, msg);
+1 −0
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ filegroup {
        "android/bluetooth/IBluetoothVolumeControl.aidl",
        "android/bluetooth/IBluetoothHidHost.aidl",
        "android/bluetooth/IBluetoothLeAudio.aidl",
        "android/bluetooth/IBluetoothLeBroadcastCallback.aidl",
        "android/bluetooth/IBluetoothManager.aidl",
        "android/bluetooth/IBluetoothManagerCallback.aidl",
        "android/bluetooth/IBluetoothMap.aidl",
Loading