Merge changes from topic "cherrypicker-L22800030000756195:N75200030012855151" into main

* changes:
  [nearby] Add nearby_enable_ble_in_init flag
  Revert "[nearby] Add disable logic"
  [nearby] Add disable logic
  [nearby] Log changes
  [nearby] Sync discoveryTimestamp field name
  [nearby] Enable BLE when init
  [nearby] Add logs to broadcaster
  [nearby] Add discoveryTimestamp field
  [nearby] Fix flakeness in the unit test
  [nearby][clean up] Remove useless legacy code
  [nearby] Fix the user-debug only test
  [nearby] Catches NPE in ChreDiscoveryProvider
  [nearby] Update README to add build env script
diff --git a/nearby/README.md b/nearby/README.md
index 2731b3e..0d26563 100644
--- a/nearby/README.md
+++ b/nearby/README.md
@@ -49,6 +49,7 @@
 ```sh
 Build unbundled module using banchan
 
+$ source build/envsetup.sh
 $ banchan com.google.android.tethering mainline_modules_arm64
 $ m apps_only dist
 $ adb install out/dist/com.google.android.tethering.apex
diff --git a/nearby/framework/java/android/nearby/NearbyManager.java b/nearby/framework/java/android/nearby/NearbyManager.java
index 070a2b6..00f1c38 100644
--- a/nearby/framework/java/android/nearby/NearbyManager.java
+++ b/nearby/framework/java/android/nearby/NearbyManager.java
@@ -284,6 +284,8 @@
      */
     public void queryOffloadCapability(@NonNull @CallbackExecutor Executor executor,
             @NonNull Consumer<OffloadCapability> callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
         try {
             mService.queryOffloadCapability(new OffloadTransport(executor, callback));
         } catch (RemoteException e) {
diff --git a/nearby/service/java/com/android/server/nearby/NearbyConfiguration.java b/nearby/service/java/com/android/server/nearby/NearbyConfiguration.java
index 9b32d69..9ef905d 100644
--- a/nearby/service/java/com/android/server/nearby/NearbyConfiguration.java
+++ b/nearby/service/java/com/android/server/nearby/NearbyConfiguration.java
@@ -54,6 +54,11 @@
     public static final String NEARBY_REFACTOR_DISCOVERY_MANAGER =
             "nearby_refactor_discovery_manager";
 
+    /**
+     * Flag to guard enable BLE during Nearby Service init time.
+     */
+    public static final String NEARBY_ENABLE_BLE_IN_INIT = "nearby_enable_ble_in_init";
+
     private static final boolean IS_USER_BUILD = "user".equals(Build.TYPE);
 
     private final DeviceConfigListener mDeviceConfigListener = new DeviceConfigListener();
@@ -67,6 +72,8 @@
     private boolean mSupportTestApp;
     @GuardedBy("mDeviceConfigLock")
     private boolean mRefactorDiscoveryManager;
+    @GuardedBy("mDeviceConfigLock")
+    private boolean mEnableBleInInit;
 
     public NearbyConfiguration() {
         mDeviceConfigListener.start();
@@ -131,6 +138,15 @@
         }
     }
 
+    /**
+     * @return {@code true} if enableBLE() is called during NearbyService init time.
+     */
+    public boolean enableBleInInit() {
+        synchronized (mDeviceConfigLock) {
+            return mEnableBleInInit;
+        }
+    }
+
     private class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener {
         public void start() {
             DeviceConfig.addOnPropertiesChangedListener(getNamespace(),
@@ -149,6 +165,8 @@
                         NEARBY_SUPPORT_TEST_APP, false /* defaultValue */);
                 mRefactorDiscoveryManager = getDeviceConfigBoolean(
                         NEARBY_REFACTOR_DISCOVERY_MANAGER, false /* defaultValue */);
+                mEnableBleInInit = getDeviceConfigBoolean(
+                        NEARBY_ENABLE_BLE_IN_INIT, true /* defaultValue */);
             }
         }
     }
diff --git a/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManager.java b/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManager.java
index 0c41426..8995232 100644
--- a/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManager.java
+++ b/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManager.java
@@ -21,6 +21,7 @@
 import static com.android.server.nearby.NearbyService.TAG;
 
 import android.annotation.Nullable;
+import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 import android.nearby.DataElement;
 import android.nearby.IScanListener;
@@ -35,6 +36,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.nearby.NearbyConfiguration;
 import com.android.server.nearby.injector.Injector;
 import com.android.server.nearby.managers.registration.DiscoveryRegistration;
 import com.android.server.nearby.provider.AbstractDiscoveryProvider;
@@ -66,6 +68,7 @@
     private final BleDiscoveryProvider mBleDiscoveryProvider;
     private final Injector mInjector;
     private final Executor mExecutor;
+    private final NearbyConfiguration mNearbyConfiguration;
 
     public DiscoveryProviderManager(Context context, Injector injector) {
         Log.v(TAG, "DiscoveryProviderManager: ");
@@ -75,6 +78,7 @@
         mChreDiscoveryProvider = new ChreDiscoveryProvider(mContext,
                 new ChreCommunication(injector, mContext, mExecutor), mExecutor);
         mInjector = injector;
+        mNearbyConfiguration = new NearbyConfiguration();
     }
 
     @VisibleForTesting
@@ -86,6 +90,7 @@
         mInjector = injector;
         mBleDiscoveryProvider = bleDiscoveryProvider;
         mChreDiscoveryProvider = chreDiscoveryProvider;
+        mNearbyConfiguration = new NearbyConfiguration();
     }
 
     private static boolean isChreOnly(Set<ScanFilter> scanFilters) {
@@ -141,6 +146,10 @@
 
     /** Called after boot completed. */
     public void init() {
+        // Register BLE only scan when Bluetooth is turned off
+        if (mNearbyConfiguration.enableBleInInit()) {
+            setBleScanEnabled();
+        }
         if (mInjector.getContextHubManager() != null) {
             mChreDiscoveryProvider.init();
         }
@@ -242,7 +251,7 @@
     @GuardedBy("mMultiplexerLock")
     private void startBleProvider(Set<ScanFilter> scanFilters) {
         if (!mBleDiscoveryProvider.getController().isStarted()) {
-            Log.d(TAG, "DiscoveryProviderManager starts Ble scanning.");
+            Log.d(TAG, "DiscoveryProviderManager starts BLE scanning.");
             mBleDiscoveryProvider.getController().setListener(this);
             mBleDiscoveryProvider.getController().setProviderScanMode(mMerged.getScanMode());
             mBleDiscoveryProvider.getController().setProviderScanFilters(
@@ -313,4 +322,29 @@
     public void onMergedRegistrationsUpdated() {
         invalidateProviderScanMode();
     }
+
+    /**
+     * Registers Nearby service to Ble scan if Bluetooth is off. (Even when Bluetooth is off)
+     * @return {@code true} when Nearby currently can scan through Bluetooth or Ble or successfully
+     * registers Nearby service to Ble scan when Blutooth is off.
+     */
+    public boolean setBleScanEnabled() {
+        BluetoothAdapter adapter = mInjector.getBluetoothAdapter();
+        if (adapter == null) {
+            Log.e(TAG, "BluetoothAdapter is null.");
+            return false;
+        }
+        if (adapter.isEnabled() || adapter.isLeEnabled()) {
+            return true;
+        }
+        if (!adapter.isBleScanAlwaysAvailable()) {
+            Log.v(TAG, "Ble always on scan is disabled.");
+            return false;
+        }
+        if (!adapter.enableBLE()) {
+            Log.e(TAG, "Failed to register Ble scan.");
+            return false;
+        }
+        return true;
+    }
 }
diff --git a/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManagerLegacy.java b/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManagerLegacy.java
index 4b76eba..3ef8fb7 100644
--- a/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManagerLegacy.java
+++ b/nearby/service/java/com/android/server/nearby/managers/DiscoveryProviderManagerLegacy.java
@@ -22,6 +22,7 @@
 
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
+import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 import android.nearby.DataElement;
 import android.nearby.IScanListener;
@@ -38,6 +39,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.nearby.NearbyConfiguration;
 import com.android.server.nearby.injector.Injector;
 import com.android.server.nearby.metrics.NearbyMetrics;
 import com.android.server.nearby.presence.PresenceDiscoveryResult;
@@ -69,6 +71,7 @@
     private final Context mContext;
     private final BleDiscoveryProvider mBleDiscoveryProvider;
     private final Injector mInjector;
+    private final NearbyConfiguration mNearbyConfiguration;
     @ScanRequest.ScanMode
     private int mScanMode;
     @GuardedBy("mLock")
@@ -83,6 +86,7 @@
                         mContext, new ChreCommunication(injector, mContext, executor), executor);
         mScanTypeScanListenerRecordMap = new HashMap<>();
         mInjector = injector;
+        mNearbyConfiguration = new NearbyConfiguration();
         Log.v(TAG, "DiscoveryProviderManagerLegacy: ");
     }
 
@@ -96,6 +100,7 @@
         mBleDiscoveryProvider = bleDiscoveryProvider;
         mChreDiscoveryProvider = chreDiscoveryProvider;
         mScanTypeScanListenerRecordMap = scanTypeScanListenerRecordMap;
+        mNearbyConfiguration = new NearbyConfiguration();
     }
 
     private static boolean isChreOnly(List<ScanFilter> scanFilters) {
@@ -142,18 +147,18 @@
             for (IBinder listenerBinder : mScanTypeScanListenerRecordMap.keySet()) {
                 ScanListenerRecord record = mScanTypeScanListenerRecordMap.get(listenerBinder);
                 if (record == null) {
-                    Log.w(TAG, "DiscoveryProviderManager cannot find the scan record.");
+                    Log.w(TAG, "DiscoveryProviderManagerLegacy cannot find the scan record.");
                     continue;
                 }
                 CallerIdentity callerIdentity = record.getCallerIdentity();
                 if (!DiscoveryPermissions.noteDiscoveryResultDelivery(
                         appOpsManager, callerIdentity)) {
-                    Log.w(TAG, "[DiscoveryProviderManager] scan permission revoked "
+                    Log.w(TAG, "[DiscoveryProviderManagerLegacy] scan permission revoked "
                             + "- not forwarding results");
                     try {
                         record.getScanListener().onError(ScanCallback.ERROR_PERMISSION_DENIED);
                     } catch (RemoteException e) {
-                        Log.w(TAG, "DiscoveryProviderManager failed to report error.", e);
+                        Log.w(TAG, "DiscoveryProviderManagerLegacy failed to report error.", e);
                     }
                     return;
                 }
@@ -180,7 +185,7 @@
                     NearbyMetrics.logScanDeviceDiscovered(
                             record.hashCode(), record.getScanRequest(), nearbyDevice);
                 } catch (RemoteException e) {
-                    Log.w(TAG, "DiscoveryProviderManager failed to report onDiscovered.", e);
+                    Log.w(TAG, "DiscoveryProviderManagerLegacy failed to report onDiscovered.", e);
                 }
             }
         }
@@ -193,18 +198,18 @@
             for (IBinder listenerBinder : mScanTypeScanListenerRecordMap.keySet()) {
                 ScanListenerRecord record = mScanTypeScanListenerRecordMap.get(listenerBinder);
                 if (record == null) {
-                    Log.w(TAG, "DiscoveryProviderManager cannot find the scan record.");
+                    Log.w(TAG, "DiscoveryProviderManagerLegacy cannot find the scan record.");
                     continue;
                 }
                 CallerIdentity callerIdentity = record.getCallerIdentity();
                 if (!DiscoveryPermissions.noteDiscoveryResultDelivery(
                         appOpsManager, callerIdentity)) {
-                    Log.w(TAG, "[DiscoveryProviderManager] scan permission revoked "
+                    Log.w(TAG, "[DiscoveryProviderManagerLegacy] scan permission revoked "
                             + "- not forwarding results");
                     try {
                         record.getScanListener().onError(ScanCallback.ERROR_PERMISSION_DENIED);
                     } catch (RemoteException e) {
-                        Log.w(TAG, "DiscoveryProviderManager failed to report error.", e);
+                        Log.w(TAG, "DiscoveryProviderManagerLegacy failed to report error.", e);
                     }
                     return;
                 }
@@ -212,7 +217,7 @@
                 try {
                     record.getScanListener().onError(errorCode);
                 } catch (RemoteException e) {
-                    Log.w(TAG, "DiscoveryProviderManager failed to report onError.", e);
+                    Log.w(TAG, "DiscoveryProviderManagerLegacy failed to report onError.", e);
                 }
             }
         }
@@ -220,6 +225,10 @@
 
     /** Called after boot completed. */
     public void init() {
+        // Register BLE only scan when Bluetooth is turned off
+        if (mNearbyConfiguration.enableBleInInit()) {
+            setBleScanEnabled();
+        }
         if (mInjector.getContextHubManager() != null) {
             mChreDiscoveryProvider.init();
         }
@@ -293,10 +302,10 @@
             if (listenerBinder != null && deathRecipient != null) {
                 listenerBinder.unlinkToDeath(removedRecord.getDeathRecipient(), 0);
             }
-            Log.v(TAG, "DiscoveryProviderManager unregistered scan listener.");
+            Log.v(TAG, "DiscoveryProviderManagerLegacy unregistered scan listener.");
             NearbyMetrics.logScanStopped(removedRecord.hashCode(), removedRecord.getScanRequest());
             if (mScanTypeScanListenerRecordMap.isEmpty()) {
-                Log.v(TAG, "DiscoveryProviderManager stops provider because there is no "
+                Log.v(TAG, "DiscoveryProviderManagerLegacy stops provider because there is no "
                         + "scan listener registered.");
                 stopProviders();
                 return;
@@ -306,8 +315,8 @@
 
             // Removes current highest scan mode requested and sets the next highest scan mode.
             if (removedRecord.getScanRequest().getScanMode() == mScanMode) {
-                Log.v(TAG, "DiscoveryProviderManager starts to find the new highest scan mode "
-                        + "because the highest scan mode listener was unregistered.");
+                Log.v(TAG, "DiscoveryProviderManagerLegacy starts to find the new highest "
+                        + "scan mode because the highest scan mode listener was unregistered.");
                 @ScanRequest.ScanMode int highestScanModeRequested = ScanRequest.SCAN_MODE_NO_POWER;
                 // find the next highest scan mode;
                 for (ScanListenerRecord record : mScanTypeScanListenerRecordMap.values()) {
@@ -377,7 +386,7 @@
 
     private void startBleProvider(List<ScanFilter> scanFilters) {
         if (!mBleDiscoveryProvider.getController().isStarted()) {
-            Log.d(TAG, "DiscoveryProviderManager starts Ble scanning.");
+            Log.d(TAG, "DiscoveryProviderManagerLegacy starts BLE scanning.");
             mBleDiscoveryProvider.getController().setListener(this);
             mBleDiscoveryProvider.getController().setProviderScanMode(mScanMode);
             mBleDiscoveryProvider.getController().setProviderScanFilters(scanFilters);
@@ -387,7 +396,7 @@
 
     @VisibleForTesting
     void startChreProvider(List<ScanFilter> scanFilters) {
-        Log.d(TAG, "DiscoveryProviderManager starts CHRE scanning.");
+        Log.d(TAG, "DiscoveryProviderManagerLegacy starts CHRE scanning.");
         mChreDiscoveryProvider.getController().setProviderScanFilters(scanFilters);
         mChreDiscoveryProvider.getController().setProviderScanMode(mScanMode);
         mChreDiscoveryProvider.getController().start();
@@ -503,4 +512,29 @@
             unregisterScanListener(listener);
         }
     }
+
+    /**
+     * Registers Nearby service to Ble scan if Bluetooth is off. (Even when Bluetooth is off)
+     * @return {@code true} when Nearby currently can scan through Bluetooth or Ble or successfully
+     * registers Nearby service to Ble scan when Blutooth is off.
+     */
+    public boolean setBleScanEnabled() {
+        BluetoothAdapter adapter = mInjector.getBluetoothAdapter();
+        if (adapter == null) {
+            Log.e(TAG, "BluetoothAdapter is null.");
+            return false;
+        }
+        if (adapter.isEnabled() || adapter.isLeEnabled()) {
+            return true;
+        }
+        if (!adapter.isBleScanAlwaysAvailable()) {
+            Log.v(TAG, "Ble always on scan is disabled.");
+            return false;
+        }
+        if (!adapter.enableBLE()) {
+            Log.e(TAG, "Failed to register Ble scan.");
+            return false;
+        }
+        return true;
+    }
 }
diff --git a/nearby/service/java/com/android/server/nearby/provider/BleBroadcastProvider.java b/nearby/service/java/com/android/server/nearby/provider/BleBroadcastProvider.java
index 72edad0..66ae79c 100644
--- a/nearby/service/java/com/android/server/nearby/provider/BleBroadcastProvider.java
+++ b/nearby/service/java/com/android/server/nearby/provider/BleBroadcastProvider.java
@@ -85,6 +85,7 @@
                         case BroadcastRequest.PRESENCE_VERSION_V0:
                             bluetoothLeAdvertiser.startAdvertising(getAdvertiseSettings(),
                                     advertiseData, this);
+                            Log.v(TAG, "Start to broadcast V0 advertisement.");
                             break;
                         case BroadcastRequest.PRESENCE_VERSION_V1:
                             if (adapter.isLeExtendedAdvertisingSupported()) {
@@ -92,6 +93,7 @@
                                         getAdvertisingSetParameters(),
                                         advertiseData,
                                         null, null, null, mAdvertisingSetCallback);
+                                Log.v(TAG, "Start to broadcast V1 advertisement.");
                             } else {
                                 Log.w(TAG, "Failed to start advertising set because the chipset"
                                         + " does not supports LE Extended Advertising feature.");
diff --git a/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java b/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
index 9dd07a4..e4651b7 100644
--- a/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
+++ b/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
@@ -68,15 +68,6 @@
     @GuardedBy("mLock")
     @Nullable
     private List<android.nearby.ScanFilter> mScanFilters;
-    private android.bluetooth.le.ScanCallback mScanCallbackLegacy =
-            new android.bluetooth.le.ScanCallback() {
-                @Override
-                public void onScanResult(int callbackType, ScanResult scanResult) {
-                }
-                @Override
-                public void onScanFailed(int errorCode) {
-                }
-            };
     private android.bluetooth.le.ScanCallback mScanCallback =
             new android.bluetooth.le.ScanCallback() {
                 @Override
@@ -170,7 +161,6 @@
         if (isBleAvailable()) {
             Log.d(TAG, "BleDiscoveryProvider started");
             startScan(getScanFilters(), getScanSettings(/* legacy= */ false), mScanCallback);
-            startScan(getScanFilters(), getScanSettings(/* legacy= */ true), mScanCallbackLegacy);
             return;
         }
         Log.w(TAG, "Cannot start BleDiscoveryProvider because Ble is not available.");
@@ -187,7 +177,6 @@
         }
         Log.v(TAG, "Ble scan stopped.");
         bluetoothLeScanner.stopScan(mScanCallback);
-        bluetoothLeScanner.stopScan(mScanCallbackLegacy);
         synchronized (mLock) {
             if (mScanFilters != null) {
                 mScanFilters = null;
diff --git a/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java b/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java
index d69d42d..21ec252 100644
--- a/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java
+++ b/nearby/service/java/com/android/server/nearby/provider/ChreDiscoveryProvider.java
@@ -20,6 +20,9 @@
 
 import static com.android.server.nearby.NearbyService.TAG;
 
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -155,7 +158,7 @@
             builder.setFastPairSupported(version != ChreCommunication.INVALID_NANO_APP_VERSION);
             try {
                 callback.onQueryComplete(builder.build());
-            } catch (RemoteException e) {
+            } catch (RemoteException | NullPointerException e) {
                 e.printStackTrace();
             }
         });
@@ -350,7 +353,11 @@
                                             DataElement.DataType.ACTION,
                                             new byte[]{(byte) filterResult.getIntent()}));
                         }
-
+                        if (filterResult.hasTimestampNs()) {
+                            presenceDeviceBuilder
+                                    .setDiscoveryTimestampMillis(MILLISECONDS.convert(
+                                            filterResult.getTimestampNs(), NANOSECONDS));
+                        }
                         PublicCredential publicCredential =
                                 new PublicCredential.Builder(
                                         secretId,
diff --git a/nearby/service/proto/src/presence/blefilter.proto b/nearby/service/proto/src/presence/blefilter.proto
index e1bf455..bf9357b 100644
--- a/nearby/service/proto/src/presence/blefilter.proto
+++ b/nearby/service/proto/src/presence/blefilter.proto
@@ -115,6 +115,9 @@
   repeated DataElement data_element = 7;
   optional bytes ble_service_data = 8;
   optional ResultType result_type = 9;
+  // Timestamp when the device is discovered, in nanoseconds,
+  // relative to Android SystemClock.elapsedRealtimeNanos().
+  optional uint64 timestamp_ns = 10;
 }
 
 message BleFilterResults {
diff --git a/nearby/tests/unit/src/com/android/server/nearby/managers/DiscoveryProviderManagerLegacyTest.java b/nearby/tests/unit/src/com/android/server/nearby/managers/DiscoveryProviderManagerLegacyTest.java
index aa0dad3..0ca571a 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/managers/DiscoveryProviderManagerLegacyTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/managers/DiscoveryProviderManagerLegacyTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.nearby.managers;
 
+import static android.Manifest.permission.READ_DEVICE_CONFIG;
 import static android.nearby.PresenceCredential.IDENTITY_TYPE_PRIVATE;
 import static android.nearby.ScanRequest.SCAN_TYPE_NEARBY_PRESENCE;
 
@@ -23,10 +24,12 @@
 
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.AppOpsManager;
+import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 import android.nearby.DataElement;
 import android.nearby.IScanListener;
@@ -36,6 +39,8 @@
 import android.nearby.ScanRequest;
 import android.os.IBinder;
 
+import androidx.test.platform.app.InstrumentationRegistry;
+
 import com.android.server.nearby.injector.Injector;
 import com.android.server.nearby.provider.BleDiscoveryProvider;
 import com.android.server.nearby.provider.ChreCommunication;
@@ -86,6 +91,8 @@
     DiscoveryProviderManagerLegacy.ScanListenerDeathRecipient mScanListenerDeathRecipient;
     @Mock
     IBinder mIBinder;
+    @Mock
+    BluetoothAdapter mBluetoothAdapter;
     private DiscoveryProviderManagerLegacy mDiscoveryProviderManager;
     private Map<IBinder, DiscoveryProviderManagerLegacy.ScanListenerRecord>
             mScanTypeScanListenerRecordMap;
@@ -135,10 +142,15 @@
 
     @Before
     public void setup() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                READ_DEVICE_CONFIG);
         MockitoAnnotations.initMocks(this);
         when(mInjector.getAppOpsManager()).thenReturn(mAppOpsManager);
+        when(mInjector.getBluetoothAdapter()).thenReturn(mBluetoothAdapter);
         when(mBleDiscoveryProvider.getController()).thenReturn(mBluetoothController);
         when(mChreDiscoveryProvider.getController()).thenReturn(mChreController);
+        when(mBluetoothAdapter.isBleScanAlwaysAvailable()).thenReturn(true);
+        when(mBluetoothAdapter.isEnabled()).thenReturn(true);
 
         mScanTypeScanListenerRecordMap = new HashMap<>();
         mDiscoveryProviderManager =
@@ -164,6 +176,13 @@
     }
 
     @Test
+    public void test_enableBleWhenBleOff() throws Exception {
+        when(mBluetoothAdapter.isEnabled()).thenReturn(false);
+        mDiscoveryProviderManager.init();
+        verify(mBluetoothAdapter, times(1)).enableBLE();
+    }
+
+    @Test
     public void testStartProviders_chreOnlyChreAvailable_bleProviderNotStarted() {
         when(mChreDiscoveryProvider.available()).thenReturn(true);
 
@@ -375,4 +394,62 @@
                 .isTrue();
         assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNotNull();
     }
+
+    @Test
+    public void isBluetoothEnabledTest_bluetoothEnabled() {
+        when(mBluetoothAdapter.isEnabled()).thenReturn(true);
+        when(mBluetoothAdapter.isLeEnabled()).thenReturn(false);
+        when(mBluetoothAdapter.isBleScanAlwaysAvailable()).thenReturn(true);
+        when(mBluetoothAdapter.enableBLE()).thenReturn(true);
+
+        assertThat(mDiscoveryProviderManager.setBleScanEnabled()).isTrue();
+    }
+
+    @Test
+    public void isBluetoothEnabledTest_bleEnabled() {
+        when(mBluetoothAdapter.isEnabled()).thenReturn(false);
+        when(mBluetoothAdapter.isLeEnabled()).thenReturn(true);
+        when(mBluetoothAdapter.isBleScanAlwaysAvailable()).thenReturn(true);
+        when(mBluetoothAdapter.enableBLE()).thenReturn(true);
+
+        assertThat(mDiscoveryProviderManager.setBleScanEnabled()).isTrue();
+    }
+
+    @Test
+    public void enabledTest_enabled() {
+        when(mBluetoothAdapter.isEnabled()).thenReturn(false);
+        when(mBluetoothAdapter.isLeEnabled()).thenReturn(false);
+        when(mBluetoothAdapter.isBleScanAlwaysAvailable()).thenReturn(true);
+        when(mBluetoothAdapter.enableBLE()).thenReturn(true);
+
+        assertThat(mDiscoveryProviderManager.setBleScanEnabled()).isTrue();
+    }
+
+    @Test
+    public void enabledTest_enableFailed() {
+        when(mBluetoothAdapter.isEnabled()).thenReturn(false);
+        when(mBluetoothAdapter.isLeEnabled()).thenReturn(false);
+        when(mBluetoothAdapter.isBleScanAlwaysAvailable()).thenReturn(true);
+        when(mBluetoothAdapter.enableBLE()).thenReturn(false);
+
+        assertThat(mDiscoveryProviderManager.setBleScanEnabled()).isFalse();
+    }
+
+    @Test
+    public void enabledTest_scanIsOn() {
+        when(mBluetoothAdapter.isEnabled()).thenReturn(true);
+        when(mBluetoothAdapter.isLeEnabled()).thenReturn(false);
+        when(mBluetoothAdapter.isBleScanAlwaysAvailable()).thenReturn(false);
+
+        assertThat(mDiscoveryProviderManager.setBleScanEnabled()).isTrue();
+    }
+
+    @Test
+    public void enabledTest_failed() {
+        when(mBluetoothAdapter.isEnabled()).thenReturn(false);
+        when(mBluetoothAdapter.isLeEnabled()).thenReturn(false);
+        when(mBluetoothAdapter.isBleScanAlwaysAvailable()).thenReturn(false);
+
+        assertThat(mDiscoveryProviderManager.setBleScanEnabled()).isFalse();
+    }
 }
diff --git a/nearby/tests/unit/src/com/android/server/nearby/managers/DiscoveryProviderManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/managers/DiscoveryProviderManagerTest.java
index 7ecf631..7cea34a 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/managers/DiscoveryProviderManagerTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/managers/DiscoveryProviderManagerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.nearby.managers;
 
+import static android.Manifest.permission.READ_DEVICE_CONFIG;
 import static android.nearby.PresenceCredential.IDENTITY_TYPE_PRIVATE;
 import static android.nearby.ScanRequest.SCAN_TYPE_NEARBY_PRESENCE;
 
@@ -24,10 +25,12 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.AppOpsManager;
+import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 import android.nearby.DataElement;
 import android.nearby.IScanListener;
@@ -37,6 +40,8 @@
 import android.nearby.ScanRequest;
 import android.os.IBinder;
 
+import androidx.test.platform.app.InstrumentationRegistry;
+
 import com.android.server.nearby.injector.Injector;
 import com.android.server.nearby.provider.BleDiscoveryProvider;
 import com.android.server.nearby.provider.ChreCommunication;
@@ -80,6 +85,8 @@
     CallerIdentity mCallerIdentity;
     @Mock
     IBinder mIBinder;
+    @Mock
+    BluetoothAdapter mBluetoothAdapter;
     private Executor mExecutor;
     private DiscoveryProviderManager mDiscoveryProviderManager;
 
@@ -128,12 +135,17 @@
 
     @Before
     public void setup() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                READ_DEVICE_CONFIG);
         MockitoAnnotations.initMocks(this);
         mExecutor = Executors.newSingleThreadExecutor();
         when(mInjector.getAppOpsManager()).thenReturn(mAppOpsManager);
         when(mBleDiscoveryProvider.getController()).thenReturn(mBluetoothController);
         when(mChreDiscoveryProvider.getController()).thenReturn(mChreController);
         when(mScanListener.asBinder()).thenReturn(mIBinder);
+        when(mInjector.getBluetoothAdapter()).thenReturn(mBluetoothAdapter);
+        when(mBluetoothAdapter.isBleScanAlwaysAvailable()).thenReturn(true);
+        when(mBluetoothAdapter.isEnabled()).thenReturn(true);
 
         mDiscoveryProviderManager =
                 new DiscoveryProviderManager(mContext, mExecutor, mInjector,
@@ -157,6 +169,13 @@
     }
 
     @Test
+    public void test_enableBleWhenBleOff() throws Exception {
+        when(mBluetoothAdapter.isEnabled()).thenReturn(false);
+        mDiscoveryProviderManager.init();
+        verify(mBluetoothAdapter, times(1)).enableBLE();
+    }
+
+    @Test
     public void testStartProviders_chreOnlyChreAvailable_bleProviderNotStarted() {
         reset(mBluetoothController);
         when(mChreDiscoveryProvider.available()).thenReturn(true);
@@ -336,4 +355,62 @@
                 .isTrue();
         assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNotNull();
     }
+
+    @Test
+    public void isBluetoothEnabledTest_bluetoothEnabled() {
+        when(mBluetoothAdapter.isEnabled()).thenReturn(true);
+        when(mBluetoothAdapter.isLeEnabled()).thenReturn(false);
+        when(mBluetoothAdapter.isBleScanAlwaysAvailable()).thenReturn(true);
+        when(mBluetoothAdapter.enableBLE()).thenReturn(true);
+
+        assertThat(mDiscoveryProviderManager.setBleScanEnabled()).isTrue();
+    }
+
+    @Test
+    public void isBluetoothEnabledTest_bleEnabled() {
+        when(mBluetoothAdapter.isEnabled()).thenReturn(false);
+        when(mBluetoothAdapter.isLeEnabled()).thenReturn(true);
+        when(mBluetoothAdapter.isBleScanAlwaysAvailable()).thenReturn(true);
+        when(mBluetoothAdapter.enableBLE()).thenReturn(true);
+
+        assertThat(mDiscoveryProviderManager.setBleScanEnabled()).isTrue();
+    }
+
+    @Test
+    public void enabledTest_enabled() {
+        when(mBluetoothAdapter.isEnabled()).thenReturn(false);
+        when(mBluetoothAdapter.isLeEnabled()).thenReturn(false);
+        when(mBluetoothAdapter.isBleScanAlwaysAvailable()).thenReturn(true);
+        when(mBluetoothAdapter.enableBLE()).thenReturn(true);
+
+        assertThat(mDiscoveryProviderManager.setBleScanEnabled()).isTrue();
+    }
+
+    @Test
+    public void enabledTest_enableFailed() {
+        when(mBluetoothAdapter.isEnabled()).thenReturn(false);
+        when(mBluetoothAdapter.isLeEnabled()).thenReturn(false);
+        when(mBluetoothAdapter.isBleScanAlwaysAvailable()).thenReturn(true);
+        when(mBluetoothAdapter.enableBLE()).thenReturn(false);
+
+        assertThat(mDiscoveryProviderManager.setBleScanEnabled()).isFalse();
+    }
+
+    @Test
+    public void enabledTest_scanIsOn() {
+        when(mBluetoothAdapter.isEnabled()).thenReturn(true);
+        when(mBluetoothAdapter.isLeEnabled()).thenReturn(false);
+        when(mBluetoothAdapter.isBleScanAlwaysAvailable()).thenReturn(false);
+
+        assertThat(mDiscoveryProviderManager.setBleScanEnabled()).isTrue();
+    }
+
+    @Test
+    public void enabledTest_failed() {
+        when(mBluetoothAdapter.isEnabled()).thenReturn(false);
+        when(mBluetoothAdapter.isLeEnabled()).thenReturn(false);
+        when(mBluetoothAdapter.isBleScanAlwaysAvailable()).thenReturn(false);
+
+        assertThat(mDiscoveryProviderManager.setBleScanEnabled()).isFalse();
+    }
 }
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/BleBroadcastProviderTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/BleBroadcastProviderTest.java
index 05b556b..c064844 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/provider/BleBroadcastProviderTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/BleBroadcastProviderTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.nearby.provider;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.times;
@@ -69,7 +70,7 @@
 
         AdvertiseSettings settings = new AdvertiseSettings.Builder().build();
         mBleBroadcastProvider.onStartSuccess(settings);
-        verify(mBroadcastListener).onStatusChanged(eq(BroadcastCallback.STATUS_OK));
+        verify(mBroadcastListener, times(any())).onStatusChanged(eq(BroadcastCallback.STATUS_OK));
     }
 
     @Test
@@ -81,7 +82,7 @@
         // advertising set can not be mocked, so we will allow nulls
         mBleBroadcastProvider.mAdvertisingSetCallback.onAdvertisingSetStarted(null, -30,
                 AdvertisingSetCallback.ADVERTISE_SUCCESS);
-        verify(mBroadcastListener).onStatusChanged(eq(BroadcastCallback.STATUS_OK));
+        verify(mBroadcastListener, times(any())).onStatusChanged(eq(BroadcastCallback.STATUS_OK));
     }
 
     @Test
diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/ChreDiscoveryProviderTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/ChreDiscoveryProviderTest.java
index 154441b..8ee661f 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/provider/ChreDiscoveryProviderTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/provider/ChreDiscoveryProviderTest.java
@@ -192,7 +192,7 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
-    public void testOnNearbyDeviceDiscoveredWithDataElements() {
+    public void testOnNearbyDeviceDiscoveredWithDataElements_TIME() {
         // Disables the setting of test app support
         boolean isSupportedTestApp = getDeviceConfigBoolean(
                 NEARBY_SUPPORT_TEST_APP, false /* defaultValue */);
@@ -209,6 +209,7 @@
         // First byte is length of service data, padding zeros should be thrown away.
         final byte [] bleServiceData = new byte[] {5, 1, 2, 3, 4, 5, 0, 0, 0, 0};
         final byte [] testData = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+        final long timestampNs = 1697765417070000000L;
 
         final List<DataElement> expectedExtendedProperties = new ArrayList<>();
         expectedExtendedProperties.add(new DataElement(DATA_TYPE_CONNECTION_STATUS_KEY,
@@ -262,6 +263,7 @@
                                 .setValue(ByteString.copyFrom(testData))
                                 .setValueLength(testData.length)
                         )
+                        .setTimestampNs(timestampNs)
                         .build();
         Blefilter.BleFilterResults results =
                 Blefilter.BleFilterResults.newBuilder().addResult(result).build();
@@ -285,11 +287,18 @@
             DeviceConfig.setProperty(NAMESPACE, NEARBY_SUPPORT_TEST_APP, "true", false);
             assertThat(new NearbyConfiguration().isTestAppSupported()).isTrue();
         }
+        // Nanoseconds to Milliseconds
+        assertThat((mNearbyDevice.getValue().getPresenceDevice())
+                .getDiscoveryTimestampMillis()).isEqualTo(timestampNs / 1000000);
     }
 
     @Test
-    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    @SdkSuppress(minSdkVersion = 33, codeName = "T")
     public void testOnNearbyDeviceDiscoveredWithTestDataElements() {
+        // The feature only supports user-debug builds.
+        if (!Build.isDebuggable()) {
+            return;
+        }
         // Enables the setting of test app support
         boolean isSupportedTestApp = getDeviceConfigBoolean(
                 NEARBY_SUPPORT_TEST_APP, false /* defaultValue */);