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

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

Merge "Change the intent string to callback method"

parents d58e7d78 840037ce
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
@@ -332,9 +332,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";
@@ -346,6 +348,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
@@ -31,6 +31,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