Merge "Add version control for packet address translation design" into main
diff --git a/framework-t/src/android/net/NetworkStats.java b/framework-t/src/android/net/NetworkStats.java
index 8719960..4f816c5 100644
--- a/framework-t/src/android/net/NetworkStats.java
+++ b/framework-t/src/android/net/NetworkStats.java
@@ -46,6 +46,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 /**
@@ -455,6 +456,41 @@
             return operations;
         }
 
+        /**
+         * Set Key fields for this entry.
+         *
+         * @return this object.
+         * @hide
+         */
+        private Entry setKeys(@Nullable String iface, int uid, @State int set,
+                int tag, @Meteredness int metered, @Roaming int roaming,
+                @DefaultNetwork int defaultNetwork) {
+            this.iface = iface;
+            this.uid = uid;
+            this.set = set;
+            this.tag = tag;
+            this.metered = metered;
+            this.roaming = roaming;
+            this.defaultNetwork = defaultNetwork;
+            return this;
+        }
+
+        /**
+         * Set Value fields for this entry.
+         *
+         * @return this object.
+         * @hide
+         */
+        private Entry setValues(long rxBytes, long rxPackets, long txBytes, long txPackets,
+                long operations) {
+            this.rxBytes = rxBytes;
+            this.rxPackets = rxPackets;
+            this.txBytes = txBytes;
+            this.txPackets = txPackets;
+            this.operations = operations;
+            return this;
+        }
+
         @Override
         public String toString() {
             final StringBuilder builder = new StringBuilder();
@@ -1111,7 +1147,8 @@
             entry.txPackets = left.txPackets[i];
             entry.operations = left.operations[i];
 
-            // find remote row that matches, and subtract
+            // Find the remote row that matches and subtract.
+            // The returned row must be uniquely matched.
             final int j = right.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag,
                     entry.metered, entry.roaming, entry.defaultNetwork, i);
             if (j != -1) {
@@ -1210,30 +1247,21 @@
      * @hide
      */
     public NetworkStats groupedByIface() {
-        final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
+        // Keep backward compatibility where the method filtered out tagged stats and keep the
+        // operation counts as 0. The method used to deal with uid snapshot where tagged and
+        // non-tagged stats were mixed. And this method was also in Android O API list,
+        // so it is possible OEM can access it.
+        final NetworkStats copiedStats = this.clone();
+        copiedStats.filter(e -> e.getTag() == TAG_NONE);
 
-        final Entry entry = new Entry();
-        entry.uid = UID_ALL;
-        entry.set = SET_ALL;
-        entry.tag = TAG_NONE;
-        entry.metered = METERED_ALL;
-        entry.roaming = ROAMING_ALL;
-        entry.defaultNetwork = DEFAULT_NETWORK_ALL;
-        entry.operations = 0L;
+        final Entry temp = new Entry();
+        final NetworkStats mappedStats = copiedStats.map(entry -> temp.setKeys(entry.getIface(),
+                UID_ALL, SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL));
 
-        for (int i = 0; i < size; i++) {
-            // skip specific tags, since already counted in TAG_NONE
-            if (tag[i] != TAG_NONE) continue;
-
-            entry.iface = iface[i];
-            entry.rxBytes = rxBytes[i];
-            entry.rxPackets = rxPackets[i];
-            entry.txBytes = txBytes[i];
-            entry.txPackets = txPackets[i];
-            stats.combineValues(entry);
+        for (int i = 0; i < mappedStats.size; i++) {
+            mappedStats.operations[i] = 0L;
         }
-
-        return stats;
+        return mappedStats;
     }
 
     /**
@@ -1242,30 +1270,15 @@
      * @hide
      */
     public NetworkStats groupedByUid() {
-        final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
+        // Keep backward compatibility where the method filtered out tagged stats. The method used
+        // to deal with uid snapshot where tagged and non-tagged stats were mixed. And
+        // this method is also in Android O API list, so it is possible OEM can access it.
+        final NetworkStats copiedStats = this.clone();
+        copiedStats.filter(e -> e.getTag() == TAG_NONE);
 
-        final Entry entry = new Entry();
-        entry.iface = IFACE_ALL;
-        entry.set = SET_ALL;
-        entry.tag = TAG_NONE;
-        entry.metered = METERED_ALL;
-        entry.roaming = ROAMING_ALL;
-        entry.defaultNetwork = DEFAULT_NETWORK_ALL;
-
-        for (int i = 0; i < size; i++) {
-            // skip specific tags, since already counted in TAG_NONE
-            if (tag[i] != TAG_NONE) continue;
-
-            entry.uid = uid[i];
-            entry.rxBytes = rxBytes[i];
-            entry.rxPackets = rxPackets[i];
-            entry.txBytes = txBytes[i];
-            entry.txPackets = txPackets[i];
-            entry.operations = operations[i];
-            stats.combineValues(entry);
-        }
-
-        return stats;
+        final Entry temp = new Entry();
+        return copiedStats.map(entry -> temp.setKeys(IFACE_ALL,
+                entry.getUid(), SET_ALL, TAG_NONE, METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL));
     }
 
     /**
@@ -1292,13 +1305,37 @@
 
     /**
      * Removes the interface name from all entries.
-     * This mutates the original structure in place.
+     * This returns a newly constructed object instead of mutating the original structure.
      * @hide
      */
-    public void clearInterfaces() {
-        for (int i = 0; i < size; i++) {
-            iface[i] = null;
+    @NonNull
+    public NetworkStats clearInterfaces() {
+        final Entry temp = new Entry();
+        return map(entry -> temp.setKeys(IFACE_ALL, entry.getUid(), entry.getSet(),
+                entry.getTag(), entry.getMetered(), entry.getRoaming(), entry.getDefaultNetwork()));
+    }
+
+    /**
+     * Returns a new NetworkStats object where entries are transformed.
+     *
+     * Note that because NetworkStats is more akin to a map than to a list,
+     * the entries will be grouped after they are mapped by the key fields,
+     * e.g. uid, set, tag, defaultNetwork.
+     * Value fields with the same keys will be added together.
+     */
+    @NonNull
+    private NetworkStats map(@NonNull Function<Entry, Entry> f) {
+        final NetworkStats ret = new NetworkStats(0, 1);
+        for (Entry e : this) {
+            final NetworkStats.Entry transformed = f.apply(e);
+            if (transformed == e) {
+                throw new IllegalStateException("A new entry must be created.");
+            }
+            transformed.setValues(e.getRxBytes(), e.getRxPackets(), e.getTxBytes(),
+                    e.getTxPackets(), e.getOperations());
+            ret.combineValues(transformed);
         }
+        return ret;
     }
 
     /**
diff --git a/framework/Android.bp b/framework/Android.bp
index cab11ae..7897492 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -62,6 +62,7 @@
         ":framework-connectivity-sources",
         ":net-utils-framework-common-srcs",
         ":framework-connectivity-api-shared-srcs",
+        ":framework-remoteauth-java-sources",
     ],
     aidl: {
         generate_get_transaction_name: true,
@@ -150,6 +151,7 @@
         "//packages/modules/Connectivity/framework-t",
         "//packages/modules/Connectivity/service",
         "//packages/modules/Connectivity/service-t",
+        "//packages/modules/Connectivity/remoteauth:__subpackages__",
         "//frameworks/base/packages/Connectivity/service",
         "//frameworks/base",
 
diff --git a/nearby/service/Android.bp b/nearby/service/Android.bp
index ef07bb9..684b133 100644
--- a/nearby/service/Android.bp
+++ b/nearby/service/Android.bp
@@ -104,7 +104,7 @@
     ],
     sdk_version: "system_server_current",
     // This is included in service-connectivity which is 30+
-    // TODO: allow APEXes to have service jars with higher min_sdk than the APEX
+    // TODO (b/293613362): allow APEXes to have service jars with higher min_sdk than the APEX
     // (service-connectivity is only used on 31+) and use 31 here
     min_sdk_version: "30",
 
diff --git a/remote_auth/OWNERS b/remoteauth/OWNERS
similarity index 100%
rename from remote_auth/OWNERS
rename to remoteauth/OWNERS
diff --git a/remote_auth/README.md b/remoteauth/README.md
similarity index 100%
rename from remote_auth/README.md
rename to remoteauth/README.md
diff --git a/remoteauth/TEST_MAPPING b/remoteauth/TEST_MAPPING
new file mode 100644
index 0000000..5ad8da6
--- /dev/null
+++ b/remoteauth/TEST_MAPPING
@@ -0,0 +1,13 @@
+{
+  "presubmit": [
+    {
+      "name": "RemoteAuthUnitTests"
+    }
+  ]
+  // TODO(b/193602229): uncomment once it's supported.
+  //"mainline-presubmit": [
+  //  {
+  //    "name": "RemoteAuthUnitTests[com.google.android.tethering.apex]"
+  //  }
+  //]
+}
diff --git a/remoteauth/framework/Android.bp b/remoteauth/framework/Android.bp
new file mode 100644
index 0000000..48d10b6
--- /dev/null
+++ b/remoteauth/framework/Android.bp
@@ -0,0 +1,49 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Sources included in the framework-connectivity jar
+filegroup {
+    name: "framework-remoteauth-java-sources",
+    srcs: [
+        "java/**/*.java",
+        "java/**/*.aidl",
+    ],
+    path: "java",
+    visibility: [
+        "//packages/modules/Connectivity/framework:__subpackages__",
+    ],
+}
+
+filegroup {
+    name: "framework-remoteauth-sources",
+    defaults: ["framework-sources-module-defaults"],
+    srcs: [
+        ":framework-remoteauth-java-sources",
+    ],
+}
+
+// Build of only framework-remoteauth (not as part of connectivity) for
+// unit tests
+java_library {
+    name: "framework-remoteauth-static",
+    srcs: [":framework-remoteauth-java-sources"],
+    sdk_version: "module_current",
+    libs: [],
+    static_libs: [],
+    visibility: ["//packages/modules/Connectivity/remoteauth/tests:__subpackages__"],
+}
diff --git a/remoteauth/framework/java/android/remoteauth/README.md b/remoteauth/framework/java/android/remoteauth/README.md
new file mode 100644
index 0000000..13fefee
--- /dev/null
+++ b/remoteauth/framework/java/android/remoteauth/README.md
@@ -0,0 +1 @@
+This is the source root for the RemoteAuth framework
\ No newline at end of file
diff --git a/remoteauth/framework/java/android/remoteauth/aidl/README.md b/remoteauth/framework/java/android/remoteauth/aidl/README.md
new file mode 100644
index 0000000..1e9422e
--- /dev/null
+++ b/remoteauth/framework/java/android/remoteauth/aidl/README.md
@@ -0,0 +1 @@
+This is where the RemoteAuth AIDL files will go
\ No newline at end of file
diff --git a/remoteauth/service/Android.bp b/remoteauth/service/Android.bp
new file mode 100644
index 0000000..5c5a2fb
--- /dev/null
+++ b/remoteauth/service/Android.bp
@@ -0,0 +1,59 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+    name: "remoteauth-service-srcs",
+    srcs: ["java/**/*.java"],
+}
+
+// Main lib for remoteauth services.
+java_library {
+    name: "service-remoteauth-pre-jarjar",
+    srcs: [":remoteauth-service-srcs"],
+
+    defaults: [
+        "framework-system-server-module-defaults"
+    ],
+    libs: [],
+    static_libs: [],
+    sdk_version: "system_server_current",
+    // This is included in service-connectivity which is 30+
+    // TODO (b/293613362): allow APEXes to have service jars with higher min_sdk than the APEX
+    // (service-connectivity is only used on 31+) and use 31 here
+    min_sdk_version: "30",
+
+    dex_preopt: {
+        enabled: false,
+        app_image: false,
+    },
+    visibility: [
+        "//packages/modules/RemoteAuth/apex",
+    ],
+    apex_available: [
+        "com.android.tethering",
+    ],
+}
+
+genrule {
+    name: "statslog-remoteauth-java-gen",
+    tools: ["stats-log-api-gen"],
+    cmd: "$(location stats-log-api-gen) --java $(out) --module remoteauth " +
+         " --javaPackage com.android.server.remoteauth.proto --javaClass RemoteAuthStatsLog" +
+         " --minApiLevel 33",
+    out: ["com/android/server/remoteauth/proto/RemoteAuthStatsLog.java"],
+}
diff --git a/remoteauth/service/java/com/android/server/remoteauth/README.md b/remoteauth/service/java/com/android/server/remoteauth/README.md
new file mode 100644
index 0000000..423ab45
--- /dev/null
+++ b/remoteauth/service/java/com/android/server/remoteauth/README.md
@@ -0,0 +1 @@
+This is the source root for the RemoteAuthService
\ No newline at end of file
diff --git a/remoteauth/tests/unit/Android.bp b/remoteauth/tests/unit/Android.bp
new file mode 100644
index 0000000..8c08a1b
--- /dev/null
+++ b/remoteauth/tests/unit/Android.bp
@@ -0,0 +1,48 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "RemoteAuthUnitTests",
+    defaults: ["mts-target-sdk-version-current"],
+    sdk_version: "test_current",
+    min_sdk_version: "31",
+
+    // Include all test java files.
+    srcs: ["src/**/*.java"],
+
+    libs: [
+        "android.test.base",
+        "android.test.mock",
+        "android.test.runner",
+    ],
+    compile_multilib: "both",
+
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "framework-remoteauth-static",
+        "junit",
+        "platform-test-annotations",
+        "service-remoteauth-pre-jarjar",
+        "truth-prebuilt",
+    ],
+    test_suites: [
+        "general-tests",
+        "mts-tethering",
+    ],
+}
diff --git a/remoteauth/tests/unit/AndroidManifest.xml b/remoteauth/tests/unit/AndroidManifest.xml
new file mode 100644
index 0000000..0449409
--- /dev/null
+++ b/remoteauth/tests/unit/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.remoteauth.test">
+
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.BLUETOOTH" />
+    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
+    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.remoteauth.test"
+        android:label="RemoteAuth Mainline Module Tests" />
+</manifest>
diff --git a/remoteauth/tests/unit/AndroidTest.xml b/remoteauth/tests/unit/AndroidTest.xml
new file mode 100644
index 0000000..325fdc6
--- /dev/null
+++ b/remoteauth/tests/unit/AndroidTest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 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.
+  -->
+<configuration description="Runs RemoteAuth 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="RemoteAuthUnitTests.apk" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-tag" value="RemoteAuthUnitTests" />
+    <option name="config-descriptor:metadata" key="mainline-param"
+            value="com.google.android.tethering.apex" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.remoteauth.test" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+
+    <!-- Only run RemoteAuthUnitTests in MTS if the RemoteAuth Mainline module is installed. -->
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.tethering" />
+    </object>
+</configuration>
diff --git a/remoteauth/tests/unit/src/android/remoteauth/RemoteAuthManagerTest.java b/remoteauth/tests/unit/src/android/remoteauth/RemoteAuthManagerTest.java
new file mode 100644
index 0000000..5cf3e6b
--- /dev/null
+++ b/remoteauth/tests/unit/src/android/remoteauth/RemoteAuthManagerTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.remoteauth;
+
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link RemoteAuth}. */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RemoteAuthManagerTest {
+    @Test
+    public void testStub() {
+        assertTrue(true);
+    }
+}
diff --git a/remoteauth/tests/unit/src/com/android/server/remoteauth/RemoteAuthServiceTest.java b/remoteauth/tests/unit/src/com/android/server/remoteauth/RemoteAuthServiceTest.java
new file mode 100644
index 0000000..c6199ff
--- /dev/null
+++ b/remoteauth/tests/unit/src/com/android/server/remoteauth/RemoteAuthServiceTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.remoteauth;
+
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Unit tests for {@link RemoteAuthServer}. */
+@RunWith(AndroidJUnit4.class)
+public class RemoteAuthServiceTest {
+    @Test
+    public void testStub() {
+        assertTrue(true);
+    }
+}
diff --git a/service-t/src/com/android/server/net/NetworkStatsService.java b/service-t/src/com/android/server/net/NetworkStatsService.java
index e7ef510..6635fd3 100644
--- a/service-t/src/com/android/server/net/NetworkStatsService.java
+++ b/service-t/src/com/android/server/net/NetworkStatsService.java
@@ -1744,8 +1744,7 @@
             // information. This is because no caller needs this information for now, and it
             // makes it easier to change the implementation later by using the histories in the
             // recorder.
-            stats.clearInterfaces();
-            return stats;
+            return stats.clearInterfaces();
         } catch (RemoteException e) {
             Log.wtf(TAG, "Error compiling UID stats", e);
             return new NetworkStats(0L, 0);
diff --git a/service/Android.bp b/service/Android.bp
index e1376a1..a65d664 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -236,6 +236,7 @@
         "service-connectivity-pre-jarjar",
         "service-connectivity-tiramisu-pre-jarjar",
         "service-nearby-pre-jarjar",
+        "service-remoteauth-pre-jarjar",
     ],
     // The below libraries are not actually needed to build since no source is compiled
     // (only combining prebuilt static_libs), but they are necessary so that R8 has the right
@@ -303,6 +304,7 @@
         ":framework-connectivity-jarjar-rules",
         ":service-connectivity-jarjar-gen",
         ":service-nearby-jarjar-gen",
+        ":service-remoteauth-jarjar-gen",
     ],
     out: ["connectivity-jarjar-rules.txt"],
     visibility: ["//packages/modules/Connectivity:__subpackages__"],
@@ -354,6 +356,24 @@
     visibility: ["//visibility:private"],
 }
 
+java_genrule {
+    name: "service-remoteauth-jarjar-gen",
+    tool_files: [
+        ":service-remoteauth-pre-jarjar{.jar}",
+        "jarjar-excludes.txt",
+    ],
+    tools: [
+        "jarjar-rules-generator",
+    ],
+    out: ["service_remoteauth_jarjar_rules.txt"],
+    cmd: "$(location jarjar-rules-generator) " +
+        "$(location :service-remoteauth-pre-jarjar{.jar}) " +
+        "--prefix com.android.server.remoteauth " +
+        "--excludes $(location jarjar-excludes.txt) " +
+        "--output $(out)",
+    visibility: ["//visibility:private"],
+}
+
 genrule {
     name: "statslog-connectivity-java-gen",
     tools: ["stats-log-api-gen"],
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 83afa83..bc703a9 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -1704,8 +1704,7 @@
         mUserAllContext.registerReceiver(mPackageIntentReceiver, packageIntentFilter,
                 null /* broadcastPermission */, mHandler);
 
-        mNetworkActivityTracker =
-                new LegacyNetworkActivityTracker(mContext, mNetd, mHandler, mDeps.isAtLeastU());
+        mNetworkActivityTracker = new LegacyNetworkActivityTracker(mContext, mNetd, mHandler);
 
         final NetdCallback netdCallback = new NetdCallback();
         try {
@@ -11139,9 +11138,10 @@
                 new RemoteCallbackList<>();
         // Indicate the current system default network activity is active or not.
         // This needs to be volatile to allow non handler threads to read this value without lock.
-        private volatile boolean mIsDefaultNetworkActive;
+        // If there is no default network, default network is considered active to keep the existing
+        // behavior. Initial value is used until first connect to the default network.
+        private volatile boolean mIsDefaultNetworkActive = true;
         private final ArrayMap<String, IdleTimerParams> mActiveIdleTimers = new ArrayMap<>();
-        private final boolean mIsAtLeastU;
 
         private static class IdleTimerParams {
             public final int timeout;
@@ -11154,11 +11154,10 @@
         }
 
         LegacyNetworkActivityTracker(@NonNull Context context, @NonNull INetd netd,
-                @NonNull Handler handler, boolean isAtLeastU) {
+                @NonNull Handler handler) {
             mContext = context;
             mNetd = netd;
             mHandler = handler;
-            mIsAtLeastU = isAtLeastU;
         }
 
         private void ensureRunningOnConnectivityServiceThread() {
@@ -11319,13 +11318,14 @@
                 boolean hasIdleTimer) {
             if (defaultNetwork != null) {
                 mIsDefaultNetworkActive = true;
-                // On T-, callbacks are called only when the network has the idle timer.
-                if (mIsAtLeastU || hasIdleTimer) {
+                // Callbacks are called only when the network has the idle timer.
+                if (hasIdleTimer) {
                     reportNetworkActive();
                 }
             } else {
-                // If there is no default network, default network is considered inactive.
-                mIsDefaultNetworkActive = false;
+                // If there is no default network, default network is considered active to keep the
+                // existing behavior.
+                mIsDefaultNetworkActive = true;
             }
         }
 
diff --git a/tests/benchmark/src/android/net/netstats/benchmarktests/NetworkStatsTest.kt b/tests/benchmark/src/android/net/netstats/benchmarktests/NetworkStatsTest.kt
index 8492268..e80548b 100644
--- a/tests/benchmark/src/android/net/netstats/benchmarktests/NetworkStatsTest.kt
+++ b/tests/benchmark/src/android/net/netstats/benchmarktests/NetworkStatsTest.kt
@@ -28,7 +28,6 @@
 import java.io.BufferedInputStream
 import java.io.DataInputStream
 import java.io.File
-import java.io.FileInputStream
 import java.io.FileOutputStream
 import java.nio.file.Files
 import java.util.concurrent.TimeUnit
@@ -44,6 +43,8 @@
 class NetworkStatsTest {
     companion object {
         private val DEFAULT_BUFFER_SIZE = 8192
+        private val FILE_CACHE_WARM_UP_REPEAT_COUNT = 10
+        private val TEST_REPEAT_COUNT = 10
         private val UID_COLLECTION_BUCKET_DURATION_MS = TimeUnit.HOURS.toMillis(2)
         private val UID_RECORDER_ROTATE_AGE_MS = TimeUnit.DAYS.toMillis(15)
         private val UID_RECORDER_DELETE_AGE_MS = TimeUnit.DAYS.toMillis(90)
@@ -63,14 +64,14 @@
         }
 
         // Test results shows the test cases who read the file first will take longer time to
-        // execute, and reading time getting shorter each time. Read files several times prior to
-        // tests to minimize the impact. This cannot live in setUp() since the time
-        // spent on the file reading will be attributed to the time spent on the individual
-        // test case.
+        // execute, and reading time getting shorter each time due to file caching mechanism.
+        // Read files several times prior to tests to minimize the impact.
+        // This cannot live in setUp() since the time spent on the file reading will be
+        // attributed to the time spent on the individual test case.
         @JvmStatic
         @BeforeClass
         fun setUpOnce() {
-            for (i in 1..10) {
+            repeat(FILE_CACHE_WARM_UP_REPEAT_COUNT) {
                 val collection = NetworkStatsCollection(UID_COLLECTION_BUCKET_DURATION_MS)
                 for (file in uidTestFiles) {
                     readFile(file, collection)
@@ -78,20 +79,19 @@
             }
         }
 
-        private fun getInputStreamForResource(resourceId: Int): DataInputStream {
-            return DataInputStream(
+        private fun getInputStreamForResource(resourceId: Int): DataInputStream =
+            DataInputStream(
                 InstrumentationRegistry.getContext()
                     .getResources().openRawResource(resourceId)
             )
-        }
 
         private fun unzipToTempDir(zis: ZipInputStream): File {
             val statsDir =
                 Files.createTempDirectory(NetworkStatsTest::class.simpleName).toFile()
-            while (true) {
-                val entryName = zis.nextEntry?.name ?: break
-                val file = File(statsDir, entryName)
-                FileOutputStream(file).use { zis.copyTo(it, DEFAULT_BUFFER_SIZE) }
+            generateSequence { zis.nextEntry }.forEach { entry ->
+                FileOutputStream(File(statsDir, entry.name)).use {
+                    zis.copyTo(it, DEFAULT_BUFFER_SIZE)
+                }
             }
             return statsDir
         }
@@ -99,20 +99,23 @@
         // List [xt|uid|uid_tag].<start>-<end> files under the given directory.
         private fun getSortedListForPrefix(statsDir: File, prefix: String): List<File> {
             assertTrue(statsDir.exists())
-            return (statsDir.list() ?: arrayOf()).mapNotNull {
-                if (it.startsWith("$prefix.")) File(statsDir, it) else null
-            }.sorted()
+            return statsDir.list() { dir, name -> name.startsWith("$prefix.") }
+                .orEmpty()
+                .map { it -> File(statsDir, it) }
+                .sorted()
         }
 
         private fun readFile(file: File, reader: Reader) =
-            BufferedInputStream(FileInputStream(file)).use {
+            BufferedInputStream(file.inputStream()).use {
                 reader.read(it)
             }
     }
 
     @Test
     fun testReadCollection_manyUids() {
-        for (i in 1..10) {
+        // The file cache is warmed up by the @BeforeClass method, so now the test can repeat
+        // this a number of time to have a stable number.
+        repeat(TEST_REPEAT_COUNT) {
             val collection = NetworkStatsCollection(UID_COLLECTION_BUCKET_DURATION_MS)
             for (file in uidTestFiles) {
                 readFile(file, collection)
@@ -122,13 +125,15 @@
 
     @Test
     fun testReadFromRecorder_manyUids() {
-        for (i in 1..10) {
+        val mockObserver = mock<NonMonotonicObserver<String>>()
+        val mockDropBox = mock<DropBoxManager>()
+        repeat(TEST_REPEAT_COUNT) {
             val recorder = NetworkStatsRecorder(
                 FileRotator(
                     testFilesDir, PREFIX_UID, UID_RECORDER_ROTATE_AGE_MS, UID_RECORDER_DELETE_AGE_MS
                 ),
-                mock<NonMonotonicObserver<String>>(),
-                mock(DropBoxManager::class.java),
+                mockObserver,
+                mockDropBox,
                 PREFIX_UID,
                 UID_COLLECTION_BUCKET_DURATION_MS,
                 false /* includeTags */,
@@ -138,5 +143,5 @@
         }
     }
 
-    inline fun <reified T : Any> mock(): T = mock(T::class.java)
+    inline fun <reified T> mock(): T = mock(T::class.java)
 }
diff --git a/tests/unit/java/android/net/NetworkStatsTest.java b/tests/unit/java/android/net/NetworkStatsTest.java
index 126ad55..4ff131b 100644
--- a/tests/unit/java/android/net/NetworkStatsTest.java
+++ b/tests/unit/java/android/net/NetworkStatsTest.java
@@ -1073,30 +1073,35 @@
         final NetworkStats.Entry entry1 = new NetworkStats.Entry(
                 "test1", 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
                 DEFAULT_NETWORK_NO, 1024L, 50L, 100L, 20L, 0L);
-
         final NetworkStats.Entry entry2 = new NetworkStats.Entry(
                 "test2", 10101, SET_DEFAULT, 0xF0DD, METERED_NO, ROAMING_NO,
                 DEFAULT_NETWORK_NO, 51200, 25L, 101010L, 50L, 0L);
+        final NetworkStats.Entry entry3 = new NetworkStats.Entry(
+                "test3", 10101, SET_DEFAULT, 0xF0DD, METERED_NO, ROAMING_NO,
+                DEFAULT_NETWORK_NO, 1, 2L, 3L, 4L, 5L);
 
         stats.insertEntry(entry1);
         stats.insertEntry(entry2);
+        stats.insertEntry(entry3);
 
         // Verify that the interfaces have indeed been recorded.
-        assertEquals(2, stats.size());
+        assertEquals(3, stats.size());
         assertValues(stats, 0, "test1", 10100, SET_DEFAULT, TAG_NONE, METERED_NO,
                 ROAMING_NO, DEFAULT_NETWORK_NO, 1024L, 50L, 100L, 20L, 0L);
         assertValues(stats, 1, "test2", 10101, SET_DEFAULT, 0xF0DD, METERED_NO,
                 ROAMING_NO, DEFAULT_NETWORK_NO, 51200, 25L, 101010L, 50L, 0L);
+        assertValues(stats, 2, "test3", 10101, SET_DEFAULT, 0xF0DD, METERED_NO,
+                ROAMING_NO, DEFAULT_NETWORK_NO, 1, 2L, 3L, 4L, 5L);
 
         // Clear interfaces.
-        stats.clearInterfaces();
+        final NetworkStats ifaceClearedStats = stats.clearInterfaces();
 
-        // Verify that the interfaces are cleared.
-        assertEquals(2, stats.size());
-        assertValues(stats, 0, null /* iface */, 10100, SET_DEFAULT, TAG_NONE, METERED_NO,
-                ROAMING_NO, DEFAULT_NETWORK_NO, 1024L, 50L, 100L, 20L, 0L);
-        assertValues(stats, 1, null /* iface */, 10101, SET_DEFAULT, 0xF0DD, METERED_NO,
-                ROAMING_NO, DEFAULT_NETWORK_NO, 51200, 25L, 101010L, 50L, 0L);
+        // Verify that the interfaces are cleared, and key-duplicated items are merged.
+        assertEquals(2, ifaceClearedStats.size());
+        assertValues(ifaceClearedStats, 0, null /* iface */, 10100, SET_DEFAULT, TAG_NONE,
+                METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1024L, 50L, 100L, 20L, 0L);
+        assertValues(ifaceClearedStats, 1, null /* iface */, 10101, SET_DEFAULT, 0xF0DD,
+                METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 51201, 27L, 101013L, 54L, 5L);
     }
 
     private static void assertContains(NetworkStats stats,  String iface, int uid, int set,
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 931670a..4dadfee 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -11335,17 +11335,18 @@
     }
 
     @Test
-    public void testOnNetworkActive_NewEthernetConnects_Callback() throws Exception {
-        // On T-, LegacyNetworkActivityTracker calls onNetworkActive callback only for networks that
+    public void testOnNetworkActive_NewEthernetConnects_CallbackNotCalled() throws Exception {
+        // LegacyNetworkActivityTracker calls onNetworkActive callback only for networks that
         // tracker adds the idle timer to. And the tracker does not set the idle timer for the
         // ethernet network.
         // So onNetworkActive is not called when the ethernet becomes the default network
-        doTestOnNetworkActive_NewNetworkConnects(TRANSPORT_ETHERNET, mDeps.isAtLeastU());
+        doTestOnNetworkActive_NewNetworkConnects(TRANSPORT_ETHERNET, false /* expectCallback */);
     }
 
     @Test
     public void testIsDefaultNetworkActiveNoDefaultNetwork() throws Exception {
-        assertFalse(mCm.isDefaultNetworkActive());
+        // isDefaultNetworkActive returns true if there is no default network, which is known issue.
+        assertTrue(mCm.isDefaultNetworkActive());
 
         final LinkProperties cellLp = new LinkProperties();
         cellLp.setInterfaceName(MOBILE_IFNAME);
@@ -11357,7 +11358,7 @@
         mCellAgent.disconnect();
         waitForIdle();
 
-        assertFalse(mCm.isDefaultNetworkActive());
+        assertTrue(mCm.isDefaultNetworkActive());
     }
 
     @Test
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 2726267..ad178c0 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -1346,7 +1346,8 @@
         final ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
 
         final int verifyTimes = profileState.length;
-        verify(userContext, times(verifyTimes)).startService(intentArgumentCaptor.capture());
+        verify(userContext, timeout(TEST_TIMEOUT_MS).times(verifyTimes))
+                .startService(intentArgumentCaptor.capture());
 
         for (int i = 0; i < verifyTimes; i++) {
             final Intent intent = intentArgumentCaptor.getAllValues().get(i);
@@ -1658,7 +1659,7 @@
             verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), anyLong(), any());
         } else {
             final IkeSessionCallback ikeCb = captor.getValue();
-            ikeCb.onClosedWithException(exception);
+            mExecutor.execute(() -> ikeCb.onClosedWithException(exception));
         }
 
         verifyPowerSaveTempWhitelistApp(TEST_VPN_PKG);
@@ -1677,7 +1678,7 @@
             int retryIndex = 0;
             final IkeSessionCallback ikeCb2 = verifyRetryAndGetNewIkeCb(retryIndex++);
 
-            ikeCb2.onClosedWithException(exception);
+            mExecutor.execute(() -> ikeCb2.onClosedWithException(exception));
             verifyRetryAndGetNewIkeCb(retryIndex++);
         }
     }
@@ -1688,11 +1689,8 @@
 
         // Verify retry is scheduled
         final long expectedDelayMs = mTestDeps.getNextRetryDelayMs(retryIndex);
-        final ArgumentCaptor<Long> delayCaptor = ArgumentCaptor.forClass(Long.class);
-        verify(mExecutor, atLeastOnce()).schedule(any(Runnable.class), delayCaptor.capture(),
-                eq(TimeUnit.MILLISECONDS));
-        final List<Long> delays = delayCaptor.getAllValues();
-        assertEquals(expectedDelayMs, (long) delays.get(delays.size() - 1));
+        verify(mExecutor, timeout(TEST_TIMEOUT_MS)).schedule(any(Runnable.class),
+                eq(expectedDelayMs), eq(TimeUnit.MILLISECONDS));
 
         verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS + expectedDelayMs))
                 .createIkeSession(any(), any(), any(), any(), ikeCbCaptor.capture(), any());
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsBaseTest.java b/tests/unit/java/com/android/server/net/NetworkStatsBaseTest.java
index a058a46..2c9f212 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsBaseTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsBaseTest.java
@@ -41,6 +41,7 @@
 abstract class NetworkStatsBaseTest {
     static final String TEST_IFACE = "test0";
     static final String TEST_IFACE2 = "test1";
+    static final String TEST_IFACE3 = "test2";
     static final String TUN_IFACE = "test_nss_tun0";
     static final String TUN_IFACE2 = "test_nss_tun1";
 
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index b8b0289..6292d45 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -1250,8 +1250,9 @@
                 TEST_IFACE2, IMSI_1, null /* wifiNetworkKey */,
                 false /* isTemporarilyNotMetered */, false /* isRoaming */);
 
-        final NetworkStateSnapshot[] states = new NetworkStateSnapshot[] {
-                mobileState, buildWifiState()};
+        final NetworkStateSnapshot[] states = new NetworkStateSnapshot[]{
+                mobileState, buildWifiState(false, TEST_IFACE, null),
+                buildWifiState(false, TEST_IFACE3, null)};
         mService.notifyNetworkStatus(NETWORKS_MOBILE, states, getActiveIface(states),
                 new UnderlyingNetworkInfo[0]);
         setMobileRatTypeAndWaitForIdle(TelephonyManager.NETWORK_TYPE_LTE);
@@ -1266,16 +1267,22 @@
         final NetworkStats.Entry entry3 = new NetworkStats.Entry(
                 TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xBEEF, METERED_NO, ROAMING_NO,
                 DEFAULT_NETWORK_NO, 1024L, 8L, 512L, 4L, 2L);
+        // Add an entry that with different wifi interface, but expected to be merged into entry3
+        // after clearing interface information.
+        final NetworkStats.Entry entry4 = new NetworkStats.Entry(
+                TEST_IFACE3, UID_BLUE, SET_DEFAULT, 0xBEEF, METERED_NO, ROAMING_NO,
+                DEFAULT_NETWORK_NO, 1L, 2L, 3L, 4L, 5L);
 
         final TetherStatsParcel[] emptyTetherStats = {};
         // The interfaces that expect to be used to query the stats.
-        final String[] wifiIfaces = {TEST_IFACE};
+        final String[] wifiIfaces = {TEST_IFACE, TEST_IFACE3};
         incrementCurrentTime(HOUR_IN_MILLIS);
         mockDefaultSettings();
-        mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
+        mockNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 4)
                 .insertEntry(entry1)
                 .insertEntry(entry2)
-                .insertEntry(entry3), emptyTetherStats, wifiIfaces);
+                .insertEntry(entry3)
+                .insertEntry(entry4), emptyTetherStats, wifiIfaces);
 
         // getUidStatsForTransport (through getNetworkStatsUidDetail) adds all operation counts
         // with active interface, and the interface here is mobile interface, so this test makes
@@ -1293,7 +1300,7 @@
         assertValues(wifiStats, null /* iface */, UID_RED, SET_DEFAULT, 0xF00D,
                 METERED_NO, ROAMING_NO, METERED_NO, 50L, 5L, 50L, 5L, 1L);
         assertValues(wifiStats, null /* iface */, UID_BLUE, SET_DEFAULT, 0xBEEF,
-                METERED_NO, ROAMING_NO, METERED_NO, 1024L, 8L, 512L, 4L, 2L);
+                METERED_NO, ROAMING_NO, METERED_NO, 1025L, 10L, 515L, 8L, 7L);
 
         final String[] mobileIfaces = {TEST_IFACE2};
         mockNetworkStatsUidDetail(buildEmptyStats(), emptyTetherStats, mobileIfaces);