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

Commit 78af57b4 authored by William Escande's avatar William Escande Committed by Gerrit Code Review
Browse files

Merge changes I2eb4f0b8,I0f59e1a1,Id1fddef6,I9f6ab7bd,I80c5878e, ... into main

* changes:
  Hap: clean visibility
  Hap: Split binder from service -> easier reference
  Hap: merge Start into constructor
  Hap: re-order variable and move some to final
  Hap: early return and switch syntax
  Hap: Use wrapper to manage broadcast callback
parents 45125a5d 3be91a48
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -59,6 +59,7 @@ import android.os.Build;
import android.os.ParcelUuid;
import android.os.PowerExemptionManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
@@ -92,6 +93,7 @@ import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

public final class Utils {
    private static final String TAG = "BluetoothUtils";
@@ -1258,6 +1260,23 @@ public final class Utils {
                || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
    }

    /** A {@link Consumer} that automatically ignores any {@link RemoteException}s. */
    @FunctionalInterface
    @SuppressWarnings("FunctionalInterfaceMethodChanged")
    public interface RemoteExceptionIgnoringConsumer<T> extends Consumer<T> {
        /** Called by {@code accept}. */
        void acceptOrThrow(T t) throws RemoteException;

        @Override
        default void accept(T t) {
            try {
                acceptOrThrow(t);
            } catch (RemoteException ex) {
                // Ignore RemoteException
            }
        }
    }

    /**
     * Returns the longest prefix of a string for which the UTF-8 encoding fits into the given
     * number of bytes, with the additional guarantee that the string is not truncated in the middle
+333 −0
Original line number Diff line number Diff line
/*
 * Copyright 2024 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 com.android.bluetooth.hap;

import static android.Manifest.permission.BLUETOOTH_CONNECT;
import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;

import static java.util.Objects.requireNonNull;

import android.annotation.RequiresPermission;
import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHapClient;
import android.bluetooth.BluetoothHapPresetInfo;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.IBluetoothHapClient;
import android.bluetooth.IBluetoothHapClientCallback;
import android.content.AttributionSource;
import android.util.Log;

import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.ProfileService;
import com.android.internal.annotations.VisibleForTesting;

import java.util.Collections;
import java.util.List;

/** HapClientBinder class */
@VisibleForTesting
class HapClientBinder extends IBluetoothHapClient.Stub
        implements ProfileService.IProfileServiceBinder {
    private static final String TAG = HapClientBinder.class.getSimpleName();
    private HapClientService mService;

    HapClientBinder(HapClientService svc) {
        mService = svc;
    }

    @Override
    public void cleanup() {
        mService = null;
    }

    @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
    private HapClientService getService(AttributionSource source) {
        requireNonNull(source);
        // Cache mService because it can change while getService is called
        HapClientService service = mService;

        if (Utils.isInstrumentationTestMode()) {
            return service;
        }

        if (!Utils.checkServiceAvailable(service, TAG)
                || !Utils.checkCallerIsSystemOrActiveOrManagedUser(service, TAG)
                || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
            Log.w(TAG, "Hearing Access call not allowed for non-active user");
            return null;
        }

        service.enforceCallingOrSelfPermission(BLUETOOTH_PRIVILEGED, null);
        return service;
    }

    @Override
    public List<BluetoothDevice> getConnectedDevices(AttributionSource source) {
        HapClientService service = getService(source);
        if (service == null) {
            return Collections.emptyList();
        }

        return service.getConnectedDevices();
    }

    @Override
    public List<BluetoothDevice> getDevicesMatchingConnectionStates(
            int[] states, AttributionSource source) {
        HapClientService service = getService(source);
        if (service == null) {
            return Collections.emptyList();
        }

        return service.getDevicesMatchingConnectionStates(states);
    }

    @Override
    public int getConnectionState(BluetoothDevice device, AttributionSource source) {
        HapClientService service = getService(source);
        if (service == null) {
            return BluetoothProfile.STATE_DISCONNECTED;
        }

        requireNonNull(device);

        return service.getConnectionState(device);
    }

    @Override
    public boolean setConnectionPolicy(
            BluetoothDevice device, int connectionPolicy, AttributionSource source) {
        HapClientService service = getService(source);
        if (service == null) {
            return false;
        }

        requireNonNull(device);
        if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED
                && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
            throw new IllegalArgumentException(
                    "Invalid connectionPolicy value: " + connectionPolicy);
        }

        return service.setConnectionPolicy(device, connectionPolicy);
    }

    @Override
    public int getConnectionPolicy(BluetoothDevice device, AttributionSource source) {
        HapClientService service = getService(source);
        if (service == null) {
            return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
        }

        requireNonNull(device);

        return service.getConnectionPolicy(device);
    }

    @Override
    public int getActivePresetIndex(BluetoothDevice device, AttributionSource source) {
        HapClientService service = getService(source);
        if (service == null) {
            return BluetoothHapClient.PRESET_INDEX_UNAVAILABLE;
        }

        requireNonNull(device);

        return service.getActivePresetIndex(device);
    }

    @Override
    public BluetoothHapPresetInfo getActivePresetInfo(
            BluetoothDevice device, AttributionSource source) {
        HapClientService service = getService(source);
        if (service == null) {
            return null;
        }

        requireNonNull(device);

        return service.getActivePresetInfo(device);
    }

    @Override
    public int getHapGroup(BluetoothDevice device, AttributionSource source) {
        HapClientService service = getService(source);
        if (service == null) {
            return BluetoothCsipSetCoordinator.GROUP_ID_INVALID;
        }

        requireNonNull(device);

        return service.getHapGroup(device);
    }

    @Override
    public void selectPreset(BluetoothDevice device, int presetIndex, AttributionSource source) {
        HapClientService service = getService(source);
        if (service == null) {
            return;
        }

        requireNonNull(device);

        service.selectPreset(device, presetIndex);
    }

    @Override
    public void selectPresetForGroup(int groupId, int presetIndex, AttributionSource source) {
        HapClientService service = getService(source);
        if (service == null) {
            return;
        }

        service.selectPresetForGroup(groupId, presetIndex);
    }

    @Override
    public void switchToNextPreset(BluetoothDevice device, AttributionSource source) {
        HapClientService service = getService(source);
        if (service == null) {
            return;
        }

        requireNonNull(device);

        service.switchToNextPreset(device);
    }

    @Override
    public void switchToNextPresetForGroup(int groupId, AttributionSource source) {
        HapClientService service = getService(source);
        if (service == null) {
            return;
        }

        service.switchToNextPresetForGroup(groupId);
    }

    @Override
    public void switchToPreviousPreset(BluetoothDevice device, AttributionSource source) {
        HapClientService service = getService(source);
        if (service == null) {
            return;
        }

        requireNonNull(device);

        service.switchToPreviousPreset(device);
    }

    @Override
    public void switchToPreviousPresetForGroup(int groupId, AttributionSource source) {
        HapClientService service = getService(source);
        if (service == null) {
            return;
        }

        service.switchToPreviousPresetForGroup(groupId);
    }

    @Override
    public BluetoothHapPresetInfo getPresetInfo(
            BluetoothDevice device, int presetIndex, AttributionSource source) {
        HapClientService service = getService(source);
        if (service == null) {
            return null;
        }

        requireNonNull(device);

        return service.getPresetInfo(device, presetIndex);
    }

    @Override
    public List<BluetoothHapPresetInfo> getAllPresetInfo(
            BluetoothDevice device, AttributionSource source) {
        HapClientService service = getService(source);
        if (service == null) {
            return Collections.emptyList();
        }

        requireNonNull(device);

        return service.getAllPresetInfo(device);
    }

    @Override
    public int getFeatures(BluetoothDevice device, AttributionSource source) {
        HapClientService service = getService(source);
        if (service == null) {
            return 0x00;
        }

        requireNonNull(device);

        return service.getFeatures(device);
    }

