[Offload] Add DataElement to filter CHRE only scan request
Test: Unit test
Bug: 263431028
Ignore-AOSP-First: nearby_not_in_aosp_yet
Change-Id: I913f61dc34e31f1e944593ed6c880e7fbd3ae5bb
diff --git a/nearby/framework/java/android/nearby/DataElement.java b/nearby/framework/java/android/nearby/DataElement.java
index 0aa3499..4592c33 100644
--- a/nearby/framework/java/android/nearby/DataElement.java
+++ b/nearby/framework/java/android/nearby/DataElement.java
@@ -54,11 +54,14 @@
DataType.EDDYSTONE_EPHEMERAL_IDENTIFIER,
DataType.ACCOUNT_KEY_DATA,
DataType.CONNECTION_STATUS,
- DataType.BATTERY
+ DataType.BATTERY,
+ DataType.SCAN_MODE
})
public @interface DataType {
int BLE_SERVICE_DATA = 100;
int BLE_ADDRESS = 101;
+ // This is to indicate if the scan is offload only
+ int SCAN_MODE = 102;
int SALT = 0;
int PRIVATE_IDENTITY = 1;
int TRUSTED_IDENTITY = 2;
@@ -80,6 +83,7 @@
return type == DataType.BLE_SERVICE_DATA
|| type == DataType.ACCOUNT_KEY_DATA
|| type == DataType.BLE_ADDRESS
+ || type == DataType.SCAN_MODE
|| type == DataType.SALT
|| type == DataType.PRIVATE_IDENTITY
|| type == DataType.TRUSTED_IDENTITY
diff --git a/nearby/framework/java/android/nearby/ScanRequest.java b/nearby/framework/java/android/nearby/ScanRequest.java
index c717ac7..9421820 100644
--- a/nearby/framework/java/android/nearby/ScanRequest.java
+++ b/nearby/framework/java/android/nearby/ScanRequest.java
@@ -62,6 +62,12 @@
*/
public static final int SCAN_MODE_NO_POWER = -1;
/**
+ * A special scan mode to indicate that client only wants to use CHRE to scan.
+ *
+ * @hide
+ */
+ public static final int SCAN_MODE_CHRE_ONLY = 3;
+ /**
* Used to read a ScanRequest from a Parcel.
*/
@NonNull
diff --git a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
index 85b1228..e4c56e4 100644
--- a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
+++ b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
@@ -23,6 +23,7 @@
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.content.Context;
+import android.nearby.DataElement;
import android.nearby.IScanListener;
import android.nearby.NearbyDeviceParcelable;
import android.nearby.PresenceScanFilter;
@@ -51,6 +52,7 @@
/** Manages all aspects of discovery providers. */
public class DiscoveryProviderManager implements AbstractDiscoveryProvider.Listener {
+
protected final Object mLock = new Object();
private final Context mContext;
private final BleDiscoveryProvider mBleDiscoveryProvider;
@@ -123,6 +125,18 @@
mInjector = injector;
}
+ @VisibleForTesting
+ DiscoveryProviderManager(Context context, Injector injector,
+ BleDiscoveryProvider bleDiscoveryProvider,
+ ChreDiscoveryProvider chreDiscoveryProvider,
+ Map<IBinder, ScanListenerRecord> scanTypeScanListenerRecordMap) {
+ mContext = context;
+ mInjector = injector;
+ mBleDiscoveryProvider = bleDiscoveryProvider;
+ mChreDiscoveryProvider = chreDiscoveryProvider;
+ mScanTypeScanListenerRecordMap = scanTypeScanListenerRecordMap;
+ }
+
/** Called after boot completed. */
public void init() {
if (mInjector.getContextHubManager() != null) {
@@ -225,34 +239,68 @@
// Returns false when fail to start all the providers. Returns true if any one of the provider
// starts successfully.
- private boolean startProviders(ScanRequest scanRequest) {
- if (scanRequest.isBleEnabled()) {
- if (mChreDiscoveryProvider.available()
- && scanRequest.getScanType() == SCAN_TYPE_NEARBY_PRESENCE) {
- startChreProvider();
- } else {
- startBleProvider();
+ @VisibleForTesting
+ boolean startProviders(ScanRequest scanRequest) {
+ if (!scanRequest.isBleEnabled()) {
+ Log.w(TAG, "failed to start any provider because client disabled BLE");
+ return false;
+ }
+ List<ScanFilter> scanFilters = getPresenceScanFilters();
+
+ if (!mChreDiscoveryProvider.available()) {
+ if (scanRequest.getScanType() == SCAN_TYPE_NEARBY_PRESENCE && isChreOnly(scanFilters)) {
+ Log.w(TAG, "failed to start any provider because client wants CHRE only and CHRE"
+ + " is not available");
+ return false;
}
+ startBleProvider(scanFilters);
return true;
}
+
+ if (scanRequest.getScanType() == SCAN_TYPE_NEARBY_PRESENCE) {
+ startChreProvider(scanFilters);
+ return true;
+ }
+
+ startBleProvider(scanFilters);
+ return true;
+ }
+
+ private static boolean isChreOnly(List<ScanFilter> scanFilters) {
+ for (ScanFilter scanFilter : scanFilters) {
+ List<DataElement> dataElements =
+ ((PresenceScanFilter) scanFilter).getExtendedProperties();
+ for (DataElement dataElement : dataElements) {
+ if (dataElement.getKey() != DataElement.DataType.SCAN_MODE) {
+ continue;
+ }
+ byte[] scanModeValue = dataElement.getValue();
+ if (scanModeValue == null || scanModeValue.length == 0) {
+ break;
+ }
+ if (Byte.toUnsignedInt(scanModeValue[0]) == ScanRequest.SCAN_MODE_CHRE_ONLY) {
+ return true;
+ }
+ }
+
+ }
return false;
}
- private void startBleProvider() {
+ private void startBleProvider(List<ScanFilter> scanFilters) {
if (!mBleDiscoveryProvider.getController().isStarted()) {
Log.d(TAG, "DiscoveryProviderManager starts Ble scanning.");
mBleDiscoveryProvider.getController().setListener(this);
mBleDiscoveryProvider.getController().setProviderScanMode(mScanMode);
- mBleDiscoveryProvider.getController().setProviderScanFilters(
- getPresenceScanFilters());
+ mBleDiscoveryProvider.getController().setProviderScanFilters(scanFilters);
mBleDiscoveryProvider.getController().start();
}
}
@VisibleForTesting
- void startChreProvider() {
+ void startChreProvider(List<ScanFilter> scanFilters) {
Log.d(TAG, "DiscoveryProviderManager starts CHRE scanning.");
- mChreDiscoveryProvider.getController().setProviderScanFilters(getPresenceScanFilters());
+ mChreDiscoveryProvider.getController().setProviderScanFilters(scanFilters);
mChreDiscoveryProvider.getController().setProviderScanMode(mScanMode);
mChreDiscoveryProvider.getController().start();
}
@@ -329,7 +377,8 @@
}
}
- private static class ScanListenerRecord {
+ @VisibleForTesting
+ static class ScanListenerRecord {
private final ScanRequest mScanRequest;
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/DiscoveryProviderManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/DiscoveryProviderManagerTest.java
index 1af959d..b1737e9 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/provider/DiscoveryProviderManagerTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/DiscoveryProviderManagerTest.java
@@ -19,30 +19,54 @@
import static android.nearby.PresenceCredential.IDENTITY_TYPE_PRIVATE;
import static android.nearby.ScanRequest.SCAN_TYPE_NEARBY_PRESENCE;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
import android.content.Context;
+import android.nearby.DataElement;
+import android.nearby.IScanListener;
import android.nearby.NearbyDeviceParcelable;
import android.nearby.PresenceScanFilter;
import android.nearby.PublicCredential;
-import android.nearby.ScanFilter;
+import android.nearby.ScanRequest;
+import android.os.IBinder;
import com.android.server.nearby.injector.Injector;
+import com.android.server.nearby.util.identity.CallerIdentity;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
public class DiscoveryProviderManagerTest {
+ private static final int SCAN_MODE_CHRE_ONLY = 3;
+ private static final int DATA_TYPE_SCAN_MODE = 102;
+
@Mock Injector mInjector;
@Mock Context mContext;
@Mock AppOpsManager mAppOpsManager;
+ @Mock BleDiscoveryProvider mBleDiscoveryProvider;
+ @Mock ChreDiscoveryProvider mChreDiscoveryProvider;
+ @Mock DiscoveryProviderController mBluetoothController;
+ @Mock DiscoveryProviderController mChreController;
+ @Mock IScanListener mScanListener;
+ @Mock CallerIdentity mCallerIdentity;
+ @Mock DiscoveryProviderManager.ScanListenerDeathRecipient mScanListenerDeathRecipient;
+ @Mock IBinder mIBinder;
+
private DiscoveryProviderManager mDiscoveryProviderManager;
+ private Map<IBinder, DiscoveryProviderManager.ScanListenerRecord>
+ mScanTypeScanListenerRecordMap;
private static final int RSSI = -60;
@@ -50,11 +74,15 @@
public void setup() {
MockitoAnnotations.initMocks(this);
when(mInjector.getAppOpsManager()).thenReturn(mAppOpsManager);
+ when(mBleDiscoveryProvider.getController()).thenReturn(mBluetoothController);
+ when(mChreDiscoveryProvider.getController()).thenReturn(mChreController);
+ mScanTypeScanListenerRecordMap = new HashMap<>();
mDiscoveryProviderManager =
- new DiscoveryProviderManager(mContext, mInjector);
+ new DiscoveryProviderManager(mContext, mInjector, mBleDiscoveryProvider,
+ mChreDiscoveryProvider,
+ mScanTypeScanListenerRecordMap);
}
-
@Test
public void testOnNearbyDeviceDiscovered() {
NearbyDeviceParcelable nearbyDeviceParcelable = new NearbyDeviceParcelable.Builder()
@@ -69,8 +97,95 @@
}
@Test
+ public void testStartProviders_chreOnlyChreAvailable_bleProviderNotStarted() {
+ when(mChreDiscoveryProvider.available()).thenReturn(true);
+
+ ScanRequest scanRequest = new ScanRequest.Builder()
+ .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
+ .addScanFilter(getChreOnlyPresenceScanFilter()).build();
+ DiscoveryProviderManager.ScanListenerRecord record =
+ new DiscoveryProviderManager.ScanListenerRecord(scanRequest, mScanListener,
+ mCallerIdentity, mScanListenerDeathRecipient);
+ mScanTypeScanListenerRecordMap.put(mIBinder, record);
+
+
+ boolean startProviders = mDiscoveryProviderManager.startProviders(scanRequest);
+ verify(mBluetoothController, never()).start();
+ assertThat(startProviders).isTrue();
+ }
+
+ @Test
+ public void testStartProviders_chreOnlyChreAvailable_multipleFilters_bleProviderNotStarted() {
+ when(mChreDiscoveryProvider.available()).thenReturn(true);
+
+ ScanRequest scanRequest = new ScanRequest.Builder()
+ .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
+ .addScanFilter(getChreOnlyPresenceScanFilter())
+ .addScanFilter(getPresenceScanFilter()).build();
+ DiscoveryProviderManager.ScanListenerRecord record =
+ new DiscoveryProviderManager.ScanListenerRecord(scanRequest, mScanListener,
+ mCallerIdentity, mScanListenerDeathRecipient);
+ mScanTypeScanListenerRecordMap.put(mIBinder, record);
+
+ boolean startProviders = mDiscoveryProviderManager.startProviders(scanRequest);
+ verify(mBluetoothController, never()).start();
+ assertThat(startProviders).isTrue();
+ }
+
+ @Test
+ public void testStartProviders_chreOnlyChreUnavailable_bleProviderNotStarted() {
+ when(mChreDiscoveryProvider.available()).thenReturn(false);
+
+ ScanRequest scanRequest = new ScanRequest.Builder()
+ .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
+ .addScanFilter(getChreOnlyPresenceScanFilter()).build();
+ DiscoveryProviderManager.ScanListenerRecord record =
+ new DiscoveryProviderManager.ScanListenerRecord(scanRequest, mScanListener,
+ mCallerIdentity, mScanListenerDeathRecipient);
+ mScanTypeScanListenerRecordMap.put(mIBinder, record);
+
+ boolean startProviders = mDiscoveryProviderManager.startProviders(scanRequest);
+ verify(mBluetoothController, never()).start();
+ assertThat(startProviders).isFalse();
+ }
+
+ @Test
+ public void testStartProviders_notChreOnlyChreAvailable_bleProviderNotStarted() {
+ when(mChreDiscoveryProvider.available()).thenReturn(true);
+
+ ScanRequest scanRequest = new ScanRequest.Builder()
+ .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
+ .addScanFilter(getPresenceScanFilter()).build();
+ DiscoveryProviderManager.ScanListenerRecord record =
+ new DiscoveryProviderManager.ScanListenerRecord(scanRequest, mScanListener,
+ mCallerIdentity, mScanListenerDeathRecipient);
+ mScanTypeScanListenerRecordMap.put(mIBinder, record);
+
+ boolean startProviders = mDiscoveryProviderManager.startProviders(scanRequest);
+ verify(mBluetoothController, never()).start();
+ assertThat(startProviders).isTrue();
+ }
+
+ @Test
+ public void testStartProviders_notChreOnlyChreUnavailable_bleProviderStarted() {
+ when(mChreDiscoveryProvider.available()).thenReturn(false);
+
+ ScanRequest scanRequest = new ScanRequest.Builder()
+ .setScanType(SCAN_TYPE_NEARBY_PRESENCE)
+ .addScanFilter(getPresenceScanFilter()).build();
+ DiscoveryProviderManager.ScanListenerRecord record =
+ new DiscoveryProviderManager.ScanListenerRecord(scanRequest, mScanListener,
+ mCallerIdentity, mScanListenerDeathRecipient);
+ mScanTypeScanListenerRecordMap.put(mIBinder, record);
+
+ boolean startProviders = mDiscoveryProviderManager.startProviders(scanRequest);
+ verify(mBluetoothController, atLeastOnce()).start();
+ assertThat(startProviders).isTrue();
+ }
+
+ @Test
public void testStartChreProvider() {
- mDiscoveryProviderManager.startChreProvider();
+ mDiscoveryProviderManager.startChreProvider(List.of(getPresenceScanFilter()));
}
private static PresenceScanFilter getPresenceScanFilter() {
@@ -92,4 +207,27 @@
.addPresenceAction(action)
.build();
}
+
+ private static PresenceScanFilter getChreOnlyPresenceScanFilter() {
+ final byte[] secretId = new byte[]{1, 2, 3, 4};
+ final byte[] authenticityKey = new byte[]{0, 1, 1, 1};
+ final byte[] publicKey = new byte[]{1, 1, 2, 2};
+ final byte[] encryptedMetadata = new byte[]{1, 2, 3, 4, 5};
+ final byte[] metadataEncryptionKeyTag = new byte[]{1, 1, 3, 4, 5};
+
+ PublicCredential credential = new PublicCredential.Builder(
+ secretId, authenticityKey, publicKey, encryptedMetadata, metadataEncryptionKeyTag)
+ .setIdentityType(IDENTITY_TYPE_PRIVATE)
+ .build();
+
+ final int action = 123;
+ DataElement scanModeElement = new DataElement(DATA_TYPE_SCAN_MODE,
+ new byte[]{SCAN_MODE_CHRE_ONLY});
+ return new PresenceScanFilter.Builder()
+ .addCredential(credential)
+ .setMaxPathLoss(RSSI)
+ .addPresenceAction(action)
+ .addExtendedProperty(scanModeElement)
+ .build();
+ }
}