Connect to IBluetoothFinder and use it
Bug: 325088217 Test: atest NearbyUnitTests Change-Id: I3650da59c86ef83d9c08926f422a37773edc4af8
This commit is contained in:
parent
ce69cd9459
commit
cd6c6822c8
3 changed files with 320 additions and 8 deletions
|
|
@ -43,6 +43,7 @@ java_library {
|
|||
],
|
||||
static_libs: [
|
||||
"androidx.core_core",
|
||||
"android.hardware.bluetooth.finder-V1-java",
|
||||
"guava",
|
||||
"libprotobuf-java-lite",
|
||||
"modules-utils-build",
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
||||
/** An empty implementation of the corresponding HAL API call. */
|
||||
public void sendEids(List<PoweredOffFindingEphemeralId> eids) {}
|
||||
private IBluetoothFinder mBluetoothFinder;
|
||||
private IBinder.DeathRecipient mServiceDeathRecipient;
|
||||
private final Object mLock = new Object();
|
||||
|
||||
/** A placeholder implementation of the corresponding HAL API call. */
|
||||
public void setPoweredOffFinderMode(boolean enable) {
|
||||
mPoweredOffFindingModeEnabled = enable;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/** A placeholder implementation of the corresponding HAL API call. */
|
||||
@VisibleForTesting
|
||||
protected IBluetoothFinder getServiceMockable() {
|
||||
return IBluetoothFinder.Stub.asInterface(
|
||||
ServiceManager.waitForDeclaredService(HAL_INSTANCE_NAME));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected IBinder getServiceBinderMockable() {
|
||||
return mBluetoothFinder.asBinder();
|
||||
}
|
||||
|
||||
private class BluetoothFinderDeathRecipient implements IBinder.DeathRecipient {
|
||||
@Override
|
||||
public void binderDied() {
|
||||
Log.e(TAG, "BluetoothFinder service died.");
|
||||
synchronized (mLock) {
|
||||
mBluetoothFinder = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 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) {
|
||||
final String methodStr = "setPoweredOffMode";
|
||||
if (!checkHalAndLogFailure(methodStr)) return;
|
||||
try {
|
||||
mBluetoothFinder.setPoweredOffFinderMode(enable);
|
||||
} catch (RemoteException e) {
|
||||
handleRemoteException(e, methodStr);
|
||||
} catch (ServiceSpecificException e) {
|
||||
handleServiceSpecificException(e, methodStr);
|
||||
}
|
||||
}
|
||||
|
||||
/** 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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue