Merge changes I0b32f2de,I4d58a25a
* changes:
Call initializeBluetoothAdapter on PHASE_BOOT_COMPLETED
Use IBinder as key of scanTypeScanListenerRecordMap
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
new file mode 100644
index 0000000..a41d7a3
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 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.fastpair;
+
+import android.content.Context;
+import android.nearby.FastPairDevice;
+import android.nearby.NearbyDevice;
+import android.util.Log;
+
+import com.android.server.nearby.common.locator.Locator;
+import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
+import com.android.server.nearby.util.FastPairDecoder;
+import com.android.server.nearby.util.Hex;
+
+import com.google.protobuf.ByteString;
+
+import service.proto.Cache;
+
+/**
+ * Handler that handle fast pair related broadcast.
+ */
+public class FastPairAdvHandler {
+ Context mContext;
+ String mBleAddress;
+
+ /**
+ * Constructor function.
+ */
+ public FastPairAdvHandler(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Handles all of the scanner result. Fast Pair will handle model id broadcast bloomfilter
+ * broadcast and battery level broadcast.
+ */
+ public void handleBroadcast(NearbyDevice device) {
+ FastPairDevice fastPairDevice = (FastPairDevice) device;
+ if (mBleAddress != null && mBleAddress.equals(fastPairDevice.getBluetoothAddress())) {
+ return;
+ }
+ mBleAddress = fastPairDevice.getBluetoothAddress();
+ byte[] model = FastPairDecoder.getModelId(fastPairDevice.getData());
+ Log.d("FastPairService",
+ "On discovery model id" + Hex.bytesToStringLowercase(model));
+ // Use api to get anti spoofing key from model id.
+ Locator.get(mContext, FastPairHalfSheetManager.class).showHalfSheet(
+ Cache.ScanFastPairStoreItem.newBuilder()
+ .setAddress(mBleAddress)
+ .setAntiSpoofingPublicKey(ByteString.EMPTY)
+ .build());
+ }
+}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
index d6625a4..253e942 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
@@ -16,28 +16,20 @@
package com.android.server.nearby.fastpair;
-import static com.android.server.nearby.fastpair.Constant.DEVICE_PAIRING_FRAGMENT_TYPE;
-import static com.android.server.nearby.fastpair.Constant.EXTRA_BINDER;
-import static com.android.server.nearby.fastpair.Constant.EXTRA_BUNDLE;
-import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_INFO;
-import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_TYPE;
-
import android.annotation.Nullable;
import android.annotation.WorkerThread;
import android.app.KeyguardManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
+import android.nearby.FastPairDevice;
import android.nearby.NearbyDevice;
+import android.nearby.NearbyManager;
import android.nearby.ScanCallback;
-import android.os.Bundle;
-import android.os.UserHandle;
+import android.nearby.ScanRequest;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -57,15 +49,15 @@
import com.android.server.nearby.fastpair.cache.DiscoveryItem;
import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
-import com.android.server.nearby.fastpair.halfsheet.HalfSheetCallback;
+import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
import com.android.server.nearby.fastpair.pairinghandler.PairingProgressHandlerBase;
-import com.android.server.nearby.provider.FastPairDataProvider;
-import com.android.server.nearby.util.Environment;
+import com.android.server.nearby.util.FastPairDecoder;
+import com.android.server.nearby.util.ForegroundThread;
+import com.android.server.nearby.util.Hex;
import com.google.protobuf.ByteString;
import java.security.GeneralSecurityException;
-import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
@@ -73,7 +65,6 @@
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import java.util.stream.Collectors;
import service.proto.Cache;
import service.proto.Rpcs;
@@ -86,7 +77,6 @@
public class FastPairManager {
private static final String ACTION_PREFIX = UserActionHandler.PREFIX;
private static final int WAIT_FOR_UNLOCK_MILLIS = 5000;
- private static final String ACTIVITY_INTENT_ACTION = "android.nearby.SHOW_HALFSHEET";
/** A notification ID which should be dismissed */
public static final String EXTRA_NOTIFICATION_ID = ACTION_PREFIX + "EXTRA_NOTIFICATION_ID";
public static final String ACTION_RESOURCES_APK = "android.nearby.SHOW_HALFSHEET";
@@ -97,8 +87,6 @@
private static Executor sFastPairExecutor;
- private String mHalfSheetApkPkgName;
-
final LocatorContextWrapper mLocatorContextWrapper;
final IntentFilter mIntentFilter;
final Locator mLocator;
@@ -107,16 +95,29 @@
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
Log.d("FastPairService", " screen on");
+ NearbyManager nearbyManager = (NearbyManager) mLocatorContextWrapper
+ .getApplicationContext().getSystemService(Context.NEARBY_SERVICE);
+
+ Log.d("FastPairService", " the nearby manager is " + nearbyManager);
+
+ if (nearbyManager != null) {
+ nearbyManager.startScan(
+ new ScanRequest.Builder()
+ .setScanType(ScanRequest.SCAN_TYPE_FAST_PAIR).build(),
+ ForegroundThread.getExecutor(),
+ mScanCallback);
+ } else {
+ Log.d("FastPairService", " the nearby manager is null");
+ }
+
} else if (intent.getAction().equals(ACTION_START_PAIRING)) {
byte[] model = intent.getByteArrayExtra(EXTRA_MODEL_ID);
String address = intent.getStringExtra(EXTRA_ADDRESS);
Log.d("FastPairService", "start pair " + address);
- Rpcs.GetObservedDeviceResponse response =
- FastPairDataProvider.getInstance().loadFastPairDeviceMetadata(model);
- ByteString publicKey = response.getDevice().getAntiSpoofingKeyPair().getPublicKey();
- showHalfSheet(Cache.ScanFastPairStoreItem.newBuilder().setAddress(address)
- .setAntiSpoofingPublicKey(publicKey)
- .build());
+ Locator.get(mLocatorContextWrapper, FastPairHalfSheetManager.class).showHalfSheet(
+ Cache.ScanFastPairStoreItem.newBuilder().setAddress(address)
+ .setAntiSpoofingPublicKey(ByteString.EMPTY)
+ .build());
} else {
Log.d("FastPairService", " screen off");
}
@@ -136,17 +137,23 @@
final ScanCallback mScanCallback = new ScanCallback() {
@Override
public void onDiscovered(@NonNull NearbyDevice device) {
- Log.d("FastPair", "Ondiscovery " + device.getName());
+ Locator.get(mLocatorContextWrapper, FastPairAdvHandler.class).handleBroadcast(device);
}
@Override
public void onUpdated(@NonNull NearbyDevice device) {
-
+ FastPairDevice fastPairDevice = (FastPairDevice) device;
+ byte[] modelArray = FastPairDecoder.getModelId(fastPairDevice.getData());
+ Log.d("FastPairService",
+ "update model id" + Hex.bytesToStringLowercase(modelArray));
}
@Override
public void onLost(@NonNull NearbyDevice device) {
-
+ FastPairDevice fastPairDevice = (FastPairDevice) device;
+ byte[] modelArray = FastPairDecoder.getModelId(fastPairDevice.getData());
+ Log.d("FastPairService",
+ "lost model id" + Hex.bytesToStringLowercase(modelArray));
}
};
@@ -156,7 +163,7 @@
public void initiate() {
mIntentFilter.addAction(Intent.ACTION_SCREEN_ON);
mIntentFilter.addAction(Intent.ACTION_SCREEN_OFF);
- mIntentFilter.addAction("NEARBY_START_PAIRING");
+ mIntentFilter.addAction(ACTION_START_PAIRING);
mLocatorContextWrapper.getContext()
.registerReceiver(mScreenBroadcastReceiver, mIntentFilter);
@@ -291,7 +298,7 @@
}
}
- /** Check if the pairing is initial pairing with fast pair 2.0 design. */
+ /** Checks if the pairing is initial pairing with fast pair 2.0 design. */
public static boolean isThroughFastPair2InitialPairing(
DiscoveryItem item, @Nullable byte[] accountKey) {
return accountKey == null && item.getAuthenticationPublicKeySecp256R1() != null;
@@ -338,69 +345,4 @@
return manager == null ? null : manager.getAdapter();
}
- /**
- * Gets the package name of HalfSheet.apk
- * getHalfSheetApkPkgName may invoke PackageManager multiple times and it does not have
- * race condition check. Since there is no lock for mHalfSheetApkPkgName.
- */
- String getHalfSheetApkPkgName() {
- if (mHalfSheetApkPkgName != null) {
- return mHalfSheetApkPkgName;
- }
- List<ResolveInfo> resolveInfos = mLocatorContextWrapper.getContext()
- .getPackageManager().queryIntentActivities(
- new Intent(ACTION_RESOURCES_APK),
- PackageManager.MATCH_SYSTEM_ONLY);
-
- // remove apps that don't live in the nearby apex
- resolveInfos.removeIf(info ->
- !Environment.isAppInNearbyApex(info.activityInfo.applicationInfo));
-
- if (resolveInfos.isEmpty()) {
- // Resource APK not loaded yet, print a stack trace to see where this is called from
- Log.e("FastPairManager", "Attempted to fetch resources before halfsheet "
- + " APK is installed or package manager can't resolve correctly!",
- new IllegalStateException());
- return null;
- }
-
- if (resolveInfos.size() > 1) {
- // multiple apps found, log a warning, but continue
- Log.w("FastPairManager", "Found > 1 APK that can resolve halfsheet APK intent: "
- + resolveInfos.stream()
- .map(info -> info.activityInfo.applicationInfo.packageName)
- .collect(Collectors.joining(", ")));
- }
-
- // Assume the first ResolveInfo is the one we're looking for
- ResolveInfo info = resolveInfos.get(0);
- mHalfSheetApkPkgName = info.activityInfo.applicationInfo.packageName;
- Log.i("FastPairManager", "Found halfsheet APK at: " + mHalfSheetApkPkgName);
- return mHalfSheetApkPkgName;
- }
-
- /**
- * Invokes half sheet in the other apk. This function can only be called in Nearby because other
- * app can't get the correct component name.
- */
- void showHalfSheet(Cache.ScanFastPairStoreItem scanFastPairStoreItem) {
- if (mLocatorContextWrapper != null) {
- String packageName = getHalfSheetApkPkgName();
- HalfSheetCallback callback = new HalfSheetCallback();
- callback.setmFastPairController(Locator
- .getFromContextWrapper(mLocatorContextWrapper, FastPairController.class));
- Bundle bundle = new Bundle();
- bundle.putBinder(EXTRA_BINDER, callback);
- mLocatorContextWrapper.getContext()
- .startActivityAsUser(new Intent(ACTIVITY_INTENT_ACTION)
- .putExtra(EXTRA_HALF_SHEET_INFO,
- scanFastPairStoreItem.toByteArray())
- .putExtra(EXTRA_HALF_SHEET_TYPE, DEVICE_PAIRING_FRAGMENT_TYPE)
- .putExtra(EXTRA_BUNDLE, bundle)
- .setComponent(new ComponentName(packageName,
- packageName + ".HalfSheetActivity")),
- UserHandle.CURRENT);
-
- }
- }
}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairModule.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairModule.java
index e92bb8a..d7946d1 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairModule.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairModule.java
@@ -49,7 +49,9 @@
} else if (type.equals(FastPairCacheManager.class)) {
locator.bind(FastPairCacheManager.class, new FastPairCacheManager(context));
} else if (type.equals(FastPairHalfSheetManager.class)) {
- locator.bind(FastPairHalfSheetManager.class, new FastPairHalfSheetManager());
+ locator.bind(FastPairHalfSheetManager.class, new FastPairHalfSheetManager(context));
+ } else if (type.equals(FastPairAdvHandler.class)) {
+ locator.bind(FastPairAdvHandler.class, new FastPairAdvHandler(context));
} else if (type.equals(Clock.class)) {
locator.bind(Clock.class, new Clock() {
@Override
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java
index 4c43965..9fdd01a 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManager.java
@@ -16,15 +16,73 @@
package com.android.server.nearby.fastpair.halfsheet;
+import static com.android.server.nearby.fastpair.Constant.DEVICE_PAIRING_FRAGMENT_TYPE;
+import static com.android.server.nearby.fastpair.Constant.EXTRA_BINDER;
+import static com.android.server.nearby.fastpair.Constant.EXTRA_BUNDLE;
+import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_INFO;
+import static com.android.server.nearby.fastpair.Constant.EXTRA_HALF_SHEET_TYPE;
+import static com.android.server.nearby.fastpair.FastPairManager.ACTION_RESOURCES_APK;
+
import android.bluetooth.BluetoothDevice;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
import android.util.Log;
+import com.android.server.nearby.common.locator.Locator;
+import com.android.server.nearby.fastpair.FastPairController;
import com.android.server.nearby.fastpair.cache.DiscoveryItem;
+import com.android.server.nearby.util.Environment;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import service.proto.Cache;
/**
* Fast Pair ux manager for half sheet.
*/
public class FastPairHalfSheetManager {
+ private static final String ACTIVITY_INTENT_ACTION = "android.nearby.SHOW_HALFSHEET";
+
+ private String mHalfSheetApkPkgName;
+ private Context mContext;
+
+ /**
+ * Construct function
+ */
+ public FastPairHalfSheetManager(Context context) {
+ mContext = context;
+ }
+
+
+ /**
+ * Invokes half sheet in the other apk. This function can only be called in Nearby because other
+ * app can't get the correct component name.
+ */
+ public void showHalfSheet(Cache.ScanFastPairStoreItem scanFastPairStoreItem) {
+ if (mContext != null) {
+ String packageName = getHalfSheetApkPkgName();
+ HalfSheetCallback callback = new HalfSheetCallback();
+ callback.setmFastPairController(Locator.get(mContext, FastPairController.class));
+ Bundle bundle = new Bundle();
+ bundle.putBinder(EXTRA_BINDER, callback);
+ mContext
+ .startActivityAsUser(new Intent(ACTIVITY_INTENT_ACTION)
+ .putExtra(EXTRA_HALF_SHEET_INFO,
+ scanFastPairStoreItem.toByteArray())
+ .putExtra(EXTRA_HALF_SHEET_TYPE, DEVICE_PAIRING_FRAGMENT_TYPE)
+ .putExtra(EXTRA_BUNDLE, bundle)
+ .setComponent(new ComponentName(packageName,
+ packageName + ".HalfSheetActivity")),
+ UserHandle.CURRENT);
+
+ }
+ }
/**
* Shows pairing fail half sheet.
@@ -77,4 +135,45 @@
*/
public void notifyPairingProcessDone(boolean success, String address, DiscoveryItem item) {
}
+
+ /**
+ * Gets the package name of HalfSheet.apk
+ * getHalfSheetApkPkgName may invoke PackageManager multiple times and it does not have
+ * race condition check. Since there is no lock for mHalfSheetApkPkgName.
+ */
+ String getHalfSheetApkPkgName() {
+ if (mHalfSheetApkPkgName != null) {
+ return mHalfSheetApkPkgName;
+ }
+ List<ResolveInfo> resolveInfos = mContext
+ .getPackageManager().queryIntentActivities(
+ new Intent(ACTION_RESOURCES_APK),
+ PackageManager.MATCH_SYSTEM_ONLY);
+
+ // remove apps that don't live in the nearby apex
+ resolveInfos.removeIf(info ->
+ !Environment.isAppInNearbyApex(info.activityInfo.applicationInfo));
+
+ if (resolveInfos.isEmpty()) {
+ // Resource APK not loaded yet, print a stack trace to see where this is called from
+ Log.e("FastPairManager", "Attempted to fetch resources before halfsheet "
+ + " APK is installed or package manager can't resolve correctly!",
+ new IllegalStateException());
+ return null;
+ }
+
+ if (resolveInfos.size() > 1) {
+ // multiple apps found, log a warning, but continue
+ Log.w("FastPairManager", "Found > 1 APK that can resolve halfsheet APK intent: "
+ + resolveInfos.stream()
+ .map(info -> info.activityInfo.applicationInfo.packageName)
+ .collect(Collectors.joining(", ")));
+ }
+
+ // Assume the first ResolveInfo is the one we're looking for
+ ResolveInfo info = resolveInfos.get(0);
+ mHalfSheetApkPkgName = info.activityInfo.applicationInfo.packageName;
+ Log.i("FastPairManager", "Found halfsheet APK at: " + mHalfSheetApkPkgName);
+ return mHalfSheetApkPkgName;
+ }
}
diff --git a/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java b/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java
index b7c63c9..cac815b 100644
--- a/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java
+++ b/nearby/service/java/com/android/server/nearby/provider/FastPairDataProvider.java
@@ -32,17 +32,18 @@
private static FastPairDataProvider sInstance;
- private ProxyFastPairDataProvider mProxyProvider;
+ private ProxyFastPairDataProvider mProxyFastPairDataProvider;
/** Initializes FastPairDataProvider singleton. */
public static synchronized FastPairDataProvider init(Context context) {
+
if (sInstance == null) {
sInstance = new FastPairDataProvider(context);
}
- if (sInstance.mProxyProvider == null) {
+ if (sInstance.mProxyFastPairDataProvider == null) {
Log.wtf(TAG, "no proxy fast pair data provider found");
} else {
- sInstance.mProxyProvider.register();
+ sInstance.mProxyFastPairDataProvider.register();
}
return sInstance;
}
@@ -53,16 +54,18 @@
}
private FastPairDataProvider(Context context) {
- mProxyProvider = ProxyFastPairDataProvider.create(
+ mProxyFastPairDataProvider = ProxyFastPairDataProvider.create(
context, FastPairDataProviderBase.ACTION_FAST_PAIR_DATA_PROVIDER);
+ Log.d("FastPairService", "the fast pair proxy provider is init"
+ + (mProxyFastPairDataProvider == null));
}
/** loadFastPairDeviceMetadata. */
@WorkerThread
@Nullable
public Rpcs.GetObservedDeviceResponse loadFastPairDeviceMetadata(byte[] modelId) {
- if (mProxyProvider != null) {
- return mProxyProvider.loadFastPairDeviceMetadata(modelId);
+ if (mProxyFastPairDataProvider != null) {
+ return mProxyFastPairDataProvider.loadFastPairDeviceMetadata(modelId);
}
throw new IllegalStateException("No ProxyFastPairDataProvider yet constructed");
}
diff --git a/nearby/service/java/com/android/server/nearby/util/FastPairDecoder.java b/nearby/service/java/com/android/server/nearby/util/FastPairDecoder.java
new file mode 100644
index 0000000..5bb76c9
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/util/FastPairDecoder.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2022 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.util;
+
+import android.annotation.Nullable;
+import android.bluetooth.le.ScanRecord;
+import android.os.ParcelUuid;
+import android.util.SparseArray;
+
+import com.android.server.nearby.common.ble.BleFilter;
+import com.android.server.nearby.common.ble.BleRecord;
+
+import java.util.Arrays;
+
+/**
+ * Parses Fast Pair information out of {@link BleRecord}s.
+ *
+ * <p>There are 2 different packet formats that are supported, which is used can be determined by
+ * packet length:
+ *
+ * <p>For 3-byte packets, the full packet is the model ID.
+ *
+ * <p>For all other packets, the first byte is the header, followed by the model ID, followed by
+ * zero or more extra fields. Each field has its own header byte followed by the field value. The
+ * packet header is formatted as 0bVVVLLLLR (V = version, L = model ID length, R = reserved) and
+ * each extra field header is 0bLLLLTTTT (L = field length, T = field type).
+ */
+public class FastPairDecoder {
+
+ private static final int FIELD_TYPE_BLOOM_FILTER = 0;
+ private static final int FIELD_TYPE_BLOOM_FILTER_SALT = 1;
+ private static final int FIELD_TYPE_BLOOM_FILTER_NO_NOTIFICATION = 2;
+ private static final int FIELD_TYPE_BATTERY = 3;
+ private static final int FIELD_TYPE_BATTERY_NO_NOTIFICATION = 4;
+ public static final int FIELD_TYPE_CONNECTION_STATE = 5;
+ private static final int FIELD_TYPE_RANDOM_RESOLVABLE_DATA = 6;
+
+
+ /** FE2C is the 16-bit Service UUID. The rest is the base UUID. See BluetoothUuid (hidden). */
+ private static final ParcelUuid FAST_PAIR_SERVICE_PARCEL_UUID =
+ ParcelUuid.fromString("0000FE2C-0000-1000-8000-00805F9B34FB");
+
+ /** The filter you use to scan for Fast Pair BLE advertisements. */
+ public static final BleFilter FILTER =
+ new BleFilter.Builder().setServiceData(FAST_PAIR_SERVICE_PARCEL_UUID,
+ new byte[0]).build();
+
+ // NOTE: Ensure that all bitmasks are always ints, not bytes so that bitshifting works correctly
+ // without needing worry about signing errors.
+ private static final int HEADER_VERSION_BITMASK = 0b11100000;
+ private static final int HEADER_LENGTH_BITMASK = 0b00011110;
+ private static final int HEADER_VERSION_OFFSET = 5;
+ private static final int HEADER_LENGTH_OFFSET = 1;
+
+ private static final int EXTRA_FIELD_LENGTH_BITMASK = 0b11110000;
+ private static final int EXTRA_FIELD_TYPE_BITMASK = 0b00001111;
+ private static final int EXTRA_FIELD_LENGTH_OFFSET = 4;
+ private static final int EXTRA_FIELD_TYPE_OFFSET = 0;
+
+ private static final int MIN_ID_LENGTH = 3;
+ private static final int MAX_ID_LENGTH = 14;
+ private static final int HEADER_INDEX = 0;
+ private static final int HEADER_LENGTH = 1;
+ private static final int FIELD_HEADER_LENGTH = 1;
+
+ // Not using java.util.IllegalFormatException because it is unchecked.
+ private static class IllegalFormatException extends Exception {
+ private IllegalFormatException(String message) {
+ super(message);
+ }
+ }
+
+ /**
+ * Gets model id data from broadcast
+ */
+ @Nullable
+ public static byte[] getModelId(@Nullable byte[] serviceData) {
+ if (serviceData == null) {
+ return null;
+ }
+
+ if (serviceData.length >= MIN_ID_LENGTH) {
+ if (serviceData.length == MIN_ID_LENGTH) {
+ // If the length == 3, all bytes are the ID. See flag docs for more about
+ // endianness.
+ return serviceData;
+ } else {
+ // Otherwise, the first byte is a header which contains the length of the big-endian
+ // model ID that follows. The model ID will be trimmed if it contains leading zeros.
+ int idIndex = 1;
+ int end = idIndex + getIdLength(serviceData);
+ while (serviceData[idIndex] == 0 && end - idIndex > MIN_ID_LENGTH) {
+ idIndex++;
+ }
+ return Arrays.copyOfRange(serviceData, idIndex, end);
+ }
+ }
+ return null;
+ }
+
+ /** Gets the FastPair service data array if available, otherwise returns null. */
+ @Nullable
+ public static byte[] getServiceDataArray(BleRecord bleRecord) {
+ return bleRecord.getServiceData(FAST_PAIR_SERVICE_PARCEL_UUID);
+ }
+
+ /** Gets the FastPair service data array if available, otherwise returns null. */
+ @Nullable
+ public static byte[] getServiceDataArray(ScanRecord scanRecord) {
+ return scanRecord.getServiceData(FAST_PAIR_SERVICE_PARCEL_UUID);
+ }
+
+ /** Gets the bloom filter from the extra fields if available, otherwise returns null. */
+ @Nullable
+ public static byte[] getBloomFilter(@Nullable byte[] serviceData) {
+ return getExtraField(serviceData, FIELD_TYPE_BLOOM_FILTER);
+ }
+
+ /** Gets the bloom filter salt from the extra fields if available, otherwise returns null. */
+ @Nullable
+ public static byte[] getBloomFilterSalt(byte[] serviceData) {
+ return getExtraField(serviceData, FIELD_TYPE_BLOOM_FILTER_SALT);
+ }
+
+ /**
+ * Gets the suppress notification with bloom filter from the extra fields if available,
+ * otherwise returns null.
+ */
+ @Nullable
+ public static byte[] getBloomFilterNoNotification(@Nullable byte[] serviceData) {
+ return getExtraField(serviceData, FIELD_TYPE_BLOOM_FILTER_NO_NOTIFICATION);
+ }
+
+
+ /**
+ * Get random resolvableData
+ */
+ @Nullable
+ public static byte[] getRandomResolvableData(byte[] serviceData) {
+ return getExtraField(serviceData, FIELD_TYPE_RANDOM_RESOLVABLE_DATA);
+ }
+
+ @Nullable
+ private static byte[] getExtraField(@Nullable byte[] serviceData, int fieldId) {
+ if (serviceData == null || serviceData.length < HEADER_INDEX + HEADER_LENGTH) {
+ return null;
+ }
+ try {
+ return getExtraFields(serviceData).get(fieldId);
+ } catch (IllegalFormatException e) {
+ return null;
+ }
+ }
+
+ /** Gets extra field data at the end of the packet, defined by the extra field header. */
+ private static SparseArray<byte[]> getExtraFields(byte[] serviceData)
+ throws IllegalFormatException {
+ SparseArray<byte[]> extraFields = new SparseArray<>();
+ if (getVersion(serviceData) != 0) {
+ return extraFields;
+ }
+ int headerIndex = getFirstExtraFieldHeaderIndex(serviceData);
+ while (headerIndex < serviceData.length) {
+ int length = getExtraFieldLength(serviceData, headerIndex);
+ int index = headerIndex + FIELD_HEADER_LENGTH;
+ int type = getExtraFieldType(serviceData, headerIndex);
+ int end = index + length;
+ if (extraFields.get(type) == null) {
+ if (end <= serviceData.length) {
+ extraFields.put(type, Arrays.copyOfRange(serviceData, index, end));
+ } else {
+ throw new IllegalFormatException(
+ "Invalid length, " + end + " is longer than service data size "
+ + serviceData.length);
+ }
+ }
+ headerIndex = end;
+ }
+ return extraFields;
+ }
+
+ /** Checks whether or not a valid ID is included in the service data packet. */
+ public static boolean hasBeaconIdBytes(BleRecord bleRecord) {
+ byte[] serviceData = bleRecord.getServiceData(FAST_PAIR_SERVICE_PARCEL_UUID);
+ return checkModelId(serviceData);
+ }
+
+ /** Check whether byte array is FastPair model id or not. */
+ public static boolean checkModelId(@Nullable byte[] scanResult) {
+ return scanResult != null
+ // The 3-byte format has no header byte (all bytes are the ID).
+ && (scanResult.length == MIN_ID_LENGTH
+ // Header byte exists. We support only format version 0. (A different version
+ // indicates
+ // a breaking change in the format.)
+ || (scanResult.length > MIN_ID_LENGTH
+ && getVersion(scanResult) == 0
+ && isIdLengthValid(scanResult)));
+ }
+
+ /** Checks whether or not bloom filter is included in the service data packet. */
+ public static boolean hasBloomFilter(BleRecord bleRecord) {
+ return (getBloomFilter(getServiceDataArray(bleRecord)) != null
+ || getBloomFilterNoNotification(getServiceDataArray(bleRecord)) != null);
+ }
+
+ /** Checks whether or not bloom filter is included in the service data packet. */
+ public static boolean hasBloomFilter(ScanRecord scanRecord) {
+ return (getBloomFilter(getServiceDataArray(scanRecord)) != null
+ || getBloomFilterNoNotification(getServiceDataArray(scanRecord)) != null);
+ }
+
+ private static int getVersion(byte[] serviceData) {
+ return serviceData.length == MIN_ID_LENGTH
+ ? 0
+ : (serviceData[HEADER_INDEX] & HEADER_VERSION_BITMASK) >> HEADER_VERSION_OFFSET;
+ }
+
+ private static int getIdLength(byte[] serviceData) {
+ return serviceData.length == MIN_ID_LENGTH
+ ? MIN_ID_LENGTH
+ : (serviceData[HEADER_INDEX] & HEADER_LENGTH_BITMASK) >> HEADER_LENGTH_OFFSET;
+ }
+
+ private static int getFirstExtraFieldHeaderIndex(byte[] serviceData) {
+ return HEADER_INDEX + HEADER_LENGTH + getIdLength(serviceData);
+ }
+
+ private static int getExtraFieldLength(byte[] serviceData, int extraFieldIndex) {
+ return (serviceData[extraFieldIndex] & EXTRA_FIELD_LENGTH_BITMASK)
+ >> EXTRA_FIELD_LENGTH_OFFSET;
+ }
+
+ private static int getExtraFieldType(byte[] serviceData, int extraFieldIndex) {
+ return (serviceData[extraFieldIndex] & EXTRA_FIELD_TYPE_BITMASK) >> EXTRA_FIELD_TYPE_OFFSET;
+ }
+
+ private static boolean isIdLengthValid(byte[] serviceData) {
+ int idLength = getIdLength(serviceData);
+ return MIN_ID_LENGTH <= idLength
+ && idLength <= MAX_ID_LENGTH
+ && idLength + HEADER_LENGTH <= serviceData.length;
+ }
+}
+
diff --git a/nearby/service/java/com/android/server/nearby/util/Hex.java b/nearby/service/java/com/android/server/nearby/util/Hex.java
new file mode 100644
index 0000000..c827ec5
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/util/Hex.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 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.util;
+
+/**
+ * Hex class that contains hex related functions.
+ */
+public class Hex {
+ private static final char[] HEX_LOWERCASE = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+ };
+
+ /**
+ * Bytes array to lower case string.
+ */
+ public static String bytesToStringLowercase(byte[] bytes) {
+ char[] hexChars = new char[bytes.length * 2];
+ int j = 0;
+ for (int i = 0; i < bytes.length; i++) {
+ int v = bytes[i] & 0xFF;
+ hexChars[j++] = HEX_LOWERCASE[v >>> 4];
+ hexChars[j++] = HEX_LOWERCASE[v & 0x0F];
+ }
+ return new String(hexChars);
+ }
+}
diff --git a/nearby/tests/unit/Android.bp b/nearby/tests/unit/Android.bp
index 5287891..64ef292 100644
--- a/nearby/tests/unit/Android.bp
+++ b/nearby/tests/unit/Android.bp
@@ -26,27 +26,27 @@
srcs: ["src/**/*.java"],
libs: [
- "android.test.runner",
"android.test.base",
"android.test.mock",
+ "android.test.runner",
],
compile_multilib: "both",
static_libs: [
+ "Robolectric_all-target",
+ "androidx.test.ext.junit",
"androidx.test.rules",
"compatibility-device-util-axt",
- "service-appsearch",
- "truth-prebuilt",
- "androidx.test.ext.junit",
- "junit",
"framework-nearby-pre-jarjar",
"guava",
- "robolectric_android-all-stub",
- "Robolectric_all-target",
+ "junit",
"libprotobuf-java-lite",
"mockito-target",
"platform-test-annotations",
+ "robolectric_android-all-stub",
+ "service-appsearch",
"service-nearby",
+ "truth-prebuilt",
],
test_suites: [
"general-tests",