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

Commit da47f8d9 authored by Jack He's avatar Jack He Committed by Gerrit Code Review
Browse files

Merge "le_broadcast: Initial alignment of service with the API"

parents b4e5a976 d959560e
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
@@ -3511,6 +3511,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;
@@ -3618,6 +3621,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