Merge "Rename "service-nearby" to "service-nearby-pre-jarjar"" into tm-dev
diff --git a/nearby/framework/java/android/nearby/NearbyManager.java b/nearby/framework/java/android/nearby/NearbyManager.java
index 7fb14ef..a217677 100644
--- a/nearby/framework/java/android/nearby/NearbyManager.java
+++ b/nearby/framework/java/android/nearby/NearbyManager.java
@@ -24,6 +24,7 @@
 import android.annotation.SystemService;
 import android.content.Context;
 import android.os.RemoteException;
+import android.provider.Settings;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.Preconditions;
@@ -46,7 +47,15 @@
 @SystemService(Context.NEARBY_SERVICE)
 public class NearbyManager {
 
-    private static final String TAG = "NearbyManager";
+    /**
+     * Whether allows Fast Pair to scan.
+     *
+     * (0 = disabled, 1 = enabled)
+     *
+     * @hide
+     */
+    public static final String FAST_PAIR_SCAN_ENABLED = "fast_pair_scan_enabled";
+
     @GuardedBy("sScanListeners")
     private static final WeakHashMap<ScanCallback, WeakReference<ScanListenerTransport>>
             sScanListeners = new WeakHashMap<>();
@@ -139,7 +148,6 @@
         }
     }
 
-
     /**
      * Start broadcasting the request using nearby specification.
      *
@@ -162,6 +170,30 @@
         // TODO(b/218187205): implement broadcast.
     }
 
+    /**
+     * Read from {@link Settings} whether Fast Pair scan is enabled.
+     *
+     * @param context the {@link Context} to query the setting.
+     * @param def the default value if no setting value.
+     * @return whether the Fast Pair is enabled.
+     */
+    public static boolean getFastPairScanEnabled(@NonNull Context context, boolean def) {
+        final int enabled = Settings.Secure.getInt(
+                context.getContentResolver(), FAST_PAIR_SCAN_ENABLED, (def ? 1 : 0));
+        return enabled != 0;
+    }
+
+    /**
+     * Write into {@link Settings} whether Fast Pair scan is enabled
+     *
+     * @param context the {@link Context} to set the setting.
+     * @param enable whether the Fast Pair scan should be enabled.
+     */
+    public static void setFastPairScanEnabled(@NonNull Context context, boolean enable) {
+        Settings.Secure.putInt(
+                context.getContentResolver(), FAST_PAIR_SCAN_ENABLED, enable ? 1 : 0);
+    }
+
     private static class ScanListenerTransport extends IScanListener.Stub {
 
         private @ScanRequest.ScanType int mScanType;
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
index 08e98db..4ee16b7 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairAdvHandler.java
@@ -27,17 +27,19 @@
 import android.nearby.NearbyDevice;
 import android.util.Log;
 
+import com.android.server.nearby.common.ble.decode.FastPairDecoder;
 import com.android.server.nearby.common.bloomfilter.BloomFilter;
 import com.android.server.nearby.common.bloomfilter.FastPairBloomFilterHasher;
 import com.android.server.nearby.common.locator.Locator;
+import com.android.server.nearby.fastpair.cache.FastPairCacheManager;
 import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
 import com.android.server.nearby.provider.FastPairDataProvider;
 import com.android.server.nearby.util.DataUtils;
-import com.android.server.nearby.util.FastPairDecoder;
 import com.android.server.nearby.util.Hex;
 
 import java.util.List;
 
+import service.proto.Cache;
 import service.proto.Data;
 import service.proto.Rpcs;
 
@@ -47,6 +49,7 @@
 public class FastPairAdvHandler {
     Context mContext;
     String mBleAddress;
+
     /** The types about how the bloomfilter is processed. */
     public enum ProcessBloomFilterType {
         IGNORE, // The bloomfilter is not handled. e.g. distance is too far away.
@@ -69,16 +72,18 @@
     public void handleBroadcast(NearbyDevice device) {
         FastPairDevice fastPairDevice = (FastPairDevice) device;
         mBleAddress = fastPairDevice.getBluetoothAddress();
-        List<Account> accountList =
-                FastPairDataProvider.getInstance().loadFastPairEligibleAccounts();
+        FastPairDataProvider dataProvider = FastPairDataProvider.getInstance();
+        if (dataProvider == null) {
+            return;
+        }
+        List<Account> accountList = dataProvider.loadFastPairEligibleAccounts();
         if (FastPairDecoder.checkModelId(fastPairDevice.getData())) {
             byte[] model = FastPairDecoder.getModelId(fastPairDevice.getData());
             Log.d(TAG, "On discovery model id " + Hex.bytesToStringLowercase(model));
             // Use api to get anti spoofing key from model id.
             try {
                 Rpcs.GetObservedDeviceResponse response =
-                        FastPairDataProvider.getInstance()
-                                .loadFastPairAntispoofkeyDeviceMetadata(model);
+                        dataProvider.loadFastPairAntispoofkeyDeviceMetadata(model);
                 if (response == null) {
                     Log.e(TAG, "server does not have model id "
                             + Hex.bytesToStringLowercase(model));
@@ -94,26 +99,41 @@
         } else {
             // Start to process bloom filter
             try {
-                Log.d(TAG, "account list size " + accountList.size());
                 byte[] bloomFilterByteArray = FastPairDecoder
                         .getBloomFilter(fastPairDevice.getData());
                 byte[] bloomFilterSalt = FastPairDecoder
                         .getBloomFilterSalt(fastPairDevice.getData());
                 if (bloomFilterByteArray == null || bloomFilterByteArray.length == 0) {
-                    Log.d(TAG, "bloom filter byte size is 0");
                     return;
                 }
                 for (Account account : accountList) {
                     List<Data.FastPairDeviceWithAccountKey> listDevices =
-                            FastPairDataProvider.getInstance()
-                                    .loadFastPairDeviceWithAccountKey(account);
+                            dataProvider.loadFastPairDeviceWithAccountKey(account);
                     Data.FastPairDeviceWithAccountKey recognizedDevice =
                             findRecognizedDevice(listDevices,
                                     new BloomFilter(bloomFilterByteArray,
                                             new FastPairBloomFilterHasher()), bloomFilterSalt);
+
                     if (recognizedDevice != null) {
                         Log.d(TAG, "find matched device show notification to remind"
                                 + " user to pair");
+                        // Check if the device is already paired
+                        List<Cache.StoredFastPairItem> storedFastPairItemList =
+                                Locator.get(mContext, FastPairCacheManager.class)
+                                        .getAllSavedStoredFastPairItem();
+                        Cache.StoredFastPairItem recognizedStoredFastPairItem =
+                                findRecognizedDeviceFromCachedItem(storedFastPairItemList,
+                                        new BloomFilter(bloomFilterByteArray,
+                                                new FastPairBloomFilterHasher()), bloomFilterSalt);
+                        if (recognizedStoredFastPairItem != null) {
+                            // The bloomfilter is recognized in the cache so the device is paired
+                            // before
+                            Log.d(TAG, "bloom filter is recognized in the cache");
+                            continue;
+                        } else {
+                            Log.d(TAG, "bloom filter is not recognized not paired before");
+                        }
+
                         return;
                     }
                 }
@@ -132,10 +152,27 @@
     @Nullable
     static Data.FastPairDeviceWithAccountKey findRecognizedDevice(
             List<Data.FastPairDeviceWithAccountKey> devices, BloomFilter bloomFilter, byte[] salt) {
+        Log.d(TAG, "saved devices size in the account is " + devices.size());
         for (Data.FastPairDeviceWithAccountKey device : devices) {
-            if (device.getAccountKey().toByteArray() == null) {
-                continue;
+            byte[] rotatedKey = concat(device.getAccountKey().toByteArray(), salt);
+            StringBuilder sb = new StringBuilder();
+            for (byte b : rotatedKey) {
+                sb.append(b);
             }
+            if (bloomFilter.possiblyContains(rotatedKey)) {
+                Log.d(TAG, "match " + sb.toString());
+                return device;
+            } else {
+                Log.d(TAG, "not match " + sb.toString());
+            }
+        }
+        return null;
+    }
+
+    @Nullable
+    static Cache.StoredFastPairItem findRecognizedDeviceFromCachedItem(
+            List<Cache.StoredFastPairItem> devices, BloomFilter bloomFilter, byte[] salt) {
+        for (Cache.StoredFastPairItem device : devices) {
             byte[] rotatedKey = concat(device.getAccountKey().toByteArray(), salt);
             if (bloomFilter.possiblyContains(rotatedKey)) {
                 return device;
@@ -143,4 +180,5 @@
         }
         return null;
     }
+
 }
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java b/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java
index 1264ade..896056b 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairController.java
@@ -237,12 +237,14 @@
                             ByteString.copyFrom(hashValue));
             // account data place holder here
             try {
-                List<Account> accountList =
-                        FastPairDataProvider.getInstance().loadFastPairEligibleAccounts();
+                FastPairDataProvider fastPairDataProvider = FastPairDataProvider.getInstance();
+                if (fastPairDataProvider == null) {
+                    return;
+                }
+                List<Account> accountList = fastPairDataProvider.loadFastPairEligibleAccounts();
                 if (accountList.size() > 0) {
-                    FastPairDataProvider.getInstance().optIn(accountList.get(0));
-                    FastPairDataProvider.getInstance().upload(
-                            accountList.get(0), uploadInfo);
+                    fastPairDataProvider.optIn(accountList.get(0));
+                    fastPairDataProvider.upload(accountList.get(0), uploadInfo);
                 }
             } catch (IllegalStateException e) {
                 Log.e(TAG, "OEM does not construct fast pair data proxy correctly");
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 04f72f0..7604771 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/FastPairManager.java
@@ -16,7 +16,6 @@
 
 package com.android.server.nearby.fastpair;
 
-import static com.android.server.nearby.fastpair.Constant.SETTINGS_TRUE_VALUE;
 import static com.android.server.nearby.fastpair.Constant.TAG;
 
 import android.annotation.Nullable;
@@ -41,6 +40,7 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.server.nearby.common.ble.decode.FastPairDecoder;
 import com.android.server.nearby.common.bluetooth.BluetoothException;
 import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection;
 import com.android.server.nearby.common.bluetooth.fastpair.FastPairDualConnection;
@@ -48,6 +48,7 @@
 import com.android.server.nearby.common.bluetooth.fastpair.Preferences;
 import com.android.server.nearby.common.bluetooth.fastpair.ReflectionException;
 import com.android.server.nearby.common.bluetooth.fastpair.SimpleBroadcastReceiver;
+import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothDevice;
 import com.android.server.nearby.common.eventloop.Annotations;
 import com.android.server.nearby.common.eventloop.EventLoop;
 import com.android.server.nearby.common.eventloop.NamedRunnable;
@@ -58,10 +59,12 @@
 import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager;
 import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager;
 import com.android.server.nearby.fastpair.pairinghandler.PairingProgressHandlerBase;
-import com.android.server.nearby.util.FastPairDecoder;
 import com.android.server.nearby.util.ForegroundThread;
 import com.android.server.nearby.util.Hex;
 
+import com.google.common.collect.ImmutableList;
+import com.google.protobuf.ByteString;
+
 import java.security.GeneralSecurityException;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
@@ -71,6 +74,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
+import service.proto.Cache;
 import service.proto.Rpcs;
 
 /**
@@ -147,13 +151,8 @@
                 .registerReceiver(mScreenBroadcastReceiver, mIntentFilter);
 
         Locator.getFromContextWrapper(mLocatorContextWrapper, FastPairCacheManager.class);
-        try {
-            mScanEnabled = getScanEnabledFromSettings();
-            mScanEnabled = ENFORCED_SCAN_ENABLED_VALUE;
-        } catch (Settings.SettingNotFoundException e) {
-            Log.w(TAG,
-                    "initiate: Failed to get initial scan enabled status from Settings.", e);
-        }
+        mScanEnabled = NearbyManager.getFastPairScanEnabled(mLocatorContextWrapper, true);
+        mScanEnabled = ENFORCED_SCAN_ENABLED_VALUE;
         registerFastPairScanChangeContentObserver(mLocatorContextWrapper.getContentResolver());
     }
 
@@ -180,7 +179,6 @@
             @Nullable String companionApp,
             FootprintsDeviceManager footprints,
             PairingProgressHandlerBase pairingProgressHandlerBase) {
-
         return executor.submit(
                 () -> pairInternal(context, item, companionApp, accountKey, footprints,
                         pairingProgressHandlerBase), /* result= */ null);
@@ -197,7 +195,8 @@
             @Nullable byte[] accountKey,
             FootprintsDeviceManager footprints,
             PairingProgressHandlerBase pairingProgressHandlerBase) {
-        FastPairHalfSheetManager manager = Locator.get(context, FastPairHalfSheetManager.class);
+        FastPairHalfSheetManager fastPairHalfSheetManager =
+                Locator.get(context, FastPairHalfSheetManager.class);
         try {
             pairingProgressHandlerBase.onPairingStarted();
             if (pairingProgressHandlerBase.skipWaitingScreenUnlock()) {
@@ -268,24 +267,17 @@
                     if (sharedSecret != null) {
                         Locator.get(context, FastPairController.class).addDeviceToFootprint(
                                 connection.getPublicAddress(), sharedSecret.getKey(), item);
+                        cacheFastPairDevice(context, connection.getPublicAddress(),
+                                sharedSecret.getKey(), item);
                     }
                 }
-
-                byte[] key = pairingProgressHandlerBase.getKeyForLocalCache(accountKey,
-                        connection, sharedSecret);
-
-                // We don't cache initial pairing case here but cache it when upload to footprints.
-                if (key != null) {
-                    // CacheManager to save the content here
-                }
             } else {
                 // Fast Pair one
                 connection.pair();
             }
-
             // TODO(b/213373051): Merge logic with pairingProgressHandlerBase or delete the
             // pairingProgressHandlerBase class.
-            manager.showPairingSuccessHalfSheet(connection.getPublicAddress());
+            fastPairHalfSheetManager.showPairingSuccessHalfSheet(connection.getPublicAddress());
             pairingProgressHandlerBase.onPairingSuccess(connection.getPublicAddress());
         } catch (BluetoothException
                 | InterruptedException
@@ -298,11 +290,38 @@
 
             // TODO(b/213373051): Merge logic with pairingProgressHandlerBase or delete the
             // pairingProgressHandlerBase class.
-            manager.showPairingFailed();
+            fastPairHalfSheetManager.showPairingFailed();
             pairingProgressHandlerBase.onPairingFailed(e);
         }
     }
 
+    private static void cacheFastPairDevice(Context context, String publicAddress, byte[] key,
+            DiscoveryItem item) {
+        try {
+            Locator.get(context, EventLoop.class).postAndWait(
+                    new NamedRunnable("FastPairCacheDevice") {
+                        @Override
+                        public void run() {
+                            Cache.StoredFastPairItem storedFastPairItem =
+                                    Cache.StoredFastPairItem.newBuilder()
+                                            .setMacAddress(publicAddress)
+                                            .setAccountKey(ByteString.copyFrom(key))
+                                            .setModelId(item.getTriggerId())
+                                            .addAllFeatures(item.getFastPairInformation() == null
+                                                    ? ImmutableList.of() :
+                                                    item.getFastPairInformation().getFeaturesList())
+                                            .setDiscoveryItem(item.getCopyOfStoredItem())
+                                            .build();
+                            Locator.get(context, FastPairCacheManager.class)
+                                    .putStoredFastPairItem(storedFastPairItem);
+                        }
+                    }
+            );
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Fail to insert paired device into cache");
+        }
+    }
+
     /** Checks if the pairing is initial pairing with fast pair 2.0 design. */
     public static boolean isThroughFastPair2InitialPairing(
             DiscoveryItem item, @Nullable byte[] accountKey) {
@@ -319,7 +338,7 @@
         // regardless.
         if (keyguardManager.isKeyguardLocked()) {
             Log.v(TAG, "FastPair: Screen is locked, waiting until unlocked "
-                            + "to show status notifications.");
+                    + "to show status notifications.");
             try (SimpleBroadcastReceiver isUnlockedReceiver =
                          SimpleBroadcastReceiver.oneShotReceiver(
                                  context, FlagUtils.getPreferencesBuilder().build(),
@@ -334,19 +353,16 @@
             @Override
             public void onChange(boolean selfChange, Uri uri) {
                 super.onChange(selfChange, uri);
-                try {
-                    setScanEnabled(getScanEnabledFromSettings());
-                } catch (Settings.SettingNotFoundException e) {
-                    Log.e(TAG, "Failed to get scan switch updates in Settings page.", e);
-                }
+                setScanEnabled(
+                        NearbyManager.getFastPairScanEnabled(mLocatorContextWrapper, mScanEnabled));
             }
         };
         try {
             resolver.registerContentObserver(
-                    Settings.Secure.getUriFor(Settings.Secure.FAST_PAIR_SCAN_ENABLED),
+                    Settings.Secure.getUriFor(NearbyManager.FAST_PAIR_SCAN_ENABLED),
                     /* notifyForDescendants= */ false,
                     mFastPairScanChangeContentObserver);
-        }  catch (SecurityException e) {
+        } catch (SecurityException e) {
             Log.e(TAG, "Failed to register content observer for fast pair scan.", e);
         }
     }
@@ -378,13 +394,6 @@
         return (NearbyManager) mLocatorContextWrapper
                 .getApplicationContext().getSystemService(Context.NEARBY_SERVICE);
     }
-
-    private boolean getScanEnabledFromSettings() throws Settings.SettingNotFoundException {
-        return Settings.Secure.getInt(
-                mLocatorContextWrapper.getContext().getContentResolver(),
-                Settings.Secure.FAST_PAIR_SCAN_ENABLED) == SETTINGS_TRUE_VALUE;
-    }
-
     private void setScanEnabled(boolean scanEnabled) {
         if (mScanEnabled == scanEnabled) {
             return;
@@ -395,7 +404,7 @@
     }
 
     /**
-     *  Starts or stops scanning according to mAllowScan value.
+     * Starts or stops scanning according to mAllowScan value.
      */
     private void invalidateScan() {
         NearbyManager nearbyManager = getNearbyManager();
@@ -415,6 +424,30 @@
     }
 
     /**
+     * When certain device is forgotten we need to remove the info from database because the info
+     * is no longer useful.
+     */
+    private void processBluetoothConnectionEvent(Intent intent) {
+        int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+                BluetoothDevice.ERROR);
+        if (bondState == BluetoothDevice.BOND_NONE) {
+            BluetoothDevice device =
+                    intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+            if (device != null) {
+                Log.d("FastPairService", "Forget device detect");
+                processBackgroundTask(new Runnable() {
+                    @Override
+                    public void run() {
+                        mLocatorContextWrapper.getLocator().get(FastPairCacheManager.class)
+                                .removeStoredFastPairItem(device.getAddress());
+                    }
+                });
+            }
+
+        }
+    }
+
+    /**
      * Helper function to get bluetooth adapter.
      */
     @Nullable
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItemDbHelper.java b/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItemDbHelper.java
deleted file mode 100644
index e171462..0000000
--- a/nearby/service/java/com/android/server/nearby/fastpair/cache/DiscoveryItemDbHelper.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2021 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.cache;
-
-import android.content.Context;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-
-/**
- * Discovery db helper
- */
-public class DiscoveryItemDbHelper extends SQLiteOpenHelper {
-
-    public static final int DATABASE_VERSION = 1;
-    public static final String DATABASE_NAME = "ScanResult.db";
-    private static final String SQL_CREATE_DB =
-            "CREATE TABLE " + DiscoveryItemContract.DiscoveryItemEntry.TABLE_NAME
-                    + " (" + DiscoveryItemContract.DiscoveryItemEntry._ID
-                    + "INTEGER PRIMARY KEY,"
-                    + DiscoveryItemContract.DiscoveryItemEntry.COLUMN_MODEL_ID
-                    + " TEXT," + DiscoveryItemContract.DiscoveryItemEntry.COLUMN_SCAN_BYTE
-                    + " BLOB)";
-    private static final String SQL_DELETE_DB =
-            "DROP TABLE IF EXISTS " + DiscoveryItemContract.DiscoveryItemEntry.TABLE_NAME;
-
-    public DiscoveryItemDbHelper(Context context) {
-        super(context, DATABASE_NAME, null, DATABASE_VERSION);
-    }
-
-    @Override
-    public void onCreate(SQLiteDatabase db) {
-        db.execSQL(SQL_CREATE_DB);
-    }
-
-    @Override
-    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-        // Since the outdated data has no value so just remove the data.
-        db.execSQL(SQL_DELETE_DB);
-        onCreate(db);
-    }
-
-    @Override
-    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-        super.onDowngrade(db, oldVersion, newVersion);
-    }
-}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairCacheManager.java b/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairCacheManager.java
index fb2adb2..b840091 100644
--- a/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairCacheManager.java
+++ b/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairCacheManager.java
@@ -39,18 +39,18 @@
  */
 public class FastPairCacheManager {
     private final Context mContext;
-    private final DiscoveryItemDbHelper mDiscoveryItemDbHelper;
+    private final FastPairDbHelper mFastPairDbHelper;
 
     public FastPairCacheManager(Context context) {
         mContext = context;
-        mDiscoveryItemDbHelper = new DiscoveryItemDbHelper(context);
+        mFastPairDbHelper = new FastPairDbHelper(context);
     }
 
     /**
      * Clean up function to release db
      */
     public void cleanUp() {
-        mDiscoveryItemDbHelper.close();
+        mFastPairDbHelper.close();
     }
 
     /**
@@ -74,10 +74,12 @@
     }
 
     /**
-     * Save discovery item into database.
+     * Save discovery item into database. Discovery item is item that discovered through Ble before
+     * pairing success.
      */
     public boolean saveDiscoveryItem(DiscoveryItem item) {
-        SQLiteDatabase db = mDiscoveryItemDbHelper.getWritableDatabase();
+
+        SQLiteDatabase db = mFastPairDbHelper.getWritableDatabase();
         ContentValues values = new ContentValues();
         values.put(DiscoveryItemContract.DiscoveryItemEntry.COLUMN_MODEL_ID, item.getTriggerId());
         values.put(DiscoveryItemContract.DiscoveryItemEntry.COLUMN_SCAN_BYTE,
@@ -103,8 +105,7 @@
      * Get discovery item from item id.
      */
     public Cache.StoredDiscoveryItem getStoredDiscoveryItem(String itemId) {
-
-        SQLiteDatabase db = mDiscoveryItemDbHelper.getReadableDatabase();
+        SQLiteDatabase db = mFastPairDbHelper.getReadableDatabase();
         String[] projection = {
                 DiscoveryItemContract.DiscoveryItemEntry.COLUMN_MODEL_ID,
                 DiscoveryItemContract.DiscoveryItemEntry.COLUMN_SCAN_BYTE
@@ -140,7 +141,7 @@
      */
     public List<Cache.StoredDiscoveryItem> getAllSavedStoreDiscoveryItem() {
         List<Cache.StoredDiscoveryItem> storedDiscoveryItemList = new ArrayList<>();
-        SQLiteDatabase db = mDiscoveryItemDbHelper.getReadableDatabase();
+        SQLiteDatabase db = mFastPairDbHelper.getReadableDatabase();
         String[] projection = {
                 DiscoveryItemContract.DiscoveryItemEntry.COLUMN_MODEL_ID,
                 DiscoveryItemContract.DiscoveryItemEntry.COLUMN_SCAN_BYTE
@@ -169,6 +170,7 @@
         cursor.close();
         return storedDiscoveryItemList;
     }
+
     /**
      * Get scan result from local database use model id
      */
@@ -176,4 +178,105 @@
         return Cache.StoredScanResult.getDefaultInstance();
     }
 
+    /**
+     * Gets the paired Fast Pair item that paired to the phone through mac address.
+     */
+    public Cache.StoredFastPairItem getStoredFastPairItemFromMacAddress(String macAddress) {
+        SQLiteDatabase db = mFastPairDbHelper.getReadableDatabase();
+        String[] projection = {
+                StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_ACCOUNT_KEY,
+                StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_MAC_ADDRESS,
+                StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_STORED_FAST_PAIR_BYTE
+        };
+        String selection =
+                StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_MAC_ADDRESS + " =? ";
+        String[] selectionArgs = {macAddress};
+        Cursor cursor = db.query(
+                StoredFastPairItemContract.StoredFastPairItemEntry.TABLE_NAME,
+                projection,
+                selection,
+                selectionArgs,
+                null,
+                null,
+                null
+        );
+
+        if (cursor.moveToNext()) {
+            byte[] res = cursor.getBlob(cursor.getColumnIndexOrThrow(
+                    StoredFastPairItemContract.StoredFastPairItemEntry
+                            .COLUMN_STORED_FAST_PAIR_BYTE));
+            try {
+                Cache.StoredFastPairItem item = Cache.StoredFastPairItem.parseFrom(res);
+                return item;
+            } catch (InvalidProtocolBufferException e) {
+                Log.e("FastPairCacheManager", "storediscovery has error");
+            }
+        }
+        cursor.close();
+        return Cache.StoredFastPairItem.getDefaultInstance();
+    }
+
+    /**
+     * Save paired fast pair item into the database.
+     */
+    public boolean putStoredFastPairItem(Cache.StoredFastPairItem storedFastPairItem) {
+        SQLiteDatabase db = mFastPairDbHelper.getWritableDatabase();
+        ContentValues values = new ContentValues();
+        values.put(StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_MAC_ADDRESS,
+                storedFastPairItem.getMacAddress());
+        values.put(StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_ACCOUNT_KEY,
+                storedFastPairItem.getAccountKey().toString());
+        values.put(StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_STORED_FAST_PAIR_BYTE,
+                storedFastPairItem.toByteArray());
+        db.insert(StoredFastPairItemContract.StoredFastPairItemEntry.TABLE_NAME, null, values);
+        return true;
+
+    }
+
+    /**
+     * Removes certain storedFastPairItem so that it can update timely.
+     */
+    public void removeStoredFastPairItem(String macAddress) {
+        SQLiteDatabase db = mFastPairDbHelper.getWritableDatabase();
+        int res = db.delete(StoredFastPairItemContract.StoredFastPairItemEntry.TABLE_NAME,
+                StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_MAC_ADDRESS + "=?",
+                new String[]{macAddress});
+
+    }
+
+    /**
+     * Get all of the store fast pair item related info in the cache.
+     */
+    public List<Cache.StoredFastPairItem> getAllSavedStoredFastPairItem() {
+        List<Cache.StoredFastPairItem> storedFastPairItemList = new ArrayList<>();
+        SQLiteDatabase db = mFastPairDbHelper.getReadableDatabase();
+        String[] projection = {
+                StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_MAC_ADDRESS,
+                StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_ACCOUNT_KEY,
+                StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_STORED_FAST_PAIR_BYTE
+        };
+        Cursor cursor = db.query(
+                StoredFastPairItemContract.StoredFastPairItemEntry.TABLE_NAME,
+                projection,
+                null,
+                null,
+                null,
+                null,
+                null
+        );
+
+        while (cursor.moveToNext()) {
+            byte[] res = cursor.getBlob(cursor.getColumnIndexOrThrow(StoredFastPairItemContract
+                    .StoredFastPairItemEntry.COLUMN_STORED_FAST_PAIR_BYTE));
+            try {
+                Cache.StoredFastPairItem item = Cache.StoredFastPairItem.parseFrom(res);
+                storedFastPairItemList.add(item);
+            } catch (InvalidProtocolBufferException e) {
+                Log.e("FastPairCacheManager", "storediscovery has error");
+            }
+
+        }
+        cursor.close();
+        return storedFastPairItemList;
+    }
 }
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairDbHelper.java b/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairDbHelper.java
new file mode 100644
index 0000000..d950d8d
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/cache/FastPairDbHelper.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2021 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.cache;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+/**
+ * Fast Pair db helper handle all of the db actions related Fast Pair.
+ */
+public class FastPairDbHelper extends SQLiteOpenHelper {
+
+    public static final int DATABASE_VERSION = 1;
+    public static final String DATABASE_NAME = "FastPair.db";
+    private static final String SQL_CREATE_DISCOVERY_ITEM_DB =
+            "CREATE TABLE IF NOT EXISTS " + DiscoveryItemContract.DiscoveryItemEntry.TABLE_NAME
+                    + " (" + DiscoveryItemContract.DiscoveryItemEntry._ID
+                    + "INTEGER PRIMARY KEY,"
+                    + DiscoveryItemContract.DiscoveryItemEntry.COLUMN_MODEL_ID
+                    + " TEXT," + DiscoveryItemContract.DiscoveryItemEntry.COLUMN_SCAN_BYTE
+                    + " BLOB)";
+    private static final String SQL_DELETE_DISCOVERY_ITEM_DB =
+            "DROP TABLE IF EXISTS " + DiscoveryItemContract.DiscoveryItemEntry.TABLE_NAME;
+    private static final String SQL_CREATE_FAST_PAIR_ITEM_DB =
+            "CREATE TABLE IF NOT EXISTS "
+                    + StoredFastPairItemContract.StoredFastPairItemEntry.TABLE_NAME
+                    + " (" + StoredFastPairItemContract.StoredFastPairItemEntry._ID
+                    + "INTEGER PRIMARY KEY,"
+                    + StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_MAC_ADDRESS
+                    + " TEXT,"
+                    + StoredFastPairItemContract.StoredFastPairItemEntry.COLUMN_ACCOUNT_KEY
+                    + " TEXT,"
+                    + StoredFastPairItemContract
+                    .StoredFastPairItemEntry.COLUMN_STORED_FAST_PAIR_BYTE
+                    + " BLOB)";
+    private static final String SQL_DELETE_FAST_PAIR_ITEM_DB =
+            "DROP TABLE IF EXISTS " + StoredFastPairItemContract.StoredFastPairItemEntry.TABLE_NAME;
+
+    public FastPairDbHelper(Context context) {
+        super(context, DATABASE_NAME, null, DATABASE_VERSION);
+    }
+
+    @Override
+    public void onCreate(SQLiteDatabase db) {
+        db.execSQL(SQL_CREATE_DISCOVERY_ITEM_DB);
+        db.execSQL(SQL_CREATE_FAST_PAIR_ITEM_DB);
+    }
+
+    @Override
+    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        // Since the outdated data has no value so just remove the data.
+        db.execSQL(SQL_DELETE_DISCOVERY_ITEM_DB);
+        db.execSQL(SQL_DELETE_FAST_PAIR_ITEM_DB);
+        onCreate(db);
+    }
+
+    @Override
+    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        super.onDowngrade(db, oldVersion, newVersion);
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/fastpair/cache/StoredFastPairItemContract.java b/nearby/service/java/com/android/server/nearby/fastpair/cache/StoredFastPairItemContract.java
new file mode 100644
index 0000000..9980565
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/fastpair/cache/StoredFastPairItemContract.java
@@ -0,0 +1,37 @@
+/*
+ * 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.cache;
+
+import android.provider.BaseColumns;
+
+/**
+ * Defines fast pair item database schema.
+ */
+public class StoredFastPairItemContract {
+    private StoredFastPairItemContract() {}
+
+    /**
+     * StoredFastPairItem entry related info.
+     */
+    public static class StoredFastPairItemEntry implements BaseColumns {
+        public static final String TABLE_NAME = "STORED_FAST_PAIR_ITEM";
+        public static final String COLUMN_MAC_ADDRESS = "MAC_ADDRESS";
+        public static final String COLUMN_ACCOUNT_KEY = "ACCOUNT_KEY";
+
+        public static final String COLUMN_STORED_FAST_PAIR_BYTE = "STORED_FAST_PAIR_BYTE";
+    }
+}
diff --git a/nearby/tests/cts/fastpair/Android.bp b/nearby/tests/cts/fastpair/Android.bp
index 599fe5c..6dc1af8 100644
--- a/nearby/tests/cts/fastpair/Android.bp
+++ b/nearby/tests/cts/fastpair/Android.bp
@@ -37,6 +37,7 @@
         "general-tests",
         "mts-tethering",
     ],
+    certificate: "platform",
     platform_apis: true,
     sdk_version: "module_current",
     min_sdk_version: "30",
diff --git a/nearby/tests/cts/fastpair/AndroidManifest.xml b/nearby/tests/cts/fastpair/AndroidManifest.xml
index 463a926..e77d70f 100644
--- a/nearby/tests/cts/fastpair/AndroidManifest.xml
+++ b/nearby/tests/cts/fastpair/AndroidManifest.xml
@@ -18,6 +18,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.nearby.cts">
   <uses-sdk android:minSdkVersion="32" android:targetSdkVersion="32" />
+  <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+
   <application>
     <uses-library android:name="android.test.runner"/>
   </application>
diff --git a/nearby/tests/cts/fastpair/AndroidTest.xml b/nearby/tests/cts/fastpair/AndroidTest.xml
index 59cc779..360bbf3 100644
--- a/nearby/tests/cts/fastpair/AndroidTest.xml
+++ b/nearby/tests/cts/fastpair/AndroidTest.xml
@@ -16,7 +16,8 @@
 <configuration description="Config for CTS Nearby Fast Pair test cases">
   <option name="test-suite-tag" value="cts" />
   <option name="config-descriptor:metadata" key="component" value="location" />
-  <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+  <!-- Instant cannot access NearbyManager. -->
+  <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
   <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
   <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
   <option name="config-descriptor:metadata" key="parameter" value="all_foldable_states" />
@@ -32,4 +33,4 @@
       class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
     <option name="mainline-module-package-name" value="com.google.android.tethering" />
   </object>
-</configuration>
\ No newline at end of file
+</configuration>
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyFrameworkInitializerTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyFrameworkInitializerTest.java
index 370bfe1..cf43cb1 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyFrameworkInitializerTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyFrameworkInitializerTest.java
@@ -16,12 +16,16 @@
 
 package android.nearby.cts;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
 import android.nearby.NearbyFrameworkInitializer;
 import android.os.Build;
 
 import androidx.annotation.RequiresApi;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -31,15 +35,11 @@
 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
 public class NearbyFrameworkInitializerTest {
 
-//    // TODO(b/215435710) This test cannot pass now because our test cannot access system API.
-//    // run "adb root && adb shell setenforce permissive" and uncomment testServicesRegistered,
-//    // test passes.
-//    @Test
-//    public void testServicesRegistered() {
-//        Context ctx = InstrumentationRegistry.getInstrumentation().getContext();
-//        assertNotNull( "NearbyManager not registered",
-//                ctx.getSystemService(Context.NEARBY_SERVICE));
-//    }
+    @Test
+    public void testServicesRegistered() {
+        Context ctx = InstrumentationRegistry.getInstrumentation().getContext();
+        assertThat(ctx.getSystemService(Context.NEARBY_SERVICE)).isNotNull();
+    }
 
     // registerServiceWrappers can only be called during initialization and should throw otherwise
     @Test(expected = IllegalStateException.class)
diff --git a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
index 938eab2..eedcce1 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
@@ -18,6 +18,8 @@
 
 import static android.nearby.PresenceCredential.IDENTITY_TYPE_PRIVATE;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Mockito.when;
 import static org.mockito.MockitoAnnotations.initMocks;
 
@@ -36,6 +38,7 @@
 import androidx.annotation.RequiresApi;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -63,7 +66,10 @@
     @Before
     public void setUp() {
         initMocks(this);
+
         when(mContext.getSystemService(Context.NEARBY_SERVICE)).thenReturn(mNearbyManager);
+        when(mContext.getContentResolver()).thenReturn(
+                InstrumentationRegistry.getInstrumentation().getContext().getContentResolver());
     }
 
     @Test
@@ -110,4 +116,11 @@
                 callback);
         mNearbyManager.stopBroadcast(callback);
     }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testSettingsEnable() {
+        NearbyManager.setFastPairScanEnabled(mContext, false);
+        assertThat(NearbyManager.getFastPairScanEnabled(mContext, true)).isFalse();
+    }
 }
diff --git a/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/FastPairSettingsProviderTest.kt b/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/FastPairSettingsProviderTest.kt
index 761aaf6..af3f75f 100644
--- a/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/FastPairSettingsProviderTest.kt
+++ b/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/FastPairSettingsProviderTest.kt
@@ -38,10 +38,10 @@
         val appContext = ApplicationProvider.getApplicationContext<Context>()
         val contentResolver = appContext.contentResolver
 
-        Settings.Secure.putInt(contentResolver, Settings.Secure.FAST_PAIR_SCAN_ENABLED, flag.value)
+        Settings.Secure.putInt(contentResolver, "fast_pair_scan_enabled", flag.value)
 
         val actualValue = Settings.Secure.getInt(
-                contentResolver, Settings.Secure.FAST_PAIR_SCAN_ENABLED, /* default value */ -1)
+                contentResolver, "fast_pair_scan_enabled", /* default value */ -1)
         assertThat(actualValue).isEqualTo(flag.value)
     }
 
diff --git a/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/FastPairSettingsProviderTest.kt b/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/FastPairSettingsProviderTest.kt
index b73e737..c549073 100644
--- a/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/FastPairSettingsProviderTest.kt
+++ b/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/FastPairSettingsProviderTest.kt
@@ -40,14 +40,14 @@
     @Test
     fun testSettingsFastPairScan_fromUnTrustedApp_readsSucceed() {
         Settings.Secure.getInt(contentResolver,
-                Settings.Secure.FAST_PAIR_SCAN_ENABLED, /* default value */ -1)
+                "fast_pair_scan_enabled", /* default value */ -1)
     }
 
     /** Verify untrusted app can't write Fast Pair scan enabled setting. */
     @Test
     fun testSettingsFastPairScan_fromUnTrustedApp_writesFailed() {
         assertFailsWith<SecurityException> {
-            Settings.Secure.putInt(contentResolver, Settings.Secure.FAST_PAIR_SCAN_ENABLED, 1)
+            Settings.Secure.putInt(contentResolver, "fast_pair_scan_enabled", 1)
         }
     }
 }
diff --git a/nearby/tests/multidevices/clients/AndroidManifest.xml b/nearby/tests/multidevices/clients/AndroidManifest.xml
index b6dc5e8..9641756 100644
--- a/nearby/tests/multidevices/clients/AndroidManifest.xml
+++ b/nearby/tests/multidevices/clients/AndroidManifest.xml
@@ -44,7 +44,7 @@
              Must stay in the same process as Nearby Discovery Service.
         -->
         <service
-            android:name=".fastpair.seeker.FastPairTestDataProviderService"
+            android:name=".fastpair.seeker.dataprovider.FastPairTestDataProviderService"
             android:exported="true"
             android:permission="android.permission.WRITE_SECURE_SETTINGS"
             android:visibleToInstantApps="true">
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/BluetoothA2dpSinkService.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/BluetoothA2dpSinkService.kt
deleted file mode 100644
index f65dfab..0000000
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/BluetoothA2dpSinkService.kt
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * 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 android.nearby.multidevices.fastpair.provider
-
-import android.Manifest.permission.BLUETOOTH_CONNECT
-import android.Manifest.permission.BLUETOOTH_SCAN
-import android.annotation.TargetApi
-import android.bluetooth.BluetoothClass
-import android.bluetooth.BluetoothDevice
-import android.bluetooth.BluetoothManager
-import android.bluetooth.BluetoothProfile
-import android.content.Context
-import android.os.Build
-import androidx.annotation.RequiresPermission
-import androidx.annotation.VisibleForTesting
-
-/** Maintains an environment for Bluetooth A2DP sink profile. */
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
-class BluetoothA2dpSinkService(private val context: Context) {
-    private val bluetoothAdapter =
-        (context.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager)?.adapter!!
-    private var a2dpSinkProxy: BluetoothProfile? = null
-
-    /**
-     * Starts the Bluetooth A2DP sink profile proxy.
-     *
-     * @param onServiceConnected the callback for the first time onServiceConnected.
-     */
-    fun start(onServiceConnected: () -> Unit) {
-        // Get the A2DP proxy before continuing with initialization.
-        bluetoothAdapter.getProfileProxy(
-            context,
-            object : BluetoothProfile.ServiceListener {
-                override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
-                    // When Bluetooth turns off and then on again, this is called again. But we only care
-                    // the first time. There doesn't seem to be a way to unregister our listener.
-                    if (a2dpSinkProxy == null) {
-                        a2dpSinkProxy = proxy
-                        onServiceConnected()
-                    }
-                }
-
-                override fun onServiceDisconnected(profile: Int) {}
-            },
-            BLUETOOTH_PROFILE_A2DP_SINK
-        )
-    }
-
-    /**
-     * Checks the device is paired or not.
-     *
-     * @param remoteBluetoothDevice the device to check is paired or not.
-     */
-    @RequiresPermission(BLUETOOTH_CONNECT)
-    fun isPaired(remoteBluetoothDevice: BluetoothDevice?): Boolean =
-        bluetoothAdapter.bondedDevices.contains(remoteBluetoothDevice)
-
-    /**
-     * Gets the current Bluetooth scan mode of the local Bluetooth adapter.
-     */
-    @RequiresPermission(BLUETOOTH_SCAN)
-    fun getScanMode(): Int = bluetoothAdapter.scanMode
-
-    /**
-     * Clears the bounded devices.
-     *
-     * @param removeBondDevice the callback to remove bounded devices.
-     */
-    @RequiresPermission(BLUETOOTH_CONNECT)
-    fun clearBoundedDevices(removeBondDevice: (BluetoothDevice) -> Unit) {
-        for (device in bluetoothAdapter.bondedDevices) {
-            if (device.bluetoothClass.majorDeviceClass == BluetoothClass.Device.Major.PHONE) {
-                removeBondDevice(device)
-            }
-        }
-    }
-
-    /**
-     * Clears the connected but unbounded devices.
-     *
-     * Sometimes a device will still be connected even though it's not bonded. :( Clear that too.
-     *
-     * @param disconnectDevice the callback to clear connected but unbounded devices.
-     */
-    fun clearConnectedUnboundedDevices(
-        disconnectDevice: (BluetoothProfile, BluetoothDevice) -> Unit,
-    ) {
-        for (device in a2dpSinkProxy!!.connectedDevices) {
-            disconnectDevice(a2dpSinkProxy!!, device)
-        }
-    }
-
-    companion object {
-        /** Hidden SystemApi field in [android.bluetooth.BluetoothProfile] interface. */
-        @VisibleForTesting
-        const val BLUETOOTH_PROFILE_A2DP_SINK = 11
-    }
-}
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/BluetoothStateChangeReceiver.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/BluetoothStateChangeReceiver.kt
deleted file mode 100644
index f9c77f7..0000000
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/BluetoothStateChangeReceiver.kt
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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 android.nearby.multidevices.fastpair.provider
-
-import android.Manifest.permission.BLUETOOTH
-import android.Manifest.permission.BLUETOOTH_CONNECT
-import android.bluetooth.BluetoothAdapter
-import android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE
-import android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE
-import android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE
-import android.bluetooth.BluetoothDevice
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import android.util.Log
-import androidx.annotation.RequiresPermission
-import androidx.annotation.VisibleForTesting
-
-/** Processes the state of the local Bluetooth adapter. */
-class BluetoothStateChangeReceiver(private val context: Context) : BroadcastReceiver() {
-    @VisibleForTesting
-    var listener: EventListener? = null
-
-    /**
-     * Registers this Bluetooth state change receiver.
-     *
-     * @param listener the listener for Bluetooth state events.
-     */
-    fun register(listener: EventListener) {
-        this.listener = listener
-        val bondStateFilter =
-            IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED).apply {
-                addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)
-                addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED)
-            }
-        context.registerReceiver(
-            this,
-            bondStateFilter,
-            /* broadcastPermission= */ null,
-            /* scheduler= */ null
-        )
-    }
-
-    /** Unregisters this Bluetooth state change receiver. */
-    fun unregister() {
-        context.unregisterReceiver(this)
-        this.listener = null
-    }
-
-    /**
-     * Callback method for receiving Intent broadcast for Bluetooth state.
-     *
-     * See [android.content.BroadcastReceiver#onReceive].
-     *
-     * @param context the Context in which the receiver is running.
-     * @param intent the Intent being received.
-     */
-    @RequiresPermission(allOf = [BLUETOOTH, BLUETOOTH_CONNECT])
-    override fun onReceive(context: Context, intent: Intent) {
-        Log.i(TAG, "BluetoothStateChangeReceiver received intent, action=${intent.action}")
-
-        when (intent.action) {
-            BluetoothAdapter.ACTION_SCAN_MODE_CHANGED -> {
-                val scanMode =
-                    intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, SCAN_MODE_NONE)
-                val scanModeStr = scanModeToString(scanMode)
-                Log.i(TAG, "ACTION_SCAN_MODE_CHANGED, the new scanMode: $scanModeStr")
-                listener?.onScanModeChange(scanModeStr)
-            }
-            BluetoothDevice.ACTION_BOND_STATE_CHANGED -> {
-                val remoteDevice = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
-                val remoteDeviceString =
-                    if (remoteDevice != null) "${remoteDevice.name}-${remoteDevice.address}" else "none"
-                var boundStateString = "ERROR"
-                when (intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.ERROR)) {
-                    BluetoothDevice.BOND_NONE -> {
-                        boundStateString = "BOND_NONE"
-                    }
-                    BluetoothDevice.BOND_BONDING -> {
-                        boundStateString = "BOND_BONDING"
-                    }
-                    BluetoothDevice.BOND_BONDED -> {
-                        boundStateString = "BOND_BONDED"
-                    }
-                }
-                Log.i(
-                    TAG,
-                    "The bound state of the remote device ($remoteDeviceString) change to $boundStateString."
-                )
-            }
-            else -> {}
-        }
-    }
-
-    private fun scanModeToString(scanMode: Int): String {
-        return when (scanMode) {
-            SCAN_MODE_CONNECTABLE_DISCOVERABLE -> "DISCOVERABLE"
-            SCAN_MODE_CONNECTABLE -> "CONNECTABLE"
-            SCAN_MODE_NONE -> "NOT CONNECTABLE"
-            else -> "UNKNOWN($scanMode)"
-        }
-    }
-
-    /** Interface for listening the events from Bluetooth adapter. */
-    interface EventListener {
-        /**
-         * Reports the current scan mode of the local Adapter.
-         *
-         * @param mode the current scan mode in string.
-         */
-        fun onScanModeChange(mode: String)
-    }
-
-    companion object {
-        private const val TAG = "BluetoothStateReceiver"
-    }
-}
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/FastPairProviderSimulatorController.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/FastPairProviderSimulatorController.kt
new file mode 100644
index 0000000..0eacb71
--- /dev/null
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/FastPairProviderSimulatorController.kt
@@ -0,0 +1,96 @@
+/*
+ * 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 android.nearby.multidevices.fastpair.provider
+
+import android.bluetooth.le.AdvertiseSettings
+import android.content.Context
+import android.nearby.fastpair.provider.FastPairSimulator
+import android.nearby.fastpair.provider.bluetooth.BluetoothController
+import com.google.android.mobly.snippet.util.Log
+import com.google.common.io.BaseEncoding
+
+class FastPairProviderSimulatorController(
+    private val context: Context,
+    private val modelId: String,
+    private val antiSpoofingKeyString: String,
+    private val eventListener: EventListener,
+) : BluetoothController.EventListener {
+    private lateinit var bluetoothController: BluetoothController
+    lateinit var simulator: FastPairSimulator
+
+    fun startProviderSimulator() {
+        bluetoothController = BluetoothController(context, this)
+        bluetoothController.registerBluetoothStateReceiver()
+        bluetoothController.enableBluetooth()
+        bluetoothController.connectA2DPSinkProfile()
+    }
+
+    fun stopProviderSimulator() {
+        simulator.destroy()
+        bluetoothController.unregisterBluetoothStateReceiver()
+    }
+
+    override fun onA2DPSinkProfileConnected() {
+        createFastPairSimulator()
+    }
+
+    override fun onBondStateChanged(bondState: Int) {
+    }
+
+    override fun onConnectionStateChanged(connectionState: Int) {
+    }
+
+    override fun onScanModeChange(mode: Int) {
+        eventListener.onScanModeChange(FastPairSimulator.scanModeToString(mode))
+    }
+
+    private fun createFastPairSimulator() {
+        val antiSpoofingKey = BaseEncoding.base64().decode(antiSpoofingKeyString)
+        simulator = FastPairSimulator(context, FastPairSimulator.Options.builder(modelId)
+            .setAdvertisingModelId(modelId)
+            .setBluetoothAddress(null)
+            .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
+            .setAdvertisingChangedCallback {
+                val isAdvertising = simulator.isAdvertising
+                Log.i("FastPairSimulator callback(), isAdvertising: $isAdvertising")
+                eventListener.onAdvertisingChange(isAdvertising)
+            }
+            .setAntiSpoofingPrivateKey(antiSpoofingKey)
+            .setUseRandomSaltForAccountKeyRotation(false)
+            .setDataOnlyConnection(false)
+            .setShowsPasskeyConfirmation(false)
+            .setRemoveAllDevicesDuringPairing(true)
+            .build())
+    }
+
+    /** Interface for listening the events from Fast Pair Provider Simulator. */
+    interface EventListener {
+        /**
+         * Reports the current scan mode of the local Adapter.
+         *
+         * @param mode the current scan mode in string.
+         */
+        fun onScanModeChange(mode: String)
+
+        /**
+         * Indicates the advertising state of the Fast Pair provider simulator has changed.
+         *
+         * @param isAdvertising the current advertising state, true if advertising otherwise false.
+         */
+        fun onAdvertisingChange(isAdvertising: Boolean)
+    }
+}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/FastPairProviderSimulatorSnippet.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/FastPairProviderSimulatorSnippet.kt
index a03085c..356823e 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/FastPairProviderSimulatorSnippet.kt
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/FastPairProviderSimulatorSnippet.kt
@@ -17,25 +17,18 @@
 package android.nearby.multidevices.fastpair.provider
 
 import android.annotation.TargetApi
