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

Commit c3c0d0b5 authored by Jack He's avatar Jack He Committed by Automerger Merge Worker
Browse files

Merge "Change the intent string to callback method" am: 2376e0d8 am: fa327cf7 am: 5c156bb6

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Bluetooth/+/2014996

Change-Id: I80fd4db6d2c00c5634204ffcbbdfb454e17de9b8
parents 656de6db 5c156bb6
Loading
Loading
Loading
Loading
+60 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothLeAudio;
import android.bluetooth.IBluetoothLeAudioCallback;
import android.bluetooth.IBluetoothLeBroadcastCallback;
import android.content.AttributionSource;
import android.content.BroadcastReceiver;
@@ -123,6 +124,9 @@ public class LeAudioService extends ProfileService {
    @VisibleForTesting
    RemoteCallbackList<IBluetoothLeBroadcastCallback> mBroadcastCallbacks;

    @VisibleForTesting
    RemoteCallbackList<IBluetoothLeAudioCallback> mLeAudioCallbacks;

    private class LeAudioGroupDescriptor {
        LeAudioGroupDescriptor() {
            mIsConnected = false;
@@ -215,6 +219,7 @@ public class LeAudioService extends ProfileService {
        filter.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
        mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver();
        registerReceiver(mConnectionStateChangedReceiver, filter);
        mLeAudioCallbacks = new RemoteCallbackList<IBluetoothLeAudioCallback>();

        // Initialize Broadcast native interface
        if (mAdapterService.isLeAudioBroadcastSourceSupported()) {
@@ -291,6 +296,10 @@ public class LeAudioService extends ProfileService {
            mBroadcastCallbacks.kill();
        }

        if (mLeAudioCallbacks != null) {
            mLeAudioCallbacks.kill();
        }

        mBroadcastStateMap.clear();
        mBroadcastIdMap.clear();
        mBroadcastsPlaybackMap.clear();
@@ -1500,6 +1509,21 @@ public class LeAudioService extends ProfileService {
        }
    }

    private void notifyUnicastCodecConfigChanged(int groupId,
                                                 BluetoothLeAudioCodecStatus status) {
        if (mLeAudioCallbacks != null) {
            int n = mLeAudioCallbacks.beginBroadcast();
            for (int i = 0; i < n; i++) {
                try {
                    mLeAudioCallbacks.getBroadcastItem(i).onCodecConfigChanged(groupId, status);
                } catch (RemoteException e) {
                    continue;
                }
            }
            mLeAudioCallbacks.finishBroadcast();
        }
    }

    private void notifyBroadcastStarted(Integer instanceId, int reason) {
        if (!mBroadcastIdMap.containsKey(instanceId)) {
            Log.e(TAG, "Unknown Broadcast ID for broadcast instance: " + instanceId);
@@ -1953,6 +1977,42 @@ public class LeAudioService extends ProfileService {
            }
        }

        @Override
        public void registerCallback(IBluetoothLeAudioCallback callback,
                AttributionSource source, SynchronousResultReceiver receiver) {
            LeAudioService service = getService(source);
            if ((service == null) || (service.mLeAudioCallbacks == null)) {
                receiver.propagateException(new IllegalStateException("Service is unavailable"));
                return;
            }

            enforceBluetoothPrivilegedPermission(service);
            try {
                service.mLeAudioCallbacks.register(callback);
                receiver.send(null);
            } catch (RuntimeException e) {
                receiver.propagateException(e);
            }
        }

        @Override
        public void unregisterCallback(IBluetoothLeAudioCallback callback,
                AttributionSource source, SynchronousResultReceiver receiver) {
            LeAudioService service = getService(source);
            if ((service == null) || (service.mLeAudioCallbacks == null)) {
                receiver.propagateException(new IllegalStateException("Service is unavailable"));
                return;
            }

            enforceBluetoothPrivilegedPermission(service);
            try {
                service.mLeAudioCallbacks.unregister(callback);
                receiver.send(null);
            } catch (RuntimeException e) {
                receiver.propagateException(e);
            }
        }

        @Override
        public void registerLeBroadcastCallback(IBluetoothLeBroadcastCallback callback,
                AttributionSource source, SynchronousResultReceiver receiver) {
+6 −0
Original line number Diff line number Diff line
@@ -349,9 +349,11 @@ package android.bluetooth {
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getAudioLocation(@NonNull android.bluetooth.BluetoothDevice);
    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public android.bluetooth.BluetoothLeAudioCodecStatus getCodecStatus(@NonNull android.bluetooth.BluetoothDevice);
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice);
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothLeAudio.Callback);
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void setCodecConfigPreference(@NonNull android.bluetooth.BluetoothDevice, @NonNull android.bluetooth.BluetoothLeAudioCodecConfig);
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void setVolume(int);
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void unregisterCallback(@NonNull android.bluetooth.BluetoothLeAudio.Callback);
    field @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public static final String ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED = "android.bluetooth.action.LE_AUDIO_ACTIVE_DEVICE_CHANGED";
    field @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public static final String ACTION_LE_AUDIO_GROUP_NODE_STATUS_CHANGED = "android.bluetooth.action.LE_AUDIO_GROUP_NODE_STATUS_CHANGED";
    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static final String ACTION_LE_AUDIO_GROUP_STATUS_CHANGED = "android.bluetooth.action.LE_AUDIO_GROUP_STATUS_CHANGED";
@@ -363,6 +365,10 @@ package android.bluetooth {
    field public static final int GROUP_NODE_REMOVED = 2; // 0x2
  }

  public static interface BluetoothLeAudio.Callback {
    method public void onCodecConfigChanged(int, @NonNull android.bluetooth.BluetoothLeAudioCodecStatus);
  }

  public final class BluetoothLeAudioCodecConfigMetadata implements android.os.Parcelable {
    method @NonNull public static android.bluetooth.BluetoothLeAudioCodecConfigMetadata fromRawBytes(@NonNull byte[]);
    method public long getAudioLocation();
+192 −17
Original line number Diff line number Diff line
@@ -20,11 +20,13 @@ package android.bluetooth;
import static android.bluetooth.BluetoothUtils.getSyncTimeout;

import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
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;
@@ -38,7 +40,11 @@ import android.util.Log;
import com.android.modules.utils.SynchronousResultReceiver;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;

/**
@@ -56,8 +62,45 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable {
    private static final boolean DBG = false;
    private static final boolean VDBG = false;

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

    private CloseGuard mCloseGuard;

    /**
     * This class provides a callback that is invoked when audio codec config changes on
     * the remote device.
     *
     * @hide
     */
    @SystemApi
    public interface Callback {
        /**
         * Callback invoked when callback is registered and when codec config
         * changes on the remote device.
         *
         * @param groupId the group id
         * @param status latest codec status for this group
         * @hide
         */
        @SystemApi
        void onCodecConfigChanged(int groupId,
                                  @NonNull BluetoothLeAudioCodecStatus status);
    }

    @SuppressLint("AndroidFrameworkBluetoothPermission")
    private final IBluetoothLeAudioCallback mCallback = new 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));
            }
        }
    };

    /**
     * Intent used to broadcast the change in connection state of the LeAudio
     * profile. Please note that in the binaural case, there will be two different LE devices for
@@ -166,23 +209,6 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable {
    public static final String ACTION_LE_AUDIO_CONF_CHANGED =
            "android.bluetooth.action.LE_AUDIO_CONF_CHANGED";

    /**
     * Intent used to broadcast the audio codec config changed information.
     *
     * <p>This intent will have 2 extras:
     * <ul>
     * <li> {@link BluetoothLeAudioCodecStatus#EXTRA_LE_AUDIO_CODEC_STATUS} - The codec status.
     * </li>
     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device if the device is currently
     * connected, otherwise it is not included.</li>
     * </ul>
     *
     * @hide
     */
    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    public static final String ACTION_LE_AUDIO_CODEC_CONFIG_CHANGED =
            "android.bluetooth.action.LE_AUDIO_CODEC_CONFIG_CHANGED";

    /**
     * Indicates unspecified audio content.
@@ -377,6 +403,37 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable {
                }
    };


    @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.registerCallback(mCallback,
                                                mAttributionSource, recv);
                                        recv.awaitResultNoInterrupt(getSyncTimeout())
                                                .getValue(null);
                                    }
                                } catch (TimeoutException e) {
                                    Log.e(TAG, "Failed to register callback", e);
                                } catch (RemoteException e) {
                                    throw e.rethrowFromSystemServer();
                                }
                            }
                        }
                    }
                }
            };

    /**
     * Create a BluetoothLeAudio proxy object for interacting with the local
     * Bluetooth LeAudio service.
@@ -386,6 +443,16 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable {
        mAdapter = adapter;
        mAttributionSource = adapter.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");
    }
@@ -394,6 +461,15 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable {
     * @hide
     */
    public void close() {
        IBluetoothManager mgr = mAdapter.getBluetoothManager();
        if (mgr != null) {
            try {
                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
            } catch (RemoteException e) {
                Log.e(TAG, "", e);
            }
        }

        mProfileConnector.disconnect();
    }

@@ -610,6 +686,105 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable {
        return defaultValue;
    }

    /**
     * Register a {@link Callback} that will be invoked during the
     * operation of this profile.
     *
     * Repeated registration of the same <var>callback</var> object will have no effect after
     * the first call to this method, even when the <var>executor</var> is different. API caller
     * would have to call {@link #unregisterCallback(Callback)} with
     * the same callback object before registering it again.
     *
     * @param executor an {@link Executor} to execute given callback
     * @param callback user implementation of the {@link Callback}
     * @throws NullPointerException if a null executor or callback is given
     * @throws IllegalArgumentException the callback is already registered
     * @hide
     */
    @SystemApi
    @RequiresBluetoothConnectPermission
    @RequiresPermission(allOf = {
            android.Manifest.permission.BLUETOOTH_CONNECT,
            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
    })
    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()) {
                try {
                    final IBluetoothLeAudio service = getService();
                    if (service != null) {
                        final SynchronousResultReceiver<Integer> recv =
                                new SynchronousResultReceiver();
                        service.registerCallback(mCallback, mAttributionSource, recv);
                        recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
                    }
                } catch (IllegalStateException | TimeoutException e) {
                    Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
                    throw new IllegalStateException("Unexpected error", e);
                } 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);
        }
    }

    /**
     * Unregister the specified {@link Callback}.
     * <p>The same {@link Callback} object used when calling
     * {@link #registerCallback(Executor, Callback)} must be used.
     *
     * <p>Callbacks are automatically unregistered when application process goes away
     *
     * @param callback user implementation of the {@link Callback}
     * @throws NullPointerException when callback is null
     * @throws IllegalArgumentException when no callback is registered
     * @hide
     */
    @SystemApi
    @RequiresBluetoothConnectPermission
    @RequiresPermission(allOf = {
            android.Manifest.permission.BLUETOOTH_CONNECT,
            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
    })
    public void unregisterCallback(@NonNull Callback callback) {
        Objects.requireNonNull(callback, "callback cannot be null");
        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.unregisterCallback(mCallback, mAttributionSource, recv);
                    recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
                }
            } catch (TimeoutException e) {
                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
    }

    /**
     * Select a connected device as active.
     *
+1 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ filegroup {
        "android/bluetooth/IBluetoothVolumeControlCallback.aidl",
        "android/bluetooth/IBluetoothHidHost.aidl",
        "android/bluetooth/IBluetoothLeAudio.aidl",
        "android/bluetooth/IBluetoothLeAudioCallback.aidl",
        "android/bluetooth/IBluetoothLeBroadcastCallback.aidl",
        "android/bluetooth/IBluetoothManager.aidl",
        "android/bluetooth/IBluetoothManagerCallback.aidl",
+19 −0
Original line number Diff line number Diff line
/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.bluetooth;

parcelable BluetoothLeAudioCodecStatus;
Loading