Merge "Catch new exceptions from BpfMap" into tm-dev
diff --git a/nearby/framework/java/android/nearby/INearbyManager.aidl b/nearby/framework/java/android/nearby/INearbyManager.aidl
index 3fd5ecc..0291fff 100644
--- a/nearby/framework/java/android/nearby/INearbyManager.aidl
+++ b/nearby/framework/java/android/nearby/INearbyManager.aidl
@@ -31,10 +31,10 @@
     int registerScanListener(in ScanRequest scanRequest, in IScanListener listener,
             String packageName, @nullable String attributionTag);
 
-    void unregisterScanListener(in IScanListener listener);
+    void unregisterScanListener(in IScanListener listener, String packageName, @nullable String attributionTag);
 
     void startBroadcast(in BroadcastRequestParcelable broadcastRequest,
             in IBroadcastListener callback, String packageName, @nullable String attributionTag);
 
-    void stopBroadcast(in IBroadcastListener callback);
+    void stopBroadcast(in IBroadcastListener callback, String packageName, @nullable String attributionTag);
 }
\ No newline at end of file
diff --git a/nearby/framework/java/android/nearby/NearbyManager.java b/nearby/framework/java/android/nearby/NearbyManager.java
index 9073f78..106c290 100644
--- a/nearby/framework/java/android/nearby/NearbyManager.java
+++ b/nearby/framework/java/android/nearby/NearbyManager.java
@@ -70,6 +70,8 @@
         int ERROR = 2;
     }
 
+    private static final String TAG = "NearbyManager";
+
     /**
      * Whether allows Fast Pair to scan.
      *
@@ -204,7 +206,11 @@
                 ScanListenerTransport transport = reference != null ? reference.get() : null;
                 if (transport != null) {
                     transport.unregister();
-                    mService.unregisterScanListener(transport);
+                    mService.unregisterScanListener(transport, mContext.getPackageName(),
+                            mContext.getAttributionTag());
+                } else {
+                    Log.e(TAG, "Cannot stop scan with this callback "
+                            + "because it is never registered.");
                 }
             }
         } catch (RemoteException e) {
@@ -259,7 +265,11 @@
                 BroadcastListenerTransport transport = reference != null ? reference.get() : null;
                 if (transport != null) {
                     transport.unregister();
-                    mService.stopBroadcast(transport);
+                    mService.stopBroadcast(transport, mContext.getPackageName(),
+                            mContext.getAttributionTag());
+                } else {
+                    Log.e(TAG, "Cannot stop broadcast with this callback "
+                            + "because it is never registered.");
                 }
             }
         } catch (RemoteException e) {
diff --git a/nearby/service/java/com/android/server/nearby/NearbyService.java b/nearby/service/java/com/android/server/nearby/NearbyService.java
index 2dee835..b9e98a2 100644
--- a/nearby/service/java/com/android/server/nearby/NearbyService.java
+++ b/nearby/service/java/com/android/server/nearby/NearbyService.java
@@ -110,22 +110,36 @@
     }
 
     @Override
-    public void unregisterScanListener(IScanListener listener) {
+    public void unregisterScanListener(IScanListener listener, String packageName,
+            @Nullable String attributionTag) {
+        // Permissions check
+        enforceBluetoothPrivilegedPermission(mContext);
+        CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag);
+        DiscoveryPermissions.enforceDiscoveryPermission(mContext, identity);
+
         mProviderManager.unregisterScanListener(listener);
     }
 
     @Override
     public void startBroadcast(BroadcastRequestParcelable broadcastRequestParcelable,
             IBroadcastListener listener, String packageName, @Nullable String attributionTag) {
+        // Permissions check
         enforceBluetoothPrivilegedPermission(mContext);
         BroadcastPermissions.enforceBroadcastPermission(
                 mContext, CallerIdentity.fromBinder(mContext, packageName, attributionTag));
+
         mBroadcastProviderManager.startBroadcast(
                 broadcastRequestParcelable.getBroadcastRequest(), listener);
     }
 
     @Override
-    public void stopBroadcast(IBroadcastListener listener) {
+    public void stopBroadcast(IBroadcastListener listener, String packageName,
+            @Nullable String attributionTag) {
+        // Permissions check
+        enforceBluetoothPrivilegedPermission(mContext);
+        CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag);
+        BroadcastPermissions.enforceBroadcastPermission(mContext, identity);
+
         mBroadcastProviderManager.stopBroadcast(listener);
     }
 
diff --git a/nearby/service/java/com/android/server/nearby/provider/BroadcastProviderManager.java b/nearby/service/java/com/android/server/nearby/provider/BroadcastProviderManager.java
index 72fe29a..3fffda5 100644
--- a/nearby/service/java/com/android/server/nearby/provider/BroadcastProviderManager.java
+++ b/nearby/service/java/com/android/server/nearby/provider/BroadcastProviderManager.java
@@ -65,26 +65,26 @@
      */
     public void startBroadcast(BroadcastRequest broadcastRequest, IBroadcastListener listener) {
         synchronized (mLock) {
-            NearbyConfiguration configuration = new NearbyConfiguration();
-            if (!configuration.isPresenceBroadcastLegacyEnabled()) {
-                reportBroadcastStatus(listener, BroadcastCallback.STATUS_FAILURE);
-                return;
-            }
-            if (broadcastRequest.getType() != BroadcastRequest.BROADCAST_TYPE_NEARBY_PRESENCE) {
-                reportBroadcastStatus(listener, BroadcastCallback.STATUS_FAILURE);
-                return;
-            }
-            PresenceBroadcastRequest presenceBroadcastRequest =
-                    (PresenceBroadcastRequest) broadcastRequest;
-            if (presenceBroadcastRequest.getVersion() != BroadcastRequest.PRESENCE_VERSION_V0) {
-                reportBroadcastStatus(listener, BroadcastCallback.STATUS_FAILURE);
-                return;
-            }
-            FastAdvertisement fastAdvertisement = FastAdvertisement.createFromRequest(
-                    presenceBroadcastRequest);
-            byte[] advertisementPackets = fastAdvertisement.toBytes();
-            mBroadcastListener = listener;
             mExecutor.execute(() -> {
+                NearbyConfiguration configuration = new NearbyConfiguration();
+                if (!configuration.isPresenceBroadcastLegacyEnabled()) {
+                    reportBroadcastStatus(listener, BroadcastCallback.STATUS_FAILURE);
+                    return;
+                }
+                if (broadcastRequest.getType() != BroadcastRequest.BROADCAST_TYPE_NEARBY_PRESENCE) {
+                    reportBroadcastStatus(listener, BroadcastCallback.STATUS_FAILURE);
+                    return;
+                }
+                PresenceBroadcastRequest presenceBroadcastRequest =
+                        (PresenceBroadcastRequest) broadcastRequest;
+                if (presenceBroadcastRequest.getVersion() != BroadcastRequest.PRESENCE_VERSION_V0) {
+                    reportBroadcastStatus(listener, BroadcastCallback.STATUS_FAILURE);
+                    return;
+                }
+                FastAdvertisement fastAdvertisement = FastAdvertisement.createFromRequest(
+                        presenceBroadcastRequest);
+                byte[] advertisementPackets = fastAdvertisement.toBytes();
+                mBroadcastListener = listener;
                 mBleBroadcastProvider.start(advertisementPackets, this);
             });
         }
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 6824ca6..483bfe8 100644
--- a/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
+++ b/nearby/tests/cts/fastpair/src/android/nearby/cts/NearbyManagerTest.java
@@ -128,6 +128,13 @@
 
     @Test
     @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void test_stopScan_noPrivilegedPermission() {
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mNearbyManager.stopScan(mScanCallback));
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
     public void testStartStopBroadcast() throws InterruptedException {
         PrivateCredential credential = new PrivateCredential.Builder(SECRETE_ID, AUTHENTICITY_KEY,
                 META_DATA_ENCRYPTION_KEY, DEVICE_NAME)
diff --git a/nearby/tests/integration/untrusted/Android.bp b/nearby/tests/integration/untrusted/Android.bp
index 53dbfb7..57499e4 100644
--- a/nearby/tests/integration/untrusted/Android.bp
+++ b/nearby/tests/integration/untrusted/Android.bp
@@ -21,10 +21,14 @@
     defaults: ["mts-target-sdk-version-current"],
     sdk_version: "test_current",
 
-    srcs: ["src/**/*.kt"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
     static_libs: [
         "androidx.test.ext.junit",
         "androidx.test.rules",
+        "androidx.test.uiautomator_uiautomator",
         "junit",
         "kotlin-test",
         "truth-prebuilt",
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 3bfac6d..7bf9f63 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
@@ -28,12 +28,14 @@
 import android.nearby.ScanRequest
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.uiautomator.LogcatWaitMixin
 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
+import java.time.Duration
+import java.util.Calendar
 
 @RunWith(AndroidJUnit4::class)
 class NearbyManagerTest {
@@ -75,13 +77,9 @@
         }
     }
 
-    /**
-     * Verify untrusted app can't stop scan because it needs BLUETOOTH_PRIVILEGED
-     * permission which is not for use by third-party applications.
-     */
+    /** Verify untrusted app can't stop scan because it never successfully registers a callback. */
     @Test
-    @Ignore("Permission check for stopXXX not yet implement: b/229338477#comment24")
-    fun testNearbyManagerStopScan_fromUnTrustedApp_throwsException() {
+    fun testNearbyManagerStopScan_fromUnTrustedApp_logsError() {
         val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
         val scanCallback = object : ScanCallback {
             override fun onDiscovered(device: NearbyDevice) {}
@@ -90,10 +88,17 @@
 
             override fun onLost(device: NearbyDevice) {}
         }
+        val startTime = Calendar.getInstance().time
 
-        assertThrows(SecurityException::class.java) {
-            nearbyManager.stopScan(scanCallback)
-        }
+        nearbyManager.stopScan(scanCallback)
+
+        assertThat(
+            LogcatWaitMixin().waitForSpecificLog(
+                "Cannot stop scan with this callback because it is never registered.",
+                startTime,
+                WAIT_INVALID_OPERATIONS_LOGS_TIMEOUT
+            )
+        ).isTrue()
     }
 
     /**
@@ -127,17 +132,26 @@
     }
 
     /**
-     * Verify untrusted app can't stop broadcast because it needs BLUETOOTH_PRIVILEGED
-     * permission which is not for use by third-party applications.
+     * Verify untrusted app can't stop broadcast because it never successfully registers a callback.
      */
     @Test
-    @Ignore("Permission check for stopXXX not yet implement: b/229338477#comment24")
-    fun testNearbyManagerStopBroadcast_fromUnTrustedApp_throwsException() {
+    fun testNearbyManagerStopBroadcast_fromUnTrustedApp_logsError() {
         val nearbyManager = appContext.getSystemService(Context.NEARBY_SERVICE) as NearbyManager
         val broadcastCallback = BroadcastCallback { }
+        val startTime = Calendar.getInstance().time
 
-        assertThrows(SecurityException::class.java) {
-            nearbyManager.stopBroadcast(broadcastCallback)
-        }
+        nearbyManager.stopBroadcast(broadcastCallback)
+
+        assertThat(
+            LogcatWaitMixin().waitForSpecificLog(
+                "Cannot stop broadcast with this callback because it is never registered.",
+                startTime,
+                WAIT_INVALID_OPERATIONS_LOGS_TIMEOUT
+            )
+        ).isTrue()
+    }
+
+    companion object {
+        private val WAIT_INVALID_OPERATIONS_LOGS_TIMEOUT = Duration.ofSeconds(5)
     }
 }
diff --git a/nearby/tests/integration/untrusted/src/androidx/test/uiautomator/LogcatParser.kt b/nearby/tests/integration/untrusted/src/androidx/test/uiautomator/LogcatParser.kt
new file mode 100644
index 0000000..604e6df
--- /dev/null
+++ b/nearby/tests/integration/untrusted/src/androidx/test/uiautomator/LogcatParser.kt
@@ -0,0 +1,52 @@
+/*
+ * 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 androidx.test.uiautomator
+
+import java.text.SimpleDateFormat
+import java.util.Date
+import java.util.Locale
+
+/** A parser for logcat logs processing. */
+object LogcatParser {
+    private val LOGCAT_LOGS_PATTERN = "^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}.\\d{3} ".toRegex()
+    private const val LOGCAT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSS"
+
+    /**
+     * Filters out the logcat logs which contains specific log and appears not before specific time.
+     *
+     * @param logcatLogs the concatenated logcat logs to filter
+     * @param specificLog the log string expected to appear
+     * @param startTime the time point to start finding the specific log
+     * @return a list of logs that match the condition
+     */
+    fun findSpecificLogAfter(
+        logcatLogs: String,
+        specificLog: String,
+        startTime: Date
+    ): List<String> = logcatLogs.split("\n")
+        .filter { it.contains(specificLog) && !parseLogTime(it)!!.before(startTime) }
+
+    /**
+     * Parses the logcat log string to extract the timestamp.
+     *
+     * @param logString the log string to parse
+     * @return the timestamp of the log
+     */
+    private fun parseLogTime(logString: String): Date? =
+        SimpleDateFormat(LOGCAT_DATE_FORMAT, Locale.US)
+            .parse(LOGCAT_LOGS_PATTERN.find(logString)!!.value)
+}
diff --git a/nearby/tests/integration/untrusted/src/androidx/test/uiautomator/LogcatWaitMixin.java b/nearby/tests/integration/untrusted/src/androidx/test/uiautomator/LogcatWaitMixin.java
new file mode 100644
index 0000000..86e39dc
--- /dev/null
+++ b/nearby/tests/integration/untrusted/src/androidx/test/uiautomator/LogcatWaitMixin.java
@@ -0,0 +1,71 @@
+/*
+ * 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 androidx.test.uiautomator;
+
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.util.Date;
+
+/** A helper class to wait the specific log appear in the logcat logs. */
+public class LogcatWaitMixin extends WaitMixin<UiDevice> {
+
+    private static final String LOG_TAG = LogcatWaitMixin.class.getSimpleName();
+
+    public LogcatWaitMixin() {
+        this(UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()));
+    }
+
+    public LogcatWaitMixin(UiDevice device) {
+        super(device);
+    }
+
+    /**
+     * Waits the {@code specificLog} appear in the logcat logs after the specific {@code startTime}.
+     *
+     * @param waitTime the maximum time for waiting
+     * @return true if the specific log appear within timeout and after the startTime
+     */
+    public boolean waitForSpecificLog(
+            @NonNull String specificLog, @NonNull Date startTime, @NonNull Duration waitTime) {
+        return wait(createWaitCondition(specificLog, startTime), waitTime.toMillis());
+    }
+
+    @NonNull
+    Condition<UiDevice, Boolean> createWaitCondition(
+            @NonNull String specificLog, @NonNull Date startTime) {
+        return new Condition<UiDevice, Boolean>() {
+            @Override
+            Boolean apply(UiDevice device) {
+                String logcatLogs;
+                try {
+                    logcatLogs = device.executeShellCommand("logcat -v time -v year -d");
+                } catch (IOException e) {
+                    Log.e(LOG_TAG, "Fail to dump logcat logs on the device!", e);
+                    return Boolean.FALSE;
+                }
+                return !LogcatParser.INSTANCE
+                        .findSpecificLogAfter(logcatLogs, specificLog, startTime)
+                        .isEmpty();
+            }
+        };
+    }
+}
diff --git a/nearby/tests/multidevices/README.md b/nearby/tests/multidevices/README.md
new file mode 100644
index 0000000..1208451
--- /dev/null
+++ b/nearby/tests/multidevices/README.md
@@ -0,0 +1,145 @@
+# Nearby Mainline Fast Pair end-to-end tests
+
+This document refers to the Mainline Fast Pair project source code in the
+packages/modules/Connectivity/nearby. This is not an officially supported Google
+product.
+
+## About the Fast Pair Project
+
+The Connectivity Nearby mainline module is created in the Android T to host
+Better Together related functionality. Fast Pair is one of the main
+functionalities to provide seamless onboarding and integrated experiences for
+peripheral devices (for example, headsets like Google Pixel Buds) in the Nearby
+component.
+
+## Fully automated test
+
+### Prerequisites
+
+The fully automated end-to-end (e2e) tests are host-driven tests (which means
+test logics are in the host test scripts) using Mobly runner in Python. The two
+phones are installed with the test snippet
+`NearbyMultiDevicesClientsSnippets.apk` in the test time to let the host scripts
+control both sides for testing. Here's the overview of the test environment.
+
+Workstation (runs Python test scripts and controls Android devices through USB
+ADB) \
+├── Phone 1: As Fast Pair seeker role, to scan, pair Fast Pair devices nearby \
+└── Phone 2: As Fast Pair provider role, to simulate a Fast Pair device (for
+example, a Bluetooth headset)
+
+Note: These two phones need to be physically within 0.3 m of each other.
+
+### Prepare Phone 1 (Fast Pair seeker role)
+
+This is the phone to scan/pair Fast Pair devices nearby using the Nearby
+Mainline module. Test it by flashing with the Android T ROM.
+
+### Prepare Phone 2 (Fast Pair provider role)
+
+This is the phone to simulate a Fast Pair device (for example, a Bluetooth
+headset). Flash it with a customized ROM with the following changes:
+
+*   Adjust Bluetooth profile configurations. \
+    The Fast Pair provider simulator is an opposite role to the seeker. It needs
+    to enable/disable the following Bluetooth profile:
+    *   Disable A2DP (profile_supported_a2dp)
+    *   Disable the AVRCP controller (profile_supported_avrcp_controller)
+    *   Enable A2DP sink (profile_supported_a2dp_sink)
+    *   Enable the HFP client connection service (profile_supported_hfpclient,
+        hfp_client_connection_service_enabled)
+    *   Enable the AVRCP target (profile_supported_avrcp_target)
+    *   Enable the automatic audio focus request
+        (a2dp_sink_automatically_request_audio_focus)
+*   Adjust Bluetooth TX power limitation in Bluetooth module and disable the
+    Fast Pair in Google Play service (aka GMS)
+
+```shell
+adb root
+adb shell am broadcast \
+  -a 'com.google.android.gms.phenotype.FLAG_OVERRIDE' \
+  --es package "com.google.android.gms.nearby" \
+  --es user "\*" \
+  --esa flags "enabled" \
+  --esa types "boolean" \
+  --esa values "false" \
+  com.google.android.gms
+```
+
+### Running tests
+
+To run the tests, enter:
+
+```shell
+atest -v CtsNearbyMultiDevicesTestSuite
+```
+
+## Manual testing the seeker side with headsets
+
+Use this testing with headsets such as Google Pixel buds.
+
+The `FastPairTestDataProviderService.apk` is a run-time configurable Fast Pair
+data provider service (`FastPairDataProviderService`):
+
+`packages/modules/Connectivity/nearby/tests/multidevices/clients/test_service/fastpair_seeker_data_provider`
+
+It has a test data manager(`FastPairTestDataManager`) to receive intent
+broadcasts to add or clear the test data cache (`FastPairTestDataCache`). This
+cache provides the data to return to the Fast Pair module for onXXX calls (for
+example, `onLoadFastPairAntispoofKeyDeviceMetadata`) so you can feed the
+metadata for your device.
+
+Here are some sample uses:
+
+*   Send FastPairAntispoofKeyDeviceMetadata for PixelBuds-A to
+    FastPairTestDataCache \
+    `./fast_pair_data_provider_shell.sh -m=718c17
+    -a=../test_data/fastpair/pixelbuds-a_antispoofkey_devicemeta_json.txt`
+*   Send FastPairAccountDevicesMetadata for PixelBuds-A to FastPairTestDataCache
+    \
+    `./fast_pair_data_provider_shell.sh
+    -d=../test_data/fastpair/pixelbuds-a_account_devicemeta_json.txt`
+*   Send FastPairAntispoofKeyDeviceMetadata for Provider Simulator to
+    FastPairTestDataCache \
+    `./fast_pair_data_provider_shell.sh -m=00000c
+    -a=../test_data/fastpair/simulator_antispoofkey_devicemeta_json.txt`
+*   Send FastPairAccountDevicesMetadata for Provider Simulator to
+    FastPairTestDataCache \
+    `./fast_pair_data_provider_shell.sh
+    -d=../test_data/fastpair/simulator_account_devicemeta_json.txt`
+*   Clear FastPairTestDataCache \
+    `./fast_pair_data_provider_shell.sh -c`
+
+See
+[host/tool/fast_pair_data_provider_shell.sh](host/tool/fast_pair_data_provider_shell.sh)
+for more documentation.
+
+To install the data provider as system private app, consider remounting the
+system partition:
+
+```
+adb root && adb remount
+```
+
+Push it in:
+
+```
+adb push ${ANDROID_PRODUCT_OUT}/system/app/NearbyFastPairSeekerDataProvider
+/system/priv-app/
+```
+
+Then reboot:
+
+```
+adb reboot
+```
+
+## Manual testing the seeker side with provider simulator app
+
+The `NearbyFastPairProviderSimulatorApp.apk` is a simple Android app to let you
+control the state of the Fast Pair provider simulator. Install this app on phone
+2 (Fast Pair provider role) to work correctly.
+
+See
+[clients/test_support/fastpair_provider/simulator_ap/Android.bp](clients/test_support/fastpair_provider/simulator_ap/Android.bp)
+for more documentation.
diff --git a/nearby/tests/unit/src/com/android/server/nearby/NearbyServiceTest.java b/nearby/tests/unit/src/com/android/server/nearby/NearbyServiceTest.java
index e250254..8a18cca 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/NearbyServiceTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/NearbyServiceTest.java
@@ -87,8 +87,19 @@
     }
 
     @Test
+    public void test_unregister_noPrivilegedPermission_throwsException() {
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(java.lang.SecurityException.class,
+                () -> mService.unregisterScanListener(mScanListener, PACKAGE_NAME,
+                        /* attributionTag= */ null));
+    }
+
+    @Test
     public void test_unregister() {
-        mService.unregisterScanListener(mScanListener);
+        setMockInjector(/* isMockOpsAllowed= */ true);
+        mService.registerScanListener(mScanRequest, mScanListener, PACKAGE_NAME,
+                /* attributionTag= */ null);
+        mService.unregisterScanListener(mScanListener,  PACKAGE_NAME, /* attributionTag= */ null);
     }
 
     private ScanRequest createScanRequest() {
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index 217a9a6..b2d8b5e 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -159,8 +159,11 @@
 import java.time.ZoneOffset;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
@@ -374,9 +377,19 @@
 
     private long mLastStatsSessionPoll;
 
-    /** Map from UID to number of opened sessions */
-    @GuardedBy("mOpenSessionCallsPerUid")
+    private final Object mOpenSessionCallsLock = new Object();
+    /**
+     * Map from UID to number of opened sessions. This is used for rate-limt an app to open
+     * session frequently
+     */
+    @GuardedBy("mOpenSessionCallsLock")
     private final SparseIntArray mOpenSessionCallsPerUid = new SparseIntArray();
+    /**
+     * Map from key {@code OpenSessionKey} to count of opened sessions. This is for recording
+     * the caller of open session and it is only for debugging.
+     */
+    @GuardedBy("mOpenSessionCallsLock")
+    private final HashMap<OpenSessionKey, Integer> mOpenSessionCallsPerCaller = new HashMap<>();
 
     private final static int DUMP_STATS_SESSION_COUNT = 20;
 
@@ -407,6 +420,44 @@
                 Clock.systemUTC());
     }
 
+    /**
+     * This class is a key that used in {@code mOpenSessionCallsPerCaller} to identify the count of
+     * the caller.
+     */
+    private static class OpenSessionKey {
+        public final int uid;
+        public final String packageName;
+
+        OpenSessionKey(int uid, @NonNull String packageName) {
+            this.uid = uid;
+            this.packageName = packageName;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder sb = new StringBuilder();
+            sb.append("{");
+            sb.append("uid=").append(uid).append(",");
+            sb.append("package=").append(packageName);
+            sb.append("}");
+            return sb.toString();
+        }
+
+        @Override
+        public boolean equals(@NonNull Object o) {
+            if (this == o) return true;
+            if (o.getClass() != getClass()) return false;
+
+            final OpenSessionKey key = (OpenSessionKey) o;
+            return this.uid == key.uid && TextUtils.equals(this.packageName, key.packageName);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(uid, packageName);
+        }
+    }
+
     private final class NetworkStatsHandler extends Handler {
         NetworkStatsHandler(@NonNull Looper looper) {
             super(looper);
@@ -794,16 +845,27 @@
         return openSessionInternal(flags, callingPackage);
     }
 
-    private boolean isRateLimitedForPoll(int callingUid) {
-        if (callingUid == android.os.Process.SYSTEM_UID) {
-            return false;
-        }
-
+    private boolean isRateLimitedForPoll(@NonNull OpenSessionKey key) {
         final long lastCallTime;
         final long now = SystemClock.elapsedRealtime();
-        synchronized (mOpenSessionCallsPerUid) {
-            int calls = mOpenSessionCallsPerUid.get(callingUid, 0);
-            mOpenSessionCallsPerUid.put(callingUid, calls + 1);
+
+        synchronized (mOpenSessionCallsLock) {
+            Integer callsPerCaller = mOpenSessionCallsPerCaller.get(key);
+            if (callsPerCaller == null) {
+                mOpenSessionCallsPerCaller.put((key), 1);
+            } else {
+                mOpenSessionCallsPerCaller.put(key, Integer.sum(callsPerCaller, 1));
+            }
+
+            int callsPerUid = mOpenSessionCallsPerUid.get(key.uid, 0);
+            mOpenSessionCallsPerUid.put(key.uid, callsPerUid + 1);
+
+            if (key.uid == android.os.Process.SYSTEM_UID) {
+                return false;
+            }
+
+            // To avoid a non-system user to be rate-limited after system users open sessions,
+            // so update mLastStatsSessionPoll after checked if the uid is SYSTEM_UID.
             lastCallTime = mLastStatsSessionPoll;
             mLastStatsSessionPoll = now;
         }
@@ -811,7 +873,7 @@
         return now - lastCallTime < POLL_RATE_LIMIT_MS;
     }
 
-    private int restrictFlagsForCaller(int flags) {
+    private int restrictFlagsForCaller(int flags, @NonNull String callingPackage) {
         // All non-privileged callers are not allowed to turn off POLL_ON_OPEN.
         final boolean isPrivileged = PermissionUtils.checkAnyPermissionOf(mContext,
                 NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
@@ -821,14 +883,15 @@
         }
         // Non-system uids are rate limited for POLL_ON_OPEN.
         final int callingUid = Binder.getCallingUid();
-        flags = isRateLimitedForPoll(callingUid)
+        final OpenSessionKey key = new OpenSessionKey(callingUid, callingPackage);
+        flags = isRateLimitedForPoll(key)
                 ? flags & (~NetworkStatsManager.FLAG_POLL_ON_OPEN)
                 : flags;
         return flags;
     }
 
     private INetworkStatsSession openSessionInternal(final int flags, final String callingPackage) {
-        final int restrictedFlags = restrictFlagsForCaller(flags);
+        final int restrictedFlags = restrictFlagsForCaller(flags, callingPackage);
         if ((restrictedFlags & (NetworkStatsManager.FLAG_POLL_ON_OPEN
                 | NetworkStatsManager.FLAG_POLL_FORCE)) != 0) {
             final long ident = Binder.clearCallingIdentity();
@@ -1938,6 +2001,9 @@
         for (int uid : uids) {
             deleteKernelTagData(uid);
         }
+
+       // TODO: Remove the UID's entries from mOpenSessionCallsPerUid and
+       // mOpenSessionCallsPerCaller
     }
 
     /**
@@ -2061,25 +2127,21 @@
             pw.decreaseIndent();
 
             // Get the top openSession callers
-            final SparseIntArray calls;
-            synchronized (mOpenSessionCallsPerUid) {
-                calls = mOpenSessionCallsPerUid.clone();
+            final HashMap calls;
+            synchronized (mOpenSessionCallsLock) {
+                calls = new HashMap<>(mOpenSessionCallsPerCaller);
             }
-
-            final int N = calls.size();
-            final long[] values = new long[N];
-            for (int j = 0; j < N; j++) {
-                values[j] = ((long) calls.valueAt(j) << 32) | calls.keyAt(j);
-            }
-            Arrays.sort(values);
-
-            pw.println("Top openSession callers (uid=count):");
+            final List<Map.Entry<OpenSessionKey, Integer>> list = new ArrayList<>(calls.entrySet());
+            Collections.sort(list,
+                    (left, right) -> Integer.compare(left.getValue(), right.getValue()));
+            final int num = list.size();
+            final int end = Math.max(0, num - DUMP_STATS_SESSION_COUNT);
+            pw.println("Top openSession callers:");
             pw.increaseIndent();
-            final int end = Math.max(0, N - DUMP_STATS_SESSION_COUNT);
-            for (int j = N - 1; j >= end; j--) {
-                final int uid = (int) (values[j] & 0xffffffff);
-                final int count = (int) (values[j] >> 32);
-                pw.print(uid); pw.print("="); pw.println(count);
+            for (int j = num - 1; j >= end; j--) {
+                final Map.Entry<OpenSessionKey, Integer> entry = list.get(j);
+                pw.print(entry.getKey()); pw.print("="); pw.println(entry.getValue());
+
             }
             pw.decreaseIndent();
             pw.println();
diff --git a/service/ServiceConnectivityResources/Android.bp b/service/ServiceConnectivityResources/Android.bp
index f491cc7..02b2875 100644
--- a/service/ServiceConnectivityResources/Android.bp
+++ b/service/ServiceConnectivityResources/Android.bp
@@ -23,6 +23,7 @@
     name: "ServiceConnectivityResources",
     sdk_version: "module_30",
     min_sdk_version: "30",
+    target_sdk_version: "33",
     resource_dirs: [
         "res",
     ],
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index 48a1c79..33f3af5 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -38,4 +38,20 @@
         <option name="hidden-api-checks" value="false" />
         <option name="isolated-storage" value="false" />
     </test>
+    <!-- When this test is run in a Mainline context (e.g. with `mts-tradefed`), only enable it if
+        one of the Mainline modules below is present on the device used for testing. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <!-- Tethering Module (internal version). -->
+        <option name="mainline-module-package-name" value="com.google.android.tethering" />
+        <!-- Tethering Module (AOSP version). -->
+        <option name="mainline-module-package-name" value="com.android.tethering" />
+        <!-- NetworkStack Module (internal version). Should always be installed with CaptivePortalLogin. -->
+        <option name="mainline-module-package-name" value="com.google.android.networkstack" />
+        <!-- NetworkStack Module (AOSP version). Should always be installed with CaptivePortalLogin. -->
+        <option name="mainline-module-package-name" value="com.android.networkstack" />
+        <!-- Resolver Module (internal version). -->
+        <option name="mainline-module-package-name" value="com.google.android.resolv" />
+        <!-- Resolver Module (AOSP version). -->
+        <option name="mainline-module-package-name" value="com.android.resolv" />
+    </object>
 </configuration>
diff --git a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
index de4f41b..d618915 100644
--- a/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkStatsManagerTest.java
@@ -35,7 +35,14 @@
 
 import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import android.app.AppOpsManager;
+import android.app.Instrumentation;
 import android.app.usage.NetworkStats;
 import android.app.usage.NetworkStatsManager;
 import android.content.Context;
@@ -56,15 +63,24 @@
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
 import android.telephony.TelephonyManager;
-import android.test.InstrumentationTestCase;
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
 import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.SystemUtil;
 import com.android.modules.utils.build.SdkLevel;
+import com.android.testutils.ConnectivityModuleTest;
 import com.android.testutils.DevSdkIgnoreRule;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -78,7 +94,13 @@
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
-public class NetworkStatsManagerTest extends InstrumentationTestCase {
+@ConnectivityModuleTest
+@AppModeFull(reason = "instant apps cannot be granted USAGE_STATS")
+@RunWith(AndroidJUnit4.class)
+public class NetworkStatsManagerTest {
+    @Rule
+    public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule(SC_V2 /* ignoreClassUpTo */);
+
     private static final String LOG_TAG = "NetworkStatsManagerTest";
     private static final String APPOPS_SET_SHELL_COMMAND = "appops set {0} {1} {2}";
     private static final String APPOPS_GET_SHELL_COMMAND = "appops get {0} {1}";
@@ -179,9 +201,11 @@
             };
 
     private String mPkg;
+    private Context mContext;
     private NetworkStatsManager mNsm;
     private ConnectivityManager mCm;
     private PackageManager mPm;
+    private Instrumentation mInstrumentation;
     private long mStartTime;
     private long mEndTime;
 
@@ -239,44 +263,40 @@
         }
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mNsm = (NetworkStatsManager) getInstrumentation().getContext()
-                .getSystemService(Context.NETWORK_STATS_SERVICE);
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        mNsm = mContext.getSystemService(NetworkStatsManager.class);
         mNsm.setPollForce(true);
 
-        mCm = (ConnectivityManager) getInstrumentation().getContext()
-                .getSystemService(Context.CONNECTIVITY_SERVICE);
+        mCm = mContext.getSystemService(ConnectivityManager.class);
+        mPm = mContext.getPackageManager();
+        mPkg = mContext.getPackageName();
 
-        mPm = getInstrumentation().getContext().getPackageManager();
-
-        mPkg = getInstrumentation().getContext().getPackageName();
-
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mWriteSettingsMode = getAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS);
         setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, "allow");
         mUsageStatsMode = getAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         if (mWriteSettingsMode != null) {
             setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, mWriteSettingsMode);
         }
         if (mUsageStatsMode != null) {
             setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, mUsageStatsMode);
         }
-        super.tearDown();
     }
 
     private void setAppOpsMode(String appop, String mode) throws Exception {
         final String command = MessageFormat.format(APPOPS_SET_SHELL_COMMAND, mPkg, appop, mode);
-        SystemUtil.runShellCommand(command);
+        SystemUtil.runShellCommand(mInstrumentation, command);
     }
 
     private String getAppOpsMode(String appop) throws Exception {
         final String command = MessageFormat.format(APPOPS_GET_SHELL_COMMAND, mPkg, appop);
-        String result = SystemUtil.runShellCommand(command);
+        String result = SystemUtil.runShellCommand(mInstrumentation, command);
         if (result == null) {
             Log.w(LOG_TAG, "App op " + appop + " could not be read.");
         }
@@ -284,7 +304,7 @@
     }
 
     private boolean isInForeground() throws IOException {
-        String result = SystemUtil.runShellCommand(getInstrumentation(),
+        String result = SystemUtil.runShellCommand(mInstrumentation,
                 "cmd activity get-uid-state " + Process.myUid());
         return result.contains("FOREGROUND");
     }
@@ -381,15 +401,14 @@
     private String getSubscriberId(int networkIndex) {
         int networkType = mNetworkInterfacesToTest[networkIndex].getNetworkType();
         if (ConnectivityManager.TYPE_MOBILE == networkType) {
-            TelephonyManager tm = (TelephonyManager) getInstrumentation().getContext()
-                    .getSystemService(Context.TELEPHONY_SERVICE);
+            TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
             return ShellIdentityUtils.invokeMethodWithShellPermissions(tm,
                     (telephonyManager) -> telephonyManager.getSubscriberId());
         }
         return "";
     }
 
-    @AppModeFull
+    @Test
     public void testDeviceSummary() throws Exception {
         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
             if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
@@ -425,7 +444,7 @@
         }
     }
 
-    @AppModeFull
+    @Test
     public void testUserSummary() throws Exception {
         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
             if (!shouldTestThisNetworkType(i, MINUTE / 2)) {
@@ -461,7 +480,7 @@
         }
     }
 
-    @AppModeFull
+    @Test
     public void testAppSummary() throws Exception {
         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
             // Use tolerance value that large enough to make sure stats of at
@@ -537,7 +556,7 @@
         }
     }
 
-    @AppModeFull
+    @Test
     public void testAppDetails() throws Exception {
         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
             // Relatively large tolerance to accommodate for history bucket size.
@@ -580,7 +599,7 @@
         }
     }
 
-    @AppModeFull
+    @Test
     public void testUidDetails() throws Exception {
         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
             // Relatively large tolerance to accommodate for history bucket size.
@@ -634,7 +653,7 @@
         }
     }
 
-    @AppModeFull
+    @Test
     public void testTagDetails() throws Exception {
         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
             // Relatively large tolerance to accommodate for history bucket size.
@@ -741,7 +760,7 @@
                 bucket.getRxBytes(), bucket.getTxBytes()));
     }
 
-    @AppModeFull
+    @Test
     public void testUidTagStateDetails() throws Exception {
         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
             // Relatively large tolerance to accommodate for history bucket size.
@@ -818,7 +837,7 @@
         }
     }
 
-    @AppModeFull
+    @Test
     public void testCallback() throws Exception {
         for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
             // Relatively large tolerance to accommodate for history bucket size.
@@ -851,9 +870,10 @@
         }
     }
 
-    @AppModeFull
-    @DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
+    @Test
     public void testDataMigrationUtils() throws Exception {
+        if (!SdkLevel.isAtLeastT()) return;
+
         final List<String> prefixes = List.of(PREFIX_UID, PREFIX_XT, PREFIX_UID_TAG);
         for (final String prefix : prefixes) {
             final long duration = TextUtils.equals(PREFIX_XT, prefix) ? TimeUnit.HOURS.toMillis(1)
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index c44fc6c..b7da17b 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -805,6 +805,32 @@
         }
     }
 
+    // This was only added in the T SDK, but this test needs to build against the R+S SDKs, too.
+    private static int toSdkSandboxUid(int appUid) {
+        final int firstSdkSandboxUid = 20000;
+        return appUid + (firstSdkSandboxUid - Process.FIRST_APPLICATION_UID);
+    }
+
+    // This function assumes the UID range for user 0 ([1, 99999])
+    private static UidRangeParcel[] uidRangeParcelsExcludingUids(Integer... excludedUids) {
+        int start = 1;
+        Arrays.sort(excludedUids);
+        List<UidRangeParcel> parcels = new ArrayList<UidRangeParcel>();
+        for (int excludedUid : excludedUids) {
+            if (excludedUid == start) {
+                start++;
+            } else {
+                parcels.add(new UidRangeParcel(start, excludedUid - 1));
+                start = excludedUid + 1;
+            }
+        }
+        if (start <= 99999) {
+            parcels.add(new UidRangeParcel(start, 99999));
+        }
+
+        return parcels.toArray(new UidRangeParcel[0]);
+    }
+
     private void waitForIdle() {
         HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);
         waitForIdle(mCellNetworkAgent, TIMEOUT_MS);
@@ -9016,10 +9042,16 @@
                 allowList);
         waitForIdle();
 
-        UidRangeParcel firstHalf = new UidRangeParcel(1, VPN_UID - 1);
-        UidRangeParcel secondHalf = new UidRangeParcel(VPN_UID + 1, 99999);
+        final Set<Integer> excludedUids = new ArraySet<Integer>();
+        excludedUids.add(VPN_UID);
+        if (SdkLevel.isAtLeastT()) {
+            // On T onwards, the corresponding SDK sandbox UID should also be excluded
+            excludedUids.add(toSdkSandboxUid(VPN_UID));
+        }
+        final UidRangeParcel[] uidRangeParcels = uidRangeParcelsExcludingUids(
+                excludedUids.toArray(new Integer[0]));
         InOrder inOrder = inOrder(mMockNetd);
-        expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf);
+        expectNetworkRejectNonSecureVpn(inOrder, true, uidRangeParcels);
 
         // Connect a network when lockdown is active, expect to see it blocked.
         mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
@@ -9043,7 +9075,7 @@
         vpnUidCallback.assertNoCallback();
         vpnUidDefaultCallback.assertNoCallback();
         vpnDefaultCallbackAsUid.assertNoCallback();
-        expectNetworkRejectNonSecureVpn(inOrder, false, firstHalf, secondHalf);
+        expectNetworkRejectNonSecureVpn(inOrder, false, uidRangeParcels);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -9060,13 +9092,14 @@
         vpnUidDefaultCallback.assertNoCallback();
         vpnDefaultCallbackAsUid.assertNoCallback();
 