-import android.bluetooth.le.AdvertiseSettings
 import android.content.Context
 import android.os.Build
 import androidx.test.platform.app.InstrumentationRegistry
-import android.nearby.fastpair.provider.FastPairSimulator
 import com.google.android.mobly.snippet.Snippet
 import com.google.android.mobly.snippet.rpc.AsyncRpc
 import com.google.android.mobly.snippet.rpc.Rpc
-import com.google.android.mobly.snippet.util.Log
-import com.google.common.io.BaseEncoding.base64
 
 /** Expose Mobly RPC methods for Python side to simulate fast pair provider role. */
 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
 class FastPairProviderSimulatorSnippet : Snippet {
     private val context: Context = InstrumentationRegistry.getInstrumentation().context
-    private val bluetoothA2dpSinkService = BluetoothA2dpSinkService(context)
-    private val bluetoothStateChangeReceiver = BluetoothStateChangeReceiver(context)
-    private lateinit var fastPairSimulator: FastPairSimulator
-    private lateinit var providerStatusEvents: ProviderStatusEvents
+    private lateinit var fastPairProviderSimulatorController: FastPairProviderSimulatorController
 
     /**
      * Starts the Fast Pair provider simulator.
@@ -47,47 +40,21 @@
      */
     @AsyncRpc(description = "Starts FP provider simulator for seekers to discover.")
     fun startProviderSimulator(callbackId: String, modelId: String, antiSpoofingKeyString: String) {
-        providerStatusEvents = ProviderStatusEvents(callbackId)
-        bluetoothStateChangeReceiver.register(listener = providerStatusEvents)
-        bluetoothA2dpSinkService.start { createFastPairSimulator(modelId, antiSpoofingKeyString) }
+        fastPairProviderSimulatorController = FastPairProviderSimulatorController(
+            context, modelId, antiSpoofingKeyString, ProviderStatusEvents(callbackId)
+        )
+        fastPairProviderSimulatorController.startProviderSimulator()
     }
 
     /** Stops the Fast Pair provider simulator. */
     @Rpc(description = "Stops FP provider simulator.")
     fun stopProviderSimulator() {
-        fastPairSimulator.destroy()
-        bluetoothStateChangeReceiver.unregister()
+        fastPairProviderSimulatorController.stopProviderSimulator()
     }
 
     /** Gets BLE mac address of the Fast Pair provider simulator. */
     @Rpc(description = "Gets BLE mac address of the Fast Pair provider simulator.")
     fun getBluetoothLeAddress(): String {
-        return fastPairSimulator.bleAddress!!
-    }
-
-    private fun createFastPairSimulator(modelId: String, antiSpoofingKeyString: String) {
-        val antiSpoofingKey = base64().decode(antiSpoofingKeyString)
-        fastPairSimulator =
-            FastPairSimulator(
-                context,
-                FastPairSimulator.Options.builder(
-                    modelId
-                )
-                    .setAdvertisingModelId(modelId)
-                    .setBluetoothAddress(null)
-                    .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
-                    .setCallback {
-                        val isAdvertising = fastPairSimulator.isAdvertising
-                        Log.i("FastPairSimulator callback(), isAdvertising: $isAdvertising")
-                        providerStatusEvents.onAdvertisingChange(isAdvertising)
-                    }
-                    .setAntiSpoofingPrivateKey(antiSpoofingKey)
-                    .setUseRandomSaltForAccountKeyRotation(false)
-                    .setDataOnlyConnection(false)
-                    .setIsMemoryTest(false)
-                    .setShowsPasskeyConfirmation(false)
-                    .setRemoveAllDevicesDuringPairing(true)
-                    .build()
-            )
+        return fastPairProviderSimulatorController.simulator.bleAddress!!
     }
 }
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/ProviderStatusEvents.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/ProviderStatusEvents.kt
index eef4b8b..20983d3 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/ProviderStatusEvents.kt
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/provider/ProviderStatusEvents.kt
@@ -20,7 +20,7 @@
 
 /** The Mobly snippet events to report to the Python side. */
 class ProviderStatusEvents(private val callbackId: String) :
-  BluetoothStateChangeReceiver.EventListener {
+  FastPairProviderSimulatorController.EventListener {
 
   /**
    * Indicates the Bluetooth scan mode of the Fast Pair provider simulator has changed.
@@ -36,7 +36,7 @@
    *
    * @param isAdvertising the current advertising state, true if advertising otherwise false.
    */
-  fun onAdvertisingChange(isAdvertising: Boolean) {
+  override fun onAdvertisingChange(isAdvertising: Boolean) {
     postSnippetEvent(callbackId, "onAdvertisingChange") {
       putBoolean("isAdvertising", isAdvertising)
     }
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/CompanionAppUtils.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/CompanionAppUtils.kt
deleted file mode 100644
index 7ed4372..0000000
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/CompanionAppUtils.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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 android.nearby.multidevices.fastpair.seeker
-
-fun generateCompanionAppLaunchIntentUri(
-        companionAppPackageName: String? = null,
-        activityName: String? = null,
-        action: String? = null
-): String {
-    if (companionAppPackageName.isNullOrEmpty() || activityName.isNullOrEmpty()) {
-        return ""
-    }
-    var intentUriString = "intent:#Intent;"
-    if (!action.isNullOrEmpty()) {
-        intentUriString += "action=$action;"
-    }
-    intentUriString += "package=$companionAppPackageName;"
-    intentUriString += "component=$companionAppPackageName/$activityName;"
-    return "${intentUriString}end"
-}
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairSeekerSnippet.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairSeekerSnippet.kt
index 6c28b97..8f5d120 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairSeekerSnippet.kt
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairSeekerSnippet.kt
@@ -21,6 +21,7 @@
 import android.nearby.NearbyManager
 import android.nearby.ScanCallback
 import android.nearby.ScanRequest
+import android.nearby.multidevices.fastpair.seeker.dataprovider.FastPairTestDataCache
 import androidx.test.core.app.ApplicationProvider
 import com.google.android.mobly.snippet.Snippet
 import com.google.android.mobly.snippet.rpc.AsyncRpc
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairTestDataCache.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/dataprovider/FastPairTestDataCache.kt
similarity index 98%
rename from nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairTestDataCache.kt
rename to nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/dataprovider/FastPairTestDataCache.kt
index 28523c1..ad2b40d 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairTestDataCache.kt
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/dataprovider/FastPairTestDataCache.kt
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package android.nearby.multidevices.fastpair.seeker
+package android.nearby.multidevices.fastpair.seeker.dataprovider
 
 import android.nearby.FastPairAccountKeyDeviceMetadata
 import android.nearby.FastPairAntispoofkeyDeviceMetadata
 import android.nearby.FastPairDeviceMetadata
 import android.nearby.FastPairDiscoveryItem
-import com.google.common.io.BaseEncoding.base64
+import com.google.common.io.BaseEncoding
 import com.google.gson.Gson
 import com.google.gson.annotations.SerializedName
 
@@ -297,6 +297,6 @@
         }
     }
 
-    private fun String.base64Decode(): ByteArray = base64().decode(this)
-    private fun ByteArray.base64Encode(): String = base64().encode(this)
+    private fun String.base64Decode(): ByteArray = BaseEncoding.base64().decode(this)
+    private fun ByteArray.base64Encode(): String = BaseEncoding.base64().encode(this)
 }
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairTestDataProvider.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/dataprovider/FastPairTestDataProvider.kt
similarity index 98%
rename from nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairTestDataProvider.kt
rename to nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/dataprovider/FastPairTestDataProvider.kt
index a1f0369..9ff513a 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairTestDataProvider.kt
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/dataprovider/FastPairTestDataProvider.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.nearby.multidevices.fastpair.seeker
+package android.nearby.multidevices.fastpair.seeker.dataprovider
 
 import android.accounts.Account
 import android.nearby.FastPairDataProviderBase
diff --git a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairTestDataProviderService.kt b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/dataprovider/FastPairTestDataProviderService.kt
similarity index 95%
rename from nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairTestDataProviderService.kt
rename to nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/dataprovider/FastPairTestDataProviderService.kt
index 5a1c832..3c0c84c 100644
--- a/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/FastPairTestDataProviderService.kt
+++ b/nearby/tests/multidevices/clients/src/android/nearby/multidevices/fastpair/seeker/dataprovider/FastPairTestDataProviderService.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.nearby.multidevices.fastpair.seeker
+package android.nearby.multidevices.fastpair.seeker.dataprovider
 
 import android.app.Service
 import android.content.Intent
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/AppLogger.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/AppLogger.java
deleted file mode 100644
index befc64b..0000000
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/AppLogger.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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 android.nearby.fastpair.provider.simulator.app;
-
-import android.util.Log;
-
-import com.google.errorprone.annotations.FormatMethod;
-
-/** Sends log to logcat with TAG. */
-public class AppLogger {
-    private static final String TAG = "FastPairSimulator";
-
-    @FormatMethod
-    public static void log(String message, Object... objects) {
-        Log.i(TAG, String.format(message, objects));
-    }
-
-    @FormatMethod
-    public static void warning(String message, Object... objects) {
-        Log.w(TAG, String.format(message, objects));
-    }
-
-    @FormatMethod
-    public static void error(String message, Object... objects) {
-        Log.e(TAG, String.format(message, objects));
-    }
-
-    private AppLogger() {
-    }
-}
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/MainActivity.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/MainActivity.java
index 9252173..97f3bf4 100644
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/MainActivity.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/MainActivity.java
@@ -20,8 +20,6 @@
 import static android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Event.Code.BLUETOOTH_STATE_BOND;
 import static android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Event.Code.BLUETOOTH_STATE_CONNECTION;
 import static android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Event.Code.BLUETOOTH_STATE_SCAN_MODE;
-import static android.nearby.fastpair.provider.simulator.app.AppLogger.log;
-import static android.nearby.fastpair.provider.simulator.app.AppLogger.warning;
 
 import static com.google.common.io.BaseEncoding.base16;
 import static com.google.common.io.BaseEncoding.base64;
@@ -41,6 +39,7 @@
 import android.nearby.fastpair.provider.FastPairSimulator.BatteryValue;
 import android.nearby.fastpair.provider.FastPairSimulator.KeyInputCallback;
 import android.nearby.fastpair.provider.FastPairSimulator.PasskeyEventCallback;
+import android.nearby.fastpair.provider.bluetooth.BluetoothController;
 import android.nearby.fastpair.provider.simulator.SimulatorStreamProtocol.Event;
 import android.nearby.fastpair.provider.simulator.testing.RemoteDevice;
 import android.nearby.fastpair.provider.simulator.testing.RemoteDevicesManager;
@@ -96,6 +95,9 @@
  */
 @SuppressLint("SetTextI18n")
 public class MainActivity extends Activity {
+    public static final String TAG = "FastPairProviderSimulatorApp";
+    private final Logger mLogger = new Logger(TAG);
+
     /** Device has a display and the ability to input Yes/No. */
     private static final int IO_CAPABILITY_IO = 1;
 
@@ -213,7 +215,7 @@
             return;
         }
 
-        log("Send data to output stream: %s", eventBuilder.getCode().getNumber());
+        mLogger.log("Send data to output stream: %s", eventBuilder.getCode().getNumber());
         mRemoteDevicesManager.writeDataToRemoteDevice(
                 mRemoteDeviceId,
                 eventBuilder.build().toByteString(),
@@ -446,7 +448,7 @@
 
     private void setupRemoteDevices() {
         if (Strings.isNullOrEmpty(getIntent().getStringExtra(EXTRA_REMOTE_DEVICE_ID))) {
-            log("Can't get remote device id");
+            mLogger.log("Can't get remote device id");
             return;
         }
         mRemoteDeviceId = getIntent().getStringExtra(EXTRA_REMOTE_DEVICE_ID);
@@ -463,7 +465,7 @@
                                     REMOTE_DEVICE_OUTPUT_STREAM_URI),
                             mInputStreamListener));
         } catch (IOException e) {
-            warning("Failed to create stream IO handler: %s", e);
+            mLogger.log(e, "Failed to create stream IO handler");
         }
     }
 
