Merge "Address API review feedback" into tm-dev
diff --git a/nearby/framework/java/android/nearby/INearbyManager.aidl b/nearby/framework/java/android/nearby/INearbyManager.aidl
index e5c4102..62e109e 100644
--- a/nearby/framework/java/android/nearby/INearbyManager.aidl
+++ b/nearby/framework/java/android/nearby/INearbyManager.aidl
@@ -28,7 +28,7 @@
  */
 interface INearbyManager {
 
-    void registerScanListener(in ScanRequest scanRequest, in IScanListener listener);
+    int registerScanListener(in ScanRequest scanRequest, in IScanListener listener);
 
     void unregisterScanListener(in IScanListener listener);
 
diff --git a/nearby/framework/java/android/nearby/NearbyManager.java b/nearby/framework/java/android/nearby/NearbyManager.java
index 1f6edd3..2654046 100644
--- a/nearby/framework/java/android/nearby/NearbyManager.java
+++ b/nearby/framework/java/android/nearby/NearbyManager.java
@@ -16,9 +16,12 @@
 
 package android.nearby;
 
+import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
@@ -48,6 +51,25 @@
 public class NearbyManager {
 
     /**
+     * Represents the scanning state.
+     *
+     * @hide
+     */
+    @IntDef({
+            ScanStatus.UNKNOWN,
+            ScanStatus.SUCCESS,
+            ScanStatus.ERROR,
+    })
+    public @interface ScanStatus {
+        // Default, invalid state.
+        int UNKNOWN = 0;
+        // The successful state.
+        int SUCCESS = 1;
+        // Failed state.
+        int ERROR = 2;
+    }
+
+    /**
      * Whether allows Fast Pair to scan.
      *
      * (0 = disabled, 1 = enabled)
@@ -68,7 +90,7 @@
     /**
      * Creates a new NearbyManager.
      *
-     * @param service The service object.
+     * @param service the service object
      */
     NearbyManager(@NonNull INearbyManager service) {
         mService = service;
@@ -93,11 +115,16 @@
      * Start scan for nearby devices with given parameters. Devices matching {@link ScanRequest}
      * will be delivered through the given callback.
      *
-     * @param scanRequest Various parameters clients send when requesting scanning.
-     * @param executor Executor where the listener method is called.
-     * @param scanCallback The callback to notify clients when there is a scan result.
+     * @param scanRequest various parameters clients send when requesting scanning
+     * @param executor executor where the listener method is called
+     * @param scanCallback the callback to notify clients when there is a scan result
+     *
+     * @return whether scanning was successfully started
      */
-    public void startScan(@NonNull ScanRequest scanRequest,
+    @RequiresPermission(allOf = {android.Manifest.permission.BLUETOOTH_SCAN,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED})
+    @ScanStatus
+    public int startScan(@NonNull ScanRequest scanRequest,
             @CallbackExecutor @NonNull Executor executor,
             @NonNull ScanCallback scanCallback) {
         Objects.requireNonNull(scanRequest, "scanRequest must not be null");
@@ -115,8 +142,12 @@
                     Preconditions.checkState(transport.isRegistered());
                     transport.setExecutor(executor);
                 }
-                mService.registerScanListener(scanRequest, transport);
+                @ScanStatus int status = mService.registerScanListener(scanRequest, transport);
+                if (status != ScanStatus.SUCCESS) {
+                    return status;
+                }
                 sScanListeners.put(scanCallback, new WeakReference<>(transport));
+                return ScanStatus.SUCCESS;
             }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -131,9 +162,11 @@
      * Suppressed lint: Registration methods should have overload that accepts delivery Executor.
      * Already have executor in startScan() method.
      *
-     * @param scanCallback  The callback that was used to start the scan.
+     * @param scanCallback the callback that was used to start the scan
      */
     @SuppressLint("ExecutorRegistration")
+    @RequiresPermission(allOf = {android.Manifest.permission.BLUETOOTH_SCAN,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED})
     public void stopScan(@NonNull ScanCallback scanCallback) {
         Preconditions.checkArgument(scanCallback != null,
                 "invalid null scanCallback");
@@ -155,10 +188,12 @@
     /**
      * Start broadcasting the request using nearby specification.
      *
-     * @param broadcastRequest Request for the nearby broadcast.
-     * @param executor Executor for running the callback.
-     * @param callback Callback for notifying the client.
+     * @param broadcastRequest request for the nearby broadcast
+     * @param executor executor for running the callback
+     * @param callback callback for notifying the client
      */
+    @RequiresPermission(allOf = {Manifest.permission.BLUETOOTH_ADVERTISE,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED})
     public void startBroadcast(@NonNull BroadcastRequest broadcastRequest,
             @CallbackExecutor @NonNull Executor executor, @NonNull BroadcastCallback callback) {
         try {
@@ -184,9 +219,11 @@
     /**
      * Stop the broadcast associated with the given callback.
      *
-     * @param callback The callback that was used for starting the broadcast.
+     * @param callback the callback that was used for starting the broadcast
      */
     @SuppressLint("ExecutorRegistration")
+    @RequiresPermission(allOf = {Manifest.permission.BLUETOOTH_ADVERTISE,
+            android.Manifest.permission.BLUETOOTH_PRIVILEGED})
     public void stopBroadcast(@NonNull BroadcastCallback callback) {
         try {
             synchronized (sBroadcastListeners) {
@@ -206,9 +243,9 @@
     /**
      * 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.
+     * @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(
@@ -219,8 +256,8 @@
     /**
      * 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.
+     * @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(
diff --git a/nearby/service/java/com/android/server/nearby/NearbyService.java b/nearby/service/java/com/android/server/nearby/NearbyService.java
index ca9bca3..d721575 100644
--- a/nearby/service/java/com/android/server/nearby/NearbyService.java
+++ b/nearby/service/java/com/android/server/nearby/NearbyService.java
@@ -31,6 +31,7 @@
 import android.nearby.IBroadcastListener;
 import android.nearby.INearbyManager;
 import android.nearby.IScanListener;
+import android.nearby.NearbyManager;
 import android.nearby.ScanRequest;
 import android.util.Log;
 
@@ -99,8 +100,12 @@
     }
 
     @Override
-    public void registerScanListener(ScanRequest scanRequest, IScanListener listener) {
-        mProviderManager.registerScanListener(scanRequest, listener);
+    @NearbyManager.ScanStatus
+    public int registerScanListener(ScanRequest scanRequest, IScanListener listener) {
+        if (mProviderManager.registerScanListener(scanRequest, listener)) {
+            return NearbyManager.ScanStatus.SUCCESS;
+        }
+        return NearbyManager.ScanStatus.ERROR;
     }
 
     @Override
diff --git a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
index fbbfae1..7ff3110 100644
--- a/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
+++ b/nearby/service/java/com/android/server/nearby/provider/DiscoveryProviderManager.java
@@ -95,7 +95,7 @@
     /**
      * Registers the listener in the manager and starts scan according to the requested scan mode.
      */
-    public void registerScanListener(ScanRequest scanRequest, IScanListener listener) {
+    public boolean registerScanListener(ScanRequest scanRequest, IScanListener listener) {
         synchronized (mLock) {
             IBinder listenerBinder = listener.asBinder();
             if (mScanTypeScanListenerRecordMap.containsKey(listener.asBinder())) {
@@ -103,11 +103,13 @@
                         .get(listenerBinder).getScanRequest();
                 if (scanRequest.equals(savedScanRequest)) {
                     Log.d(TAG, "Already registered the scanRequest: " + scanRequest);
-                    return;
+                    return true;
                 }
             }
 
-            startProviders(scanRequest);
+            if (!startProviders(scanRequest)) {
+                return false;
+            }
 
             ScanListenerRecord scanListenerRecord = new ScanListenerRecord(scanRequest, listener);
             mScanTypeScanListenerRecordMap.put(listenerBinder, scanListenerRecord);
@@ -116,6 +118,7 @@
                 mScanMode = scanRequest.getScanMode();
                 invalidateProviderScanMode();
             }
+            return true;
         }
     }
 
@@ -159,10 +162,14 @@
         }
     }
 
-    private void startProviders(ScanRequest scanRequest) {
+    // Returns false when fail to start all the providers. Returns true if any one of the provider
+    // starts successfully.
+    private boolean startProviders(ScanRequest scanRequest) {
         if (scanRequest.isBleEnabled()) {
             startBleProvider(scanRequest);
+            return true;
         }
+        return false;
     }
 
     private void startBleProvider(ScanRequest scanRequest) {
diff --git a/nearby/tests/multidevices/host/Android.bp b/nearby/tests/multidevices/host/Android.bp
index bad8a26..533153a 100644
--- a/nearby/tests/multidevices/host/Android.bp
+++ b/nearby/tests/multidevices/host/Android.bp
@@ -16,11 +16,11 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-// Run the tests: atest -v CtsSeekerDiscoverProviderTest
+// Run the tests: atest -v CtsNearbyMultiDevicesTestSuite
 python_test_host {
-    name: "CtsSeekerDiscoverProviderTest",
-    main: "seeker_discover_provider_test.py",
-    srcs: ["seeker_discover_provider_test.py"],
+    name: "CtsNearbyMultiDevicesTestSuite",
+    main: "suite_main.py",
+    srcs: ["*.py"],
     libs: ["NearbyMultiDevicesHostHelper"],
     test_suites: [
         "cts",
diff --git a/nearby/tests/multidevices/host/AndroidTest.xml b/nearby/tests/multidevices/host/AndroidTest.xml
index cca0826..f8294f4 100644
--- a/nearby/tests/multidevices/host/AndroidTest.xml
+++ b/nearby/tests/multidevices/host/AndroidTest.xml
@@ -10,7 +10,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<configuration description="Config for CTS seeker scan provider test">
+<configuration description="Config for CTS Nearby Mainline multi devices end-to-end test suite">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="wifi" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
@@ -34,6 +34,10 @@
           <option name="dep-module" value="mobly" />
           <option name="dep-module" value="retry" />
         </target_preparer>
+        <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+            <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
+            <option name="screen-always-on" value="on" />
+        </target_preparer>
     </device>
     <device name="device2">
         <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
@@ -44,11 +48,15 @@
             <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
             <option name="run-command" value="wm dismiss-keyguard" />
         </target_preparer>
+        <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+            <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
+            <option name="screen-always-on" value="on" />
+        </target_preparer>
     </device>
 
     <test class="com.android.tradefed.testtype.mobly.MoblyBinaryHostTest">
       <!-- The mobly-par-file-name should match the module name -->
-      <option name="mobly-par-file-name" value="CtsSeekerDiscoverProviderTest" />
+      <option name="mobly-par-file-name" value="CtsNearbyMultiDevicesTestSuite" />
       <!-- Timeout limit in milliseconds for all test cases of the python binary -->
       <option name="mobly-test-timeout" value="60000" />
     </test>
diff --git a/nearby/tests/multidevices/host/seeker_discover_provider_test.py b/nearby/tests/multidevices/host/seeker_discover_provider_test.py
index 03f3661..c82812a 100644
--- a/nearby/tests/multidevices/host/seeker_discover_provider_test.py
+++ b/nearby/tests/multidevices/host/seeker_discover_provider_test.py
@@ -14,25 +14,25 @@
 
 """CTS-V Nearby Mainline Fast Pair end-to-end test case: seeker can discover the provider."""
 
-import logging
-import sys
+from typing import List
 
 from mobly import base_test
-from mobly import test_runner
 from mobly.controllers import android_device
 
+from test_helper import constants
 from test_helper import fast_pair_provider_simulator
 from test_helper import fast_pair_seeker
 
-# Default model ID to simulate on provider side.
-DEFAULT_MODEL_ID = '00000C'
-# Default public key to simulate as registered headsets.
-DEFAULT_ANTI_SPOOFING_KEY = 'Cbj9eCJrTdDgSYxLkqtfADQi86vIaMvxJsQ298sZYWE='
+# The model ID to simulate on provider side.
+PROVIDER_SIMULATOR_MODEL_ID = constants.DEFAULT_MODEL_ID
+# The public key to simulate as registered headsets.
+PROVIDER_SIMULATOR_ANTI_SPOOFING_KEY = constants.DEFAULT_ANTI_SPOOFING_KEY
+
 # Time in seconds for events waiting.
-SETUP_TIMEOUT_SEC = 5
-BECOME_DISCOVERABLE_TIMEOUT_SEC = 10
-START_ADVERTISING_TIMEOUT_SEC = 5
-SCAN_TIMEOUT_SEC = 30
+SETUP_TIMEOUT_SEC = constants.SETUP_TIMEOUT_SEC
+BECOME_DISCOVERABLE_TIMEOUT_SEC = constants.BECOME_DISCOVERABLE_TIMEOUT_SEC
+START_ADVERTISING_TIMEOUT_SEC = constants.START_ADVERTISING_TIMEOUT_SEC
+SCAN_TIMEOUT_SEC = constants.SCAN_TIMEOUT_SEC
 
 # Abbreviations for common use type.
 FastPairProviderSimulator = fast_pair_provider_simulator.FastPairProviderSimulator
@@ -42,17 +42,16 @@
 class SeekerDiscoverProviderTest(base_test.BaseTestClass):
     """Fast Pair seeker discover provider test."""
 
+    _duts: List[android_device.AndroidDevice]
     _provider: FastPairProviderSimulator
     _seeker: FastPairSeeker
 
     def setup_class(self) -> None:
         super().setup_class()
-        self.duts = self.register_controller(android_device)
+        self._duts = self.register_controller(android_device)
 
         # Assume the 1st phone is provider, the 2nd is seeker.
-        provider_ad, seeker_ad = self.duts
-        provider_ad.debug_tag = 'FastPairProviderSimulator'
-        seeker_ad.debug_tag = 'MainlineFastPairSeeker'
+        provider_ad, seeker_ad = self._duts
         self._provider = FastPairProviderSimulator(provider_ad)
         self._seeker = FastPairSeeker(seeker_ad)
         self._provider.load_snippet()
@@ -61,7 +60,8 @@
     def setup_test(self) -> None:
         super().setup_test()
         self._provider.setup_provider_simulator(SETUP_TIMEOUT_SEC)
-        self._provider.start_model_id_advertising(DEFAULT_MODEL_ID, DEFAULT_ANTI_SPOOFING_KEY)
+        self._provider.start_model_id_advertising(
+            PROVIDER_SIMULATOR_MODEL_ID, PROVIDER_SIMULATOR_ANTI_SPOOFING_KEY)
         self._provider.wait_for_discoverable_mode(BECOME_DISCOVERABLE_TIMEOUT_SEC)
         self._provider.wait_for_advertising_start(START_ADVERTISING_TIMEOUT_SEC)
         self._seeker.start_scan()
@@ -71,21 +71,12 @@
         self._seeker.stop_scan()
         self._provider.teardown_provider_simulator()
         # Create per-test excepts of logcat.
-        for dut in self.duts:
+        for dut in self._duts:
             dut.services.create_output_excerpts_all(self.current_test_info)
 
     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=SCAN_TIMEOUT_SEC,
-            expected_model_id=DEFAULT_MODEL_ID,
+            expected_model_id=PROVIDER_SIMULATOR_MODEL_ID,
             expected_ble_mac_address=provider_ble_mac_address)
-
-
-if __name__ == '__main__':
-    # Take test args
-    index = sys.argv.index('--')
-    sys.argv = sys.argv[:1] + sys.argv[index + 1:]
-
-    logging.basicConfig(filename="/tmp/seeker_scan_provider_test_log.txt", level=logging.INFO)
-    test_runner.main()
diff --git a/nearby/tests/multidevices/host/suite_main.py b/nearby/tests/multidevices/host/suite_main.py
new file mode 100644
index 0000000..788fbd5
--- /dev/null
+++ b/nearby/tests/multidevices/host/suite_main.py
@@ -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.
+
+"""The entry point for Nearby Mainline multi devices end-to-end test suite."""
+
+import logging
+import sys
+
+from mobly import suite_runner
+
+import seeker_discover_provider_test
+
+_BOOTSTRAP_LOGGING_FILENAME = '/tmp/nearby_multi_devices_test_suite_log.txt'
+_TEST_CLASSES_LIST = [
+    seeker_discover_provider_test.SeekerDiscoverProviderTest,
+]
+
+
+def _valid_argument(arg: str) -> bool:
+    return arg.startswith(('--config', '-c', '--tests', '--test_case'))
+
+
+if __name__ == '__main__':
+    logging.basicConfig(filename=_BOOTSTRAP_LOGGING_FILENAME, level=logging.INFO)
+    suite_runner.run_suite(argv=[arg for arg in sys.argv if _valid_argument(arg)],
+                           test_classes=_TEST_CLASSES_LIST)
diff --git a/nearby/tests/multidevices/host/test_helper/constants.py b/nearby/tests/multidevices/host/test_helper/constants.py
new file mode 100644
index 0000000..6b5185f
--- /dev/null
+++ b/nearby/tests/multidevices/host/test_helper/constants.py
@@ -0,0 +1,25 @@
+#  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.
+
+# Default model ID to simulate on provider side.
+DEFAULT_MODEL_ID = '00000c'
+
+# Default public key to simulate as registered headsets.
+DEFAULT_ANTI_SPOOFING_KEY = 'Cbj9eCJrTdDgSYxLkqtfADQi86vIaMvxJsQ298sZYWE='
+
+# Time in seconds for events waiting.
+SETUP_TIMEOUT_SEC = 5
+BECOME_DISCOVERABLE_TIMEOUT_SEC = 10
+START_ADVERTISING_TIMEOUT_SEC = 5
+SCAN_TIMEOUT_SEC = 30
diff --git a/nearby/tests/multidevices/host/test_helper/fast_pair_provider_simulator.py b/nearby/tests/multidevices/host/test_helper/fast_pair_provider_simulator.py
index 8563e8e..8a98112 100644
--- a/nearby/tests/multidevices/host/test_helper/fast_pair_provider_simulator.py
+++ b/nearby/tests/multidevices/host/test_helper/fast_pair_provider_simulator.py
@@ -43,6 +43,7 @@
 
     def __init__(self, ad: AndroidDevice) -> None:
         self._ad = ad
+        self._ad.debug_tag = 'FastPairProviderSimulator'
         self._provider_status_callback = None
 
     def load_snippet(self) -> None:
@@ -92,6 +93,9 @@
             0x00000C).
           anti_spoofing_key: A public key for registered headsets.
         """
+        self._ad.log.info(
+            'Provider simulator starts advertising as model id "%s" with anti-spoofing key "%s".',
+            model_id, anti_spoofing_key)
         self._provider_status_callback = (
             self._ad.fp.startModelIdAdvertising(model_id, anti_spoofing_key))
 
diff --git a/nearby/tests/multidevices/host/test_helper/fast_pair_seeker.py b/nearby/tests/multidevices/host/test_helper/fast_pair_seeker.py
index 85c37df..9bb2689 100644
--- a/nearby/tests/multidevices/host/test_helper/fast_pair_seeker.py
+++ b/nearby/tests/multidevices/host/test_helper/fast_pair_seeker.py
@@ -37,6 +37,7 @@
 
     def __init__(self, ad: AndroidDevice) -> None:
         self._ad = ad
+        self._ad.debug_tag = 'MainlineFastPairSeeker'
         self._scan_result_callback = None
 
     def load_snippet(self) -> None: