Check network slicing declaration for network request

When the application wants to request network with
NET_CAPABILITY_PRIORITIZE_BANDWIDTH or
NET_CAPABILITY_PRIORITIZE_LATENCY, it has to declare
PackageManager.PROPERTY_NETWORK_SLICE_DECLARATIONS property and also
adds the declaration in a separate XML files. Otherwise, the request
will fail with a SecurityException being thrown.

Test: atest FrameworksNetTests CtsNetTestCases
Bug: 266524688
Change-Id: I6affc857b803211517368da288e1b2fdc06a955b
diff --git a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
index a166675..2cfda9e 100644
--- a/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
+++ b/framework/src/android/net/connectivity/ConnectivityCompatChanges.java
@@ -62,6 +62,17 @@
     // This was a platform change ID with value 191844585L before T
     public static final long RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER = 235355681L;
 
+    /**
+     * The self certified capabilities check should be enabled after android 13.
+     *
+     * <p> See {@link android.net.NetworkCapabilities} for more details.
+     *
+     * @hide
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    public static final long ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION = 266524688;
+
     private ConnectivityCompatChanges() {
     }
 }
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index e32ea8f..3a38f45 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -113,6 +113,7 @@
 import android.app.BroadcastOptions;
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
+import android.app.compat.CompatChanges;
 import android.app.usage.NetworkStatsManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -121,6 +122,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.res.XmlResourceParser;
 import android.database.ContentObserver;
 import android.net.CaptivePortal;
 import android.net.CaptivePortalData;
@@ -195,6 +197,7 @@
 import android.net.Uri;
 import android.net.VpnManager;
 import android.net.VpnTransportInfo;
+import android.net.connectivity.ConnectivityCompatChanges;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
 import android.net.netd.aidl.NativeUidRangeConfig;
@@ -269,6 +272,7 @@
 import com.android.networkstack.apishim.ConstantsShim;
 import com.android.networkstack.apishim.common.BroadcastOptionsShim;
 import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
+import com.android.server.connectivity.ApplicationSelfCertifiedNetworkCapabilities;
 import com.android.server.connectivity.AutodestructReference;
 import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker;
 import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker.AutomaticOnOffKeepalive;
@@ -279,6 +283,7 @@
 import com.android.server.connectivity.DnsManager.PrivateDnsValidationUpdate;
 import com.android.server.connectivity.DscpPolicyTracker;
 import com.android.server.connectivity.FullScore;
+import com.android.server.connectivity.InvalidTagException;
 import com.android.server.connectivity.KeepaliveTracker;
 import com.android.server.connectivity.LingerMonitor;
 import com.android.server.connectivity.MockableSystemProperties;
@@ -300,6 +305,8 @@
 
 import libcore.io.IoUtils;
 
+import org.xmlpull.v1.XmlPullParserException;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -902,6 +909,13 @@
     // Only the handler thread is allowed to access this field.
     private long mIngressRateLimit = -1;
 
+    // This is the cache for the packageName -> ApplicationSelfCertifiedNetworkCapabilities. This
+    // value can be accessed from both handler thread and any random binder thread. Therefore,
+    // accessing this value requires holding a lock.
+    @GuardedBy("mSelfCertifiedCapabilityCache")
+    private final Map<String, ApplicationSelfCertifiedNetworkCapabilities>
+            mSelfCertifiedCapabilityCache = new HashMap<>();
+
     /**
      * Implements support for the legacy "one network per network type" model.
      *
@@ -1452,6 +1466,20 @@
         public BroadcastOptionsShim makeBroadcastOptionsShim(BroadcastOptions options) {
             return BroadcastOptionsShimImpl.newInstance(options);
         }
+
+        /**
+         * Wrapper method for
+         * {@link android.app.compat.CompatChanges#isChangeEnabled(long, String, UserHandle)}.
+         *
+         * @param changeId    The ID of the compatibility change in question.
+         * @param packageName The package name of the app in question.
+         * @param user        The user that the operation is done for.
+         * @return {@code true} if the change is enabled for the specified package.
+         */
+        public boolean isChangeEnabled(long changeId, @NonNull final String packageName,
+                @NonNull final UserHandle user) {
+            return CompatChanges.isChangeEnabled(changeId, packageName, user);
+        }
     }
 
     public ConnectivityService(Context context) {
@@ -6319,6 +6347,11 @@
         if (isMappedInOemNetworkPreference(packageName)) {
             handleSetOemNetworkPreference(mOemNetworkPreferences, null);
         }
+
+        // Invalidates cache entry when the package is updated.
+        synchronized (mSelfCertifiedCapabilityCache) {
+            mSelfCertifiedCapabilityCache.remove(packageName);
+        }
     }
 
     private final BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
@@ -6947,8 +6980,69 @@
                 asUid, requests, nr, msgr, binder, callbackFlags, callingAttributionTag);
     }
 
+    private boolean shouldCheckCapabilitiesDeclaration(
+            @NonNull final NetworkCapabilities networkCapabilities, final int callingUid,
+            @NonNull final String callingPackageName) {
+        final UserHandle user = UserHandle.getUserHandleForUid(callingUid);
+        // Only run the check if the change is enabled.
+        if (!mDeps.isChangeEnabled(
+                ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION,
+                callingPackageName, user)) {
+            return false;
+        }
+
+        return networkCapabilities.hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                || networkCapabilities.hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY);
+    }
+
+    private void enforceRequestCapabilitiesDeclaration(@NonNull final String callerPackageName,
+            @NonNull final NetworkCapabilities networkCapabilities) {
+        // This check is added to fix the linter error for "current min is 30", which is not going
+        // to happen because Connectivity service always run in S+.
+        if (!SdkLevel.isAtLeastS()) {
+            Log.wtf(TAG, "Connectivity service should always run in at least SDK S");
+            return;
+        }
+        ApplicationSelfCertifiedNetworkCapabilities applicationNetworkCapabilities;
+        try {
+            synchronized (mSelfCertifiedCapabilityCache) {
+                applicationNetworkCapabilities = mSelfCertifiedCapabilityCache.get(
+                        callerPackageName);
+                if (applicationNetworkCapabilities == null) {
+                    final PackageManager packageManager = mContext.getPackageManager();
+                    final PackageManager.Property networkSliceProperty = packageManager.getProperty(
+                            ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES,
+                            callerPackageName
+                    );
+                    final XmlResourceParser parser = packageManager
+                            .getResourcesForApplication(callerPackageName)
+                            .getXml(networkSliceProperty.getResourceId());
+                    applicationNetworkCapabilities =
+                            ApplicationSelfCertifiedNetworkCapabilities.createFromXml(parser);
+                    mSelfCertifiedCapabilityCache.put(callerPackageName,
+                            applicationNetworkCapabilities);
+                }
+
+            }
+        } catch (PackageManager.NameNotFoundException ne) {
+            throw new SecurityException(
+                    "Cannot find " + ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES
+                            + " property");
+        } catch (XmlPullParserException | IOException | InvalidTagException e) {
+            throw new SecurityException(e.getMessage());
+        }
+
+        applicationNetworkCapabilities.enforceSelfCertifiedNetworkCapabilitiesDeclared(
+                networkCapabilities);
+    }
     private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities,
             String callingPackageName, String callingAttributionTag, final int callingUid) {
+        if (shouldCheckCapabilitiesDeclaration(networkCapabilities, callingUid,
+                callingPackageName)) {
+            enforceRequestCapabilitiesDeclaration(callingPackageName, networkCapabilities);
+        }
         if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) {
             // For T+ devices, callers with carrier privilege could request with CBS capabilities.
             if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_CBS)
diff --git a/service/src/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilities.java b/service/src/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilities.java
new file mode 100644
index 0000000..76e966f
--- /dev/null
+++ b/service/src/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilities.java
@@ -0,0 +1,209 @@
+/*
+ * 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.connectivity;
+
+
+import android.annotation.NonNull;
+import android.net.NetworkCapabilities;
+import android.util.Log;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayDeque;
+
+
+/**
+ * The class for parsing and checking the self-declared application network capabilities.
+ *
+ * ApplicationSelfCertifiedNetworkCapabilities is an immutable class that
+ * can parse the self-declared application network capabilities in the application resources. The
+ * class also provides a helper method to check whether the requested network capabilities
+ * already self-declared.
+ */
+public final class ApplicationSelfCertifiedNetworkCapabilities {
+
+    public static final String PRIORITIZE_LATENCY = "NET_CAPABILITY_PRIORITIZE_LATENCY";
+    public static final String PRIORITIZE_BANDWIDTH = "NET_CAPABILITY_PRIORITIZE_BANDWIDTH";
+
+    private static final String TAG =
+            ApplicationSelfCertifiedNetworkCapabilities.class.getSimpleName();
+    private static final String NETWORK_CAPABILITIES_DECLARATION_TAG =
+            "network-capabilities-declaration";
+    private static final String USES_NETWORK_CAPABILITY_TAG = "uses-network-capability";
+    private static final String NAME_TAG = "name";
+
+    private long mRequestedNetworkCapabilities = 0;
+
+    /**
+     * Creates {@link ApplicationSelfCertifiedNetworkCapabilities} from a xml parser.
+     *
+     * <p> Here is an example of the xml syntax:
+     *
+     * <pre>
+     * {@code
+     *  <network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+     *     <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+     *     <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+     * </network-capabilities-declaration>
+     * }
+     * </pre>
+     * <p>
+     *
+     * @param xmlParser The underlying {@link XmlPullParser} that will read the xml.
+     * @return An ApplicationSelfCertifiedNetworkCapabilities object.
+     * @throws InvalidTagException    if the capabilities in xml config contains invalid tag.
+     * @throws XmlPullParserException if xml parsing failed.
+     * @throws IOException            if unable to read the xml file properly.
+     */
+    @NonNull
+    public static ApplicationSelfCertifiedNetworkCapabilities createFromXml(
+            @NonNull final XmlPullParser xmlParser)
+            throws InvalidTagException, XmlPullParserException, IOException {
+        return new ApplicationSelfCertifiedNetworkCapabilities(parseXml(xmlParser));
+    }
+
+    private static long parseXml(@NonNull final XmlPullParser xmlParser)
+            throws InvalidTagException, XmlPullParserException, IOException {
+        long requestedNetworkCapabilities = 0;
+        final ArrayDeque<String> openTags = new ArrayDeque<>();
+
+        while (checkedNextTag(xmlParser, openTags) != XmlPullParser.START_TAG) {
+            continue;
+        }
+
+        // Validates the tag is "network-capabilities-declaration"
+        if (!xmlParser.getName().equals(NETWORK_CAPABILITIES_DECLARATION_TAG)) {
+            throw new InvalidTagException("Invalid tag: " + xmlParser.getName());
+        }
+
+        checkedNextTag(xmlParser, openTags);
+        int eventType = xmlParser.getEventType();
+        while (eventType != XmlPullParser.END_DOCUMENT) {
+            switch (eventType) {
+                case XmlPullParser.START_TAG:
+                    // USES_NETWORK_CAPABILITY_TAG should directly be declared under
+                    // NETWORK_CAPABILITIES_DECLARATION_TAG.
+                    if (xmlParser.getName().equals(USES_NETWORK_CAPABILITY_TAG)
+                            && openTags.size() == 1) {
+                        int capability = parseDeclarationTag(xmlParser);
+                        if (capability >= 0) {
+                            requestedNetworkCapabilities |= 1L << capability;
+                        }
+                    } else {
+                        Log.w(TAG, "Unknown tag: " + xmlParser.getName() + " ,tags stack size: "
+                                + openTags.size());
+                    }
+                    break;
+                default:
+                    break;
+            }
+            eventType = checkedNextTag(xmlParser, openTags);
+        }
+        // Checks all the tags are parsed.
+        if (!openTags.isEmpty()) {
+            throw new InvalidTagException("Unbalanced tag: " + openTags.peek());
+        }
+        return requestedNetworkCapabilities;
+    }
+
+    private static int parseDeclarationTag(@NonNull final XmlPullParser xmlParser) {
+        String name = null;
+        for (int i = 0; i < xmlParser.getAttributeCount(); i++) {
+            final String attrName = xmlParser.getAttributeName(i);
+            if (attrName.equals(NAME_TAG)) {
+                name = xmlParser.getAttributeValue(i);
+            } else {
+                Log.w(TAG, "Unknown attribute name: " + attrName);
+            }
+        }
+        if (name != null) {
+            switch (name) {
+                case PRIORITIZE_BANDWIDTH:
+                    return NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH;
+                case PRIORITIZE_LATENCY:
+                    return NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY;
+                default:
+                    Log.w(TAG, "Unknown capability declaration name: " + name);
+            }
+        } else {
+            Log.w(TAG, "uses-network-capability name must be specified");
+        }
+        // Invalid capability
+        return -1;
+    }
+
+    private static int checkedNextTag(@NonNull final XmlPullParser xmlParser,
+            @NonNull final ArrayDeque<String> openTags)
+            throws XmlPullParserException, IOException, InvalidTagException {
+        if (xmlParser.getEventType() == XmlPullParser.START_TAG) {
+            openTags.addFirst(xmlParser.getName());
+        } else if (xmlParser.getEventType() == XmlPullParser.END_TAG) {
+            if (!openTags.isEmpty() && openTags.peekFirst().equals(xmlParser.getName())) {
+                openTags.removeFirst();
+            } else {
+                throw new InvalidTagException("Unbalanced tag: " + xmlParser.getName());
+            }
+        }
+        return xmlParser.next();
+    }
+
+    private ApplicationSelfCertifiedNetworkCapabilities(long requestedNetworkCapabilities) {
+        mRequestedNetworkCapabilities = requestedNetworkCapabilities;
+    }
+
+    /**
+     * Enforces self-certified capabilities are declared.
+     *
+     * @param networkCapabilities the input NetworkCapabilities to check against.
+     * @throws SecurityException if the capabilities are not properly self-declared.
+     */
+    public void enforceSelfCertifiedNetworkCapabilitiesDeclared(
+            @NonNull final NetworkCapabilities networkCapabilities) {
+        if (networkCapabilities.hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                && !hasPrioritizeBandwidth()) {
+            throw new SecurityException(
+                    "Missing " + ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_BANDWIDTH
+                            + " declaration");
+        }
+        if (networkCapabilities.hasCapability(
+                NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                && !hasPrioritizeLatency()) {
+            throw new SecurityException(
+                    "Missing " + ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_LATENCY
+                            + " declaration");
+        }
+    }
+
+    /**
+     * Checks if NET_CAPABILITY_PRIORITIZE_LATENCY is declared.
+     */
+    private boolean hasPrioritizeLatency() {
+        return (mRequestedNetworkCapabilities & (1L
+                << NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)) != 0;
+    }
+
+    /**
+     * Checks if NET_CAPABILITY_PRIORITIZE_BANDWIDTH is declared.
+     */
+    private boolean hasPrioritizeBandwidth() {
+        return (mRequestedNetworkCapabilities & (1L
+                << NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)) != 0;
+    }
+}
diff --git a/service/src/com/android/server/connectivity/InvalidTagException.java b/service/src/com/android/server/connectivity/InvalidTagException.java
new file mode 100644
index 0000000..b924d27
--- /dev/null
+++ b/service/src/com/android/server/connectivity/InvalidTagException.java
@@ -0,0 +1,28 @@
+/*
+ * 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.connectivity;
+
+
+/**
+ * An exception thrown when a Tag is not valid in self_certified_network_capabilities.xml.
+ */
+public class InvalidTagException extends Exception {
+
+    public InvalidTagException(String message) {
+        super(message);
+    }
+}
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index ff9bd31..891c2dd 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -47,6 +47,9 @@
     data: [
         ":CtsHostsideNetworkTestsApp",
         ":CtsHostsideNetworkTestsApp2",
+        ":CtsHostsideNetworkCapTestsAppWithoutProperty",
+        ":CtsHostsideNetworkCapTestsAppWithProperty",
+        ":CtsHostsideNetworkCapTestsAppSdk33",
     ] + next_app_data,
     per_testcase_directory: true,
 }
diff --git a/tests/cts/hostside/networkslicingtestapp/Android.bp b/tests/cts/hostside/networkslicingtestapp/Android.bp
new file mode 100644
index 0000000..2aa3f69
--- /dev/null
+++ b/tests/cts/hostside/networkslicingtestapp/Android.bp
@@ -0,0 +1,65 @@
+//
+// 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"],
+}
+
+java_defaults {
+    name: "CtsHostsideNetworkCapTestsAppDefaults",
+    platform_apis: true,
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "modules-utils-build",
+        "cts-net-utils",
+    ],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+        "sts",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsHostsideNetworkCapTestsAppWithoutProperty",
+    defaults: [
+           "cts_support_defaults",
+           "CtsHostsideNetworkCapTestsAppDefaults"
+    ],
+    manifest: "AndroidManifestWithoutProperty.xml",
+}
+
+android_test_helper_app {
+    name: "CtsHostsideNetworkCapTestsAppWithProperty",
+    defaults: [
+           "cts_support_defaults",
+           "CtsHostsideNetworkCapTestsAppDefaults"
+    ],
+    manifest: "AndroidManifestWithProperty.xml",
+}
+
+android_test_helper_app {
+    name: "CtsHostsideNetworkCapTestsAppSdk33",
+    defaults: [
+           "cts_support_defaults",
+           "CtsHostsideNetworkCapTestsAppDefaults"
+    ],
+    target_sdk_version: "33",
+    manifest: "AndroidManifestWithoutProperty.xml",
+}
diff --git a/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithProperty.xml b/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithProperty.xml
new file mode 100644
index 0000000..3ef0376
--- /dev/null
+++ b/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithProperty.xml
@@ -0,0 +1,33 @@
+<?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="com.android.cts.net.hostside.networkslicingtestapp">
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+
+    <application>
+        <uses-library android:name="android.test.runner"/>
+        <property android:name="android.net.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES"
+                  android:resource="@xml/self_certified_network_capabilities_both" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.net.hostside.networkslicingtestapp"/>
+
+</manifest>
diff --git a/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithoutProperty.xml b/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithoutProperty.xml
new file mode 100644
index 0000000..fe66684
--- /dev/null
+++ b/tests/cts/hostside/networkslicingtestapp/AndroidManifestWithoutProperty.xml
@@ -0,0 +1,30 @@
+<?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="com.android.cts.net.hostside.networkslicingtestapp">
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+
+    <application>
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.net.hostside.networkslicingtestapp"/>
+
+</manifest>
diff --git a/tests/cts/hostside/networkslicingtestapp/res/xml/self_certified_network_capabilities_both.xml b/tests/cts/hostside/networkslicingtestapp/res/xml/self_certified_network_capabilities_both.xml
new file mode 100644
index 0000000..4066be2
--- /dev/null
+++ b/tests/cts/hostside/networkslicingtestapp/res/xml/self_certified_network_capabilities_both.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+</network-capabilities-declaration>
diff --git a/tests/cts/hostside/networkslicingtestapp/src/com.android.cts.net.hostside.networkslicingtestapp/NetworkSelfDeclaredCapabilitiesTest.java b/tests/cts/hostside/networkslicingtestapp/src/com.android.cts.net.hostside.networkslicingtestapp/NetworkSelfDeclaredCapabilitiesTest.java
new file mode 100644
index 0000000..39792fc
--- /dev/null
+++ b/tests/cts/hostside/networkslicingtestapp/src/com.android.cts.net.hostside.networkslicingtestapp/NetworkSelfDeclaredCapabilitiesTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.cts.net.hostside.networkslicingtestapp;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.os.Build;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class NetworkSelfDeclaredCapabilitiesTest {
+
+    @Rule
+    public final DevSdkIgnoreRule mDevSdkIgnoreRule = new DevSdkIgnoreRule();
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void requestNetwork_withoutRequestCapabilities() {
+        final ConnectivityManager cm =
+                (ConnectivityManager)
+                        InstrumentationRegistry.getInstrumentation()
+                                .getContext()
+                                .getSystemService(Context.CONNECTIVITY_SERVICE);
+        final NetworkRequest request =
+                new NetworkRequest.Builder().build();
+        final ConnectivityManager.NetworkCallback callback =
+                new ConnectivityManager.NetworkCallback();
+        cm.requestNetwork(request, callback);
+        cm.unregisterNetworkCallback(callback);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void requestNetwork_withSelfDeclaredCapabilities() {
+        final ConnectivityManager cm =
+                (ConnectivityManager)
+                        InstrumentationRegistry.getInstrumentation()
+                                .getContext()
+                                .getSystemService(Context.CONNECTIVITY_SERVICE);
+        final NetworkRequest request =
+                new NetworkRequest.Builder()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                        .build();
+        final ConnectivityManager.NetworkCallback callback =
+                new ConnectivityManager.NetworkCallback();
+        cm.requestNetwork(request, callback);
+        cm.unregisterNetworkCallback(callback);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    public void requestNetwork_lackingRequiredSelfDeclaredCapabilities() {
+        final ConnectivityManager cm =
+                (ConnectivityManager)
+                        InstrumentationRegistry.getInstrumentation()
+                                .getContext()
+                                .getSystemService(Context.CONNECTIVITY_SERVICE);
+        final NetworkRequest request =
+                new NetworkRequest.Builder()
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                        .build();
+        final ConnectivityManager.NetworkCallback callback =
+                new ConnectivityManager.NetworkCallback();
+        assertThrows(
+                SecurityException.class,
+                () -> cm.requestNetwork(request, callback));
+    }
+
+}
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideSelfDeclaredNetworkCapabilitiesCheckTest.java b/tests/cts/hostside/src/com/android/cts/net/HostsideSelfDeclaredNetworkCapabilitiesCheckTest.java
new file mode 100644
index 0000000..4c2985d
--- /dev/null
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideSelfDeclaredNetworkCapabilitiesCheckTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.cts.net;
+
+public class HostsideSelfDeclaredNetworkCapabilitiesCheckTest extends HostsideNetworkTestCase {
+
+    private static final String TEST_WITH_PROPERTY_IN_CURRENT_SDK_APK =
+            "CtsHostsideNetworkCapTestsAppWithProperty.apk";
+    private static final String TEST_WITHOUT_PROPERTY_IN_CURRENT_SDK_APK =
+            "CtsHostsideNetworkCapTestsAppWithoutProperty.apk";
+    private static final String TEST_IN_SDK_33_APK =
+            "CtsHostsideNetworkCapTestsAppSdk33.apk";
+    private static final String TEST_APP_PKG =
+            "com.android.cts.net.hostside.networkslicingtestapp";
+    private static final String TEST_CLASS_NAME = ".NetworkSelfDeclaredCapabilitiesTest";
+    private static final String WITH_SELF_DECLARED_CAPABILITIES_METHOD =
+            "requestNetwork_withSelfDeclaredCapabilities";
+    private static final String LACKING_SELF_DECLARED_CAPABILITIES_METHOD =
+            "requestNetwork_lackingRequiredSelfDeclaredCapabilities";
+    private static final String WITHOUT_REQUEST_CAPABILITIES_METHOD =
+            "requestNetwork_withoutRequestCapabilities";
+
+
+    public void testRequestNetworkInCurrentSdkWithProperty() throws Exception {
+        uninstallPackage(TEST_APP_PKG, false);
+        installPackage(TEST_WITH_PROPERTY_IN_CURRENT_SDK_APK);
+        // If the self-declared capabilities are defined,
+        // the ConnectivityManager.requestNetwork() call should always pass.
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                WITH_SELF_DECLARED_CAPABILITIES_METHOD);
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                WITHOUT_REQUEST_CAPABILITIES_METHOD);
+        uninstallPackage(TEST_APP_PKG, true);
+    }
+
+    public void testRequestNetworkInCurrentSdkWithoutProperty() throws Exception {
+        uninstallPackage(TEST_APP_PKG, false);
+        installPackage(TEST_WITHOUT_PROPERTY_IN_CURRENT_SDK_APK);
+        // If the self-declared capabilities are not defined,
+        // the ConnectivityManager.requestNetwork() call will fail if the properly is not declared.
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                LACKING_SELF_DECLARED_CAPABILITIES_METHOD);
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                WITHOUT_REQUEST_CAPABILITIES_METHOD);
+        uninstallPackage(TEST_APP_PKG, true);
+    }
+
+    public void testRequestNetworkInSdk33() throws Exception {
+        uninstallPackage(TEST_APP_PKG, false);
+        installPackage(TEST_IN_SDK_33_APK);
+        // In Sdk33, the ConnectivityManager.requestNetwork() call should always pass.
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                WITH_SELF_DECLARED_CAPABILITIES_METHOD);
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                WITHOUT_REQUEST_CAPABILITIES_METHOD);
+        uninstallPackage(TEST_APP_PKG, true);
+    }
+
+    public void testReinstallPackageWillUpdateProperty() throws Exception {
+        uninstallPackage(TEST_APP_PKG, false);
+        installPackage(TEST_WITHOUT_PROPERTY_IN_CURRENT_SDK_APK);
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                LACKING_SELF_DECLARED_CAPABILITIES_METHOD);
+        uninstallPackage(TEST_APP_PKG, true);
+
+
+        // Updates package.
+        installPackage(TEST_WITH_PROPERTY_IN_CURRENT_SDK_APK);
+        runDeviceTests(TEST_APP_PKG,
+                TEST_APP_PKG + TEST_CLASS_NAME,
+                WITH_SELF_DECLARED_CAPABILITIES_METHOD);
+        uninstallPackage(TEST_APP_PKG, true);
+
+    }
+}
+
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 5b98237..1a358b2 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -183,6 +183,8 @@
 import static com.android.testutils.RecorderCallback.CallbackEntry.UNAVAILABLE;
 import static com.android.testutils.TestPermissionUtil.runAsShell;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -228,6 +230,7 @@
 import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
 import android.app.usage.NetworkStatsManager;