@@ -503,7 +505,7 @@
     private boolean setModelId(String modelId) {
         String validModelId = getValidModelId(modelId);
         if (TextUtils.isEmpty(validModelId)) {
-            log("Can't do setModelId because inputted modelId is invalid!");
+            mLogger.log("Can't do setModelId because inputted modelId is invalid!");
             return false;
         }
 
@@ -612,7 +614,7 @@
         try {
             Preconditions.checkArgument(base16().decode(bluetoothAddress).length == 6);
         } catch (IllegalArgumentException e) {
-            log("Invalid BLUETOOTH_ADDRESS extra (%s), using default.", bluetoothAddress);
+            mLogger.log("Invalid BLUETOOTH_ADDRESS extra (%s), using default.", bluetoothAddress);
             bluetoothAddress = null;
         }
         final String finalBluetoothAddress = bluetoothAddress;
@@ -669,11 +671,10 @@
                                 mAppLaunchSwitch.isChecked() ? MODEL_ID_APP_LAUNCH : modelId)
                         .setBluetoothAddress(finalBluetoothAddress)
                         .setTxPowerLevel(toTxPowerLevel(txPower))
-                        .setCallback(this::updateStatusView)
+                        .setAdvertisingChangedCallback(this::updateStatusView)
                         .setAntiSpoofingPrivateKey(antiSpoofingKey)
                         .setUseRandomSaltForAccountKeyRotation(useRandomSaltForAccountKeyRotation)
                         .setDataOnlyConnection(device != null && device.getDataOnlyConnection())
-                        .setIsMemoryTest(mInputStreamListener != null)
                         .setShowsPasskeyConfirmation(
                                 device.getDeviceType().equals(DeviceType.ANDROID_AUTO))
                         .setRemoveAllDevicesDuringPairing(mRemoveAllDevicesDuringPairing)
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulator.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulator.java
index 931f2e0..aa7daa6 100644
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulator.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulator.java
@@ -163,6 +163,10 @@
     public static final String TAG = "FastPairSimulator";
     private final Logger mLogger;
 
+    private static final int BECOME_DISCOVERABLE_TIMEOUT_SEC = 3;
+
+    private static final int SCAN_MODE_REFRESH_SEC = 30;
+
     /**
      * Headphones. Generated by
      * http://bluetooth-pentest.narod.ru/software/bluetooth_class_of_device-service_generator.html
@@ -170,18 +174,16 @@
     private static final Value CLASS_OF_DEVICE =
             new Value(base16().decode("200418"), ByteOrder.BIG_ENDIAN);
 
-    private static final byte[] SUPPORTED_SERVICES_LTV =
-            new Ltv(
-                    TransportDiscoveryService.SERVICE_UUIDS_16_BIT_LIST_TYPE,
-                    toBytes(ByteOrder.LITTLE_ENDIAN, A2DP_SINK_SERVICE_UUID))
-                    .getBytes();
+    private static final byte[] SUPPORTED_SERVICES_LTV = new Ltv(
+            TransportDiscoveryService.SERVICE_UUIDS_16_BIT_LIST_TYPE,
+            toBytes(ByteOrder.LITTLE_ENDIAN, A2DP_SINK_SERVICE_UUID)
+    ).getBytes();
     private static final byte[] TDS_CONTROL_POINT_RESPONSE_PARAMETER =
             Bytes.concat(new byte[]{BLUETOOTH_SIG_ORGANIZATION_ID}, SUPPORTED_SERVICES_LTV);
 
     private static final String SIMULATOR_FAKE_BLE_ADDRESS = "11:22:33:44:55:66";
 
     private static final long ADVERTISING_REFRESH_DELAY_1_MIN = TimeUnit.MINUTES.toMillis(1);
-    private static final long ADVERTISING_REFRESH_DELAY_5_MINS = TimeUnit.MINUTES.toMillis(5);
 
     /** The user will be prompted to accept or deny the incoming pairing request */
     public static final int PAIRING_VARIANT_CONSENT = 3;
@@ -250,140 +252,139 @@
     private final ScheduledExecutorService mExecutor =
             Executors.newSingleThreadScheduledExecutor(); // exempt
     private final BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
-    private final BroadcastReceiver mBroadcastReceiver =
-            new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    if (mShouldFailPairing) {
-                        mLogger.log("Pairing disabled by test app switch");
-                        return;
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (mShouldFailPairing) {
+                mLogger.log("Pairing disabled by test app switch");
+                return;
+            }
+            if (mIsDestroyed) {
+                // Sometimes this receiver does not successfully unregister in destroy()
+                // which causes events to occur after the simulator is stopped, so ignore
+                // those events.
+                mLogger.log("Intent received after simulator destroyed, ignoring");
+                return;
+            }
+            BluetoothDevice device = intent.getParcelableExtra(
+                    BluetoothDevice.EXTRA_DEVICE);
+            switch (intent.getAction()) {
+                case BluetoothAdapter.ACTION_SCAN_MODE_CHANGED:
+                    if (isDiscoverable()) {
+                        mIsDiscoverableLatch.countDown();
                     }
-                    if (mIsDestroyed) {
-                        // Sometimes this receiver does not successfully unregister in destroy()
-                        // which causes events to occur after the simulator is stopped, so ignore
-                        // those events.
-                        mLogger.log("Intent received after simulator destroyed, ignoring");
-                        return;
+                    break;
+                case BluetoothDevice.ACTION_PAIRING_REQUEST:
+                    int variant = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
+                            ERROR);
+                    int key = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, ERROR);
+                    mLogger.log(
+                            "Pairing request, variant=%d, key=%s", variant,
+                            key == ERROR ? "(none)" : key);
+
+                    // Prevent Bluetooth Settings from getting the pairing request.
+                    abortBroadcast();
+
+                    mPairingDevice = device;
+                    if (mSecret == null) {
+                        // We haven't done the handshake over GATT to agree on the shared
+                        // secret. For now, just accept anyway (so we can still simulate
+                        // old 1.0 model IDs).
+                        mLogger.log("No handshake, auto-accepting anyway.");
+                        setPasskeyConfirmation(true);
+                    } else if (variant
+                            == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION) {
+                        // Store the passkey. And check it, since there's a race (see
+                        // method for why). Usually this check is a no-op and we'll get
+                        // the passkey later over GATT.
+                        mLocalPasskey = key;
+                        checkPasskey();
+                    } else if (variant == PAIRING_VARIANT_DISPLAY_PASSKEY) {
+                        if (mPasskeyEventCallback != null) {
+                            mPasskeyEventCallback.onPasskeyRequested(
+                                    FastPairSimulator.this::enterPassKey);
+                        } else {
+                            mLogger.log("passkeyEventCallback is not set!");
+                            enterPassKey(key);
+                        }
+                    } else if (variant == PAIRING_VARIANT_CONSENT) {
+                        setPasskeyConfirmation(true);
+
+                    } else if (variant == BluetoothDevice.PAIRING_VARIANT_PIN) {
+                        if (mPasskeyEventCallback != null) {
+                            mPasskeyEventCallback.onPasskeyRequested(
+                                    (int pin) -> {
+                                        byte[] newPin = convertPinToBytes(
+                                                String.format(Locale.ENGLISH, "%d", pin));
+                                        mPairingDevice.setPin(newPin);
+                                    });
+                        }
+                    } else {
+                        // Reject the pairing request if it's not using the Numeric
+                        // Comparison (aka Passkey Confirmation) method.
+                        setPasskeyConfirmation(false);
                     }
-                    BluetoothDevice device = intent.getParcelableExtra(
-                            BluetoothDevice.EXTRA_DEVICE);
-                    switch (intent.getAction()) {
-                        case BluetoothAdapter.ACTION_SCAN_MODE_CHANGED:
-                            if (isDiscoverable()) {
-                                mIsDiscoverableLatch.countDown();
+                    break;
+                case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
+                    int bondState =
+                            intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+                                    BluetoothDevice.BOND_NONE);
+                    mLogger.log("Bond state to %s changed to %d", device, bondState);
+                    switch (bondState) {
+                        case BluetoothDevice.BOND_BONDING:
+                            // If we've started bonding, we shouldn't be advertising.
+                            mAdvertiser.stopAdvertising();
+                            // Not discoverable anymore, but still connectable.
+                            setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
+                            break;
+                        case BluetoothDevice.BOND_BONDED:
+                            // Once bonded, advertise the account keys.
+                            mAdvertiser.startAdvertising(accountKeysServiceData());
+                            setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
+
+                            // If it is subsequent pair, we need to add paired device here.
+                            if (mIsSubsequentPair
+                                    && mSecret != null
+                                    && mSecret.length == AES_BLOCK_LENGTH) {
+                                addAccountKey(mSecret, mPairingDevice);
                             }
                             break;
-                        case BluetoothDevice.ACTION_PAIRING_REQUEST:
-                            int variant = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
-                                    ERROR);
-                            int key = intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, ERROR);
-                            mLogger.log(
-                                    "Pairing request, variant=%d, key=%s", variant,
-                                    key == ERROR ? "(none)" : key);
-
-                            // Prevent Bluetooth Settings from getting the pairing request.
-                            abortBroadcast();
-
-                            mPairingDevice = device;
-                            if (mSecret == null) {
-                                // We haven't done the handshake over GATT to agree on the shared
-                                // secret. For now, just accept anyway (so we can still simulate
-                                // old 1.0 model IDs).
-                                mLogger.log("No handshake, auto-accepting anyway.");
-                                setPasskeyConfirmation(true);
-                            } else if (variant
-                                    == BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION) {
-                                // Store the passkey. And check it, since there's a race (see
-                                // method for why). Usually this check is a no-op and we'll get
-                                // the passkey later over GATT.
-                                mLocalPasskey = key;
-                                checkPasskey();
-                            } else if (variant == PAIRING_VARIANT_DISPLAY_PASSKEY) {
-                                if (mPasskeyEventCallback != null) {
-                                    mPasskeyEventCallback.onPasskeyRequested(
-                                            FastPairSimulator.this::enterPassKey);
-                                } else {
-                                    mLogger.log("passkeyEventCallback is not set!");
-                                    enterPassKey(key);
-                                }
-                            } else if (variant == PAIRING_VARIANT_CONSENT) {
-                                setPasskeyConfirmation(true);
-
-                            } else if (variant == BluetoothDevice.PAIRING_VARIANT_PIN) {
-                                if (mPasskeyEventCallback != null) {
-                                    mPasskeyEventCallback.onPasskeyRequested(
-                                            (int pin) -> {
-                                                byte[] newPin = convertPinToBytes(
-                                                        String.format(Locale.ENGLISH, "%d", pin));
-                                                mPairingDevice.setPin(newPin);
-                                            });
-                                }
-                            } else {
-                                // Reject the pairing request if it's not using the Numeric
-                                // Comparison (aka Passkey Confirmation) method.
-                                setPasskeyConfirmation(false);
-                            }
-                            break;
-                        case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
-                            int bondState =
-                                    intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
-                                            BluetoothDevice.BOND_NONE);
-                            mLogger.log("Bond state to %s changed to %d", device, bondState);
-                            switch (bondState) {
-                                case BluetoothDevice.BOND_BONDING:
-                                    // If we've started bonding, we shouldn't be advertising.
-                                    mAdvertiser.stopAdvertising();
-                                    // Not discoverable anymore, but still connectable.
-                                    setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
-                                    break;
-                                case BluetoothDevice.BOND_BONDED:
-                                    // Once bonded, advertise the account keys.
-                                    mAdvertiser.startAdvertising(accountKeysServiceData());
-                                    setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
-
-                                    // If it is subsequent pair, we need to add paired device here.
-                                    if (mIsSubsequentPair
-                                            && mSecret != null
-                                            && mSecret.length == AES_BLOCK_LENGTH) {
-                                        addAccountKey(mSecret, mPairingDevice);
-                                    }
-                                    break;
-                                case BluetoothDevice.BOND_NONE:
-                                    // If the bonding process fails, we should be advertising again.
-                                    mAdvertiser.startAdvertising(getServiceData());
-                                    break;
-                                default:
-                                    break;
-                            }
-                            break;
-                        case BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED:
-                            mLogger.log(
-                                    "Connection state to %s changed to %d",
-                                    device,
-                                    intent.getIntExtra(
-                                            BluetoothAdapter.EXTRA_CONNECTION_STATE,
-                                            BluetoothAdapter.STATE_DISCONNECTED));
-                            break;
-                        case BluetoothAdapter.ACTION_STATE_CHANGED:
-                            int state = intent.getIntExtra(EXTRA_STATE, -1);
-                            mLogger.log("Bluetooth adapter state=%s", state);
-                            switch (state) {
-                                case STATE_ON:
-                                    startRfcommServer();
-                                    break;
-                                case STATE_OFF:
-                                    stopRfcommServer();
-                                    break;
-                                default: // fall out
-                            }
+                        case BluetoothDevice.BOND_NONE:
+                            // If the bonding process fails, we should be advertising again.
+                            mAdvertiser.startAdvertising(getServiceData());
                             break;
                         default:
-                            mLogger.log(new IllegalArgumentException(intent.toString()),
-                                    "Received unexpected intent");
                             break;
                     }
-                }
-            };
+                    break;
+                case BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED:
+                    mLogger.log(
+                            "Connection state to %s changed to %d",
+                            device,
+                            intent.getIntExtra(
+                                    BluetoothAdapter.EXTRA_CONNECTION_STATE,
+                                    BluetoothAdapter.STATE_DISCONNECTED));
+                    break;
+                case BluetoothAdapter.ACTION_STATE_CHANGED:
+                    int state = intent.getIntExtra(EXTRA_STATE, -1);
+                    mLogger.log("Bluetooth adapter state=%s", state);
+                    switch (state) {
+                        case STATE_ON:
+                            startRfcommServer();
+                            break;
+                        case STATE_OFF:
+                            stopRfcommServer();
+                            break;
+                        default: // fall out
+                    }
+                    break;
+                default:
+                    mLogger.log(new IllegalArgumentException(intent.toString()),
+                            "Received unexpected intent");
+                    break;
+            }
+        }
+    };
 
     @Nullable
     private byte[] convertPinToBytes(@Nullable String pin) {
@@ -508,7 +509,6 @@
     @Nullable
     private CountDownLatch mWriteNameCountDown;
     private final RfcommServer mRfcommServer = new RfcommServer();
-    private final boolean mDataOnlyConnection;
     private boolean mSupportDynamicBufferSize = false;
     private NotifiableGattServlet mBeaconActionsServlet;
     private final FastPairSimulatorDatabase mFastPairSimulatorDatabase;
@@ -570,8 +570,8 @@
         this.mUseLogFullEvent = useLogFullEvent;
     }
 
-    /** An optional way to get status updates. */
-    public interface Callback {
+    /** An optional way to get advertising status updates. */
+    public interface AdvertisingChangedCallback {
         /** Called when we change our BLE advertisement. */
         void onAdvertisingChanged();
     }
@@ -618,7 +618,7 @@
 
         private final boolean mEnableNameCharacteristic;
 
-        private final Callback mCallback;
+        private final AdvertisingChangedCallback mAdvertisingChangedCallback;
 
         private final boolean mIncludeTransportDataDescriptor;
 
@@ -627,8 +627,6 @@
 
         private final boolean mUseRandomSaltForAccountKeyRotation;
 
-        private final boolean mIsMemoryTest;
-
         private final boolean mBecomeDiscoverable;
 
         private final boolean mShowsPasskeyConfirmation;
@@ -648,11 +646,10 @@
                 boolean dataOnlyConnection,
                 int txPowerLevel,
                 boolean enableNameCharacteristic,
-                Callback callback,
+                AdvertisingChangedCallback advertisingChangedCallback,
                 boolean includeTransportDataDescriptor,
                 @Nullable byte[] antiSpoofingPrivateKey,
                 boolean useRandomSaltForAccountKeyRotation,
-                boolean isMemoryTest,
                 boolean becomeDiscoverable,
                 boolean showsPasskeyConfirmation,
                 boolean enableBeaconActionsCharacteristic,
@@ -665,11 +662,10 @@
             this.mDataOnlyConnection = dataOnlyConnection;
             this.mTxPowerLevel = txPowerLevel;
             this.mEnableNameCharacteristic = enableNameCharacteristic;
-            this.mCallback = callback;
+            this.mAdvertisingChangedCallback = advertisingChangedCallback;
             this.mIncludeTransportDataDescriptor = includeTransportDataDescriptor;
             this.mAntiSpoofingPrivateKey = antiSpoofingPrivateKey;
             this.mUseRandomSaltForAccountKeyRotation = useRandomSaltForAccountKeyRotation;
-            this.mIsMemoryTest = isMemoryTest;
             this.mBecomeDiscoverable = becomeDiscoverable;
             this.mShowsPasskeyConfirmation = showsPasskeyConfirmation;
             this.mEnableBeaconActionsCharacteristic = enableBeaconActionsCharacteristic;
@@ -708,8 +704,8 @@
             return mEnableNameCharacteristic;
         }
 
