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

Commit 5d4970e0 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "LeAudio: registerCallback are now oneway" into main

parents fdb3a536 77c7eb2c
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -60,9 +60,9 @@ interface IBluetoothLeAudio {
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
    void setCodecConfigPreference(in int groupId, in BluetoothLeAudioCodecConfig inputCodecConfig, in BluetoothLeAudioCodecConfig outputCodecConfig, in AttributionSource source);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
    void registerCallback(in IBluetoothLeAudioCallback callback, in AttributionSource attributionSource);
    oneway void registerCallback(in IBluetoothLeAudioCallback callback, in AttributionSource attributionSource);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
    void unregisterCallback(in IBluetoothLeAudioCallback callback, in AttributionSource attributionSource);
    oneway void unregisterCallback(in IBluetoothLeAudioCallback callback, in AttributionSource attributionSource);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
    void setCcidInformation(in ParcelUuid userUuid, in int ccid, in int contextType, in AttributionSource attributionSource);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
+80 −132
Original line number Diff line number Diff line
@@ -20,6 +20,8 @@ package android.bluetooth;
import static android.Manifest.permission.BLUETOOTH_CONNECT;
import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;

import static java.util.Objects.requireNonNull;

import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -29,13 +31,13 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
import android.content.AttributionSource;
import android.content.Context;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.util.CloseGuard;
import android.util.Log;
@@ -45,11 +47,9 @@ import com.android.bluetooth.flags.Flags;
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.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;

/**
 * This class provides the public APIs to control the LeAudio profile.
@@ -65,8 +65,6 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable {
    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
    private static final boolean VDBG = false;

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

    private CloseGuard mCloseGuard;

    /**
@@ -155,67 +153,41 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable {
        }
    }

    @SuppressLint("AndroidFrameworkBluetoothPermission")
    private final IBluetoothLeAudioCallback mCallback =
            new IBluetoothLeAudioCallback.Stub() {
    private final CallbackWrapper<Callback, IBluetoothLeAudio> mCallbackWrapper;

    private final IBluetoothLeAudioCallback mCallback = new LeAudioNotifyCallback();

    private class LeAudioNotifyCallback extends IBluetoothLeAudioCallback.Stub {
        @Override
                public void onCodecConfigChanged(
                        int groupId, @NonNull BluetoothLeAudioCodecStatus status) {
                    for (Map.Entry<BluetoothLeAudio.Callback, Executor> callbackExecutorEntry :
                            mCallbackExecutorMap.entrySet()) {
                        BluetoothLeAudio.Callback callback = callbackExecutorEntry.getKey();
                        Executor executor = callbackExecutorEntry.getValue();
                        executor.execute(() -> callback.onCodecConfigChanged(groupId, status));
                    }
        public void onCodecConfigChanged(int groupId, BluetoothLeAudioCodecStatus status) {
            mCallbackWrapper.forEach((cb) -> cb.onCodecConfigChanged(groupId, status));
        }

        @Override
        public void onGroupNodeAdded(@NonNull BluetoothDevice device, int groupId) {
            Attributable.setAttributionSource(device, mAttributionSource);
                    for (Map.Entry<BluetoothLeAudio.Callback, Executor> callbackExecutorEntry :
                            mCallbackExecutorMap.entrySet()) {
                        BluetoothLeAudio.Callback callback = callbackExecutorEntry.getKey();
                        Executor executor = callbackExecutorEntry.getValue();
                        executor.execute(() -> callback.onGroupNodeAdded(device, groupId));
                    }
            mCallbackWrapper.forEach((cb) -> cb.onGroupNodeAdded(device, groupId));
        }

        @Override
        public void onGroupNodeRemoved(@NonNull BluetoothDevice device, int groupId) {
            Attributable.setAttributionSource(device, mAttributionSource);
                    for (Map.Entry<BluetoothLeAudio.Callback, Executor> callbackExecutorEntry :
                            mCallbackExecutorMap.entrySet()) {
                        BluetoothLeAudio.Callback callback = callbackExecutorEntry.getKey();
                        Executor executor = callbackExecutorEntry.getValue();
                        executor.execute(() -> callback.onGroupNodeRemoved(device, groupId));
                    }
            mCallbackWrapper.forEach((cb) -> cb.onGroupNodeRemoved(device, groupId));
        }

        @Override
        public void onGroupStatusChanged(int groupId, int groupStatus) {
                    for (Map.Entry<BluetoothLeAudio.Callback, Executor> callbackExecutorEntry :
                            mCallbackExecutorMap.entrySet()) {
                        BluetoothLeAudio.Callback callback = callbackExecutorEntry.getKey();
                        Executor executor = callbackExecutorEntry.getValue();
                        executor.execute(() -> callback.onGroupStatusChanged(groupId, groupStatus));
                    }
            mCallbackWrapper.forEach((cb) -> cb.onGroupStatusChanged(groupId, groupStatus));
        }

        @Override
        public void onGroupStreamStatusChanged(int groupId, int groupStreamStatus) {
            if (Flags.leaudioCallbackOnGroupStreamStatus()) {
                        for (Map.Entry<BluetoothLeAudio.Callback, Executor> callbackExecutorEntry :
                                mCallbackExecutorMap.entrySet()) {
                            BluetoothLeAudio.Callback callback = callbackExecutorEntry.getKey();
                            Executor executor = callbackExecutorEntry.getValue();
                            executor.execute(
                                    () ->
                                            callback.onGroupStreamStatusChanged(
                                                    groupId, groupStreamStatus));
                mCallbackWrapper.forEach(
                        (cb) -> cb.onGroupStreamStatusChanged(groupId, groupStreamStatus));
            }
        }
    }
            };

    /**
     * Intent used to broadcast the change in connection state of the LeAudio profile. Please note
@@ -664,6 +636,7 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable {
    public static final String EXTRA_LE_AUDIO_AVAILABLE_CONTEXTS =
            "android.bluetooth.extra.LE_AUDIO_AVAILABLE_CONTEXTS";

    private final Context mContext;
    private final BluetoothAdapter mAdapter;
    private final AttributionSource mAttributionSource;

@@ -707,10 +680,29 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable {
     * service.
     */
    /* package */ BluetoothLeAudio(Context context, BluetoothAdapter adapter) {
        mContext = requireNonNull(context);
        mAdapter = adapter;
        mAttributionSource = adapter.getAttributionSource();
        mService = null;

        Consumer<IBluetoothLeAudio> registerConsumer =
                (IBluetoothLeAudio service) -> {
                    try {
                        service.registerCallback(mCallback, mAttributionSource);
                    } catch (RemoteException e) {
                        Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
                    }
                };
        Consumer<IBluetoothLeAudio> unregisterConsumer =
                (IBluetoothLeAudio service) -> {
                    try {
                        service.unregisterCallback(mCallback, mAttributionSource);
                    } catch (RemoteException e) {
                        Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
                    }
                };

        mCallbackWrapper = new CallbackWrapper(registerConsumer, unregisterConsumer);
        mCloseGuard = new CloseGuard();
        mCloseGuard.open("close");
    }
@@ -725,19 +717,7 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable {
    @Override
    public void onServiceConnected(IBinder service) {
        mService = IBluetoothLeAudio.Stub.asInterface(service);
        // re-register the service-to-app callback
        synchronized (mCallbackExecutorMap) {
            if (mCallbackExecutorMap.isEmpty()) {
                return;
            }
            try {
                if (service != null) {
                    mService.registerCallback(mCallback, mAttributionSource);
                }
            } catch (RemoteException e) {
                Log.e(TAG, "Failed to register callback", e);
            }
        }
        mCallbackWrapper.registerToNewService(mService);
    }

    /** @hide */
@@ -960,36 +940,15 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable {
            })
    public void registerCallback(
            @NonNull @CallbackExecutor Executor executor, @NonNull Callback callback) {
        Objects.requireNonNull(executor, "executor cannot be null");
        Objects.requireNonNull(callback, "callback cannot be null");
        if (DBG) log("registerCallback");

        synchronized (mCallbackExecutorMap) {
            // If the callback map is empty, we register the service-to-app callback
            if (mCallbackExecutorMap.isEmpty()) {
                if (!mAdapter.isEnabled()) {
                    /* If Bluetooth is off, just store callback and it will be registered
                     * when Bluetooth is on
                     */
                    mCallbackExecutorMap.put(callback, executor);
                    return;
                }
                try {
                    final IBluetoothLeAudio service = getService();
                    if (service != null) {
                        service.registerCallback(mCallback, mAttributionSource);
                    }
                } catch (RemoteException e) {
                    throw e.rethrowAsRuntimeException();
                }
            }
        // Enforcing permission in the framework is useless from security point of view.
        // This is being done to help normal app developer to catch the missing permission, since
        // the call to the service is oneway and the SecurityException will just be logged
        final int pid = Process.myPid();
        final int uid = Process.myUid();
        mContext.enforcePermission(BLUETOOTH_CONNECT, pid, uid, null);
        mContext.enforcePermission(BLUETOOTH_PRIVILEGED, pid, uid, null);

            // 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);
        }
        mCallbackWrapper.registerCallback(getService(), callback, executor);
    }

    /**
@@ -1013,26 +972,15 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable {
                BLUETOOTH_PRIVILEGED,
            })
    public void unregisterCallback(@NonNull Callback callback) {
        Objects.requireNonNull(callback, "callback cannot be null");
        if (DBG) log("unregisterCallback");
        // Enforcing permission in the framework is useless from security point of view.
        // This is being done to help normal app developer to catch the missing permission, since
        // the call to the service is oneway and the SecurityException will just be logged
        final int pid = Process.myPid();
        final int uid = Process.myUid();
        mContext.enforcePermission(BLUETOOTH_CONNECT, pid, uid, null);
        mContext.enforcePermission(BLUETOOTH_PRIVILEGED, pid, uid, null);

        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) {
                    service.unregisterCallback(mCallback, mAttributionSource);
                }
            } catch (RemoteException e) {
                throw e.rethrowAsRuntimeException();
            }
        }
        mCallbackWrapper.unregisterCallback(getService(), callback);
    }

    /**
@@ -1411,8 +1359,8 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable {
            @NonNull BluetoothLeAudioCodecConfig outputCodecConfig) {
        if (DBG) Log.d(TAG, "setCodecConfigPreference(" + groupId + ")");

        Objects.requireNonNull(inputCodecConfig, " inputCodecConfig shall not be null");
        Objects.requireNonNull(outputCodecConfig, " outputCodecConfig shall not be null");
        requireNonNull(inputCodecConfig);
        requireNonNull(outputCodecConfig);

        final IBluetoothLeAudio service = getService();