+import android.compat.testing.PlatformCompatChangeRule;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentProvider;
@@ -312,6 +315,7 @@
 import android.net.Uri;
 import android.net.VpnManager;
 import android.net.VpnTransportInfo;
+import android.net.connectivity.ConnectivityCompatChanges;
 import android.net.metrics.IpConnectivityLog;
 import android.net.netd.aidl.NativeUidRangeConfig;
 import android.net.networkstack.NetworkStackClientBase;
@@ -379,6 +383,7 @@
 import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo;
 import com.android.server.ConnectivityService.NetworkRequestInfo;
 import com.android.server.ConnectivityServiceTest.ConnectivityServiceDependencies.ReportedInterfaces;
+import com.android.server.connectivity.ApplicationSelfCertifiedNetworkCapabilities;
 import com.android.server.connectivity.AutomaticOnOffKeepaliveTracker;
 import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
 import com.android.server.connectivity.ClatCoordinator;
@@ -406,6 +411,9 @@
 import com.android.testutils.TestableNetworkCallback;
 import com.android.testutils.TestableNetworkOfferCallback;
 
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -475,6 +483,9 @@
     @Rule
     public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
 
+    @Rule
+    public final PlatformCompatChangeRule compatChangeRule = new PlatformCompatChangeRule();
+
     private static final int TIMEOUT_MS = 2_000;
     // Broadcasts can take a long time to be delivered. The test will not wait for that long unless
     // there is a failure, so use a long timeout.
@@ -2105,6 +2116,37 @@
             reset(mBroadcastOptionsShim);
             return mBroadcastOptionsShim;
         }
+
+        @GuardedBy("this")
+        private boolean mForceDisableCompatChangeCheck = true;
+
+        /**
+         * By default, the {@link #isChangeEnabled(long, String, UserHandle)} will always return
+         * true as the mForceDisableCompatChangeCheck is true and compat change check logic is
+         * never executed. The compat change check logic can be turned on by calling this method.
+         * If this method is called, the
+         * {@link libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges} or
+         * {@link libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges} must be
+         * used to turn on/off the compat change flag.
+         */
+        private void enableCompatChangeCheck() {
+            synchronized (this) {
+                mForceDisableCompatChangeCheck = false;
+            }
+        }
+
+        @Override
+        public boolean isChangeEnabled(long changeId,
+                @NonNull final String packageName,
+                @NonNull final UserHandle user) {
+            synchronized (this) {
+                if (mForceDisableCompatChangeCheck) {
+                    return false;
+                } else {
+                    return super.isChangeEnabled(changeId, packageName, user);
+                }
+            }
+        }
     }
 
     private class AutomaticOnOffKeepaliveTrackerDependencies
@@ -6310,6 +6352,142 @@
         }
     }
 
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @DisableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+    public void testSelfCertifiedCapabilitiesDisabled()
+            throws Exception {
+        mDeps.enableCompatChangeCheck();
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                .build();
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        mCm.requestNetwork(networkRequest, cb);
+        mCm.unregisterNetworkCallback(cb);
+    }
+
+    /** Set the networkSliceResourceId to 0 will result in NameNotFoundException be thrown. */
+    private void setupMockForNetworkCapabilitiesResources(int networkSliceResourceId)
+            throws PackageManager.NameNotFoundException {
+        if (networkSliceResourceId == 0) {
+            doThrow(new PackageManager.NameNotFoundException()).when(mPackageManager).getProperty(
+                    ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES,
+                    mContext.getPackageName());
+        } else {
+            final PackageManager.Property property = new PackageManager.Property(
+                    ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES,
+                    networkSliceResourceId,
+                    true /* isResource */,
+                    mContext.getPackageName(),
+                    "dummyClass"
+            );
+            doReturn(property).when(mPackageManager).getProperty(
+                    ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES,
+                    mContext.getPackageName());
+            doReturn(mContext.getResources()).when(mPackageManager).getResourcesForApplication(
+                    mContext.getPackageName());
+        }
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @EnableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+    public void requestNetwork_withoutPrioritizeBandwidthDeclaration_shouldThrowException()
+            throws Exception {
+        mDeps.enableCompatChangeCheck();
+        setupMockForNetworkCapabilitiesResources(
+                com.android.frameworks.tests.net.R.xml.self_certified_capabilities_latency);
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                .build();
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        final Exception e = assertThrows(SecurityException.class,
+                () -> mCm.requestNetwork(networkRequest, cb));
+        assertThat(e.getMessage(),
+                containsString(ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_BANDWIDTH));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @EnableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+    public void requestNetwork_withoutPrioritizeLatencyDeclaration_shouldThrowException()
+            throws Exception {
+        mDeps.enableCompatChangeCheck();
+        setupMockForNetworkCapabilitiesResources(
+                com.android.frameworks.tests.net.R.xml.self_certified_capabilities_bandwidth);
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                .build();
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        final Exception e = assertThrows(SecurityException.class,
+                () -> mCm.requestNetwork(networkRequest, cb));
+        assertThat(e.getMessage(),
+                containsString(ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_LATENCY));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @EnableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+    public void requestNetwork_withoutNetworkSliceProperty_shouldThrowException() throws Exception {
+        mDeps.enableCompatChangeCheck();
+        setupMockForNetworkCapabilitiesResources(0 /* networkSliceResourceId */);
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                .build();
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        final Exception e = assertThrows(SecurityException.class,
+                () -> mCm.requestNetwork(networkRequest, cb));
+        assertThat(e.getMessage(),
+                containsString(ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES));
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @EnableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+    public void requestNetwork_withNetworkSliceDeclaration_shouldSucceed() throws Exception {
+        mDeps.enableCompatChangeCheck();
+        setupMockForNetworkCapabilitiesResources(
+                com.android.frameworks.tests.net.R.xml.self_certified_capabilities_both);
+
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                .build();
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        mCm.requestNetwork(networkRequest, cb);
+        mCm.unregisterNetworkCallback(cb);
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+    @EnableCompatChanges(ConnectivityCompatChanges.ENABLE_SELF_CERTIFIED_CAPABILITIES_DECLARATION)
+    public void requestNetwork_withNetworkSliceDeclaration_shouldUseCache() throws Exception {
+        mDeps.enableCompatChangeCheck();
+        setupMockForNetworkCapabilitiesResources(
+                com.android.frameworks.tests.net.R.xml.self_certified_capabilities_both);
+
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+                .build();
+        final TestNetworkCallback cb = new TestNetworkCallback();
+        mCm.requestNetwork(networkRequest, cb);
+        mCm.unregisterNetworkCallback(cb);
+
+        // Second call should use caches
+        mCm.requestNetwork(networkRequest, cb);
+        mCm.unregisterNetworkCallback(cb);
+
+        // PackageManager's API only called once because the second call is using cache.
+        verify(mPackageManager, times(1)).getProperty(
+                ConstantsShim.PROPERTY_SELF_CERTIFIED_NETWORK_CAPABILITIES,
+                mContext.getPackageName());
+        verify(mPackageManager, times(1)).getResourcesForApplication(
+                mContext.getPackageName());
+    }
+
     /**
      * Validate the service throws if request with CBS but without carrier privilege.
      */
diff --git a/tests/unit/java/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilitiesTest.kt b/tests/unit/java/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilitiesTest.kt
new file mode 100644
index 0000000..f2d7aaa
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/ApplicationSelfCertifiedNetworkCapabilitiesTest.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.connectivity
+
+import android.net.NetworkCapabilities
+import android.os.Build
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.SmallTest
+import com.android.frameworks.tests.net.R
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(DevSdkIgnoreRunner::class)
+@SmallTest
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R)
+class ApplicationSelfCertifiedNetworkCapabilitiesTest {
+    private val mResource = InstrumentationRegistry.getContext().getResources()
+    private val bandwidthCapability = NetworkCapabilities.Builder().apply {
+        addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+    }.build()
+    private val latencyCapability = NetworkCapabilities.Builder().apply {
+        addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+    }.build()
+    private val emptyCapability = NetworkCapabilities.Builder().build()
+    private val bothCapabilities = NetworkCapabilities.Builder().apply {
+        addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_BANDWIDTH)
+        addCapability(NetworkCapabilities.NET_CAPABILITY_PRIORITIZE_LATENCY)
+    }.build()
+
+    @Test
+    fun parseXmlWithWrongTag_shouldIgnoreWrongTag() {
+        val parser = mResource.getXml(
+            R.xml.self_certified_capabilities_wrong_tag
+        )
+        val selfDeclaredCaps = ApplicationSelfCertifiedNetworkCapabilities.createFromXml(parser)
+        selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(latencyCapability)
+        selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(bandwidthCapability)
+    }
+
+    @Test
+    fun parseXmlWithWrongDeclaration_shouldThrowException() {
+        val parser = mResource.getXml(
+            R.xml.self_certified_capabilities_wrong_declaration
+        )
+        val exception = assertFailsWith<InvalidTagException> {
+            ApplicationSelfCertifiedNetworkCapabilities.createFromXml(parser)
+        }
+        assertThat(exception.message).contains("network-capabilities-declaration1")
+    }
+
+    @Test
+    fun checkIfSelfCertifiedNetworkCapabilitiesDeclared_shouldThrowExceptionWhenNoDeclaration() {
+        val parser = mResource.getXml(R.xml.self_certified_capabilities_other)
+        val selfDeclaredCaps = ApplicationSelfCertifiedNetworkCapabilities.createFromXml(parser)
+        val exception1 = assertFailsWith<SecurityException> {
+            selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(latencyCapability)
+        }
+        assertThat(exception1.message).contains(
+            ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_LATENCY
+        )
+        val exception2 = assertFailsWith<SecurityException> {
+            selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(bandwidthCapability)
+        }
+        assertThat(exception2.message).contains(
+            ApplicationSelfCertifiedNetworkCapabilities.PRIORITIZE_BANDWIDTH
+        )
+    }
+
+    @Test
+    fun checkIfSelfCertifiedNetworkCapabilitiesDeclared_shouldPassIfDeclarationExist() {
+        val parser = mResource.getXml(R.xml.self_certified_capabilities_both)
+        val selfDeclaredCaps = ApplicationSelfCertifiedNetworkCapabilities.createFromXml(parser)
+        selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(latencyCapability)
+        selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(bandwidthCapability)
+        selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(bothCapabilities)
+        selfDeclaredCaps.enforceSelfCertifiedNetworkCapabilitiesDeclared(emptyCapability)
+    }
+}
diff --git a/tests/unit/res/xml/self_certified_capabilities_bandwidth.xml b/tests/unit/res/xml/self_certified_capabilities_bandwidth.xml
new file mode 100644
index 0000000..01fdca1
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_bandwidth.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+</network-capabilities-declaration>
diff --git a/tests/unit/res/xml/self_certified_capabilities_both.xml b/tests/unit/res/xml/self_certified_capabilities_both.xml
new file mode 100644
index 0000000..4066be2
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_both.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+</network-capabilities-declaration>
diff --git a/tests/unit/res/xml/self_certified_capabilities_latency.xml b/tests/unit/res/xml/self_certified_capabilities_latency.xml
new file mode 100644
index 0000000..1c4a0e0
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_latency.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+</network-capabilities-declaration>
diff --git a/tests/unit/res/xml/self_certified_capabilities_other.xml b/tests/unit/res/xml/self_certified_capabilities_other.xml
new file mode 100644
index 0000000..5b6649c
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_other.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability android:name="other"/>
+</network-capabilities-declaration>
diff --git a/tests/unit/res/xml/self_certified_capabilities_wrong_declaration.xml b/tests/unit/res/xml/self_certified_capabilities_wrong_declaration.xml
new file mode 100644
index 0000000..6082356
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_wrong_declaration.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<network-capabilities-declaration1 xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+</network-capabilities-declaration1>
diff --git a/tests/unit/res/xml/self_certified_capabilities_wrong_tag.xml b/tests/unit/res/xml/self_certified_capabilities_wrong_tag.xml
new file mode 100644
index 0000000..c9ecc0b
--- /dev/null
+++ b/tests/unit/res/xml/self_certified_capabilities_wrong_tag.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<network-capabilities-declaration xmlns:android="http://schemas.android.com/apk/res/android">
+    <uses-network-capability1 android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_LATENCY"/>
+    <uses-network-capability android:name="NET_CAPABILITY_PRIORITIZE_BANDWIDTH"/>
+</network-capabilities-declaration>