-        public Callback getCallback() {
-            return mCallback;
+        public AdvertisingChangedCallback getAdvertisingChangedCallback() {
+            return mAdvertisingChangedCallback;
         }
 
         public boolean getIncludeTransportDataDescriptor() {
@@ -725,10 +721,6 @@
             return mUseRandomSaltForAccountKeyRotation;
         }
 
-        public boolean getIsMemoryTest() {
-            return mIsMemoryTest;
-        }
-
         public boolean getBecomeDiscoverable() {
             return mBecomeDiscoverable;
         }
@@ -766,13 +758,12 @@
                     .setModelId(Ascii.toUpperCase(modelId))
                     .setAdvertisingModelId(Ascii.toUpperCase(modelId))
                     .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
-                    .setCallback(() -> {
+                    .setAdvertisingChangedCallback(() -> {
                     })
                     .setIncludeTransportDataDescriptor(true)
                     .setUseRandomSaltForAccountKeyRotation(false)
                     .setEnableNameCharacteristic(true)
                     .setDataOnlyConnection(false)
-                    .setIsMemoryTest(false)
                     .setBecomeDiscoverable(true)
                     .setShowsPasskeyConfirmation(false)
                     .setEnableBeaconActionsCharacteristic(true)
@@ -799,7 +790,7 @@
 
             private boolean mEnableNameCharacteristic;
 
-            private Callback mCallback;
+            private AdvertisingChangedCallback mAdvertisingChangedCallback;
 
             private boolean mIncludeTransportDataDescriptor;
 
@@ -808,8 +799,6 @@
 
             private boolean mUseRandomSaltForAccountKeyRotation;
 
-            private boolean mIsMemoryTest;
-
             private boolean mBecomeDiscoverable;
 
             private boolean mShowsPasskeyConfirmation;
@@ -832,12 +821,11 @@
                 this.mDataOnlyConnection = option.mDataOnlyConnection;
                 this.mTxPowerLevel = option.mTxPowerLevel;
                 this.mEnableNameCharacteristic = option.mEnableNameCharacteristic;
-                this.mCallback = option.mCallback;
+                this.mAdvertisingChangedCallback = option.mAdvertisingChangedCallback;
                 this.mIncludeTransportDataDescriptor = option.mIncludeTransportDataDescriptor;
                 this.mAntiSpoofingPrivateKey = option.mAntiSpoofingPrivateKey;
                 this.mUseRandomSaltForAccountKeyRotation =
                         option.mUseRandomSaltForAccountKeyRotation;
-                this.mIsMemoryTest = option.mIsMemoryTest;
                 this.mBecomeDiscoverable = option.mBecomeDiscoverable;
                 this.mShowsPasskeyConfirmation = option.mShowsPasskeyConfirmation;
                 this.mEnableBeaconActionsCharacteristic = option.mEnableBeaconActionsCharacteristic;
@@ -874,9 +862,10 @@
                 return this;
             }
 
-            /** @see Callback */
-            public Builder setCallback(Callback callback) {
-                this.mCallback = callback;
+            /** @see AdvertisingChangedCallback */
+            public Builder setAdvertisingChangedCallback(
+                    AdvertisingChangedCallback advertisingChangedCallback) {
+                this.mAdvertisingChangedCallback = advertisingChangedCallback;
                 return this;
             }
 
@@ -913,11 +902,6 @@
                 return this;
             }
 
-            public Builder setIsMemoryTest(boolean isMemoryTest) {
-                this.mIsMemoryTest = isMemoryTest;
-                return this;
-            }
-
             public Builder setBecomeDiscoverable(boolean becomeDiscoverable) {
                 this.mBecomeDiscoverable = becomeDiscoverable;
                 return this;
@@ -963,11 +947,10 @@
                         mDataOnlyConnection,
                         mTxPowerLevel,
                         mEnableNameCharacteristic,
-                        mCallback,
+                        mAdvertisingChangedCallback,
                         mIncludeTransportDataDescriptor,
                         mAntiSpoofingPrivateKey,
                         mUseRandomSaltForAccountKeyRotation,
-                        mIsMemoryTest,
                         mBecomeDiscoverable,
                         mShowsPasskeyConfirmation,
                         mEnableBeaconActionsCharacteristic,
@@ -1001,7 +984,6 @@
         this.mBluetoothAddress =
                 new Value(BluetoothAddress.decode(bluetoothAddress), ByteOrder.BIG_ENDIAN);
         this.mBleAddress = options.getBleAddress();
-        this.mDataOnlyConnection = options.getDataOnlyConnection();
         this.mAdvertiser = new OreoFastPairAdvertiser(this);
 
         mFastPairSimulatorDatabase = new FastPairSimulatorDatabase(context);
@@ -1011,7 +993,7 @@
                 "Provider default device name is %s",
                 deviceName != null ? new String(deviceName, StandardCharsets.UTF_8) : null);
 
-        if (mDataOnlyConnection) {
+        if (mOptions.getDataOnlyConnection()) {
             // To get BLE address, we need to start advertising first, and then
             // {@code#setBleAddress} will be called with BLE address.
             mAdvertiser.startAdvertising(modelIdServiceData(/* forAdvertising= */ true));
@@ -1055,19 +1037,11 @@
      */
     @SuppressWarnings("FutureReturnValueIgnored")
     private void scheduleAdvertisingRefresh() {
-        mExecutor.scheduleAtFixedRate(
-                () -> {
-                    if (mIsAdvertising) {
-                        mAdvertiser.startAdvertising(getServiceData());
-                    }
-                },
-                mOptions.getIsMemoryTest()
-                        ? ADVERTISING_REFRESH_DELAY_5_MINS
-                        : ADVERTISING_REFRESH_DELAY_1_MIN,
-                mOptions.getIsMemoryTest()
-                        ? ADVERTISING_REFRESH_DELAY_5_MINS
-                        : ADVERTISING_REFRESH_DELAY_1_MIN,
-                TimeUnit.MILLISECONDS);
+        mExecutor.scheduleAtFixedRate(() -> {
+            if (mIsAdvertising) {
+                mAdvertiser.startAdvertising(getServiceData());
+            }
+        }, ADVERTISING_REFRESH_DELAY_1_MIN, ADVERTISING_REFRESH_DELAY_1_MIN, TimeUnit.MILLISECONDS);
     }
 
     public void destroy() {
@@ -1103,7 +1077,7 @@
     public void setIsAdvertising(boolean isAdvertising) {
         if (this.mIsAdvertising != isAdvertising) {
             this.mIsAdvertising = isAdvertising;
-            mOptions.getCallback().onAdvertisingChanged();
+            mOptions.getAdvertisingChangedCallback().onAdvertisingChanged();
         }
     }
 
@@ -1113,7 +1087,7 @@
 
     public void setBleAddress(String bleAddress) {
         this.mBleAddress = bleAddress;
-        if (mDataOnlyConnection) {
+        if (mOptions.getDataOnlyConnection()) {
             mBluetoothAddress = new Value(BluetoothAddress.decode(bleAddress),
                     ByteOrder.BIG_ENDIAN);
             start(bleAddress);
@@ -2086,7 +2060,7 @@
         if (isDiscoverable()) {
             mIsDiscoverableLatch.countDown();
         }
-        if (mIsDiscoverableLatch.await(3, TimeUnit.SECONDS)) {
+        if (mIsDiscoverableLatch.await(BECOME_DISCOVERABLE_TIMEOUT_SEC, TimeUnit.SECONDS)) {
             mLogger.log("Successfully became switched discoverable mode %s", discoverable);
         } else {
             throw new TimeoutException();
@@ -2105,10 +2079,8 @@
 
             if (scanMode == SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
                 mRevertDiscoverableFuture =
-                        mExecutor.schedule(
-                                () -> setScanMode(SCAN_MODE_CONNECTABLE),
-                                mOptions.getIsMemoryTest() ? 300 : 30,
-                                TimeUnit.SECONDS);
+                        mExecutor.schedule(() -> setScanMode(SCAN_MODE_CONNECTABLE),
+                                SCAN_MODE_REFRESH_SEC, TimeUnit.SECONDS);
             }
         } catch (Exception e) {
             mLogger.log(e, "Error setting scan mode to %d", scanMode);
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulatorDatabase.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulatorDatabase.java
index 254ec51..cbe39ff 100644
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulatorDatabase.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/FastPairSimulatorDatabase.java
@@ -37,7 +37,7 @@
 public class FastPairSimulatorDatabase {
 
     private static final String SHARED_PREF_NAME =
-            "android.nearby.multidevices.fastpair.provider.fastpairsimulator";
+            "android.nearby.fastpair.provider.fastpairsimulator";
     private static final String KEY_DEVICE_NAME = "DEVICE_NAME";
     private static final String KEY_ACCOUNT_KEYS = "ACCOUNT_KEYS";
     private static final int MAX_NUMBER_OF_ACCOUNT_KEYS = 8;
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/OreoFastPairAdvertiser.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/OreoFastPairAdvertiser.java
index dd664ea..bb77c11 100644
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/OreoFastPairAdvertiser.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/OreoFastPairAdvertiser.java
@@ -54,53 +54,54 @@
     public OreoFastPairAdvertiser(FastPairSimulator simulator) {
         this.mSimulator = simulator;
         this.mAdvertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
-        this.mAdvertisingSetCallback =
-                new AdvertisingSetCallback() {
-                    @Override
-                    public void onAdvertisingSetStarted(
-                            AdvertisingSet set, int txPower, int status) {
-                        if (status == AdvertisingSetCallback.ADVERTISE_SUCCESS) {
-                            mLogger.log("Advertising succeeded, advertising at %s dBm", txPower);
-                            simulator.setIsAdvertising(true);
-                            mAdvertisingSet = set;
+        this.mAdvertisingSetCallback = new AdvertisingSetCallback() {
 
-                            try {
-                                // Requires custom Android build, see callback below.
-                                Reflect.on(set).withMethod("getOwnAddress").invoke();
-                            } catch (ReflectionException e) {
-                                mLogger.log(e, "Error calling getOwnAddress for AdvertisingSet");
-                            }
-                        } else {
-                            mLogger.log(
-                                    new IllegalStateException(),
-                                    "Advertising failed, error code=%d", status);
-                        }
-                    }
+            @Override
+            public void onAdvertisingSetStarted(
+                    AdvertisingSet set, int txPower, int status) {
+                if (status == AdvertisingSetCallback.ADVERTISE_SUCCESS) {
+                    mLogger.log("Advertising succeeded, advertising at %s dBm", txPower);
+                    simulator.setIsAdvertising(true);
+                    mAdvertisingSet = set;
 
-                    @Override
-                    public void onAdvertisingDataSet(AdvertisingSet set, int status) {
-                        if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
-                            mLogger.log(
-                                    new IllegalStateException(),
-                                    "Updating advertisement failed, error code=%d",
-                                    status);
-                            stopAdvertising();
-                        }
+                    try {
+                        // Requires custom Android build, see callback below.
+                        Reflect.on(set).withMethod("getOwnAddress").invoke();
+                    } catch (ReflectionException e) {
+                        mLogger.log(e, "Error calling getOwnAddress for AdvertisingSet");
                     }
+                } else {
+                    mLogger.log(
+                            new IllegalStateException(),
+                            "Advertising failed, error code=%d", status);
+                }
+            }
 
-                    // Called via reflection with AdvertisingSet.getOwnAddress().
-                    public void onOwnAddressRead(
-                            AdvertisingSet set, int addressType, String address) {
-                        if (!address.equals(simulator.getBleAddress())) {
-                            mLogger.log(
-                                    "Read own BLE address=%s at %s",
-                                    address,
-                                    new SimpleDateFormat("HH:mm:ss:SSS", Locale.US)
-                                            .format(Calendar.getInstance().getTime()));
-                            simulator.setBleAddress(address);
-                        }
-                    }
-                };
+            @Override
+            public void onAdvertisingDataSet(AdvertisingSet set, int status) {
+                if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
+                    mLogger.log(
+                            new IllegalStateException(),
+                            "Updating advertisement failed, error code=%d",
+                            status);
+                    stopAdvertising();
+                }
+            }
+
+            // Called via reflection with AdvertisingSet.getOwnAddress().
+            public void onOwnAddressRead(
+                    AdvertisingSet set, int addressType, String address) {
+                if (!address.equals(simulator.getBleAddress())) {
+                    mLogger.log(
+                            "Read own BLE address=%s at %s",
+                            address,
+                            new SimpleDateFormat("HH:mm:ss:SSS", Locale.US)
+                                    .format(Calendar.getInstance().getTime()));
+                    // Implicitly start the advertising once BLE address callback arrived.
+                    simulator.setBleAddress(address);
+                }
+            }
+        };
     }
 
     @Override
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/BluetoothController.kt b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothController.kt
similarity index 92%
rename from nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/BluetoothController.kt
rename to nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothController.kt
index ed04eae..6a3e59e 100644
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/app/BluetoothController.kt
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/bluetooth/BluetoothController.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.nearby.fastpair.provider.simulator.app
+package android.nearby.fastpair.provider.bluetooth
 
 import android.bluetooth.BluetoothAdapter
 import android.bluetooth.BluetoothDevice
@@ -25,9 +25,9 @@
 import android.content.Intent
 import android.content.IntentFilter
 import android.nearby.fastpair.provider.FastPairSimulator
-import android.nearby.fastpair.provider.simulator.app.AppLogger.*
-import android.nearby.fastpair.provider.simulator.testing.Reflect
-import android.nearby.fastpair.provider.simulator.testing.ReflectionException
+import android.nearby.fastpair.provider.utils.Logger
+import android.nearby.fastpair.provider.utils.Reflect
+import android.nearby.fastpair.provider.utils.ReflectionException
 import android.os.SystemClock
 import android.provider.Settings
 
@@ -36,6 +36,7 @@
     private val context: Context,
     private val listener: EventListener,
 ) : BroadcastReceiver() {
+    private val mLogger = Logger(TAG)
     private val bluetoothAdapter: BluetoothAdapter =
         (context.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager)?.adapter!!
     private var remoteDevice: BluetoothDevice? = null
@@ -71,14 +72,14 @@
                 .withMethod("setIoCapability", Int::class.javaPrimitiveType)[
                     ioCapabilityClassic]
         } catch (e: ReflectionException) {
-            warning("Error setIoCapability to %s: %s", ioCapabilityClassic, e)
+            mLogger.log(e, "Error setIoCapability to %s: %s", ioCapabilityClassic)
         }
         try {
             Reflect.on(bluetoothAdapter)
                 .withMethod("setLeIoCapability", Int::class.javaPrimitiveType)[
                     ioCapabilityBLE]
         } catch (e: ReflectionException) {
-            warning("Error setLeIoCapability to %s: %s", ioCapabilityBLE, e)
+            mLogger.log(e, "Error setLeIoCapability to %s: %s", ioCapabilityBLE)
         }
 
         // Toggling airplane mode on/off to restart Bluetooth stack and reset the BLE.
@@ -90,7 +91,10 @@
                 TURN_AIRPLANE_MODE_ON
             )
         } catch (expectedOnNonCustomAndroid: SecurityException) {
-            warning("Requires custom Android to toggle airplane mode")
+            mLogger.log(
+                expectedOnNonCustomAndroid,
+                "Requires custom Android to toggle airplane mode"
+            )
             // Fall back to turn off Bluetooth.
             bluetoothAdapter.disable()
         }
@@ -102,7 +106,10 @@
                 TURN_AIRPLANE_MODE_OFF
             )
         } catch (expectedOnNonCustomAndroid: SecurityException) {
-            error("SecurityException while toggled airplane mode.")
+            mLogger.log(
+                expectedOnNonCustomAndroid,
+                "SecurityException while toggled airplane mode."
+            )
         } finally {
             // Double confirm that Bluetooth is turned on.
             bluetoothAdapter.enable()
@@ -183,8 +190,6 @@
      * @param intent the Intent being received.
      */
     override fun onReceive(context: Context, intent: Intent) {
-        log("BluetoothController received intent, action=%s", intent.action)
-
         when (intent.action) {
             BluetoothDevice.ACTION_BOND_STATE_CHANGED -> {
                 // After a device starts bonding, we only pay attention to intents about that device.
@@ -198,7 +203,7 @@
                         BluetoothDevice.BOND_NONE -> null
                         else -> remoteDevice
                     }
-                log(
+                mLogger.log(
                     "ACTION_BOND_STATE_CHANGED, the bound state of the remote device (%s) change to %s.",
                     remoteDevice?.remoteDeviceToString(),
                     bondState.bondStateToString()
@@ -211,7 +216,7 @@
                         BluetoothAdapter.EXTRA_CONNECTION_STATE,
                         BluetoothAdapter.STATE_DISCONNECTED
                     )
-                log(
+                mLogger.log(
                     "ACTION_CONNECTION_STATE_CHANGED, the new connectionState: %s",
                     remoteDeviceConnectionState
                 )
@@ -223,7 +228,7 @@
                         BluetoothAdapter.EXTRA_SCAN_MODE,
                         BluetoothAdapter.SCAN_MODE_NONE
                     )
-                log(
+                mLogger.log(
                     "ACTION_SCAN_MODE_CHANGED, the new scanMode: %s",
                     FastPairSimulator.scanModeToString(scanMode)
                 )
@@ -277,10 +282,12 @@
     }
 
     companion object {
+        private const val TAG = "BluetoothController"
+
         /** Hidden SystemApi field in [BluetoothProfile] interface. */
         private const val BLUETOOTH_PROFILE_A2DP_SINK = 11
 
         private const val TURN_AIRPLANE_MODE_OFF = 0
         private const val TURN_AIRPLANE_MODE_ON = 1
     }
-}
+}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/Reflect.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/utils/Reflect.java
similarity index 98%
rename from nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/Reflect.java
rename to nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/utils/Reflect.java
index 16fbc71..5ae5310 100644
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/Reflect.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/utils/Reflect.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.nearby.fastpair.provider.simulator.testing;
+package android.nearby.fastpair.provider.utils;
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
diff --git a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/ReflectionException.java b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/utils/ReflectionException.java
similarity index 93%
rename from nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/ReflectionException.java
rename to nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/utils/ReflectionException.java
index 2a65f39..959fd11 100644
--- a/nearby/tests/multidevices/clients/test_support/fastpair_provider/simulator_app/src/android/nearby/fastpair/provider/simulator/testing/ReflectionException.java
+++ b/nearby/tests/multidevices/clients/test_support/fastpair_provider/src/android/nearby/fastpair/provider/utils/ReflectionException.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.nearby.fastpair.provider.simulator.testing;
+package android.nearby.fastpair.provider.utils;
 
 /**
  * An exception thrown during a reflection operation. Like ReflectiveOperationException, except
diff --git a/nearby/tests/multidevices/clients/tests/Android.bp b/nearby/tests/multidevices/clients/tests/Android.bp
deleted file mode 100644
index a29a298..0000000
--- a/nearby/tests/multidevices/clients/tests/Android.bp
+++ /dev/null
@@ -1,39 +0,0 @@
-// 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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-// Run the tests: atest --host NearbyMultiDevicesClientsRoboTest
-android_robolectric_test {
-    name: "NearbyMultiDevicesClientsRoboTest",
-    srcs: ["src/**/*.kt"],
-    instrumentation_for: "NearbyMultiDevicesClientsSnippets",
-    java_resources: ["robolectric.properties"],
-
-    static_libs: [
-        "NearbyMultiDevicesClientsLib",
-        "androidx.test.ext.junit",
-        "androidx.test.rules",
-        "junit",
-        "mobly-snippet-lib",
-        "platform-test-annotations",
-        "truth-prebuilt",
-    ],
-    test_options: {
-        // timeout in seconds.
-        timeout: 36000,
-    },
-}
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/tests/AndroidManifest.xml b/nearby/tests/multidevices/clients/tests/AndroidManifest.xml
deleted file mode 100644
index c8e17e8..0000000
--- a/nearby/tests/multidevices/clients/tests/AndroidManifest.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ 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.
-  -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.nearby.multidevices"/>
diff --git a/nearby/tests/multidevices/clients/tests/robolectric.properties b/nearby/tests/multidevices/clients/tests/robolectric.properties
deleted file mode 100644
index 2ea03bb..0000000
--- a/nearby/tests/multidevices/clients/tests/robolectric.properties
+++ /dev/null
@@ -1,16 +0,0 @@
-#
-# Copyright (C) 2022 Google Inc.
-#
-# 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.
-#
-sdk=NEWEST_SDK
\ No newline at end of file
diff --git a/nearby/tests/multidevices/clients/tests/src/com/android/nearby/multidevices/common/Mockotlin.kt b/nearby/tests/multidevices/clients/tests/src/com/android/nearby/multidevices/common/Mockotlin.kt
deleted file mode 100644
index 8e70d9f..0000000
--- a/nearby/tests/multidevices/clients/tests/src/com/android/nearby/multidevices/common/Mockotlin.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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.nearby.multidevices.common
-
-import org.mockito.ArgumentCaptor
-import org.mockito.Mockito
-import org.mockito.MockitoAnnotations
-
-/**
- * Helper methods to wrap common Mockito functions that don't do quite what you would expect in
- * Kotlin. The returned null values need to be recast to their original type in Kotlin otherwise it
- * breaks.
- */
-object Mockotlin {
-
-    /**
-     * Delegates to [Mockito.any].
-     * @return null as T
-     */
-    fun <T> any() = Mockito.any<T>() as T
-
-    /**
-     * Delegates to [Mockito.eq].
-     * @return null as T
-     */
-    fun <T> eq(match: T) = Mockito.eq(match) as T
-
-    /**
-     * Delegates to [Mockito.isA].
-     * @return null as T
-     */
-    fun <T> isA(match: Class<T>): T = Mockito.isA(match) as T
-
-    /** Delegates to [Mockito.when ], uses the same API as the mockitokotlin2 library. */
-    fun <T> whenever(methodCall: T) = Mockito.`when`(methodCall)!!
-
-    /**
-     * Delegates to [Mockito.any] and calls it with Class<T>.
-     * @return Class<T>
-     */
-    inline fun <reified T> anyClass(): Class<T> {
-        Mockito.any(T::class.java)
-        return T::class.java
-    }
-
-    /**
-     * Delegates to [Mockito.anyListOf] and calls it with Class<T>.
-     * @return List<T>
-     */
-    fun <T> anyListOf(): List<T> = Mockito.anyList<T>()
-
-    /**
-     * Delegates to [Mockito.mock].
-     * @return T
-     */
-    inline fun <reified T> mock() = Mockito.mock(T::class.java)!!
-
-    /** This is the same as calling `MockitoAnnotations.initMocks(this)` */
-    fun Any.initMocks() {
-        MockitoAnnotations.initMocks(this)
-    }
-
-    /**
-     * Returns ArgumentCaptor.capture() as nullable type to avoid java.lang.IllegalStateException
-     * when null is returned.
-     */
-    fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
-}
diff --git a/nearby/tests/multidevices/clients/tests/src/com/android/nearby/multidevices/fastpair/provider/BluetoothStateChangeReceiverTest.kt b/nearby/tests/multidevices/clients/tests/src/com/android/nearby/multidevices/fastpair/provider/BluetoothStateChangeReceiverTest.kt
deleted file mode 100644
index f23ccbb..0000000
--- a/nearby/tests/multidevices/clients/tests/src/com/android/nearby/multidevices/fastpair/provider/BluetoothStateChangeReceiverTest.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.nearby.multidevices.fastpair.provider
-
-import android.Manifest
-import android.bluetooth.BluetoothAdapter
-import android.content.Context
-import android.content.Intent
-import android.nearby.multidevices.fastpair.provider.BluetoothStateChangeReceiver
-import androidx.annotation.RequiresPermission
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import com.google.android.mobly.snippet.util.Log
-import com.google.common.truth.Truth.assertThat
-import com.android.nearby.multidevices.common.Mockotlin.mock
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mockito.verify
-
-/** Robolectric tests for [BluetoothStateChangeReceiver]. */
-@RunWith(AndroidJUnit4::class)
-class BluetoothStateChangeReceiverTest {
-  private lateinit var bluetoothStateChangeReceiver: BluetoothStateChangeReceiver
-  private lateinit var context: Context
-  private val mockListener = mock<BluetoothStateChangeReceiver.EventListener>()
-
-  @Before
-  fun setUp() {
-    context = InstrumentationRegistry.getInstrumentation().context
-    bluetoothStateChangeReceiver = BluetoothStateChangeReceiver(context)
-    Log.apkLogTag = "BluetoothStateChangeReceiverTest"
-  }
-
-  @Test
-  fun testRegister_setsListener() {
-    bluetoothStateChangeReceiver.register(mockListener)
-
-    assertThat(bluetoothStateChangeReceiver.listener).isNotNull()
-  }
-
-  @Test
-  fun testUnregister_clearListener() {
-    bluetoothStateChangeReceiver.register(mockListener)
-
-    bluetoothStateChangeReceiver.unregister()
-
-    assertThat(bluetoothStateChangeReceiver.listener).isNull()
-  }
-
-  @Test
-  @RequiresPermission(Manifest.permission.BLUETOOTH)
-  fun testOnReceive_actionScanModeChanged_reportsOnScanModeChange() {
-    val intent =
-      Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED)
-        .putExtra(
-          BluetoothAdapter.EXTRA_SCAN_MODE,
-          BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE
-        )
-    bluetoothStateChangeReceiver.register(mockListener)
-
-    bluetoothStateChangeReceiver.onReceive(context, intent)
-
-    verify(mockListener).onScanModeChange("DISCOVERABLE")
-  }
-}
diff --git a/nearby/tests/multidevices/clients/tests/src/com/android/nearby/multidevices/fastpair/seeker/CompanionAppUtilsTest.kt b/nearby/tests/multidevices/clients/tests/src/com/android/nearby/multidevices/fastpair/seeker/CompanionAppUtilsTest.kt
deleted file mode 100644
index 94c0952..0000000
--- a/nearby/tests/multidevices/clients/tests/src/com/android/nearby/multidevices/fastpair/seeker/CompanionAppUtilsTest.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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.nearby.multidevices.fastpair.seeker
-
-import android.nearby.multidevices.fastpair.seeker.generateCompanionAppLaunchIntentUri
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.robolectric.RobolectricTestRunner
-
-/** Robolectric tests for CompanionAppUtils.kt. */
-@RunWith(RobolectricTestRunner::class)
-class CompanionAppUtilsTest {
-
-    @Test
-    fun testGenerateCompanionAppLaunchIntentUri_defaultNullPackage_returnsEmptyString() {
-        assertThat(generateCompanionAppLaunchIntentUri()).isEmpty()
-    }
-
-    @Test
-    fun testGenerateCompanionAppLaunchIntentUri_emptyPackageName_returnsEmptyString() {
-        assertThat(generateCompanionAppLaunchIntentUri(companionAppPackageName = "")).isEmpty()
-    }
-
-    @Test
-    fun testGenerateCompanionAppLaunchIntentUri_emptyActivityName_returnsEmptyString() {
-        val uriString = generateCompanionAppLaunchIntentUri(
-                companionAppPackageName = COMPANION_APP_PACKAGE_TEST_CONSTANT, activityName = "")
-
-        assertThat(uriString).isEmpty()
-    }
-
-    @Test
-    fun testGenerateCompanionAppLaunchIntentUri_emptyAction_returnsNoActionUriString() {
-        val uriString = generateCompanionAppLaunchIntentUri(
-                companionAppPackageName = COMPANION_APP_PACKAGE_TEST_CONSTANT,
-                activityName = COMPANION_APP_ACTIVITY_TEST_CONSTANT,
-                action = "")
-
-        assertThat(uriString).doesNotContain("action=")
-        assertThat(uriString).contains("package=$COMPANION_APP_PACKAGE_TEST_CONSTANT")
-        assertThat(uriString).contains(COMPANION_APP_ACTIVITY_TEST_CONSTANT)
-    }
-
-    @Test
-    fun testGenerateCompanionAppLaunchIntentUri_nonNullArgs_returnsUriString() {
-        val uriString = generateCompanionAppLaunchIntentUri(
-                companionAppPackageName = COMPANION_APP_PACKAGE_TEST_CONSTANT,
-                activityName = COMPANION_APP_ACTIVITY_TEST_CONSTANT,
-                action = COMPANION_APP_ACTION_TEST_CONSTANT)
-
-        assertThat(uriString).isEqualTo("intent:#Intent;" +
-                "action=android.nearby.SHOW_WELCOME;" +
-                "package=android.nearby.companion;" +
-                "component=android.nearby.companion/android.nearby.companion.MainActivity;" +
-                "end")
-    }
-
-    companion object {
-        private const val COMPANION_APP_PACKAGE_TEST_CONSTANT = "android.nearby.companion"
-        private const val COMPANION_APP_ACTIVITY_TEST_CONSTANT =
-                "android.nearby.companion.MainActivity"
-        private const val COMPANION_APP_ACTION_TEST_CONSTANT = "android.nearby.SHOW_WELCOME"
-    }
-}
diff --git a/nearby/tests/multidevices/host/seeker_discover_provider_test.py b/nearby/tests/multidevices/host/seeker_discover_provider_test.py
index f875250..a52ca15 100644
--- a/nearby/tests/multidevices/host/seeker_discover_provider_test.py
+++ b/nearby/tests/multidevices/host/seeker_discover_provider_test.py
@@ -16,8 +16,10 @@
 DEFAULT_MODEL_ID = '00000C'
 # Default public key to simulate as registered headsets.
 DEFAULT_ANTI_SPOOFING_KEY = 'Cbj9eCJrTdDgSYxLkqtfADQi86vIaMvxJsQ298sZYWE='
-# Default time in seconds for events waiting.
-DEFAULT_TIMEOUT_SEC = 60
+# Time in seconds for events waiting.
+BECOME_DISCOVERABLE_TIMEOUT_SEC = 10
+START_ADVERTISING_TIMEOUT_SEC = 5
+SCAN_TIMEOUT_SEC = 30
 
 # Abbreviations for common use type.
 FastPairProviderSimulator = fast_pair_provider_simulator.FastPairProviderSimulator
@@ -47,8 +49,8 @@
         super().setup_test()
         self._provider.start_provider_simulator(DEFAULT_MODEL_ID,
                                                 DEFAULT_ANTI_SPOOFING_KEY)
-        self._provider.wait_for_discoverable_mode(DEFAULT_TIMEOUT_SEC)
-        self._provider.wait_for_advertising_start(DEFAULT_TIMEOUT_SEC)
+        self._provider.wait_for_discoverable_mode(BECOME_DISCOVERABLE_TIMEOUT_SEC)
+        self._provider.wait_for_advertising_start(START_ADVERTISING_TIMEOUT_SEC)
         self._seeker.start_scan()
 
     def teardown_test(self) -> None:
@@ -62,7 +64,7 @@
     def test_seeker_start_scanning_find_provider(self) -> None:
         provider_ble_mac_address = self._provider.get_ble_mac_address()
         self._seeker.wait_and_assert_provider_found(
-            timeout_seconds=DEFAULT_TIMEOUT_SEC,
+            timeout_seconds=SCAN_TIMEOUT_SEC,
             expected_model_id=DEFAULT_MODEL_ID,
             expected_ble_mac_address=provider_ble_mac_address)
 
diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java
index b8233ec..adae97d 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java
@@ -25,6 +25,8 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SdkSuppress;
 
+import com.google.protobuf.ByteString;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
@@ -37,9 +39,17 @@
     private static final String MODEL_ID = "001";
     private static final String MODEL_ID2 = "002";
     private static final String APP_NAME = "APP_NAME";
+    private static final String MAC_ADDRESS = "00:11:22:33";
+    private static final ByteString ACCOUNT_KEY = ByteString.copyFromUtf8("axgs");
+    private static final String MAC_ADDRESS_B = "00:11:22:44";
+    private static final ByteString ACCOUNT_KEY_B = ByteString.copyFromUtf8("axgb");
+
     @Mock
     DiscoveryItem mDiscoveryItem;
-    @Mock DiscoveryItem mDiscoveryItem2;
+    @Mock
+    DiscoveryItem mDiscoveryItem2;
+    @Mock
+    Cache.StoredFastPairItem mStoredFastPairItem;
     Cache.StoredDiscoveryItem mStoredDiscoveryItem = Cache.StoredDiscoveryItem.newBuilder()
             .setTriggerId(MODEL_ID)
             .setAppName(APP_NAME).build();
@@ -97,4 +107,47 @@
         assertThat(fastPairCacheManager.getAllSavedStoreDiscoveryItem()).hasSize(3);
     }
 
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void saveRetrieveInfoStoredFastPairItem() {
+        Context mContext = ApplicationProvider.getApplicationContext();
+        Cache.StoredFastPairItem storedFastPairItem = Cache.StoredFastPairItem.newBuilder()
+                .setMacAddress(MAC_ADDRESS)
+                .setAccountKey(ACCOUNT_KEY)
+                .build();
+
+
+        FastPairCacheManager fastPairCacheManager = new FastPairCacheManager(mContext);
+        fastPairCacheManager.putStoredFastPairItem(storedFastPairItem);
+
+        assertThat(fastPairCacheManager.getStoredFastPairItemFromMacAddress(
+                MAC_ADDRESS).getAccountKey())
+                .isEqualTo(ACCOUNT_KEY);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void checkGetAllFastPairItems() {
+        Context mContext = ApplicationProvider.getApplicationContext();
+        Cache.StoredFastPairItem storedFastPairItem = Cache.StoredFastPairItem.newBuilder()
+                .setMacAddress(MAC_ADDRESS)
+                .setAccountKey(ACCOUNT_KEY)
+                .build();
+        Cache.StoredFastPairItem storedFastPairItemB = Cache.StoredFastPairItem.newBuilder()
+                .setMacAddress(MAC_ADDRESS_B)
+                .setAccountKey(ACCOUNT_KEY_B)
+                .build();
+
+        FastPairCacheManager fastPairCacheManager = new FastPairCacheManager(mContext);
+        fastPairCacheManager.putStoredFastPairItem(storedFastPairItem);
+        fastPairCacheManager.putStoredFastPairItem(storedFastPairItemB);
+
+        assertThat(fastPairCacheManager.getAllSavedStoredFastPairItem().size())
+                .isEqualTo(2);
+
+        fastPairCacheManager.removeStoredFastPairItem(MAC_ADDRESS_B);
+
+        assertThat(fastPairCacheManager.getAllSavedStoredFastPairItem().size())
+                .isEqualTo(1);
+    }
 }