Merge "Avoid unregistering the same receiver more than once." into tm-dev
diff --git a/nearby/tests/cts/fastpair/AndroidTest.xml b/nearby/tests/cts/fastpair/AndroidTest.xml
index 360bbf3..2800069 100644
--- a/nearby/tests/cts/fastpair/AndroidTest.xml
+++ b/nearby/tests/cts/fastpair/AndroidTest.xml
@@ -14,6 +14,9 @@
      limitations under the License.
 -->
 <configuration description="Config for CTS Nearby Fast Pair test cases">
+  <!-- Only run tests if the device under test is SDK version 33 (Android 13) or above. -->
+  <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk33ModuleController" />
+
   <option name="test-suite-tag" value="cts" />
   <option name="config-descriptor:metadata" key="component" value="location" />
   <!-- Instant cannot access NearbyManager. -->
diff --git a/nearby/tests/integration/privileged/AndroidManifest.xml b/nearby/tests/integration/privileged/AndroidManifest.xml
index 00845f1..86ec111 100644
--- a/nearby/tests/integration/privileged/AndroidManifest.xml
+++ b/nearby/tests/integration/privileged/AndroidManifest.xml
@@ -18,6 +18,10 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.nearby.integration.privileged">
 
+    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
+    <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
+    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
     <instrumentation
diff --git a/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt b/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt
index 3b6337a..66bab23 100644
--- a/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt
+++ b/nearby/tests/integration/privileged/src/android/nearby/integration/privileged/NearbyManagerTest.kt
@@ -17,22 +17,81 @@
 package android.nearby.integration.privileged
 
 import android.content.Context
+import android.nearby.BroadcastCallback
+import android.nearby.BroadcastRequest
+import android.nearby.NearbyDevice
 import android.nearby.NearbyManager
+import android.nearby.PresenceBroadcastRequest
+import android.nearby.PresenceCredential
+import android.nearby.PrivateCredential
+import android.nearby.ScanCallback
+import android.nearby.ScanRequest
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
 class NearbyManagerTest {
+    private lateinit var appContext: Context
+
+    @Before
+    fun setUp() {
+        appContext = ApplicationProvider.getApplicationContext<Context>()
+    }
 
     /** Verify privileged app can get Nearby service. */
     @Test
     fun testContextGetNearbySystemService_fromPrivilegedApp_returnsNoneNull() {
-        val appContext = ApplicationProvider.getApplicationContext<Context>()
-        val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+        assertThat(appContext.getSystemService(Context.NEARBY_SERVICE)).isNotNull()
+    }
 
-        assertThat(nearbyManager).isNotNull()
+    /** Verify privileged app can start/stop scan without exception. */
+    @Test
+    fun testNearbyManagerStartScanStopScan_fromPrivilegedApp_succeed() {
+        val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+        val scanRequest = ScanRequest.Builder()
+            .setScanMode(ScanRequest.SCAN_MODE_LOW_LATENCY)
+            .setScanType(ScanRequest.SCAN_TYPE_FAST_PAIR)
+            .setBleEnabled(true)
+            .build()
+        val scanCallback = object : ScanCallback {
+            override fun onDiscovered(device: NearbyDevice) {}
+
+            override fun onUpdated(device: NearbyDevice) {}
+
+            override fun onLost(device: NearbyDevice) {}
+        }
+
+        nearbyManager.startScan(scanRequest, /* executor */ { it.run() }, scanCallback)
+        nearbyManager.stopScan(scanCallback)
+    }
+
+    /** Verify privileged app can start/stop broadcast without exception. */
+    @Test
+    fun testNearbyManagerStartBroadcastStopBroadcast_fromPrivilegedApp_succeed() {
+        val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+        val salt = byteArrayOf(1, 2)
+        val secreteId = byteArrayOf(1, 2, 3, 4)
+        val metadataEncryptionKey = ByteArray(14)
+        val authenticityKey = byteArrayOf(0, 1, 1, 1)
+        val deviceName = "test_device"
+        val mediums = listOf(BroadcastRequest.MEDIUM_BLE)
+        val credential =
+            PrivateCredential.Builder(secreteId, authenticityKey, metadataEncryptionKey, deviceName)
+                .setIdentityType(PresenceCredential.IDENTITY_TYPE_PRIVATE)
+                .build()
+        val broadcastRequest: BroadcastRequest =
+            PresenceBroadcastRequest.Builder(mediums, salt, credential)
+                .addAction(123)
+                .build()
+        val broadcastCallback = BroadcastCallback { }
+
+        nearbyManager.startBroadcast(
+            broadcastRequest, /* executor */ { it.run() }, broadcastCallback
+        )
+        nearbyManager.stopBroadcast(broadcastCallback)
     }
 }
diff --git a/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/NearbyManagerTest.kt b/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/NearbyManagerTest.kt
index 04c5e30..3bfac6d 100644
--- a/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/NearbyManagerTest.kt
+++ b/nearby/tests/integration/untrusted/src/android/nearby/integration/untrusted/NearbyManagerTest.kt
@@ -17,20 +17,127 @@
 package android.nearby.integration.untrusted
 
 import android.content.Context
+import android.nearby.BroadcastCallback
+import android.nearby.BroadcastRequest
+import android.nearby.NearbyDevice
 import android.nearby.NearbyManager
+import android.nearby.PresenceBroadcastRequest
+import android.nearby.PresenceCredential
+import android.nearby.PrivateCredential
+import android.nearby.ScanCallback
+import android.nearby.ScanRequest
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
 class NearbyManagerTest {
+    private lateinit var appContext: Context
+
+    @Before
+    fun setUp() {
+        appContext = ApplicationProvider.getApplicationContext<Context>()
+    }
 
     /** Verify untrusted app can get Nearby service. */
     @Test
     fun testContextGetNearbyService_fromUnTrustedApp_returnsNotNull() {
-        val appContext = ApplicationProvider.getApplicationContext<Context>()
         assertThat(appContext.getSystemService(Context.NEARBY_SERVICE)).isNotNull()
     }
+
+    /**
+     * Verify untrusted app can't start scan because it needs BLUETOOTH_PRIVILEGED
+     * permission which is not for use by third-party applications.
+     */
+    @Test
+    fun testNearbyManagerStartScan_fromUnTrustedApp_throwsException() {
+        val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+        val scanRequest = ScanRequest.Builder()
+            .setScanMode(ScanRequest.SCAN_MODE_LOW_LATENCY)
+            .setScanType(ScanRequest.SCAN_TYPE_FAST_PAIR)
+            .setBleEnabled(true)
+            .build()
+        val scanCallback = object : ScanCallback {
+            override fun onDiscovered(device: NearbyDevice) {}
+
+            override fun onUpdated(device: NearbyDevice) {}
+
+            override fun onLost(device: NearbyDevice) {}
+        }
+
+        assertThrows(SecurityException::class.java) {
+            nearbyManager.startScan(scanRequest, /* executor */ { it.run() }, scanCallback)
+        }
+    }
+
+    /**
+     * Verify untrusted app can't stop scan because it needs BLUETOOTH_PRIVILEGED
+     * permission which is not for use by third-party applications.
+     */
+    @Test
+    @Ignore("Permission check for stopXXX not yet implement: b/229338477#comment24")
+    fun testNearbyManagerStopScan_fromUnTrustedApp_throwsException() {
+        val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+        val scanCallback = object : ScanCallback {
+            override fun onDiscovered(device: NearbyDevice) {}
+
+            override fun onUpdated(device: NearbyDevice) {}
+
+            override fun onLost(device: NearbyDevice) {}
+        }
+
+        assertThrows(SecurityException::class.java) {
+            nearbyManager.stopScan(scanCallback)
+        }
+    }
+
+    /**
+     * Verify untrusted app can't start broadcast because it needs BLUETOOTH_PRIVILEGED
+     * permission which is not for use by third-party applications.
+     */
+    @Test
+    fun testNearbyManagerStartBroadcast_fromUnTrustedApp_throwsException() {
+        val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+        val salt = byteArrayOf(1, 2)
+        val secreteId = byteArrayOf(1, 2, 3, 4)
+        val metadataEncryptionKey = ByteArray(14)
+        val authenticityKey = byteArrayOf(0, 1, 1, 1)
+        val deviceName = "test_device"
+        val mediums = listOf(BroadcastRequest.MEDIUM_BLE)
+        val credential =
+            PrivateCredential.Builder(secreteId, authenticityKey, metadataEncryptionKey, deviceName)
+                .setIdentityType(PresenceCredential.IDENTITY_TYPE_PRIVATE)
+                .build()
+        val broadcastRequest: BroadcastRequest =
+            PresenceBroadcastRequest.Builder(mediums, salt, credential)
+                .addAction(123)
+                .build()
+        val broadcastCallback = BroadcastCallback { }
+
+        assertThrows(SecurityException::class.java) {
+            nearbyManager.startBroadcast(
+                broadcastRequest, /* executor */ { it.run() }, broadcastCallback
+            )
+        }
+    }
+
+    /**
+     * Verify untrusted app can't stop broadcast because it needs BLUETOOTH_PRIVILEGED
+     * permission which is not for use by third-party applications.
+     */
+    @Test
+    @Ignore("Permission check for stopXXX not yet implement: b/229338477#comment24")
+    fun testNearbyManagerStopBroadcast_fromUnTrustedApp_throwsException() {
+        val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
+        val broadcastCallback = BroadcastCallback { }
+
+        assertThrows(SecurityException::class.java) {
+            nearbyManager.stopBroadcast(broadcastCallback)
+        }
+    }
 }
