diff options
author | Yahav Nussbaum <yahav@google.com> | 2024-02-13 20:56:31 +0000 |
---|---|---|
committer | Yahav Nussbaum <yahav@google.com> | 2024-02-29 21:11:09 +0000 |
commit | cd6c6822c85e5b3d8ff5643726e611c404931b5d (patch) | |
tree | b23474d55c88db8043199d0c6f1cbdfea0f31113 | |
parent | ce69cd9459fbe4ac335f6e6cc91ad589228db3ae (diff) | |
download | Connectivity-cd6c6822c85e5b3d8ff5643726e611c404931b5d.tar.gz |
Connect to IBluetoothFinder and use it
Bug: 325088217
Test: atest NearbyUnitTests
Change-Id: I3650da59c86ef83d9c08926f422a37773edc4af8
3 files changed, 320 insertions, 8 deletions
diff --git a/nearby/service/Android.bp b/nearby/service/Android.bp index d34fd837ef..749113d933 100644 --- a/nearby/service/Android.bp +++ b/nearby/service/Android.bp @@ -43,6 +43,7 @@ java_library { ], static_libs: [ "androidx.core_core", + "android.hardware.bluetooth.finder-V1-java", "guava", "libprotobuf-java-lite", "modules-utils-build", diff --git a/nearby/service/java/com/android/server/nearby/managers/BluetoothFinderManager.java b/nearby/service/java/com/android/server/nearby/managers/BluetoothFinderManager.java index 63ff516ce5..365b099a25 100644 --- a/nearby/service/java/com/android/server/nearby/managers/BluetoothFinderManager.java +++ b/nearby/service/java/com/android/server/nearby/managers/BluetoothFinderManager.java @@ -16,26 +16,151 @@ package com.android.server.nearby.managers; +import static com.android.server.nearby.NearbyService.TAG; + +import android.annotation.TargetApi; +import android.hardware.bluetooth.finder.Eid; +import android.hardware.bluetooth.finder.IBluetoothFinder; import android.nearby.PoweredOffFindingEphemeralId; +import android.os.Build; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceSpecificException; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.build.SdkLevel; import java.util.List; /** Connects to {@link IBluetoothFinder} HAL and invokes its API. */ -// A placeholder implementation until the HAL API can be used. +@TargetApi(Build.VERSION_CODES.TIRAMISU) public class BluetoothFinderManager { - private boolean mPoweredOffFindingModeEnabled = false; + private static final String HAL_INSTANCE_NAME = IBluetoothFinder.DESCRIPTOR + "/default"; + + private IBluetoothFinder mBluetoothFinder; + private IBinder.DeathRecipient mServiceDeathRecipient; + private final Object mLock = new Object(); + + private boolean initBluetoothFinderHal() { + final String methodStr = "initBluetoothFinderHal"; + if (!SdkLevel.isAtLeastV()) return false; + synchronized (mLock) { + if (mBluetoothFinder != null) { + Log.i(TAG, "Bluetooth Finder HAL is already initialized"); + return true; + } + try { + mBluetoothFinder = getServiceMockable(); + if (mBluetoothFinder == null) { + Log.e(TAG, "Unable to obtain IBluetoothFinder"); + return false; + } + Log.i(TAG, "Obtained IBluetoothFinder. Local ver: " + IBluetoothFinder.VERSION + + ", Remote ver: " + mBluetoothFinder.getInterfaceVersion()); + + IBinder serviceBinder = getServiceBinderMockable(); + if (serviceBinder == null) { + Log.e(TAG, "Unable to obtain the service binder for IBluetoothFinder"); + return false; + } + mServiceDeathRecipient = new BluetoothFinderDeathRecipient(); + serviceBinder.linkToDeath(mServiceDeathRecipient, /* flags= */ 0); + + Log.i(TAG, "Bluetooth Finder HAL initialization was successful"); + return true; + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (Exception e) { + Log.e(TAG, methodStr + " encountered an exception: " + e); + } + return false; + } + } + + @VisibleForTesting + protected IBluetoothFinder getServiceMockable() { + return IBluetoothFinder.Stub.asInterface( + ServiceManager.waitForDeclaredService(HAL_INSTANCE_NAME)); + } + + @VisibleForTesting + protected IBinder getServiceBinderMockable() { + return mBluetoothFinder.asBinder(); + } - /** An empty implementation of the corresponding HAL API call. */ - public void sendEids(List<PoweredOffFindingEphemeralId> eids) {} + private class BluetoothFinderDeathRecipient implements IBinder.DeathRecipient { + @Override + public void binderDied() { + Log.e(TAG, "BluetoothFinder service died."); + synchronized (mLock) { + mBluetoothFinder = null; + } + } + } - /** A placeholder implementation of the corresponding HAL API call. */ + /** See comments for {@link IBluetoothFinder#sendEids(Eid[])} */ + public void sendEids(List<PoweredOffFindingEphemeralId> eids) { + final String methodStr = "sendEids"; + if (!checkHalAndLogFailure(methodStr)) return; + Eid[] eidArray = eids.stream().map( + ephmeralId -> { + Eid eid = new Eid(); + eid.bytes = ephmeralId.bytes; + return eid; + }).toArray(Eid[]::new); + try { + mBluetoothFinder.sendEids(eidArray); + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + } + + /** See comments for {@link IBluetoothFinder#setPoweredOffFinderMode(boolean)} */ public void setPoweredOffFinderMode(boolean enable) { - mPoweredOffFindingModeEnabled = enable; + final String methodStr = "setPoweredOffMode"; + if (!checkHalAndLogFailure(methodStr)) return; + try { + mBluetoothFinder.setPoweredOffFinderMode(enable); + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } } - /** A placeholder implementation of the corresponding HAL API call. */ + /** See comments for {@link IBluetoothFinder#getPoweredOffFinderMode()} */ public boolean getPoweredOffFinderMode() { - return mPoweredOffFindingModeEnabled; + final String methodStr = "getPoweredOffMode"; + if (!checkHalAndLogFailure(methodStr)) return false; + try { + return mBluetoothFinder.getPoweredOffFinderMode(); + } catch (RemoteException e) { + handleRemoteException(e, methodStr); + } catch (ServiceSpecificException e) { + handleServiceSpecificException(e, methodStr); + } + return false; + } + + private boolean checkHalAndLogFailure(String methodStr) { + if ((mBluetoothFinder == null) && !initBluetoothFinderHal()) { + Log.e(TAG, "Unable to call " + methodStr + " because IBluetoothFinder is null."); + return false; + } + return true; + } + + private void handleRemoteException(RemoteException e, String methodStr) { + mBluetoothFinder = null; + Log.e(TAG, methodStr + " failed with remote exception: " + e); + } + + private void handleServiceSpecificException(ServiceSpecificException e, String methodStr) { + Log.e(TAG, methodStr + " failed with service-specific exception: " + e); } } diff --git a/nearby/tests/unit/src/com/android/server/nearby/managers/BluetoothFinderManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/managers/BluetoothFinderManagerTest.java new file mode 100644 index 0000000000..671b5c5ac7 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/managers/BluetoothFinderManagerTest.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 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.server.nearby.managers; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.bluetooth.finder.Eid; +import android.hardware.bluetooth.finder.IBluetoothFinder; +import android.nearby.PoweredOffFindingEphemeralId; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.RemoteException; +import android.os.ServiceSpecificException; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +public class BluetoothFinderManagerTest { + private BluetoothFinderManager mBluetoothFinderManager; + private boolean mGetServiceCalled = false; + + @Mock private IBluetoothFinder mIBluetoothFinderMock; + @Mock private IBinder mServiceBinderMock; + + private ArgumentCaptor<DeathRecipient> mDeathRecipientCaptor = + ArgumentCaptor.forClass(DeathRecipient.class); + + private ArgumentCaptor<Eid[]> mEidArrayCaptor = ArgumentCaptor.forClass(Eid[].class); + + private class BluetoothFinderManagerSpy extends BluetoothFinderManager { + @Override + protected IBluetoothFinder getServiceMockable() { + mGetServiceCalled = true; + return mIBluetoothFinderMock; + } + + @Override + protected IBinder getServiceBinderMockable() { + return mServiceBinderMock; + } + } + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mBluetoothFinderManager = new BluetoothFinderManagerSpy(); + } + + @Test + public void testSendEids() throws Exception { + byte[] eidBytes1 = { + (byte) 0xe1, (byte) 0xde, (byte) 0x1d, (byte) 0xe1, (byte) 0xde, (byte) 0x1d, + (byte) 0xe1, (byte) 0xde, (byte) 0x1d, (byte) 0xe1, (byte) 0xde, (byte) 0x1d, + (byte) 0xe1, (byte) 0xde, (byte) 0x1d, (byte) 0xe1, (byte) 0xde, (byte) 0x1d, + (byte) 0xe1, (byte) 0xde + }; + byte[] eidBytes2 = { + (byte) 0xf2, (byte) 0xef, (byte) 0x2e, (byte) 0xf2, (byte) 0xef, (byte) 0x2e, + (byte) 0xf2, (byte) 0xef, (byte) 0x2e, (byte) 0xf2, (byte) 0xef, (byte) 0x2e, + (byte) 0xf2, (byte) 0xef, (byte) 0x2e, (byte) 0xf2, (byte) 0xef, (byte) 0x2e, + (byte) 0xf2, (byte) 0xef + }; + PoweredOffFindingEphemeralId ephemeralId1 = new PoweredOffFindingEphemeralId(); + PoweredOffFindingEphemeralId ephemeralId2 = new PoweredOffFindingEphemeralId(); + ephemeralId1.bytes = eidBytes1; + ephemeralId2.bytes = eidBytes2; + + mBluetoothFinderManager.sendEids(List.of(ephemeralId1, ephemeralId2)); + + verify(mIBluetoothFinderMock).sendEids(mEidArrayCaptor.capture()); + assertThat(mEidArrayCaptor.getValue()[0].bytes).isEqualTo(eidBytes1); + assertThat(mEidArrayCaptor.getValue()[1].bytes).isEqualTo(eidBytes2); + } + + @Test + public void testSendEids_remoteException() throws Exception { + doThrow(new RemoteException()) + .when(mIBluetoothFinderMock).sendEids(any()); + mBluetoothFinderManager.sendEids(List.of()); + + // Verify that we get the service again following a RemoteException. + mGetServiceCalled = false; + mBluetoothFinderManager.sendEids(List.of()); + assertThat(mGetServiceCalled).isTrue(); + } + + @Test + public void testSendEids_serviceSpecificException() throws Exception { + doThrow(new ServiceSpecificException(1)) + .when(mIBluetoothFinderMock).sendEids(any()); + mBluetoothFinderManager.sendEids(List.of()); + } + + @Test + public void testSetPoweredOffFinderMode() throws Exception { + mBluetoothFinderManager.setPoweredOffFinderMode(true); + verify(mIBluetoothFinderMock).setPoweredOffFinderMode(true); + + mBluetoothFinderManager.setPoweredOffFinderMode(false); + verify(mIBluetoothFinderMock).setPoweredOffFinderMode(false); + } + + @Test + public void testSetPoweredOffFinderMode_remoteException() throws Exception { + doThrow(new RemoteException()) + .when(mIBluetoothFinderMock).setPoweredOffFinderMode(anyBoolean()); + mBluetoothFinderManager.setPoweredOffFinderMode(true); + + // Verify that we get the service again following a RemoteException. + mGetServiceCalled = false; + mBluetoothFinderManager.setPoweredOffFinderMode(true); + assertThat(mGetServiceCalled).isTrue(); + } + + @Test + public void testSetPoweredOffFinderMode_serviceSpecificException() throws Exception { + doThrow(new ServiceSpecificException(1)) + .when(mIBluetoothFinderMock).setPoweredOffFinderMode(anyBoolean()); + mBluetoothFinderManager.setPoweredOffFinderMode(true); + } + + @Test + public void testGetPoweredOffFinderMode() throws Exception { + when(mIBluetoothFinderMock.getPoweredOffFinderMode()).thenReturn(true); + assertThat(mBluetoothFinderManager.getPoweredOffFinderMode()).isTrue(); + + when(mIBluetoothFinderMock.getPoweredOffFinderMode()).thenReturn(false); + assertThat(mBluetoothFinderManager.getPoweredOffFinderMode()).isFalse(); + } + + @Test + public void testGetPoweredOffFinderMode_remoteException() throws Exception { + when(mIBluetoothFinderMock.getPoweredOffFinderMode()).thenThrow(new RemoteException()); + assertThat(mBluetoothFinderManager.getPoweredOffFinderMode()).isFalse(); + + // Verify that we get the service again following a RemoteException. + mGetServiceCalled = false; + assertThat(mBluetoothFinderManager.getPoweredOffFinderMode()).isFalse(); + assertThat(mGetServiceCalled).isTrue(); + } + + @Test + public void testGetPoweredOffFinderMode_serviceSpecificException() throws Exception { + when(mIBluetoothFinderMock.getPoweredOffFinderMode()) + .thenThrow(new ServiceSpecificException(1)); + assertThat(mBluetoothFinderManager.getPoweredOffFinderMode()).isFalse(); + } + + @Test + public void testDeathRecipient() throws Exception { + mBluetoothFinderManager.setPoweredOffFinderMode(true); + verify(mServiceBinderMock).linkToDeath(mDeathRecipientCaptor.capture(), anyInt()); + mDeathRecipientCaptor.getValue().binderDied(); + + // Verify that we get the service again following a binder death. + mGetServiceCalled = false; + mBluetoothFinderManager.setPoweredOffFinderMode(true); + assertThat(mGetServiceCalled).isTrue(); + } +} |