-        // The following requires that the UID of this test package is greater than VPN_UID. This
-        // is always true in practice because a plain AOSP build with no apps installed has almost
-        // 200 packages installed.
-        final UidRangeParcel piece1 = new UidRangeParcel(1, VPN_UID - 1);
-        final UidRangeParcel piece2 = new UidRangeParcel(VPN_UID + 1, uid - 1);
-        final UidRangeParcel piece3 = new UidRangeParcel(uid + 1, 99999);
-        expectNetworkRejectNonSecureVpn(inOrder, true, piece1, piece2, piece3);
+        excludedUids.add(uid);
+        if (SdkLevel.isAtLeastT()) {
+            // On T onwards, the corresponding SDK sandbox UID should also be excluded
+            excludedUids.add(toSdkSandboxUid(uid));
+        }
+        final UidRangeParcel[] uidRangeParcelsAlsoExcludingUs = uidRangeParcelsExcludingUids(
+                excludedUids.toArray(new Integer[0]));
+        expectNetworkRejectNonSecureVpn(inOrder, true, uidRangeParcelsAlsoExcludingUs);
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
@@ -9092,12 +9125,12 @@
         // Everything should now be blocked.
         mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
         waitForIdle();
-        expectNetworkRejectNonSecureVpn(inOrder, false, piece1, piece2, piece3);
+        expectNetworkRejectNonSecureVpn(inOrder, false, uidRangeParcelsAlsoExcludingUs);
         allowList.clear();
         mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */,
                 allowList);
         waitForIdle();
