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

Commit d83e811e authored by Himanshu Rawat's avatar Himanshu Rawat Committed by Automerger Merge Worker
Browse files

Merge changes from topics "allow_switching_hid_and_hogp",...

Merge changes from topics "allow_switching_hid_and_hogp", "bt-le-audio-dsa-hogp" into main am: 44289641

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



Change-Id: I1e4597ad31a540de7725b5d63ddb8e4659aba868
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 31ea24c8 44289641
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -42,6 +42,10 @@ oneway interface IBluetoothHidHost {
    void setConnectionPolicy(in BluetoothDevice device, int connectionPolicy, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
    void getConnectionPolicy(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
    void setPreferredTransport(in BluetoothDevice device, int transport, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
    void getPreferredTransport(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
    void getProtocolMode(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
+83 −0
Original line number Diff line number Diff line
@@ -478,6 +478,43 @@ public class HidHostService extends ProfileService {
            }
        }

        @Override
        public void setPreferredTransport(
                BluetoothDevice device,
                int transport,
                AttributionSource source,
                SynchronousResultReceiver receiver) {
            try {
                HidHostService service = getService(source);
                boolean defaultValue = false;
                if (service != null) {
                    enforceBluetoothPrivilegedPermission(service);
                    defaultValue = service.setPreferredTransport(device, transport);
                }
                receiver.send(defaultValue);
            } catch (RuntimeException e) {
                receiver.propagateException(e);
            }
        }

        @Override
        public void getPreferredTransport(
                BluetoothDevice device,
                AttributionSource source,
                SynchronousResultReceiver receiver) {
            try {
                HidHostService service = getService(source);
                int defaultValue = BluetoothDevice.TRANSPORT_AUTO;
                if (service != null) {
                    enforceBluetoothPrivilegedPermission(service);
                    defaultValue = service.getPreferredTransport(device);
                }
                receiver.send(defaultValue);
            } catch (RuntimeException e) {
                receiver.propagateException(e);
            }
        }

        /* The following APIs regarding test app for compliance */
        @Override
        public void getProtocolMode(BluetoothDevice device, AttributionSource source,
@@ -707,6 +744,37 @@ public class HidHostService extends ProfileService {
        return true;
    }

    /**
     * @see BluetoothHidHost#setPreferredTransport
     */
    boolean setPreferredTransport(BluetoothDevice device, int transport) {
        if (DBG) {
            Log.i(TAG, "setPreferredTransport: " + device + " transport: " + transport);
        }

        if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
            Log.w(TAG, "Device not bonded" + device);
            return false;
        }

        boolean hidSupported = Utils.arrayContains(device.getUuids(), BluetoothUuid.HID);
        boolean hogpSupported = Utils.arrayContains(device.getUuids(), BluetoothUuid.HOGP);
        if (transport == BluetoothDevice.TRANSPORT_BREDR && !hidSupported) {
            Log.w(TAG, "HID not supported: " + device);
            return false;
        } else if (transport == BluetoothDevice.TRANSPORT_LE && !hogpSupported) {
            Log.w(TAG, "HOGP not supported: " + device);
            return false;
        }

        /* TODO: b/324094542 - Implement setPreferredTransport API
         * Save transport preference in the persistent storage
         * If connection policy allows connection, ensure that the preferred transport is
         * connected and not the other one.
         */
        return false;
    }

    /**
     * Get the connection policy of the profile.
     *
@@ -727,6 +795,18 @@ public class HidHostService extends ProfileService {
                .getProfileConnectionPolicy(device, BluetoothProfile.HID_HOST);
    }

    /**
     * @see BluetoothHidHost#getPreferredTransport
     */
    int getPreferredTransport(BluetoothDevice device) {
        if (DBG) {
            Log.d(TAG, "getPreferredTransport: " + device);
        }

        // TODO: b/324094542 - Implement getPreferredTransport API
        return BluetoothDevice.TRANSPORT_AUTO;
    }

    /* The following APIs regarding test app for compliance */
    boolean getProtocolMode(BluetoothDevice device) {
        if (DBG) {
@@ -924,6 +1004,9 @@ public class HidHostService extends ProfileService {
        intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, prevState);
        intent.putExtra(BluetoothProfile.EXTRA_STATE, newState);
        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
        /* TODO: b/324094542 - Set correct transport as EXTRA_TRANSPORT
         * intent.putExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.TRANSPORT_AUTO);
         */
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
        sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_CONNECT,
                Utils.getTempAllowlistBroadcastOptions());
+22 −5
Original line number Diff line number Diff line
@@ -17,17 +17,16 @@
package com.android.bluetooth.hid;

import static org.mockito.Mockito.verify;

import android.platform.test.flag.junit.SetFlagsRule;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.bluetooth.flags.Flags;
import com.android.bluetooth.x.com.android.modules.utils.SynchronousResultReceiver;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -36,7 +35,7 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class HidHostServiceBinderTest {

    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
    private static final String REMOTE_DEVICE_ADDRESS = "00:00:00:00:00:00";

    @Mock
@@ -106,6 +105,24 @@ public class HidHostServiceBinderTest {
        verify(mService).getConnectionPolicy(mRemoteDevice);
    }

    @Test
    public void setPreferredTransport_callsServiceMethod() {
        mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_SWITCHING_HID_AND_HOGP);
        int preferredTransport = BluetoothDevice.TRANSPORT_AUTO;
        mBinder.setPreferredTransport(
                mRemoteDevice, preferredTransport, null, SynchronousResultReceiver.get());

        verify(mService).setPreferredTransport(mRemoteDevice, preferredTransport);
    }

    @Test
    public void getPreferredTransport_callsServiceMethod() {
        mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_SWITCHING_HID_AND_HOGP);
        mBinder.getPreferredTransport(mRemoteDevice, null, SynchronousResultReceiver.get());

        verify(mService).getPreferredTransport(mRemoteDevice);
    }

    @Test
    public void getProtocolMode_callsServiceMethod() {
        mBinder.getProtocolMode(mRemoteDevice, null,
+2 −0
Original line number Diff line number Diff line
@@ -442,7 +442,9 @@ package android.bluetooth {
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
    method @FlaggedApi("com.android.bluetooth.flags.allow_switching_hid_and_hogp") @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getPreferredTransport(@NonNull android.bluetooth.BluetoothDevice);
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
    method @FlaggedApi("com.android.bluetooth.flags.allow_switching_hid_and_hogp") @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setPreferredTransport(@NonNull android.bluetooth.BluetoothDevice, int);
    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
  }

+104 −0
Original line number Diff line number Diff line
@@ -19,12 +19,14 @@ package android.bluetooth;
import static android.bluetooth.BluetoothUtils.getSyncTimeout;

import android.Manifest;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.bluetooth.BluetoothDevice.Transport;
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
@@ -34,10 +36,12 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

import com.android.bluetooth.flags.Flags;
import com.android.modules.utils.SynchronousResultReceiver;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeoutException;

/**
@@ -64,12 +68,16 @@ public final class BluetoothHidHost implements BluetoothProfile {
     * <ul>
     *   <li>{@link #EXTRA_STATE} - The current state of the profile.
     *   <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.
     *   <li>{@link BluetoothDevice#EXTRA_TRANSPORT} - Transport of the connection.
     *   <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
     * </ul>
     *
     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link
     * #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link
     * #STATE_DISCONNECTING}.
     *
     * <p>{@link BluetoothDevice#EXTRA_TRANSPORT} can be any of {@link
     * BluetoothDevice#TRANSPORT_BREDR}, {@link BluetoothDevice#TRANSPORT_LE}.
     */
    @SuppressLint("ActionValue")
    @RequiresLegacyBluetoothPermission
@@ -464,6 +472,60 @@ public final class BluetoothHidHost implements BluetoothProfile {
        return defaultValue;
    }

    /**
     * Set preferred transport for the device
     *
     * <p>The device should already be paired, services must have been discovered. This API is
     * effective only if both the HID and HOGP are supported on the remote device.
     *
     * @param device paired bluetooth device
     * @param transport the preferred transport to set for this device
     * @return true if preferred transport is set, false on error
     * @throws IllegalArgumentException if the {@code device} invalid.
     * @hide
     */
    @FlaggedApi(Flags.FLAG_ALLOW_SWITCHING_HID_AND_HOGP)
    @SystemApi
    @RequiresBluetoothConnectPermission
    @RequiresPermission(
            allOf = {
                android.Manifest.permission.BLUETOOTH_CONNECT,
                android.Manifest.permission.BLUETOOTH_PRIVILEGED,
            })
    public boolean setPreferredTransport(
            @NonNull BluetoothDevice device, @Transport int transport) {
        if (DBG) log("setPreferredTransport(" + device + ", " + transport + ")");

        Objects.requireNonNull(device, "device must not be null");

        if (transport != BluetoothDevice.TRANSPORT_AUTO
                && transport != BluetoothDevice.TRANSPORT_BREDR
                && transport != BluetoothDevice.TRANSPORT_LE) {
            throw new IllegalArgumentException("Invalid transport value");
        }

        final IBluetoothHidHost 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()) {
            Log.w(TAG, "Not ready");
        } else if (!isValidDevice(device)) {
            throw new IllegalArgumentException("Invalid device");
        } else {
            try {
                final SynchronousResultReceiver<Boolean> recv = SynchronousResultReceiver.get();
                service.setPreferredTransport(device, transport, mAttributionSource, recv);
                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
            } catch (RemoteException | TimeoutException e) {
                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
            }
        }
        return defaultValue;
    }

    /**
     * Get the priority of the profile.
     *
@@ -524,6 +586,48 @@ public final class BluetoothHidHost implements BluetoothProfile {
        return defaultValue;
    }

    /**
     * Get the preferred transport for the device.
     *
     * @param device Bluetooth device
     * @return preferred transport for the device
     * @throws IllegalArgumentException if the {@code device} invalid.
     * @hide
     */
    @FlaggedApi(Flags.FLAG_ALLOW_SWITCHING_HID_AND_HOGP)
    @SystemApi
    @RequiresBluetoothConnectPermission
    @RequiresPermission(
            allOf = {
                android.Manifest.permission.BLUETOOTH_CONNECT,
                android.Manifest.permission.BLUETOOTH_PRIVILEGED,
            })
    public @Transport int getPreferredTransport(@NonNull BluetoothDevice device) {
        if (VDBG) log("getPreferredTransport(" + device + ")");

        Objects.requireNonNull(device, "device must not be null");

        final IBluetoothHidHost service = getService();
        final int defaultValue = BluetoothDevice.TRANSPORT_AUTO;
        if (service == null) {
            Log.w(TAG, "Proxy not attached to service");
            if (DBG) log(Log.getStackTraceString(new Throwable()));
        } else if (!isEnabled()) {
            Log.w(TAG, "Not ready");
        } else if (!isValidDevice(device)) {
            throw new IllegalArgumentException("Invalid device");
        } else {
            try {
                final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
                service.getPreferredTransport(device, mAttributionSource, recv);
                return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
            } catch (RemoteException | TimeoutException e) {
                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
            }
        }
        return defaultValue;
    }

    private boolean isEnabled() {
        return mAdapter.getState() == BluetoothAdapter.STATE_ON;
    }