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

Commit 6aac755a authored by Rahul Sabnis's avatar Rahul Sabnis
Browse files

Adds API for audio framework to notify BT that a preferred audio profile

change has taken effect

Tag: #feature
Bug: 265031867
Test: atest BluetoothAdapterTest
Change-Id: Ie93b72f3fb13eb99f0cb9da0f9f447685dec8f5a
parent 8de9a40a
Loading
Loading
Loading
Loading
+23 −0
Original line number Diff line number Diff line
@@ -639,6 +639,11 @@ public final class Utils {
        return false;
    }

    private static boolean checkCallerIsSystem() {
        int callingUid = Binder.getCallingUid();
        return UserHandle.getAppId(Process.SYSTEM_UID) == UserHandle.getAppId(callingUid);
    }

    private static boolean checkCallerIsSystemOrActiveUser() {
        int callingUid = Binder.getCallingUid();
        UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid);
@@ -660,6 +665,24 @@ public final class Utils {
        return checkCallerIsSystemOrActiveUser(tag + "." + method + "()");
    }

    /**
     * Checks if the caller to the method is system server.
     *
     * @param tag the log tag to use in case the caller is not system server
     * @param method the API method name
     * @return {@code true} if the caller is system server, {@code false} otherwise
     */
    public static boolean callerIsSystem(String tag, String method) {
        if (isInstrumentationTestMode()) {
            return true;
        }
        final boolean res = checkCallerIsSystem();
        if (!res) {
            Log.w(TAG, tag + "." + method + "()" + " - Not allowed outside system server");
        }
        return res;
    }

    private static boolean checkCallerIsSystemOrActiveOrManagedUser(Context context) {
        if (context == null) {
            return checkCallerIsSystemOrActiveUser();
+100 −12
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.bluetooth.BluetoothDevice.TRANSPORT_AUTO;
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.SECOND_IN_MILLIS;

import static com.android.bluetooth.Utils.callerIsSystem;
import static com.android.bluetooth.Utils.callerIsSystemOrActiveOrManagedUser;
import static com.android.bluetooth.Utils.enforceBluetoothPrivilegedPermission;
import static com.android.bluetooth.Utils.enforceCdmAssociation;
@@ -283,6 +284,7 @@ public class AdapterService extends Service {
    private Set<IBluetoothConnectionCallback> mBluetoothConnectionCallbacks = new HashSet<>();
    private RemoteCallbackList<IBluetoothPreferredAudioProfilesCallback>
            mPreferredAudioProfilesCallbacks;
    private Set<BluetoothDevice> mDevicesPendingAudioProfileChanges = new HashSet<>();
    //Only BluetoothManagerService should be registered
    private RemoteCallbackList<IBluetoothCallback> mCallbacks;
    private int mCurrentRequestId;
@@ -4169,6 +4171,40 @@ public class AdapterService extends Service {
            return service.getPreferredAudioProfiles(device);
        }

        @Override
        public void notifyPreferredAudioProfileChangeApplied(BluetoothDevice device,
                AttributionSource source, SynchronousResultReceiver receiver) {
            try {
                receiver.send(notifyPreferredAudioProfileChangeApplied(device, source));
            } catch (RuntimeException e) {
                receiver.propagateException(e);
            }
        }

        private int notifyPreferredAudioProfileChangeApplied(BluetoothDevice device,
                AttributionSource source) {
            AdapterService service = getService();
            if (service == null) {
                return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED;
            }
            if (!callerIsSystem(TAG, "setPreferredAudioProfiles")) {
                return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED;
            }
            Objects.requireNonNull(device);
            if (!BluetoothAdapter.checkBluetoothAddress(device.getAddress())) {
                throw new IllegalArgumentException("device cannot have an invalid address");
            }
            if (service.getBondState(device) != BluetoothDevice.BOND_BONDED) {
                return BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED;
            }
            if (!Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
                return BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION;
            }
            enforceBluetoothPrivilegedPermission(service);

            return service.notifyPreferredAudioProfileChangeApplied(device);
        }

        @Override
        public void registerPreferredAudioProfilesChangedCallback(
                IBluetoothPreferredAudioProfilesCallback callback,
@@ -4295,6 +4331,11 @@ public class AdapterService extends Service {
            return BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED;
        }

        synchronized (mDevicesPendingAudioProfileChanges) {
            if (mDevicesPendingAudioProfileChanges.contains(groupLead)) {
                return BluetoothStatusCodes.ERROR_ANOTHER_ACTIVE_REQUEST;
            }

            // Copies relevant keys & values from modeToProfile bundle
            Bundle strippedPreferences = new Bundle();
            if (modeToProfileBundle.containsKey(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY)
@@ -4308,9 +4349,56 @@ public class AdapterService extends Service {
                        modeToProfileBundle.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX));
            }

            mDevicesPendingAudioProfileChanges.add(groupLead);
            return mDatabaseManager.setPreferredAudioProfiles(groupLead,
                    strippedPreferences);
        }
    }

    /**
     * Notification from the audio framework that a preferred audio profile change has taken effect.
     * See {@link BluetoothAdapter#notifyPreferredAudioProfileChangeApplied(BluetoothDevice)} for
     * more details.
     *
     * @param device the remote device whose preferred audio profiles have been changed
     * @return whether the Bluetooth stack acknowledged the change successfully
     */
    private int notifyPreferredAudioProfileChangeApplied(BluetoothDevice device) {
        // Gets the lead device in the CSIP group to set the preference
        BluetoothDevice groupLead = mLeAudioService.getLeadDevice(device);
        if (groupLead == null) {
            return BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED;
        }

        synchronized (mDevicesPendingAudioProfileChanges) {
            if (!mDevicesPendingAudioProfileChanges.contains(groupLead)) {
                Log.e(TAG, "notifyPreferredAudioProfileChangeApplied, but no pending request for "
                        + "device: " + groupLead);
                return BluetoothStatusCodes.ERROR_UNKNOWN;
            }

            if (mPreferredAudioProfilesCallbacks != null) {
                int n = mPreferredAudioProfilesCallbacks.beginBroadcast();
                debugLog("notifyPreferredAudioProfileChangeApplied() - Broadcasting audio profile "
                        + "change applied to device: " + groupLead + " to " + n + " receivers.");
                for (int i = 0; i < n; i++) {
                    try {
                        mPreferredAudioProfilesCallbacks.getBroadcastItem(i)
                                .onPreferredAudioProfilesChanged(device,
                                        getPreferredAudioProfiles(device),
                                        BluetoothStatusCodes.SUCCESS);
                    } catch (RemoteException e) {
                        debugLog("notifyPreferredAudioProfileChangeApplied() - Callback #" + i
                                + " failed (" + e + ")");
                    }
                }
                mPreferredAudioProfilesCallbacks.finishBroadcast();
            }
            mDevicesPendingAudioProfileChanges.remove(groupLead);
        }

        return BluetoothStatusCodes.SUCCESS;
    }

    // ----API Methods--------

+2 −0
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ package android.bluetooth {
    method public boolean isBleScanAlwaysAvailable();
    method public boolean isLeEnabled();
    method @NonNull public static String nameForState(int);
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int notifyPreferredAudioProfileChangeApplied(@NonNull android.bluetooth.BluetoothDevice);
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean registerBluetoothConnectionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.BluetoothConnectionCallback);
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int registerPreferredAudioProfilesChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothAdapter.PreferredAudioProfilesChangedCallback);
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean removeActiveDevice(int);
@@ -710,6 +711,7 @@ package android.bluetooth {
    field public static final int ALLOWED = 400; // 0x190
    field public static final int ERROR_ALREADY_IN_TARGET_STATE = 26; // 0x1a
    field public static final int ERROR_ANOTHER_ACTIVE_OOB_REQUEST = 1000; // 0x3e8
    field public static final int ERROR_ANOTHER_ACTIVE_REQUEST = 29; // 0x1d
    field public static final int ERROR_AUDIO_DEVICE_ALREADY_CONNECTED = 1116; // 0x45c
    field public static final int ERROR_AUDIO_DEVICE_ALREADY_DISCONNECTED = 1117; // 0x45d
    field public static final int ERROR_AUDIO_ROUTE_BLOCKED = 1118; // 0x45e
+51 −0
Original line number Diff line number Diff line
@@ -5078,6 +5078,7 @@ public final class BluetoothAdapter {
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(value = {
            BluetoothStatusCodes.SUCCESS,
            BluetoothStatusCodes.ERROR_ANOTHER_ACTIVE_REQUEST,
            BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED,
            BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED,
            BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED,
@@ -5235,6 +5236,56 @@ public final class BluetoothAdapter {
        return defaultValue;
    }

    /**
     * Called by audio framework to inform the Bluetooth stack that a request from
     * {@link #setPreferredAudioProfiles(BluetoothDevice, Bundle)} has taken effect in the audio
     * framework. After this is called, the Bluetooth stack will invoke
     * {@link PreferredAudioProfilesChangedCallback#onPreferredAudioProfilesChanged(
     * BluetoothDevice, Bundle, int)}.
     * <p>
     * This method will return
     * {@link BluetoothStatusCodes#ERROR_BLUETOOTH_NOT_ALLOWED} if called outside system server.
     *
     * @param device is the BluetoothDevice that had its preferred audio profile changed
     * @return whether the Bluetooth stack acknowledged the change successfully
     * @throws NullPointerException if device is null
     * @throws IllegalArgumentException if the device's address is invalid
     *
     * @hide
     */
    @SystemApi
    @RequiresPermission(allOf = {
            android.Manifest.permission.BLUETOOTH_CONNECT,
            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
    })
    public int notifyPreferredAudioProfileChangeApplied(@NonNull BluetoothDevice device) {
        if (DBG) Log.d(TAG, "notifyPreferredProfileChangeApplied(" + device + ")");
        Objects.requireNonNull(device, "device cannot be null");
        if (!BluetoothAdapter.checkBluetoothAddress(device.getAddress())) {
            throw new IllegalArgumentException("device cannot have an invalid address");
        }

        final int defaultValue = BluetoothStatusCodes.ERROR_UNKNOWN;
        mServiceLock.readLock().lock();
        try {
            if (mService != null) {
                final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
                mService.notifyPreferredAudioProfileChangeApplied(device,
                        mAttributionSource, recv);
                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
            }
        } catch (RemoteException e) {
            Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
            throw e.rethrowFromSystemServer();
        } catch (TimeoutException e) {
            Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
        } finally {
            mServiceLock.readLock().unlock();
        }

        return defaultValue;
    }

    @SuppressLint("AndroidFrameworkBluetoothPermission")
    private final IBluetoothPreferredAudioProfilesCallback mPreferredAudioProfilesChangedCallback =
            new IBluetoothPreferredAudioProfilesCallback.Stub() {
+8 −1
Original line number Diff line number Diff line
@@ -25,7 +25,6 @@ import android.annotation.SystemApi;
 * occupies the max integer value.
 */
public final class BluetoothStatusCodes {

    private BluetoothStatusCodes() {}

    /**
@@ -223,6 +222,14 @@ public final class BluetoothStatusCodes {
    @SystemApi
    public static final int ERROR_CALLBACK_NOT_REGISTERED = 28;

    /**
     * Indicates that there is another active request and therefore, this operation is not allowed.
     *
     * @hide
     */
    @SystemApi
    public static final int ERROR_ANOTHER_ACTIVE_REQUEST = 29;

    /**
     * A GATT writeCharacteristic request is not permitted on the remote device.
     */
Loading