    @Override
    public void setPresetName(
            BluetoothDevice device, int presetIndex, String name, AttributionSource source) {
        HapClientService service = getService(source);
        if (service == null) {
            return;
        }

        requireNonNull(device);
        requireNonNull(name);

        service.setPresetName(device, presetIndex, name);
    }

    @Override
    public void setPresetNameForGroup(
            int groupId, int presetIndex, String name, AttributionSource source) {
        HapClientService service = getService(source);
        if (service == null) {
            return;
        }

        requireNonNull(name);

        service.setPresetNameForGroup(groupId, presetIndex, name);
    }

    @Override
    public void registerCallback(IBluetoothHapClientCallback callback, AttributionSource source) {
        HapClientService service = getService(source);
        if (service == null) {
            return;
        }

        requireNonNull(callback);

        service.registerCallback(callback);
    }

    @Override
    public void unregisterCallback(IBluetoothHapClientCallback callback, AttributionSource source) {
        HapClientService service = getService(source);
        if (service == null) {
            return;
        }

        requireNonNull(callback);

        service.unregisterCallback(callback);
    }
}
+230 −810

File changed.

Preview size limit exceeded, changes collapsed.

+302 −0
Original line number Diff line number Diff line
/*
 * Copyright 2024 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 com.android.bluetooth.hap;

import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.verify;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.IBluetoothHapClientCallback;
import android.content.AttributionSource;

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

import com.android.bluetooth.TestUtils;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

@MediumTest
@RunWith(AndroidJUnit4.class)
public class HapClientBinderTest {
    @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();

    @Mock private HapClientService mHapClientService;

    private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
    private final AttributionSource mAttributionSource = mAdapter.getAttributionSource();
    private final BluetoothDevice mDevice = TestUtils.getTestDevice(mAdapter, 0);

    private HapClientBinder mBinder;

    @Before
    public void setUp() throws Exception {
        mBinder = new HapClientBinder(mHapClientService);
    }

    @Test
    public void getConnectedDevices() {
        assertThrows(NullPointerException.class, () -> mBinder.getConnectedDevices(null));
        mBinder.getConnectedDevices(mAttributionSource);
        verify(mHapClientService).getConnectedDevices();
    }

    @Test
    public void getDevicesMatchingConnectionStates() {
        assertThrows(
                NullPointerException.class,
                () -> mBinder.getDevicesMatchingConnectionStates(null, null));
        mBinder.getDevicesMatchingConnectionStates(null, mAttributionSource);
        verify(mHapClientService).getDevicesMatchingConnectionStates(any());
    }

    @Test
    public void getConnectionState() {
        assertThrows(NullPointerException.class, () -> mBinder.getConnectionState(mDevice, null));
        assertThrows(
                NullPointerException.class,
                () -> mBinder.getConnectionState(null, mAttributionSource));

        mBinder.getConnectionState(mDevice, mAttributionSource);
        verify(mHapClientService).getConnectionState(eq(mDevice));
    }

    @Test
    public void setConnectionPolicy() {
        assertThrows(
                NullPointerException.class,
                () ->
                        mBinder.setConnectionPolicy(
                                mDevice, BluetoothProfile.CONNECTION_POLICY_ALLOWED, null));
        assertThrows(
                NullPointerException.class,
                () ->
                        mBinder.setConnectionPolicy(
                                null,
                                BluetoothProfile.CONNECTION_POLICY_ALLOWED,
                                mAttributionSource));
        assertThrows(
                IllegalArgumentException.class,
                () ->
                        mBinder.setConnectionPolicy(
                                mDevice,
                                BluetoothProfile.CONNECTION_POLICY_UNKNOWN,
                                mAttributionSource));

        mBinder.setConnectionPolicy(
                mDevice, BluetoothProfile.CONNECTION_POLICY_ALLOWED, mAttributionSource);
        verify(mHapClientService)
                .setConnectionPolicy(eq(mDevice), eq(BluetoothProfile.CONNECTION_POLICY_ALLOWED));
    }

    @Test
    public void getConnectionPolicy() {
        assertThrows(NullPointerException.class, () -> mBinder.getConnectionPolicy(mDevice, null));
        assertThrows(
                NullPointerException.class,
                () -> mBinder.getConnectionPolicy(null, mAttributionSource));
        mBinder.getConnectionPolicy(mDevice, mAttributionSource);
        verify(mHapClientService).getConnectionPolicy(eq(mDevice));
    }

    @Test
    public void getActivePresetIndex() {
        assertThrows(NullPointerException.class, () -> mBinder.getActivePresetIndex(mDevice, null));
        assertThrows(
                NullPointerException.class,
                () -> mBinder.getActivePresetIndex(null, mAttributionSource));
        mBinder.getActivePresetIndex(mDevice, mAttributionSource);
        verify(mHapClientService).getActivePresetIndex(eq(mDevice));
    }

    @Test
    public void getActivePresetInfo() {
        assertThrows(NullPointerException.class, () -> mBinder.getActivePresetInfo(mDevice, null));
        assertThrows(
                NullPointerException.class,
                () -> mBinder.getActivePresetInfo(null, mAttributionSource));
        mBinder.getActivePresetInfo(mDevice, mAttributionSource);
        verify(mHapClientService).getActivePresetInfo(eq(mDevice));
    }

    @Test
    public void getHapGroup() {
        assertThrows(NullPointerException.class, () -> mBinder.getHapGroup(mDevice, null));
        assertThrows(
                NullPointerException.class, () -> mBinder.getHapGroup(null, mAttributionSource));
        mBinder.getHapGroup(mDevice, mAttributionSource);
        verify(mHapClientService).getHapGroup(eq(mDevice));
    }

    @Test
    public void selectPreset() {
        int index = 42;
        assertThrows(NullPointerException.class, () -> mBinder.selectPreset(mDevice, index, null));
        assertThrows(
                NullPointerException.class,
                () -> mBinder.selectPreset(null, index, mAttributionSource));
        mBinder.selectPreset(mDevice, index, mAttributionSource);
        verify(mHapClientService).selectPreset(eq(mDevice), eq(index));
    }

    @Test
    public void selectPresetForGroup() {
        int index = 42;
        int groupId = 4242;
        assertThrows(
                NullPointerException.class,
                () -> mBinder.selectPresetForGroup(groupId, index, null));
        mBinder.selectPresetForGroup(groupId, index, mAttributionSource);
        verify(mHapClientService).selectPresetForGroup(eq(groupId), eq(index));
    }

    @Test
    public void switchToNextPreset() {
        assertThrows(NullPointerException.class, () -> mBinder.switchToNextPreset(mDevice, null));
        assertThrows(
                NullPointerException.class,
                () -> mBinder.switchToNextPreset(null, mAttributionSource));
        mBinder.switchToNextPreset(mDevice, mAttributionSource);
        verify(mHapClientService).switchToNextPreset(eq(mDevice));
    }

    @Test
    public void switchToNextPresetForGroup() {
        int groupId = 4242;
        assertThrows(
                NullPointerException.class,
                () -> mBinder.switchToNextPresetForGroup(groupId, null));
        mBinder.switchToNextPresetForGroup(groupId, mAttributionSource);
        verify(mHapClientService).switchToNextPresetForGroup(eq(groupId));
    }

    @Test
    public void switchToPreviousPreset() {
        assertThrows(
                NullPointerException.class, () -> mBinder.switchToPreviousPreset(mDevice, null));
        assertThrows(
                NullPointerException.class,
                () -> mBinder.switchToPreviousPreset(null, mAttributionSource));
        mBinder.switchToPreviousPreset(mDevice, mAttributionSource);
        verify(mHapClientService).switchToPreviousPreset(eq(mDevice));
    }

    @Test
    public void switchToPreviousPresetForGroup() {
        int groupId = 4242;
        assertThrows(
                NullPointerException.class,
                () -> mBinder.switchToPreviousPresetForGroup(groupId, null));
        mBinder.switchToPreviousPresetForGroup(groupId, mAttributionSource);
        verify(mHapClientService).switchToPreviousPresetForGroup(eq(groupId));
    }

    @Test
    public void getPresetInfo() {
        int index = 42;
        assertThrows(NullPointerException.class, () -> mBinder.getPresetInfo(mDevice, index, null));
        assertThrows(
                NullPointerException.class,
                () -> mBinder.getPresetInfo(null, index, mAttributionSource));
        mBinder.getPresetInfo(mDevice, index, mAttributionSource);
        verify(mHapClientService).getPresetInfo(eq(mDevice), eq(index));
    }

    @Test
    public void getAllPresetInfo() {
        assertThrows(NullPointerException.class, () -> mBinder.getAllPresetInfo(mDevice, null));
        assertThrows(
                NullPointerException.class,
                () -> mBinder.getAllPresetInfo(null, mAttributionSource));
        mBinder.getAllPresetInfo(mDevice, mAttributionSource);
        verify(mHapClientService).getAllPresetInfo(eq(mDevice));
    }

    @Test
    public void getFeatures() {
        assertThrows(NullPointerException.class, () -> mBinder.getFeatures(mDevice, null));
        assertThrows(
                NullPointerException.class, () -> mBinder.getFeatures(null, mAttributionSource));
        mBinder.getFeatures(mDevice, mAttributionSource);
        verify(mHapClientService).getFeatures(eq(mDevice));
    }

    @Test
    public void setPresetName() {
        String name = "This is a preset name";
        int index = 42;
        assertThrows(
                NullPointerException.class,
                () -> mBinder.setPresetName(null, index, name, mAttributionSource));
        assertThrows(
                NullPointerException.class,
                () -> mBinder.setPresetName(mDevice, index, null, mAttributionSource));
        assertThrows(
                NullPointerException.class,
                () -> mBinder.setPresetName(mDevice, index, name, null));
        mBinder.setPresetName(mDevice, index, name, mAttributionSource);
        verify(mHapClientService).setPresetName(eq(mDevice), eq(index), eq(name));
    }

    @Test
    public void setPresetNameForGroup() {
        String name = "This is a preset name";
        int index = 42;
        int groupId = 4242;
        assertThrows(
                NullPointerException.class,
                () -> mBinder.setPresetNameForGroup(groupId, index, null, mAttributionSource));
        assertThrows(
                NullPointerException.class,
                () -> mBinder.setPresetNameForGroup(groupId, index, name, null));
        mBinder.setPresetNameForGroup(groupId, index, name, mAttributionSource);
        verify(mHapClientService).setPresetNameForGroup(eq(groupId), eq(index), eq(name));
    }

    @Test
    public void registerCallback() {
        IBluetoothHapClientCallback callback = Mockito.mock(IBluetoothHapClientCallback.class);
        assertThrows(
                NullPointerException.class,
                () -> mBinder.registerCallback(null, mAttributionSource));
        assertThrows(NullPointerException.class, () -> mBinder.registerCallback(callback, null));
        mBinder.registerCallback(callback, mAttributionSource);
        verify(mHapClientService).registerCallback(eq(callback));
    }

    @Test
    public void unregisterCallback() {
        IBluetoothHapClientCallback callback = Mockito.mock(IBluetoothHapClientCallback.class);
        assertThrows(
                NullPointerException.class,
                () -> mBinder.unregisterCallback(null, mAttributionSource));
        assertThrows(NullPointerException.class, () -> mBinder.unregisterCallback(callback, null));
        mBinder.unregisterCallback(callback, mAttributionSource);
        verify(mHapClientService).unregisterCallback(eq(callback));
    }
}
+23 −60

File changed and moved.

Preview size limit exceeded, changes collapsed.