diff --git a/nearby/tests/multidevices/host/AndroidTest.xml b/nearby/tests/multidevices/host/AndroidTest.xml
index 5926cc1..43cf136 100644
--- a/nearby/tests/multidevices/host/AndroidTest.xml
+++ b/nearby/tests/multidevices/host/AndroidTest.xml
@@ -36,7 +36,6 @@
           <!-- Any python dependencies can be specified and will be installed with pip -->
           <!-- TODO(b/225958696): Import python dependencies -->
           <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 -->
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 d6484fb..592c4f1 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
@@ -14,10 +14,12 @@
 
 """Fast Pair provider simulator role."""
 
+import time
+
 from mobly import asserts
 from mobly.controllers import android_device
+from mobly.controllers.android_device_lib import jsonrpc_client_base
 from mobly.controllers.android_device_lib import snippet_event
-import retry
 from typing import Optional
 
 from test_helper import event_helper
@@ -104,7 +106,6 @@
         """Tears down the Fast Pair provider simulator."""
         self._ad.fp.teardownProviderSimulator()
 
-    @retry.retry(tries=3)
     def get_ble_mac_address(self) -> str:
         """Gets Bluetooth low energy mac address of the provider simulator.
 
@@ -115,7 +116,11 @@
         Returns:
           The BLE mac address of the Fast Pair provider simulator.
         """
-        return self._ad.fp.getBluetoothLeAddress()
+        for _ in range(3):
+            try:
+                return self._ad.fp.getBluetoothLeAddress()
+            except jsonrpc_client_base.ApiError:
+                time.sleep(1)
 
     def wait_for_discoverable_mode(self, timeout_seconds: int) -> None:
         """Waits onScanModeChange event to ensure provider is discoverable.
diff --git a/nearby/tests/unit/AndroidTest.xml b/nearby/tests/unit/AndroidTest.xml
index fdf665d..ad52316 100644
--- a/nearby/tests/unit/AndroidTest.xml
+++ b/nearby/tests/unit/AndroidTest.xml
@@ -15,6 +15,9 @@
   ~ limitations under the License.
   -->
 <configuration description="Runs Nearby Mainline API Tests.">
+    <!-- Only run tests if the device under test is SDK version 33 (Android 13) or above. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk33ModuleController" />
+
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="test-file-name" value="NearbyUnitTests.apk" />
     </target_preparer>
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 6de6625..ae00a3a 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -108,6 +108,7 @@
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
 import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
 import android.app.usage.NetworkStatsManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -10645,13 +10646,29 @@
         mQosCallbackTracker.unregisterCallback(callback);
     }
 
+    private boolean isNetworkPreferenceAllowedForProfile(@NonNull UserHandle profile) {
+        // UserManager.isManagedProfile returns true for all apps in managed user profiles.
+        // Enterprise device can be fully managed like device owner and such use case
+        // also should be supported. Calling app check for work profile and fully managed device
+        // is already done in DevicePolicyManager.
+        // This check is an extra caution to be sure device is fully managed or not.
+        final UserManager um = mContext.getSystemService(UserManager.class);
+        final DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        if (um.isManagedProfile(profile.getIdentifier())) {
+            return true;
+        }
+        if (SdkLevel.isAtLeastT() && dpm.getDeviceOwner() != null) return true;
+        return false;
+    }
+
     /**
-     * Request that a user profile is put by default on a network matching a given preference.
+     * Set a list of default network selection policies for a user profile or device owner.
      *
      * See the documentation for the individual preferences for a description of the supported
      * behaviors.
      *
-     * @param profile the user profile for whih the preference is being set.
+     * @param profile If the device owner is set, any profile is allowed.
+              Otherwise, the given profile can only be managed profile.
      * @param preferences the list of profile network preferences for the
      *        provided profile.
      * @param listener an optional listener to listen for completion of the operation.
@@ -10676,9 +10693,9 @@
             throw new IllegalArgumentException("Must explicitly specify a user handle ("
                     + "UserHandle.CURRENT not supported)");
         }
-        final UserManager um = mContext.getSystemService(UserManager.class);
-        if (!um.isManagedProfile(profile.getIdentifier())) {
-            throw new IllegalArgumentException("Profile must be a managed profile");
+        if (!isNetworkPreferenceAllowedForProfile(profile)) {
+            throw new IllegalArgumentException("Profile must be a managed profile "
+                    + "or the device owner must be set. ");
         }
 
         final List<ProfileNetworkPreferenceList.Preference> preferenceList =
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
index f633df4..7a613b3 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
@@ -16,7 +16,6 @@
 
 package com.android.cts.net;
 
-import android.platform.test.annotations.FlakyTest;
 import android.platform.test.annotations.SecurityTest;
 
 import com.android.ddmlib.Log;
@@ -155,7 +154,6 @@
                 "testBackgroundNetworkAccess_disabled");
     }
 
-    @FlakyTest(bugId=170180675)
     public void testAppIdleMetered_whitelisted() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
                 "testBackgroundNetworkAccess_whitelisted");
@@ -186,7 +184,6 @@
                 "testBackgroundNetworkAccess_disabled");
     }
 
-    @FlakyTest(bugId=170180675)
     public void testAppIdleNonMetered_whitelisted() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
                 "testBackgroundNetworkAccess_whitelisted");
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 6316c72..ed36a4f 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -195,6 +195,7 @@
 import android.app.AppOpsManager;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
 import android.app.usage.NetworkStatsManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -542,6 +543,7 @@
     @Mock NetworkPolicyManager mNetworkPolicyManager;
     @Mock VpnProfileStore mVpnProfileStore;
     @Mock SystemConfigManager mSystemConfigManager;
+    @Mock DevicePolicyManager mDevicePolicyManager;
     @Mock Resources mResources;
     @Mock ClatCoordinator mClatCoordinator;
     @Mock PacProxyManager mPacProxyManager;
@@ -664,6 +666,7 @@
             if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager;
             if (Context.ETHERNET_SERVICE.equals(name)) return mEthernetManager;
             if (Context.NETWORK_POLICY_SERVICE.equals(name)) return mNetworkPolicyManager;
+            if (Context.DEVICE_POLICY_SERVICE.equals(name)) return mDevicePolicyManager;
             if (Context.SYSTEM_CONFIG_SERVICE.equals(name)) return mSystemConfigManager;
             if (Context.NETWORK_STATS_SERVICE.equals(name)) return mStatsManager;
             if (Context.BATTERY_STATS_SERVICE.equals(name)) return mBatteryStatsManager;
@@ -693,6 +696,14 @@
             doReturn(value).when(mUserManager).isManagedProfile(eq(userHandle.getIdentifier()));
         }
 
+        public void setDeviceOwner(@NonNull final UserHandle userHandle, String value) {
+            // This relies on all contexts for a given user returning the same UM mock
+            final DevicePolicyManager dpmMock = createContextAsUser(userHandle, 0 /* flags */)
+                    .getSystemService(DevicePolicyManager.class);
+            doReturn(value).when(dpmMock).getDeviceOwner();
+            doReturn(value).when(mDevicePolicyManager).getDeviceOwner();
+        }
+
         @Override
         public ContentResolver getContentResolver() {
             return mContentResolver;
@@ -14733,12 +14744,42 @@
     public void testProfileNetworkPrefWrongProfile() throws Exception {
         final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID);
         mServiceContext.setWorkProfile(testHandle, false);
-        assertThrows("Should not be able to set a user pref for a non-work profile",
+        mServiceContext.setDeviceOwner(testHandle, null);
+        assertThrows("Should not be able to set a user pref for a non-work profile "
+                + "and non device owner",
                 IllegalArgumentException.class , () ->
                         mCm.setProfileNetworkPreference(testHandle,
                                 PROFILE_NETWORK_PREFERENCE_ENTERPRISE, null, null));
     }
 
+    /**
+     * Make sure requests for per-profile default networking for a device owner is
+     * accepted on T and not accepted on S
+     */
+    @Test
+    public void testProfileNetworkDeviceOwner() throws Exception {
+        final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID);
+        mServiceContext.setWorkProfile(testHandle, false);
+        mServiceContext.setDeviceOwner(testHandle, "deviceOwnerPackage");
+        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
+                new ProfileNetworkPreference.Builder();
+        profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
+        profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
+        final TestOnCompleteListener listener = new TestOnCompleteListener();
+        if (SdkLevel.isAtLeastT()) {
+            mCm.setProfileNetworkPreferences(testHandle,
+                    List.of(profileNetworkPreferenceBuilder.build()),
+                    r -> r.run(), listener);
+        } else {
+            // S should not allow setting preference on device owner
+            assertThrows("Should not be able to set a user pref for a non-work profile on S",
+                    IllegalArgumentException.class , () ->
+                            mCm.setProfileNetworkPreferences(testHandle,
+                                    List.of(profileNetworkPreferenceBuilder.build()),
+                                    r -> r.run(), listener));
+        }
+    }
+
     @Test
     public void testSubIdsClearedWithoutNetworkFactoryPermission() throws Exception {
         mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_DENIED);