-        expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf);
+        expectNetworkRejectNonSecureVpn(inOrder, true, uidRangeParcels);
         defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent);
         assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent);
         vpnUidCallback.assertNoCallback();
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 33c0868..46e7dac 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -154,7 +154,7 @@
  */
 @RunWith(DevSdkIgnoreRunner.class)
 @SmallTest
-@DevSdkIgnoreRule.IgnoreUpTo(VERSION_CODES.R)
+@DevSdkIgnoreRule.IgnoreUpTo(VERSION_CODES.S_V2)
 public class VpnTest {
     private static final String TAG = "VpnTest";
 
@@ -188,7 +188,7 @@
      *  - One pair of packages have consecutive UIDs.
      */
     static final String[] PKGS = {"com.example", "org.example", "net.example", "web.vpn"};
-    static final int[] PKG_UIDS = {66, 77, 78, 400};
+    static final int[] PKG_UIDS = {10066, 10077, 10078, 10400};
 
     // Mock packages
     static final Map<String, Integer> mPackages = new ArrayMap<>();
@@ -345,7 +345,11 @@
                 Arrays.asList(packages), null /* disallowedApplications */);
         assertEquals(rangeSet(
                 uidRange(userStart + PKG_UIDS[0], userStart + PKG_UIDS[0]),
-                uidRange(userStart + PKG_UIDS[1], userStart + PKG_UIDS[2])),
+                uidRange(userStart + PKG_UIDS[1], userStart + PKG_UIDS[2]),
+                uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[0]),
+                         Process.toSdkSandboxUid(userStart + PKG_UIDS[0])),
+                uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[1]),
+                         Process.toSdkSandboxUid(userStart + PKG_UIDS[2]))),
                 allow);
 
         // Denied list
@@ -356,7 +360,11 @@
                 uidRange(userStart, userStart + PKG_UIDS[0] - 1),
                 uidRange(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1),
                 /* Empty range between UIDS[1] and UIDS[2], should be excluded, */
-                uidRange(userStart + PKG_UIDS[2] + 1, userStop)),
+                uidRange(userStart + PKG_UIDS[2] + 1,
+                         Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+                         Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                uidRange(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)),
                 disallow);
     }
 
@@ -397,18 +405,24 @@
         assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null));
         verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStop)
+                new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
         }));
 
         // Switch to another app.
         assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
         verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStop)
+                new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
         }));
         verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(userStart, userStart + PKG_UIDS[3] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[3] + 1, userStop)
+                new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop)
         }));
     }
 
@@ -423,17 +437,25 @@
                 PKGS[1], true, Collections.singletonList(PKGS[2])));
         verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[]  {
                 new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[2] + 1, userStop)
+                new UidRangeParcel(userStart + PKG_UIDS[2] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1]) - 1),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)
         }));
         // Change allowed app list to PKGS[3].
         assertTrue(vpn.setAlwaysOnPackage(
                 PKGS[1], true, Collections.singletonList(PKGS[3])));
         verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
-                new UidRangeParcel(userStart + PKG_UIDS[2] + 1, userStop)
+                new UidRangeParcel(userStart + PKG_UIDS[2] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)
         }));
         verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStart + PKG_UIDS[3] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[3] + 1, userStop)
+                new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1),
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop)
         }));
 
         // Change the VPN app.
@@ -441,32 +463,52 @@
                 PKGS[0], true, Collections.singletonList(PKGS[3])));
         verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(userStart, userStart + PKG_UIDS[1] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStart + PKG_UIDS[3] - 1)
+                new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStart + PKG_UIDS[3] - 1),
+                new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1),
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1))
         }));
         verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(userStart, userStart + PKG_UIDS[0] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[3] - 1)
+                new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[3] - 1),
+                new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1))
         }));
 
         // Remove the list of allowed packages.
         assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null));
         verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[3] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[3] + 1, userStop)
+                new UidRangeParcel(userStart + PKG_UIDS[3] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[3] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[3] + 1), userStop)
         }));
         verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
-                new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStop),
+                new UidRangeParcel(userStart + PKG_UIDS[0] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1), userStop),
         }));
 
         // Add the list of allowed packages.
         assertTrue(vpn.setAlwaysOnPackage(
                 PKGS[0], true, Collections.singletonList(PKGS[1])));
         verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
-                new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStop)
+                new UidRangeParcel(userStart + PKG_UIDS[0] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1), userStop),
         }));
         verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStop)
+                new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
         }));
 
         // Try allowing a package with a comma, should be rejected.
@@ -479,11 +521,19 @@
                 PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app")));
         verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[1] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[1] + 1, userStop)
+                new UidRangeParcel(userStart + PKG_UIDS[1] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[1] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[1] + 1), userStop)
         }));
         verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(userStart + PKG_UIDS[0] + 1, userStart + PKG_UIDS[2] - 1),
-                new UidRangeParcel(userStart + PKG_UIDS[2] + 1, userStop)
+                new UidRangeParcel(userStart + PKG_UIDS[2] + 1,
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[0] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[0] + 1),
+                                   Process.toSdkSandboxUid(userStart + PKG_UIDS[2] - 1)),
+                new UidRangeParcel(Process.toSdkSandboxUid(userStart + PKG_UIDS[2] + 1), userStop)
         }));
     }
 
@@ -528,7 +578,10 @@
         };
         final UidRangeParcel[] exceptPkg0 = {
             new UidRangeParcel(entireUser[0].start, entireUser[0].start + PKG_UIDS[0] - 1),
-            new UidRangeParcel(entireUser[0].start + PKG_UIDS[0] + 1, entireUser[0].stop)
+            new UidRangeParcel(entireUser[0].start + PKG_UIDS[0] + 1,
+                               Process.toSdkSandboxUid(entireUser[0].start + PKG_UIDS[0] - 1)),
+            new UidRangeParcel(Process.toSdkSandboxUid(entireUser[0].start + PKG_UIDS[0] + 1),
+                               entireUser[0].stop),
         };
 
         final InOrder order = inOrder(mConnectivityManager);