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",