Merge "wifi data usage: replaced Wi-Fi SSID with a Wi-Fi network key"
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index 17eebe0..d015ef6 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -25,6 +25,8 @@
     name: "bpf_connectivity_headers",
     vendor_available: false,
     host_supported: false,
+    header_libs: ["bpf_headers"],
+    export_header_lib_headers: ["bpf_headers"],
     export_include_dirs: ["."],
     cflags: [
         "-Wall",
diff --git a/framework/src/android/net/NetworkAgentConfig.java b/framework/src/android/net/NetworkAgentConfig.java
index ad8396b..93fc379 100644
--- a/framework/src/android/net/NetworkAgentConfig.java
+++ b/framework/src/android/net/NetworkAgentConfig.java
@@ -232,6 +232,20 @@
         return mLegacyExtraInfo;
     }
 
+    /**
+     * If the {@link Network} is a VPN, whether the local traffic is exempted from the VPN.
+     * @hide
+     */
+    public boolean excludeLocalRouteVpn = false;
+
+    /**
+     * @return whether local traffic is excluded from the VPN network.
+     * @hide
+     */
+    public boolean getExcludeLocalRouteVpn() {
+        return excludeLocalRouteVpn;
+    }
+
     /** @hide */
     public NetworkAgentConfig() {
     }
@@ -251,6 +265,7 @@
             legacySubType = nac.legacySubType;
             legacySubTypeName = nac.legacySubTypeName;
             mLegacyExtraInfo = nac.mLegacyExtraInfo;
+            excludeLocalRouteVpn = nac.excludeLocalRouteVpn;
         }
     }
 
@@ -407,6 +422,17 @@
         }
 
         /**
+         * Sets whether the local traffic is exempted from VPN.
+         *
+         * @return this builder, to facilitate chaining.
+         * @hide TODO(184750836): Unhide once the implementation is completed.
+         */
+        public Builder setExcludeLocalRoutesVpn(boolean excludeLocalRoutes) {
+            mConfig.excludeLocalRouteVpn = excludeLocalRoutes;
+            return this;
+        }
+
+        /**
          * Returns the constructed {@link NetworkAgentConfig} object.
          */
         @NonNull
@@ -429,14 +455,15 @@
                 && legacyType == that.legacyType
                 && Objects.equals(subscriberId, that.subscriberId)
                 && Objects.equals(legacyTypeName, that.legacyTypeName)
-                && Objects.equals(mLegacyExtraInfo, that.mLegacyExtraInfo);
+                && Objects.equals(mLegacyExtraInfo, that.mLegacyExtraInfo)
+                && excludeLocalRouteVpn == that.excludeLocalRouteVpn;
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(allowBypass, explicitlySelected, acceptUnvalidated,
                 acceptPartialConnectivity, provisioningNotificationDisabled, subscriberId,
-                skip464xlat, legacyType, legacyTypeName, mLegacyExtraInfo);
+                skip464xlat, legacyType, legacyTypeName, mLegacyExtraInfo, excludeLocalRouteVpn);
     }
 
     @Override
@@ -453,6 +480,7 @@
                 + ", hasShownBroken = " + hasShownBroken
                 + ", legacyTypeName = '" + legacyTypeName + '\''
                 + ", legacyExtraInfo = '" + mLegacyExtraInfo + '\''
+                + ", excludeLocalRouteVpn = '" + excludeLocalRouteVpn + '\''
                 + "}";
     }
 
@@ -475,6 +503,7 @@
         out.writeInt(legacySubType);
         out.writeString(legacySubTypeName);
         out.writeString(mLegacyExtraInfo);
+        out.writeInt(excludeLocalRouteVpn ? 1 : 0);
     }
 
     public static final @NonNull Creator<NetworkAgentConfig> CREATOR =
@@ -494,6 +523,7 @@
             networkAgentConfig.legacySubType = in.readInt();
             networkAgentConfig.legacySubTypeName = in.readString();
             networkAgentConfig.mLegacyExtraInfo = in.readString();
+            networkAgentConfig.excludeLocalRouteVpn = in.readInt() != 0;
             return networkAgentConfig;
         }
 
diff --git a/tests/common/java/android/net/NetworkAgentConfigTest.kt b/tests/common/java/android/net/NetworkAgentConfigTest.kt
index afaae1c..ed9995c 100644
--- a/tests/common/java/android/net/NetworkAgentConfigTest.kt
+++ b/tests/common/java/android/net/NetworkAgentConfigTest.kt
@@ -48,9 +48,16 @@
                 setBypassableVpn(true)
             }
         }.build()
+        // This test can be run as unit test against the latest system image, as CTS to verify
+        // an Android release that is as recent as the test, or as MTS to verify the
+        // Connectivity module. In the first two cases NetworkAgentConfig will be as recent
+        // as the test. In the last case, starting from S NetworkAgentConfig is updated as part
+        // of Connectivity, so it is also as recent as the test. For MTS on Q and R,
+        // NetworkAgentConfig is not updatable, so it may have a different number of fields.
         if (isAtLeastS()) {
-            // From S, the config will have 12 items
-            assertParcelSane(config, 12)
+            // When this test is run on S+, NetworkAgentConfig is as recent as the test,
+            // so this should be the most recent known number of fields.
+            assertParcelSane(config, 13)
         } else {
             // For R or below, the config will have 10 items
             assertParcelSane(config, 10)
diff --git a/tests/common/java/android/net/NetworkProviderTest.kt b/tests/common/java/android/net/NetworkProviderTest.kt
index 626a344..ff5de1d 100644
--- a/tests/common/java/android/net/NetworkProviderTest.kt
+++ b/tests/common/java/android/net/NetworkProviderTest.kt
@@ -40,7 +40,6 @@
 import com.android.testutils.isDevSdkInRange
 import org.junit.After
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -205,7 +204,6 @@
         }
     }
 
-    @Ignore("Temporarily disable the test since prebuilt Connectivity module is not updated.")
     @IgnoreUpTo(Build.VERSION_CODES.R)
     @Test
     fun testRegisterNetworkOffer() {
diff --git a/tests/unit/java/android/net/NetworkStatsAccessTest.java b/tests/unit/java/android/net/NetworkStatsAccessTest.java
index bcbbcc92..75de022 100644
--- a/tests/unit/java/android/net/NetworkStatsAccessTest.java
+++ b/tests/unit/java/android/net/NetworkStatsAccessTest.java
@@ -45,6 +45,7 @@
 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S)
 public class NetworkStatsAccessTest {
     private static final String TEST_PKG = "com.example.test";
+    private static final int TEST_PID = 1234;
     private static final int TEST_UID = 12345;
 
     @Mock private Context mContext;
@@ -64,6 +65,13 @@
         when(mContext.getSystemServiceName(DevicePolicyManager.class))
                 .thenReturn(Context.DEVICE_POLICY_SERVICE);
         when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(mDpm);
+
+        setHasCarrierPrivileges(false);
+        setIsDeviceOwner(false);
+        setIsProfileOwner(false);
+        setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
+        setHasReadHistoryPermission(false);
+        setHasNetworkStackPermission(false);
     }
 
     @After
@@ -73,89 +81,73 @@
     @Test
     public void testCheckAccessLevel_hasCarrierPrivileges() throws Exception {
         setHasCarrierPrivileges(true);
-        setIsDeviceOwner(false);
-        setIsProfileOwner(false);
-        setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
-        setHasReadHistoryPermission(false);
         assertEquals(NetworkStatsAccess.Level.DEVICE,
-                NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+                NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
     }
 
     @Test
     public void testCheckAccessLevel_isDeviceOwner() throws Exception {
-        setHasCarrierPrivileges(false);
         setIsDeviceOwner(true);
-        setIsProfileOwner(false);
-        setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
-        setHasReadHistoryPermission(false);
         assertEquals(NetworkStatsAccess.Level.DEVICE,
-                NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+                NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
     }
 
     @Test
     public void testCheckAccessLevel_isProfileOwner() throws Exception {
-        setHasCarrierPrivileges(false);
-        setIsDeviceOwner(false);
         setIsProfileOwner(true);
-        setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
-        setHasReadHistoryPermission(false);
         assertEquals(NetworkStatsAccess.Level.USER,
-                NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+                NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
     }
 
     @Test
     public void testCheckAccessLevel_hasAppOpsBitAllowed() throws Exception {
-        setHasCarrierPrivileges(false);
-        setIsDeviceOwner(false);
         setIsProfileOwner(true);
         setHasAppOpsPermission(AppOpsManager.MODE_ALLOWED, false);
-        setHasReadHistoryPermission(false);
         assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY,
-                NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+                NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
     }
 
     @Test
     public void testCheckAccessLevel_hasAppOpsBitDefault_grantedPermission() throws Exception {
-        setHasCarrierPrivileges(false);
-        setIsDeviceOwner(false);
         setIsProfileOwner(true);
         setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, true);
-        setHasReadHistoryPermission(false);
         assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY,
-                NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+                NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
     }
 
     @Test
     public void testCheckAccessLevel_hasReadHistoryPermission() throws Exception {
-        setHasCarrierPrivileges(false);
-        setIsDeviceOwner(false);
         setIsProfileOwner(true);
-        setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
         setHasReadHistoryPermission(true);
         assertEquals(NetworkStatsAccess.Level.DEVICESUMMARY,
-                NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+                NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
     }
 
     @Test
     public void testCheckAccessLevel_deniedAppOpsBit() throws Exception {
-        setHasCarrierPrivileges(false);
-        setIsDeviceOwner(false);
-        setIsProfileOwner(false);
         setHasAppOpsPermission(AppOpsManager.MODE_ERRORED, true);
-        setHasReadHistoryPermission(false);
         assertEquals(NetworkStatsAccess.Level.DEFAULT,
-                NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+                NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
     }
 
     @Test
     public void testCheckAccessLevel_deniedAppOpsBit_deniedPermission() throws Exception {
-        setHasCarrierPrivileges(false);
-        setIsDeviceOwner(false);
-        setIsProfileOwner(false);
-        setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
-        setHasReadHistoryPermission(false);
         assertEquals(NetworkStatsAccess.Level.DEFAULT,
-                NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
+                NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
+    }
+
+    @Test
+    public void testCheckAccessLevel_hasNetworkStackPermission() throws Exception {
+        assertEquals(NetworkStatsAccess.Level.DEFAULT,
+                NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
+
+        setHasNetworkStackPermission(true);
+        assertEquals(NetworkStatsAccess.Level.DEVICE,
+                NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
+
+        setHasNetworkStackPermission(false);
+        assertEquals(NetworkStatsAccess.Level.DEFAULT,
+                NetworkStatsAccess.checkAccessLevel(mContext, TEST_PID, TEST_UID, TEST_PKG));
     }
 
     private void setHasCarrierPrivileges(boolean hasPrivileges) {
@@ -185,4 +177,10 @@
                 .thenReturn(hasPermission ? PackageManager.PERMISSION_GRANTED
                         : PackageManager.PERMISSION_DENIED);
     }
+
+    private void setHasNetworkStackPermission(boolean hasPermission) {
+        when(mContext.checkPermission(android.Manifest.permission.NETWORK_STACK,
+                TEST_PID, TEST_UID)).thenReturn(hasPermission ? PackageManager.PERMISSION_GRANTED
+                : PackageManager.PERMISSION_DENIED);
+    }
 }
diff --git a/tests/unit/java/com/android/internal/net/VpnProfileTest.java b/tests/unit/java/com/android/internal/net/VpnProfileTest.java
index a945a1f..960a9f1 100644
--- a/tests/unit/java/com/android/internal/net/VpnProfileTest.java
+++ b/tests/unit/java/com/android/internal/net/VpnProfileTest.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.net;
 
+import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
 import static com.android.testutils.ParcelUtils.assertParcelSane;
 
 import static org.junit.Assert.assertEquals;
@@ -48,6 +49,7 @@
 
     private static final int ENCODED_INDEX_AUTH_PARAMS_INLINE = 23;
     private static final int ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS = 24;
+    private static final int ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE = 25;
 
     @Test
     public void testDefaults() throws Exception {
@@ -126,7 +128,12 @@
 
     @Test
     public void testParcelUnparcel() {
-        assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 23);
+        if (isAtLeastT()) {
+            // excludeLocalRoutes is added in T.
+            assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 24);
+        } else {
+            assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 23);
+        }
     }
 
     @Test
@@ -166,7 +173,8 @@
         final String tooFewValues =
                 getEncodedDecodedIkev2ProfileMissingValues(
                         ENCODED_INDEX_AUTH_PARAMS_INLINE,
-                        ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS /* missingIndices */);
+                        ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS,
+                        ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE /* missingIndices */);
 
         assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()));
     }
@@ -183,6 +191,17 @@
     }
 
     @Test
+    public void testEncodeDecodeMissingExcludeLocalRoutes() {
+        final String tooFewValues =
+                getEncodedDecodedIkev2ProfileMissingValues(
+                        ENCODED_INDEX_EXCLUDE_LOCAL_ROUTE /* missingIndices */);
+
+        // Verify decoding without isRestrictedToTestNetworks defaults to false
+        final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+        assertFalse(decoded.excludeLocalRoutes);
+    }
+
+    @Test
     public void testEncodeDecodeLoginsNotSaved() {
         final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
         profile.saveLogin = false;