Merge changes I24580ea4,Id48b8eab

* changes:
  Allow the system to register 250 NetworkCallbacks.
  Add more test coverage for limiting the number of callbacks.
diff --git a/Android.bp b/Android.bp
index 4709323..f910cc3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -383,6 +383,7 @@
         ":libbluetooth-binder-aidl",
         ":libcamera_client_aidl",
         ":libcamera_client_framework_aidl",
+        ":packagemanager_aidl",
         ":libupdate_engine_aidl",
         ":resourcemanager_aidl",
         ":storaged_aidl",
@@ -453,15 +454,6 @@
     ],
 }
 
-filegroup {
-    name: "framework-all-sources",
-    srcs: [
-        ":framework-mime-sources",
-        ":framework-non-updatable-sources",
-        ":framework-updatable-sources",
-    ],
-}
-
 // AIDL files under these paths are mixture of public and private ones.
 // They shouldn't be exported across module boundaries.
 java_defaults {
@@ -1228,14 +1220,6 @@
     ],
 }
 
-// Creates an index of AIDL methods; used for adding UnsupportedAppUsage
-// annotations to private apis
-aidl_mapping {
-    name: "framework-aidl-mappings",
-    srcs: [":framework-all-sources"],
-    output: "framework-aidl-mappings.txt",
-}
-
 // Avoid including Parcelable classes as we don't want to have two copies of
 // Parcelable cross the libraries. This is used by telephony-common (frameworks/opt/telephony)
 // and TeleService app (packages/services/Telephony).
@@ -1357,6 +1341,18 @@
     "--api-lint-ignore-prefix junit. " +
     "--api-lint-ignore-prefix org. "
 
+filegroup {
+    name: "framework-non-updatable-stub-sources",
+    srcs: [
+        ":framework-non-updatable-sources",
+        ":opt-telephony-srcs",
+        ":opt-net-voip-srcs",
+        "core/java/**/*.logtags",
+        "**/package.html",
+    ],
+    visibility: ["//visibility:private"],
+}
+
 build = [
     "StubLibraries.bp",
     "ApiDocs.bp",
diff --git a/ApiDocs.bp b/ApiDocs.bp
index ada80bb..dcbed0d 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -58,13 +58,10 @@
 stubs_defaults {
     name: "framework-doc-stubs-default",
     srcs: [
+        ":framework-non-updatable-stub-sources",
         ":framework-mime-sources",
-        ":framework-non-updatable-sources",
         ":framework-updatable-sources",
-        "core/java/**/*.logtags",
         "test-base/src/**/*.java",
-        ":opt-telephony-srcs",
-        ":opt-net-voip-srcs",
         ":art.module.public.api{.public.stubs.source}",
         ":conscrypt.module.public.api{.public.stubs.source}",
         ":i18n.module.public.api{.public.stubs.source}",
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 6316c4a..1644a55 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -42,13 +42,7 @@
 
 stubs_defaults {
     name: "metalava-non-updatable-api-stubs-default",
-    srcs: [
-        ":framework-non-updatable-sources",
-        "core/java/**/*.logtags",
-        ":opt-telephony-srcs",
-        ":opt-net-voip-srcs",
-        "**/package.html",
-    ],
+    srcs: [":framework-non-updatable-stub-sources"],
     sdk_version: "none",
     system_modules: "none",
     java_version: "1.8",
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index 2f5f555..26010ef6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -532,18 +532,23 @@
         /**
          * Write out a tag with data identifying this job's constraints. If the constraint isn't here
          * it doesn't apply.
+         * TODO: b/183455312 Update this code to use proper serialization for NetworkRequest,
+         *       because currently store is not including everything (like, UIDs, bandwidth,
+         *       signal strength etc. are lost).
          */
         private void writeConstraintsToXml(XmlSerializer out, JobStatus jobStatus) throws IOException {
             out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS);
             if (jobStatus.hasConnectivityConstraint()) {
                 final NetworkRequest network = jobStatus.getJob().getRequiredNetwork();
+                // STOPSHIP b/183071974: improve the scheme for backward compatibility and
+                // mainline cleanliness.
                 out.attribute(null, "net-capabilities", Long.toString(
-                        BitUtils.packBits(network.networkCapabilities.getCapabilities())));
+                        BitUtils.packBits(network.getCapabilities())));
                 out.attribute(null, "net-unwanted-capabilities", Long.toString(
-                        BitUtils.packBits(network.networkCapabilities.getUnwantedCapabilities())));
+                        BitUtils.packBits(network.getUnwantedCapabilities())));
 
                 out.attribute(null, "net-transport-types", Long.toString(
-                        BitUtils.packBits(network.networkCapabilities.getTransportTypes())));
+                        BitUtils.packBits(network.getTransportTypes())));
             }
             if (jobStatus.hasIdleConstraint()) {
                 out.attribute(null, "idle", Boolean.toString(true));
@@ -967,18 +972,23 @@
                     null, "net-unwanted-capabilities");
             final String netTransportTypes = parser.getAttributeValue(null, "net-transport-types");
             if (netCapabilities != null && netTransportTypes != null) {
-                final NetworkRequest request = new NetworkRequest.Builder().build();
+                final NetworkRequest.Builder builder = new NetworkRequest.Builder()
+                        .clearCapabilities();
                 final long unwantedCapabilities = netUnwantedCapabilities != null
                         ? Long.parseLong(netUnwantedCapabilities)
-                        : BitUtils.packBits(request.networkCapabilities.getUnwantedCapabilities());
-
+                        : BitUtils.packBits(builder.build().getUnwantedCapabilities());
                 // We're okay throwing NFE here; caught by caller
-                request.networkCapabilities.setCapabilities(
-                        BitUtils.unpackBits(Long.parseLong(netCapabilities)),
-                        BitUtils.unpackBits(unwantedCapabilities));
-                request.networkCapabilities.setTransportTypes(
-                        BitUtils.unpackBits(Long.parseLong(netTransportTypes)));
-                jobBuilder.setRequiredNetwork(request);
+                for (int capability : BitUtils.unpackBits(Long.parseLong(netCapabilities))) {
+                    builder.addCapability(capability);
+                }
+                for (int unwantedCapability : BitUtils.unpackBits(
+                        Long.parseLong(netUnwantedCapabilities))) {
+                    builder.addUnwantedCapability(unwantedCapability);
+                }
+                for (int transport : BitUtils.unpackBits(Long.parseLong(netTransportTypes))) {
+                    builder.addTransportType(transport);
+                }
+                jobBuilder.setRequiredNetwork(builder.build());
             } else {
                 // Read legacy values
                 val = parser.getAttributeValue(null, "connectivity");
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 4f472df..a522237 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -21,6 +21,7 @@
 
 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.job.JobInfo;
 import android.net.ConnectivityManager;
@@ -373,15 +374,23 @@
         }
     }
 
+    private static NetworkCapabilities.Builder copyCapabilities(
+            @NonNull final NetworkRequest request) {
+        final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
+        for (int transport : request.getTransportTypes()) builder.addTransportType(transport);
+        for (int capability : request.getCapabilities()) builder.addCapability(capability);
+        return builder;
+    }
+
     private static boolean isStrictSatisfied(JobStatus jobStatus, Network network,
             NetworkCapabilities capabilities, Constants constants) {
         // A restricted job that's out of quota MUST use an unmetered network.
         if (jobStatus.getEffectiveStandbyBucket() == RESTRICTED_INDEX
                 && !jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) {
-            final NetworkCapabilities required = new NetworkCapabilities.Builder(
-                    jobStatus.getJob().getRequiredNetwork().networkCapabilities)
-                    .addCapability(NET_CAPABILITY_NOT_METERED).build();
-            return required.satisfiedByNetworkCapabilities(capabilities);
+            final NetworkCapabilities.Builder builder =
+                    copyCapabilities(jobStatus.getJob().getRequiredNetwork());
+            builder.addCapability(NET_CAPABILITY_NOT_METERED);
+            return builder.build().satisfiedByNetworkCapabilities(capabilities);
         } else {
             return jobStatus.getJob().getRequiredNetwork().canBeSatisfiedBy(capabilities);
         }
@@ -395,10 +404,10 @@
         }
 
         // See if we match after relaxing any unmetered request
-        final NetworkCapabilities relaxed = new NetworkCapabilities.Builder(
-                jobStatus.getJob().getRequiredNetwork().networkCapabilities)
-                        .removeCapability(NET_CAPABILITY_NOT_METERED).build();
-        if (relaxed.satisfiedByNetworkCapabilities(capabilities)) {
+        final NetworkCapabilities.Builder builder =
+                copyCapabilities(jobStatus.getJob().getRequiredNetwork());
+        builder.removeCapability(NET_CAPABILITY_NOT_METERED);
+        if (builder.build().satisfiedByNetworkCapabilities(capabilities)) {
             // TODO: treat this as "maybe" response; need to check quotas
             return jobStatus.getFractionRunTime() > constants.CONN_PREFETCH_RELAX_FRAC;
         } else {
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index ee53be5..3b7a617 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -50,7 +50,6 @@
     method @NonNull public static String blockedReasonsToString(int);
     method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public int getMultipathPreference(@NonNull android.net.Network);
     method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public int getRestrictBackgroundStatus(int);
-    method public static boolean isUidBlocked(int, boolean);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public boolean isUidNetworkingBlocked(int, boolean);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public boolean isUidRestrictedOnMeteredNetworks(int);
     method @RequiresPermission(android.Manifest.permission.OBSERVE_NETWORK_POLICY) public void registerNetworkPolicyCallback(@Nullable java.util.concurrent.Executor, @NonNull android.net.NetworkPolicyManager.NetworkPolicyCallback);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 4676523..d25c84c 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -155,6 +155,7 @@
     field public static final String NETWORK_SIGNAL_STRENGTH_WAKEUP = "android.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP";
     field public static final String NETWORK_STACK = "android.permission.NETWORK_STACK";
     field public static final String NETWORK_STATS_PROVIDER = "android.permission.NETWORK_STATS_PROVIDER";
+    field public static final String NFC_SET_CONTROLLER_ALWAYS_ON = "android.permission.NFC_SET_CONTROLLER_ALWAYS_ON";
     field public static final String NOTIFICATION_DURING_SETUP = "android.permission.NOTIFICATION_DURING_SETUP";
     field public static final String NOTIFY_TV_INPUTS = "android.permission.NOTIFY_TV_INPUTS";
     field public static final String OBSERVE_APP_USAGE = "android.permission.OBSERVE_APP_USAGE";
@@ -6442,9 +6443,11 @@
     method public void notifyAlertReached();
     method public void notifyLimitReached();
     method public void notifyStatsUpdated(int, @NonNull android.net.NetworkStats, @NonNull android.net.NetworkStats);
+    method public void notifyWarningReached();
     method public abstract void onRequestStatsUpdate(int);
     method public abstract void onSetAlert(long);
     method public abstract void onSetLimit(@NonNull String, long);
+    method public void onSetWarningAndLimit(@NonNull String, long, long);
     field public static final int QUOTA_UNLIMITED = -1; // 0xffffffff
   }
 
@@ -6723,10 +6726,10 @@
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enable();
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableNdefPush();
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean enableSecureNfc(boolean);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isAlwaysOnEnabled();
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean isAlwaysOnSupported();
+    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOn();
+    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean isControllerAlwaysOnSupported();
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean removeNfcUnlockHandler(android.nfc.NfcAdapter.NfcUnlockHandler);
-    method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public boolean setAlwaysOn(boolean);
+    method @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON) public boolean setControllerAlwaysOn(boolean);
     method public void setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, int);
     field public static final int FLAG_NDEF_PUSH_NO_CONFIRM = 1; // 0x1
   }
@@ -12066,13 +12069,13 @@
     ctor @Deprecated public RcsFeature();
     ctor public RcsFeature(@NonNull java.util.concurrent.Executor);
     method public void changeEnabledCapabilities(@NonNull android.telephony.ims.feature.CapabilityChangeRequest, @NonNull android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy);
-    method @NonNull public android.telephony.ims.stub.RcsCapabilityExchangeImplBase createCapabilityExchangeImpl(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.stub.CapabilityExchangeEventListener);
+    method @NonNull public android.telephony.ims.stub.RcsCapabilityExchangeImplBase createCapabilityExchangeImpl(@NonNull android.telephony.ims.stub.CapabilityExchangeEventListener);
+    method public void destroyCapabilityExchangeImpl(@NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase);
     method public final void notifyCapabilitiesStatusChanged(@NonNull android.telephony.ims.feature.RcsFeature.RcsImsCapabilities);
     method public void onFeatureReady();
     method public void onFeatureRemoved();
     method public boolean queryCapabilityConfiguration(int, int);
     method @NonNull public final android.telephony.ims.feature.RcsFeature.RcsImsCapabilities queryCapabilityStatus();
-    method public void removeCapabilityExchangeImpl(@NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase);
   }
 
   public static class RcsFeature.RcsImsCapabilities extends android.telephony.ims.feature.ImsFeature.Capabilities {
@@ -12284,7 +12287,7 @@
   }
 
   public class RcsCapabilityExchangeImplBase {
-    ctor public RcsCapabilityExchangeImplBase(@NonNull java.util.concurrent.Executor);
+    ctor public RcsCapabilityExchangeImplBase();
     method public void publishCapabilities(@NonNull String, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.PublishResponseCallback);
     method public void sendOptionsCapabilityRequest(@NonNull android.net.Uri, @NonNull java.util.Set<java.lang.String>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.OptionsResponseCallback);
     method public void subscribeForCapabilities(@NonNull java.util.Collection<android.net.Uri>, @NonNull android.telephony.ims.stub.RcsCapabilityExchangeImplBase.SubscribeResponseCallback);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 1d094c3..2f160e9 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1064,6 +1064,7 @@
     field public static final int FIRST_ISOLATED_UID = 99000; // 0x182b8
     field public static final int LAST_APP_ZYGOTE_ISOLATED_UID = 98999; // 0x182b7
     field public static final int LAST_ISOLATED_UID = 99999; // 0x1869f
+    field public static final int NFC_UID = 1027; // 0x403
     field public static final int NUM_UIDS_PER_APP_ZYGOTE = 100; // 0x64
   }
 
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 3971329..68606ec 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -18,8 +18,6 @@
 
 import static android.app.ActivityManager.procStateToString;
 import static android.content.pm.PackageManager.GET_SIGNATURES;
-import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
-import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -787,36 +785,6 @@
     }
 
     /**
-     * Returns whether network access of an UID is blocked or not based on {@code blockedReasons}
-     * corresponding to it.
-     *
-     * {@code blockedReasons} would be a bitwise {@code OR} combination of the
-     * {@code BLOCKED_REASON_*} and/or {@code BLOCKED_METERED_REASON_*} constants.
-     *
-     * @param blockedReasons Value indicating the reasons for why the network access of an UID is
-     *                       blocked. If the value is equal to
-     *                       {@link ConnectivityManager#BLOCKED_REASON_NONE}, then
-     *                       it indicates that an app's network access is not blocked.
-     * @param meteredNetwork Value indicating whether the network is metered or not.
-     * @return Whether network access is blocked or not.
-     * @hide
-     */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-    public static boolean isUidBlocked(int blockedReasons, boolean meteredNetwork) {
-        if (blockedReasons == BLOCKED_REASON_NONE) {
-            return false;
-        }
-        final int blockedOnAllNetworksReason = (blockedReasons & ~BLOCKED_METERED_REASON_MASK);
-        if (blockedOnAllNetworksReason != BLOCKED_REASON_NONE) {
-            return true;
-        }
-        if (meteredNetwork) {
-            return blockedReasons != BLOCKED_REASON_NONE;
-        }
-        return false;
-    }
-
-    /**
      * Returns the {@code string} representation of {@code blockedReasons} argument.
      *
      * @param blockedReasons Value indicating the reasons for why the network access of an UID is
diff --git a/core/java/android/net/netstats/provider/NetworkStatsProvider.java b/core/java/android/net/netstats/provider/NetworkStatsProvider.java
index 65b336a..23fc069 100644
--- a/core/java/android/net/netstats/provider/NetworkStatsProvider.java
+++ b/core/java/android/net/netstats/provider/NetworkStatsProvider.java
@@ -147,10 +147,7 @@
 
     /**
      * Notify system that the warning set by {@link #onSetWarningAndLimit} has been reached.
-     *
-     * @hide
      */
-    // TODO: Expose as system API.
     public void notifyWarningReached() {
         try {
             // Reuse the code path to notify warning reached with limit reached
@@ -198,7 +195,6 @@
      * @param quotaBytes the quota defined as the number of bytes, starting from zero and counting
      *                   from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit.
      */
-    // TODO: deprecate this once onSetWarningAndLimit is ready.
     public abstract void onSetLimit(@NonNull String iface, long quotaBytes);
 
     /**
@@ -217,10 +213,7 @@
      *                     there is no warning.
      * @param limitBytes the limit defined as the number of bytes, starting from zero and counting
      *                   from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit.
-     *
-     * @hide
      */
-    // TODO: Expose as system API.
     public void onSetWarningAndLimit(@NonNull String iface, long warningBytes, long limitBytes) {
         // Backward compatibility for those who didn't override this function.
         onSetLimit(iface, limitBytes);
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index bc3d5c4..11445e9 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -72,7 +72,7 @@
     boolean deviceSupportsNfcSecure();
     boolean setNfcSecure(boolean enable);
 
-    boolean setAlwaysOn(boolean value);
-    boolean isAlwaysOnEnabled();
-    boolean isAlwaysOnSupported();
+    boolean setControllerAlwaysOn(boolean value);
+    boolean isControllerAlwaysOn();
+    boolean isControllerAlwaysOnSupported();
 }
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index e85eb93..eed2c77 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -2254,13 +2254,13 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    public boolean setAlwaysOn(boolean value) {
+    @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
+    public boolean setControllerAlwaysOn(boolean value) {
         if (!sHasNfcFeature) {
             throw new UnsupportedOperationException();
         }
         try {
-            return sService.setAlwaysOn(value);
+            return sService.setControllerAlwaysOn(value);
         } catch (RemoteException e) {
             attemptDeadServiceRecovery(e);
             // Try one more time
@@ -2269,7 +2269,7 @@
                 return false;
             }
             try {
-                return sService.setAlwaysOn(value);
+                return sService.setControllerAlwaysOn(value);
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to recover NFC Service.");
             }
@@ -2286,10 +2286,10 @@
      */
 
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    public boolean isAlwaysOnEnabled() {
+    @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
+    public boolean isControllerAlwaysOn() {
         try {
-            return sService.isAlwaysOnEnabled();
+            return sService.isControllerAlwaysOn();
         } catch (RemoteException e) {
             attemptDeadServiceRecovery(e);
             // Try one more time
@@ -2298,7 +2298,7 @@
                 return false;
             }
             try {
-                return sService.isAlwaysOnEnabled();
+                return sService.isControllerAlwaysOn();
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to recover NFC Service.");
             }
@@ -2315,13 +2315,13 @@
      */
 
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
-    public boolean isAlwaysOnSupported() {
+    @RequiresPermission(android.Manifest.permission.NFC_SET_CONTROLLER_ALWAYS_ON)
+    public boolean isControllerAlwaysOnSupported() {
         if (!sHasNfcFeature) {
             throw new UnsupportedOperationException();
         }
         try {
-            return sService.isAlwaysOnSupported();
+            return sService.isControllerAlwaysOnSupported();
         } catch (RemoteException e) {
             attemptDeadServiceRecovery(e);
             // Try one more time
@@ -2330,7 +2330,7 @@
                 return false;
             }
             try {
-                return sService.isAlwaysOnSupported();
+                return sService.isControllerAlwaysOnSupported();
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to recover NFC Service.");
             }
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 9d16f18..9b29fb1 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -127,6 +127,7 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @TestApi
     public static final int NFC_UID = 1027;
 
     /**
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f65d7a7..4a22272 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1882,6 +1882,12 @@
         android:label="@string/permlab_preferredPaymentInfo"
         android:protectionLevel="normal" />
 
+    <!-- @SystemApi Allows access to set NFC controller always on states.
+         <p>Protection level: signature|privileged
+         @hide -->
+    <permission android:name="android.permission.NFC_SET_CONTROLLER_ALWAYS_ON"
+        android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi Allows an internal user to use privileged SecureElement APIs.
          Applications holding this permission can access OMAPI reset system API
          and bypass OMAPI AccessControlEnforcer.
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index 549e074..5aacfdd 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -13,3 +13,4 @@
 yamasani@google.com
 
 per-file preinstalled-packages* = file:/MULTIUSER_OWNERS
+per-file services.core.protolog.json =  file:/services/core/java/com/android/server/wm/OWNERS
diff --git a/keystore/java/android/security/KeyStore2.java b/keystore/java/android/security/KeyStore2.java
index 75e248e..df579bb 100644
--- a/keystore/java/android/security/KeyStore2.java
+++ b/keystore/java/android/security/KeyStore2.java
@@ -125,6 +125,8 @@
         }
     }
 
+    private static final String KEYSTORE2_SERVICE_NAME =
+            "android.system.keystore2.IKeystoreService/default";
 
     private KeyStore2() {
         mBinder = null;
@@ -137,7 +139,7 @@
     private synchronized IKeystoreService getService(boolean retryLookup) {
         if (mBinder == null || retryLookup) {
             mBinder = IKeystoreService.Stub.asInterface(ServiceManager
-                    .getService("android.system.keystore2"));
+                    .getService(KEYSTORE2_SERVICE_NAME));
         }
         return mBinder;
     }
diff --git a/packages/Connectivity/framework/api/current.txt b/packages/Connectivity/framework/api/current.txt
index ad44b27..0a9560a 100644
--- a/packages/Connectivity/framework/api/current.txt
+++ b/packages/Connectivity/framework/api/current.txt
@@ -68,6 +68,7 @@
     method public boolean bindProcessToNetwork(@Nullable android.net.Network);
     method @NonNull public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
     method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network getActiveNetwork();
+    method @Nullable @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public android.net.Network getActiveNetworkForUid(int);
     method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo();
     method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo();
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network[] getAllNetworks();
@@ -387,7 +388,9 @@
   public class NetworkRequest implements android.os.Parcelable {
     method public boolean canBeSatisfiedBy(@Nullable android.net.NetworkCapabilities);
     method public int describeContents();
+    method @NonNull public int[] getCapabilities();
     method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
+    method @NonNull public int[] getTransportTypes();
     method public boolean hasCapability(int);
     method public boolean hasTransport(int);
     method public void writeToParcel(android.os.Parcel, int);
diff --git a/packages/Connectivity/framework/api/module-lib-current.txt b/packages/Connectivity/framework/api/module-lib-current.txt
index 35e45ec..cd96a1b 100644
--- a/packages/Connectivity/framework/api/module-lib-current.txt
+++ b/packages/Connectivity/framework/api/module-lib-current.txt
@@ -120,6 +120,7 @@
   }
 
   public class NetworkRequest implements android.os.Parcelable {
+    method @NonNull public int[] getUnwantedCapabilities();
     method public boolean hasUnwantedCapability(int);
   }
 
diff --git a/packages/Connectivity/framework/api/system-current.txt b/packages/Connectivity/framework/api/system-current.txt
index 1ee79a4..95ad694 100644
--- a/packages/Connectivity/framework/api/system-current.txt
+++ b/packages/Connectivity/framework/api/system-current.txt
@@ -219,7 +219,7 @@
     method public void onAutomaticReconnectDisabled();
     method public void onBandwidthUpdateRequested();
     method public void onNetworkCreated();
-    method public void onNetworkDisconnected();
+    method public void onNetworkDestroyed();
     method public void onNetworkUnwanted();
     method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter);
     method public void onQosCallbackUnregistered(int);
@@ -238,6 +238,7 @@
     method public final void sendQosSessionLost(int, int, int);
     method public final void sendSocketKeepaliveEvent(int, int);
     method @Deprecated public void setLegacySubtype(int, @NonNull String);
+    method public void setTeardownDelayMs(@IntRange(from=0, to=0x1388) int);
     method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
     method public void unregister();
     field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2
diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
index d196c1a..c6f4e0b 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -921,6 +921,7 @@
             BLOCKED_REASON_DOZE,
             BLOCKED_REASON_APP_STANDBY,
             BLOCKED_REASON_RESTRICTED_MODE,
+            BLOCKED_REASON_LOCKDOWN_VPN,
             BLOCKED_METERED_REASON_DATA_SAVER,
             BLOCKED_METERED_REASON_USER_RESTRICTED,
             BLOCKED_METERED_REASON_ADMIN_DISABLED,
@@ -1189,8 +1190,7 @@
      *
      * @return a {@link Network} object for the current default network for the
      *         given UID or {@code null} if no default network is currently active
-     *
-     * @hide
+     * TODO: b/183465229 Cleanup getActiveNetworkForUid once b/165835257 is fixed
      */
     @RequiresPermission(android.Manifest.permission.NETWORK_STACK)
     @Nullable
@@ -3659,7 +3659,8 @@
         public void onBlockedStatusChanged(@NonNull Network network, boolean blocked) {}
 
         /**
-         * Called when access to the specified network is blocked or unblocked.
+         * Called when access to the specified network is blocked or unblocked, or the reason for
+         * access being blocked changes.
          *
          * If a NetworkCallback object implements this method,
          * {@link #onBlockedStatusChanged(Network, boolean)} will not be called.
diff --git a/packages/Connectivity/framework/src/android/net/INetworkAgent.aidl b/packages/Connectivity/framework/src/android/net/INetworkAgent.aidl
index f9d3994..d941d4b 100644
--- a/packages/Connectivity/framework/src/android/net/INetworkAgent.aidl
+++ b/packages/Connectivity/framework/src/android/net/INetworkAgent.aidl
@@ -47,5 +47,5 @@
     void onQosFilterCallbackRegistered(int qosCallbackId, in QosFilterParcelable filterParcel);
     void onQosCallbackUnregistered(int qosCallbackId);
     void onNetworkCreated();
-    void onNetworkDisconnected();
+    void onNetworkDestroyed();
 }
diff --git a/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl b/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl
index cbd6193..26cb1ed 100644
--- a/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl
+++ b/packages/Connectivity/framework/src/android/net/INetworkAgentRegistry.aidl
@@ -41,4 +41,5 @@
     void sendNrQosSessionAvailable(int callbackId, in QosSession session, in NrQosSessionAttributes attributes);
     void sendQosSessionLost(int qosCallbackId, in QosSession session);
     void sendQosCallbackError(int qosCallbackId, int exceptionType);
+    void sendTeardownDelayMs(int teardownDelayMs);
 }
diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgent.java b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
index 6b55bb7..c57da53 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkAgent.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkAgent.java
@@ -185,6 +185,20 @@
     public static final int EVENT_UNDERLYING_NETWORKS_CHANGED = BASE + 5;
 
     /**
+     * Sent by the NetworkAgent to ConnectivityService to pass the current value of the teardown
+     * delay.
+     * arg1 = teardown delay in milliseconds
+     * @hide
+     */
+    public static final int EVENT_TEARDOWN_DELAY_CHANGED = BASE + 6;
+
+    /**
+     * The maximum value for the teardown delay, in milliseconds.
+     * @hide
+     */
+    public static final int MAX_TEARDOWN_DELAY_MS = 5000;
+
+    /**
      * Sent by ConnectivityService to the NetworkAgent to inform the agent of the
      * networks status - whether we could use the network or could not, due to
      * either a bad network configuration (no internet link) or captive portal.
@@ -197,7 +211,6 @@
      */
     public static final int CMD_REPORT_NETWORK_STATUS = BASE + 7;
 
-
     /**
      * Network validation suceeded.
      * Corresponds to {@link NetworkCapabilities.NET_CAPABILITY_VALIDATED}.
@@ -376,7 +389,7 @@
      *
      * @hide
      */
-    public static final int CMD_NETWORK_DISCONNECTED = BASE + 23;
+    public static final int CMD_NETWORK_DESTROYED = BASE + 23;
 
     private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) {
         final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacySubType,
@@ -581,8 +594,8 @@
                     onNetworkCreated();
                     break;
                 }
-                case CMD_NETWORK_DISCONNECTED: {
-                    onNetworkDisconnected();
+                case CMD_NETWORK_DESTROYED: {
+                    onNetworkDestroyed();
                     break;
                 }
             }
@@ -732,8 +745,8 @@
         }
 
         @Override
-        public void onNetworkDisconnected() {
-            mHandler.sendMessage(mHandler.obtainMessage(CMD_NETWORK_DISCONNECTED));
+        public void onNetworkDestroyed() {
+            mHandler.sendMessage(mHandler.obtainMessage(CMD_NETWORK_DESTROYED));
         }
     }
 
@@ -851,6 +864,29 @@
     }
 
     /**
+     * Sets the value of the teardown delay.
+     *
+     * The teardown delay is the time between when the network disconnects and when the native
+     * network corresponding to this {@code NetworkAgent} is destroyed. By default, the native
+     * network is destroyed immediately. If {@code teardownDelayMs} is non-zero, then when this
+     * network disconnects, the system will instead immediately mark the network as restricted
+     * and unavailable to unprivileged apps, but will defer destroying the native network until the
+     * teardown delay timer expires.
+     *
+     * The interfaces in use by this network will remain in use until the native network is
+     * destroyed and cannot be reused until {@link #onNetworkDestroyed()} is called.
+     *
+     * This method may be called at any time while the network is connected. It has no effect if
+     * the network is already disconnected and the teardown delay timer is running.
+     *
+     * @param teardownDelayMs the teardown delay to set, or 0 to disable teardown delay.
+     */
+    public void setTeardownDelayMs(
+            @IntRange(from = 0, to = MAX_TEARDOWN_DELAY_MS) int teardownDelayMs) {
+        queueOrSendMessage(reg -> reg.sendTeardownDelayMs(teardownDelayMs));
+    }
+
+    /**
      * Change the legacy subtype of this network agent.
      *
      * This is only for backward compatibility and should not be used by non-legacy network agents,
@@ -1053,7 +1089,7 @@
     /**
      * Called when ConnectivityService has successfully destroy this NetworkAgent's native network.
      */
-    public void onNetworkDisconnected() {}
+    public void onNetworkDestroyed() {}
 
     /**
      * Requests that the network hardware send the specified packet at the specified interval.
diff --git a/packages/Connectivity/framework/src/android/net/NetworkRequest.java b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
index bcbc04f7..38691ef 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkRequest.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkRequest.java
@@ -699,4 +699,43 @@
     public int hashCode() {
         return Objects.hash(requestId, legacyType, networkCapabilities, type);
     }
+
+    /**
+     * Gets all the capabilities set on this {@code NetworkRequest} instance.
+     *
+     * @return an array of capability values for this instance.
+     */
+    @NonNull
+    public @NetCapability int[] getCapabilities() {
+        // No need to make a defensive copy here as NC#getCapabilities() already returns
+        // a new array.
+        return networkCapabilities.getCapabilities();
+    }
+
+    /**
+     * Gets all the unwanted capabilities set on this {@code NetworkRequest} instance.
+     *
+     * @return an array of unwanted capability values for this instance.
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public @NetCapability int[] getUnwantedCapabilities() {
+        // No need to make a defensive copy here as NC#getUnwantedCapabilities() already returns
+        // a new array.
+        return networkCapabilities.getUnwantedCapabilities();
+    }
+
+    /**
+     * Gets all the transports set on this {@code NetworkRequest} instance.
+     *
+     * @return an array of transport type values for this instance.
+     */
+    @NonNull
+    public @Transport int[] getTransportTypes() {
+        // No need to make a defensive copy here as NC#getTransportTypes() already returns
+        // a new array.
+        return networkCapabilities.getTransportTypes();
+    }
 }
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 77a2d79..7ea3738 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1556,16 +1556,16 @@
         mNetworkInfoBlockingLogs.log(action + " " + uid);
     }
 
-    private void maybeLogBlockedStatusChanged(NetworkRequestInfo nri, Network net,
-            boolean blocked) {
+    private void maybeLogBlockedStatusChanged(NetworkRequestInfo nri, Network net, int blocked) {
         if (nri == null || net == null || !LOGD_BLOCKED_NETWORKINFO) {
             return;
         }
-        final String action = blocked ? "BLOCKED" : "UNBLOCKED";
+        final String action = (blocked != 0) ? "BLOCKED" : "UNBLOCKED";
         final int requestId = nri.getActiveRequest() != null
                 ? nri.getActiveRequest().requestId : nri.mRequests.get(0).requestId;
         mNetworkInfoBlockingLogs.log(String.format(
-                "%s %d(%d) on netId %d", action, nri.mAsUid, requestId, net.getNetId()));
+                "%s %d(%d) on netId %d: %s", action, nri.mAsUid, requestId, net.getNetId(),
+                blockedReasonsToString(blocked)));
     }
 
     /**
@@ -3127,6 +3127,13 @@
                     }
                     break;
                 }
+                case NetworkAgent.EVENT_TEARDOWN_DELAY_CHANGED: {
+                    if (msg.arg1 >= 0 && msg.arg1 <= NetworkAgent.MAX_TEARDOWN_DELAY_MS) {
+                        nai.teardownDelayMs = msg.arg1;
+                    } else {
+                        logwtf(nai.toShortString() + " set invalid teardown delay " + msg.arg1);
+                    }
+                }
             }
         }
 
@@ -3697,6 +3704,23 @@
         mLegacyTypeTracker.remove(nai, wasDefault);
         rematchAllNetworksAndRequests();
         mLingerMonitor.noteDisconnect(nai);
+
+        // Immediate teardown.
+        if (nai.teardownDelayMs == 0) {
+            destroyNetwork(nai);
+            return;
+        }
+
+        // Delayed teardown.
+        try {
+            mNetd.networkSetPermissionForNetwork(nai.network.netId, INetd.PERMISSION_SYSTEM);
+        } catch (RemoteException e) {
+            Log.d(TAG, "Error marking network restricted during teardown: " + e);
+        }
+        mHandler.postDelayed(() -> destroyNetwork(nai), nai.teardownDelayMs);
+    }
+
+    private void destroyNetwork(NetworkAgentInfo nai) {
         if (nai.created) {
             // Tell netd to clean up the configuration for this network
             // (routing rules, DNS, etc).
@@ -3709,7 +3733,7 @@
             mDnsManager.removeNetwork(nai.network);
         }
         mNetIdManager.releaseNetId(nai.network.getNetId());
-        nai.onNetworkDisconnected();
+        nai.onNetworkDestroyed();
     }
 
     private boolean createNativeNetwork(@NonNull NetworkAgentInfo networkAgent) {
@@ -7367,7 +7391,7 @@
                 break;
             }
             case ConnectivityManager.CALLBACK_BLK_CHANGED: {
-                maybeLogBlockedStatusChanged(nri, networkAgent.network, arg1 != 0);
+                maybeLogBlockedStatusChanged(nri, networkAgent.network, arg1);
                 msg.arg1 = arg1;
                 break;
             }
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index f3d2012..6ea84ce 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -18,12 +18,14 @@
 
 import static android.util.TimeUtils.NANOS_PER_MS;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.INetdEventCallback;
 import android.net.MacAddress;
 import android.net.Network;
 import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
 import android.net.metrics.ConnectStats;
 import android.net.metrics.DnsEvent;
 import android.net.metrics.INetdEventListener;
@@ -98,6 +100,7 @@
     private final TokenBucket mConnectTb =
             new TokenBucket(CONNECT_LATENCY_FILL_RATE, CONNECT_LATENCY_BURST_LIMIT);
 
+    final TransportForNetIdNetworkCallback mCallback = new TransportForNetIdNetworkCallback();
 
     /**
      * There are only 3 possible callbacks.
@@ -158,6 +161,9 @@
     public NetdEventListenerService(ConnectivityManager cm) {
         // We are started when boot is complete, so ConnectivityService should already be running.
         mCm = cm;
+        // Clear all capabilities to listen all networks.
+        mCm.registerNetworkCallback(new NetworkRequest.Builder().clearCapabilities().build(),
+                mCallback);
     }
 
     private static long projectSnapshotTime(long timeMs) {
@@ -389,18 +395,13 @@
     }
 
     private long getTransports(int netId) {
-        // TODO: directly query ConnectivityService instead of going through Binder interface.
-        NetworkCapabilities nc = mCm.getNetworkCapabilities(new Network(netId));
+        final NetworkCapabilities nc = mCallback.getNetworkCapabilities(netId);
         if (nc == null) {
             return 0;
         }
         return BitUtils.packBits(nc.getTransportTypes());
     }
 
-    private static void maybeLog(String s, Object... args) {
-        if (DBG) Log.d(TAG, String.format(s, args));
-    }
-
     /** Helper class for buffering summaries of NetworkMetrics at regular time intervals */
     static class NetworkMetricsSnapshot {
 
@@ -428,4 +429,29 @@
             return String.format("%tT.%tL: %s", timeMs, timeMs, j.toString());
         }
     }
+
+    private class TransportForNetIdNetworkCallback extends ConnectivityManager.NetworkCallback {
+        private final SparseArray<NetworkCapabilities> mCapabilities = new SparseArray<>();
+
+        @Override
+        public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+            synchronized (mCapabilities) {
+                mCapabilities.put(network.getNetId(), nc);
+            }
+        }
+
+        @Override
+        public void onLost(Network network) {
+            synchronized (mCapabilities) {
+                mCapabilities.remove(network.getNetId());
+            }
+        }
+
+        @Nullable
+        public NetworkCapabilities getNetworkCapabilities(int netId) {
+            synchronized (mCapabilities) {
+                return mCapabilities.get(netId);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 97df5bf..ee32fbf 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -201,6 +201,9 @@
     // Set to true when partial connectivity was detected.
     public boolean partialConnectivity;
 
+    // Delay between when the network is disconnected and when the native network is destroyed.
+    public int teardownDelayMs;
+
     // Captive portal info of the network from RFC8908, if any.
     // Obtained by ConnectivityService and merged into NetworkAgent-provided information.
     public CaptivePortalData capportApiData;
@@ -589,13 +592,13 @@
     }
 
     /**
-     * Notify the NetworkAgent that the network is disconnected and destroyed.
+     * Notify the NetworkAgent that the native network has been destroyed.
      */
-    public void onNetworkDisconnected() {
+    public void onNetworkDestroyed() {
         try {
-            networkAgent.onNetworkDisconnected();
+            networkAgent.onNetworkDestroyed();
         } catch (RemoteException e) {
-            Log.e(TAG, "Error sending network disconnected event", e);
+            Log.e(TAG, "Error sending network destroyed event", e);
         }
     }
 
@@ -675,6 +678,12 @@
                 @QosCallbackException.ExceptionType final int exceptionType) {
             mQosCallbackTracker.sendEventQosCallbackError(qosCallbackId, exceptionType);
         }
+
+        @Override
+        public void sendTeardownDelayMs(int teardownDelayMs) {
+            mHandler.obtainMessage(NetworkAgent.EVENT_TEARDOWN_DELAY_CHANGED,
+                    teardownDelayMs, 0, new Pair<>(NetworkAgentInfo.this, null)).sendToTarget();
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 449a9be..c127858 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -423,8 +423,8 @@
     private static final int MSG_LIMIT_REACHED = 5;
     private static final int MSG_RESTRICT_BACKGROUND_CHANGED = 6;
     private static final int MSG_ADVISE_PERSIST_THRESHOLD = 7;
-    private static final int MSG_UPDATE_INTERFACE_QUOTA = 10;
-    private static final int MSG_REMOVE_INTERFACE_QUOTA = 11;
+    private static final int MSG_UPDATE_INTERFACE_QUOTAS = 10;
+    private static final int MSG_REMOVE_INTERFACE_QUOTAS = 11;
     private static final int MSG_POLICIES_CHANGED = 13;
     private static final int MSG_RESET_FIREWALL_RULES_BY_UID = 15;
     private static final int MSG_SUBSCRIPTION_OVERRIDE = 16;
@@ -2035,39 +2035,45 @@
 
             final boolean hasWarning = policy.warningBytes != LIMIT_DISABLED;
             final boolean hasLimit = policy.limitBytes != LIMIT_DISABLED;
-            if (hasLimit || policy.metered) {
-                final long quotaBytes;
-                if (hasLimit && policy.hasCycle()) {
-                    final Pair<ZonedDateTime, ZonedDateTime> cycle = NetworkPolicyManager
-                            .cycleIterator(policy).next();
-                    final long start = cycle.first.toInstant().toEpochMilli();
-                    final long end = cycle.second.toInstant().toEpochMilli();
-                    final long totalBytes = getTotalBytes(policy.template, start, end);
+            long limitBytes = Long.MAX_VALUE;
+            long warningBytes = Long.MAX_VALUE;
+            if ((hasLimit || hasWarning) && policy.hasCycle()) {
+                final Pair<ZonedDateTime, ZonedDateTime> cycle = NetworkPolicyManager
+                        .cycleIterator(policy).next();
+                final long start = cycle.first.toInstant().toEpochMilli();
+                final long end = cycle.second.toInstant().toEpochMilli();
+                final long totalBytes = getTotalBytes(policy.template, start, end);
 
-                    if (policy.lastLimitSnooze >= start) {
-                        // snoozing past quota, but we still need to restrict apps,
-                        // so push really high quota.
-                        quotaBytes = Long.MAX_VALUE;
-                    } else {
-                        // remaining "quota" bytes are based on total usage in
-                        // current cycle. kernel doesn't like 0-byte rules, so we
-                        // set 1-byte quota and disable the radio later.
-                        quotaBytes = Math.max(1, policy.limitBytes - totalBytes);
-                    }
-                } else {
-                    // metered network, but no policy limit; we still need to
-                    // restrict apps, so push really high quota.
-                    quotaBytes = Long.MAX_VALUE;
+                // If the limit notification is not snoozed, the limit quota needs to be calculated.
+                if (hasLimit && policy.lastLimitSnooze < start) {
+                    // remaining "quota" bytes are based on total usage in
+                    // current cycle. kernel doesn't like 0-byte rules, so we
+                    // set 1-byte quota and disable the radio later.
+                    limitBytes = Math.max(1, policy.limitBytes - totalBytes);
                 }
 
+                // If the warning notification was snoozed by user, or the service already knows
+                // it is over warning bytes, doesn't need to calculate warning bytes.
+                if (hasWarning && policy.lastWarningSnooze < start
+                        && !policy.isOverWarning(totalBytes)) {
+                    warningBytes = Math.max(1, policy.warningBytes - totalBytes);
+                }
+            }
+
+            if (hasWarning || hasLimit || policy.metered) {
                 if (matchingIfaces.size() > 1) {
                     // TODO: switch to shared quota once NMS supports
                     Slog.w(TAG, "shared quota unsupported; generating rule for each iface");
                 }
 
+                // Set the interface warning and limit. For interfaces which has no cycle,
+                // or metered with no policy quotas, or snoozed notification; we still need to put
+                // iptables rule hooks to restrict apps for data saver, so push really high quota.
+                // TODO: Push NetworkStatsProvider.QUOTA_UNLIMITED instead of Long.MAX_VALUE to
+                //  providers.
                 for (int j = matchingIfaces.size() - 1; j >= 0; j--) {
                     final String iface = matchingIfaces.valueAt(j);
-                    setInterfaceQuotaAsync(iface, quotaBytes);
+                    setInterfaceQuotasAsync(iface, warningBytes, limitBytes);
                     newMeteredIfaces.add(iface);
                 }
             }
@@ -2090,7 +2096,7 @@
                 for (int j = matchingIfaces.size() - 1; j >= 0; j--) {
                     final String iface = matchingIfaces.valueAt(j);
                     if (!newMeteredIfaces.contains(iface)) {
-                        setInterfaceQuotaAsync(iface, Long.MAX_VALUE);
+                        setInterfaceQuotasAsync(iface, Long.MAX_VALUE, Long.MAX_VALUE);
                         newMeteredIfaces.add(iface);
                     }
                 }
@@ -2102,7 +2108,7 @@
             for (int i = mMeteredIfaces.size() - 1; i >= 0; i--) {
                 final String iface = mMeteredIfaces.valueAt(i);
                 if (!newMeteredIfaces.contains(iface)) {
-                    removeInterfaceQuotaAsync(iface);
+                    removeInterfaceQuotasAsync(iface);
                 }
             }
             mMeteredIfaces = newMeteredIfaces;
@@ -5043,19 +5049,20 @@
                     mNetworkStats.advisePersistThreshold(persistThreshold);
                     return true;
                 }
-                case MSG_UPDATE_INTERFACE_QUOTA: {
-                    final String iface = (String) msg.obj;
-                    // int params need to be stitched back into a long
-                    final long quota = ((long) msg.arg1 << 32) | (msg.arg2 & 0xFFFFFFFFL);
-                    removeInterfaceQuota(iface);
-                    setInterfaceQuota(iface, quota);
-                    mNetworkStats.setStatsProviderLimitAsync(iface, quota);
+                case MSG_UPDATE_INTERFACE_QUOTAS: {
+                    final IfaceQuotas val = (IfaceQuotas) msg.obj;
+                    // TODO: Consider set a new limit before removing the original one.
+                    removeInterfaceLimit(val.iface);
+                    setInterfaceLimit(val.iface, val.limit);
+                    mNetworkStats.setStatsProviderWarningAndLimitAsync(val.iface, val.warning,
+                            val.limit);
                     return true;
                 }
-                case MSG_REMOVE_INTERFACE_QUOTA: {
+                case MSG_REMOVE_INTERFACE_QUOTAS: {
                     final String iface = (String) msg.obj;
-                    removeInterfaceQuota(iface);
-                    mNetworkStats.setStatsProviderLimitAsync(iface, QUOTA_UNLIMITED);
+                    removeInterfaceLimit(iface);
+                    mNetworkStats.setStatsProviderWarningAndLimitAsync(iface, QUOTA_UNLIMITED,
+                            QUOTA_UNLIMITED);
                     return true;
                 }
                 case MSG_RESET_FIREWALL_RULES_BY_UID: {
@@ -5203,15 +5210,32 @@
         }
     }
 
-    private void setInterfaceQuotaAsync(String iface, long quotaBytes) {
-        // long quotaBytes split up into two ints to fit in message
-        mHandler.obtainMessage(MSG_UPDATE_INTERFACE_QUOTA, (int) (quotaBytes >> 32),
-                (int) (quotaBytes & 0xFFFFFFFF), iface).sendToTarget();
+    private static final class IfaceQuotas {
+        @NonNull public final String iface;
+        // Warning and limit bytes of interface qutoas, could be QUOTA_UNLIMITED or Long.MAX_VALUE
+        // if not set. 0 is not acceptable since kernel doesn't like 0-byte rules.
+        public final long warning;
+        public final long limit;
+
+        private IfaceQuotas(@NonNull String iface, long warning, long limit) {
+            this.iface = iface;
+            this.warning = warning;
+            this.limit = limit;
+        }
     }
 
-    private void setInterfaceQuota(String iface, long quotaBytes) {
+    private void setInterfaceQuotasAsync(@NonNull String iface,
+            long warningBytes, long limitBytes) {
+        mHandler.obtainMessage(MSG_UPDATE_INTERFACE_QUOTAS,
+                new IfaceQuotas(iface, warningBytes, limitBytes)).sendToTarget();
+    }
+
+    private void setInterfaceLimit(String iface, long limitBytes) {
         try {
-            mNetworkManager.setInterfaceQuota(iface, quotaBytes);
+            // For legacy design the data warning is covered by global alert, where the
+            // kernel will notify upper layer for a small amount of change of traffic
+            // statistics. Thus, passing warning is not needed.
+            mNetworkManager.setInterfaceQuota(iface, limitBytes);
         } catch (IllegalStateException e) {
             Log.wtf(TAG, "problem setting interface quota", e);
         } catch (RemoteException e) {
@@ -5219,11 +5243,11 @@
         }
     }
 
-    private void removeInterfaceQuotaAsync(String iface) {
-        mHandler.obtainMessage(MSG_REMOVE_INTERFACE_QUOTA, iface).sendToTarget();
+    private void removeInterfaceQuotasAsync(String iface) {
+        mHandler.obtainMessage(MSG_REMOVE_INTERFACE_QUOTAS, iface).sendToTarget();
     }
 
-    private void removeInterfaceQuota(String iface) {
+    private void removeInterfaceLimit(String iface) {
         try {
             mNetworkManager.removeInterfaceQuota(iface);
         } catch (IllegalStateException e) {
diff --git a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
index 0cb0bc2c..0e9a9da 100644
--- a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
@@ -37,8 +37,9 @@
     public abstract void forceUpdate();
 
     /**
-     * Set the quota limit to all registered custom network stats providers.
+     * Set the warning and limit to all registered custom network stats providers.
      * Note that invocation of any interface will be sent to all providers.
      */
-    public abstract void setStatsProviderLimitAsync(@NonNull String iface, long quota);
+    public abstract void setStatsProviderWarningAndLimitAsync(@NonNull String iface, long warning,
+            long limit);
 }
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 445a425..19f5e3c 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -1693,11 +1693,14 @@
         }
 
         @Override
-        public void setStatsProviderLimitAsync(@NonNull String iface, long quota) {
-            if (LOGV) Slog.v(TAG, "setStatsProviderLimitAsync(" + iface + "," + quota + ")");
-            // TODO: Set warning accordingly.
+        public void setStatsProviderWarningAndLimitAsync(
+                @NonNull String iface, long warning, long limit) {
+            if (LOGV) {
+                Slog.v(TAG, "setStatsProviderWarningAndLimitAsync("
+                        + iface + "," + warning + "," + limit + ")");
+            }
             invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.onSetWarningAndLimit(iface,
-                    NetworkStatsProvider.QUOTA_UNLIMITED, quota));
+                    warning, limit));
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 6ad43ce..37b9d564 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -258,23 +258,22 @@
                         packageStats, options.isDowngrade(), profileName, dexMetadataPath,
                         options.getCompilationReason());
 
-                // Only report metrics for base apk for now.
-                // TODO: add ISA and APK type to metrics.
                 // OTAPreopt doesn't have stats so don't report in that case.
-                if (pkg.getBaseCodePath().equals(path) && packageStats != null) {
+                if (packageStats != null) {
                     Trace.traceBegin(Trace.TRACE_TAG_PACKAGE_MANAGER, "dex2oat-metrics");
                     try {
                         long sessionId = Math.randomLongInternal();
                         ArtStatsLogUtils.writeStatsLog(
                                 mArtStatsLogger,
                                 sessionId,
-                                path,
                                 compilerFilter,
                                 sharedGid,
                                 packageStats.getCompileTime(path),
                                 dexMetadataPath,
                                 options.getCompilationReason(),
-                                newResult);
+                                newResult,
+                                ArtStatsLogUtils.getApkType(path),
+                                dexCodeIsa);
                     } finally {
                         Trace.traceEnd(Trace.TRACE_TAG_PACKAGE_MANAGER);
                     }
diff --git a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
index c8dc1b1..f99a3c3 100644
--- a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
+++ b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
@@ -39,6 +39,7 @@
     private static final String TAG = ArtStatsLogUtils.class.getSimpleName();
     private static final String PROFILE_DEX_METADATA = "primary.prof";
     private static final String VDEX_DEX_METADATA = "primary.vdex";
+    private static final String BASE_APK= "base.apk";
 
 
     private static final int ART_COMPILATION_REASON_INSTALL_BULK_SECONDARY =
@@ -122,16 +123,34 @@
                 ART_COMPILATION_FILTER_FAKE_RUN_FROM_VDEX_FALLBACK);
     }
 
+    private static final Map<String, Integer> ISA_MAP = new HashMap();
+
+    static {
+        COMPILE_FILTER_MAP.put("arm", ArtStatsLog.
+                ART_DATUM_REPORTED__ISA__ART_ISA_ARM);
+        COMPILE_FILTER_MAP.put("arm64", ArtStatsLog.
+                ART_DATUM_REPORTED__ISA__ART_ISA_ARM64);
+        COMPILE_FILTER_MAP.put("x86", ArtStatsLog.
+                ART_DATUM_REPORTED__ISA__ART_ISA_X86);
+        COMPILE_FILTER_MAP.put("x86_64", ArtStatsLog.
+                ART_DATUM_REPORTED__ISA__ART_ISA_X86_64);
+        COMPILE_FILTER_MAP.put("mips", ArtStatsLog.
+                ART_DATUM_REPORTED__ISA__ART_ISA_MIPS);
+        COMPILE_FILTER_MAP.put("mips64", ArtStatsLog.
+                ART_DATUM_REPORTED__ISA__ART_ISA_MIPS64);
+    }
+
     public static void writeStatsLog(
             ArtStatsLogger logger,
             long sessionId,
-            String path,
             String compilerFilter,
             int uid,
             long compileTime,
             String dexMetadataPath,
             int compilationReason,
-            int result) {
+            int result,
+            int apkType,
+            String isa) {
         int dexMetadataType = getDexMetadataType(dexMetadataPath);
         logger.write(
                 sessionId,
@@ -140,7 +159,9 @@
                 compilerFilter,
                 ArtStatsLog.ART_DATUM_REPORTED__KIND__ART_DATUM_DEX2OAT_RESULT_CODE,
                 result,
-                dexMetadataType);
+                dexMetadataType,
+                apkType,
+                isa);
         logger.write(
                 sessionId,
                 uid,
@@ -148,7 +169,16 @@
                 compilerFilter,
                 ArtStatsLog.ART_DATUM_REPORTED__KIND__ART_DATUM_DEX2OAT_TOTAL_TIME,
                 compileTime,
-                dexMetadataType);
+                dexMetadataType,
+                apkType,
+                isa);
+    }
+
+    public static int getApkType(String path) {
+        if (path.equals(BASE_APK)) {
+            return ArtStatsLog.ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_BASE;
+        }
+        return ArtStatsLog.ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_SPLIT;
     }
 
     private static int getDexMetadataType(String dexMetadataPath) {
@@ -207,7 +237,9 @@
                 String compilerFilter,
                 int kind,
                 long value,
-                int dexMetadataType) {
+                int dexMetadataType,
+                int apkType,
+                String isa) {
             ArtStatsLog.write(
                     ArtStatsLog.ART_DATUM_REPORTED,
                     sessionId,
@@ -220,7 +252,10 @@
                     ArtStatsLog.ART_DATUM_REPORTED__THREAD_TYPE__ART_THREAD_MAIN,
                     kind,
                     value,
-                    dexMetadataType);
+                    dexMetadataType,
+                    apkType,
+                    ISA_MAP.getOrDefault(isa,
+                            ArtStatsLog.ART_DATUM_REPORTED__ISA__ART_ISA_UNKNOWN));
         }
     }
 }
diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
index 8dcc547..6ff8d6f 100644
--- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
+++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java
@@ -290,25 +290,6 @@
         maybeNotifyCallback();
     }
 
-    private void handleNetworkSuspended(@NonNull Network network, boolean isSuspended) {
-        mVcnContext.ensureRunningOnLooperThread();
-
-        if (!isSameNetwork(mRecordInProgress, network)) {
-            Slog.wtf(TAG, "Invalid update to isSuspended");
-            return;
-        }
-
-        final NetworkCapabilities newCaps =
-                new NetworkCapabilities(mRecordInProgress.getNetworkCapabilities());
-        if (isSuspended) {
-            newCaps.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
-        } else {
-            newCaps.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
-        }
-
-        handleCapabilitiesChanged(network, newCaps);
-    }
-
     private void handlePropertiesChanged(
             @NonNull Network network, @NonNull LinkProperties linkProperties) {
         mVcnContext.ensureRunningOnLooperThread();
@@ -366,20 +347,11 @@
         @Override
         public void onCapabilitiesChanged(
                 @NonNull Network network, @NonNull NetworkCapabilities networkCapabilities) {
+            if (networkCapabilities.equals(mRecordInProgress.getNetworkCapabilities())) return;
             handleCapabilitiesChanged(network, networkCapabilities);
         }
 
         @Override
-        public void onNetworkSuspended(@NonNull Network network) {
-            handleNetworkSuspended(network, true /* isSuspended */);
-        }
-
-        @Override
-        public void onNetworkResumed(@NonNull Network network) {
-            handleNetworkSuspended(network, false /* isSuspended */);
-        }
-
-        @Override
         public void onLinkPropertiesChanged(
                 @NonNull Network network, @NonNull LinkProperties linkProperties) {
             handlePropertiesChanged(network, linkProperties);
diff --git a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
index 29aedce..1208ecc 100644
--- a/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
+++ b/services/profcollect/src/com/android/server/profcollect/ProfcollectForwardingService.java
@@ -30,6 +30,8 @@
 import android.os.ServiceManager;
 import android.os.UpdateEngine;
 import android.os.UpdateEngineCallback;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.DeviceConfig;
 import android.util.Log;
 
@@ -301,10 +303,18 @@
 
         new Thread(() -> {
             try {
-                String reportPath = mIProfcollect.report();
+                String reportUuid = mIProfcollect.report();
+
                 if (!uploadReport) {
                     return;
                 }
+
+                final int profileId = getBBProfileId();
+                mIProfcollect.copy_report_to_bb(profileId, reportUuid);
+                String reportPath =
+                        "/data/user/" + profileId
+                        + "/com.google.android.apps.internal.betterbug/cache/"
+                        + reportUuid + ".zip";
                 Intent uploadIntent =
                         new Intent("com.google.android.apps.betterbug.intent.action.UPLOAD_PROFILE")
                         .setPackage("com.google.android.apps.internal.betterbug")
@@ -316,9 +326,27 @@
                 if (context.getPackageManager().queryBroadcastReceivers(uploadIntent, 0) != null) {
                     context.sendBroadcast(uploadIntent);
                 }
+                mIProfcollect.delete_report(reportUuid);
             } catch (RemoteException e) {
                 Log.e(LOG_TAG, e.getMessage());
             }
         }).start();
     }
+
+    /**
+     * Get BetterBug's profile ID. It is the work profile ID, if it exists. Otherwise the system
+     * user ID.
+     *
+     * @return BetterBug's profile ID.
+     */
+    private int getBBProfileId() {
+        UserManager userManager = UserManager.get(getContext());
+        int[] profiles = userManager.getProfileIds(UserHandle.USER_SYSTEM, false);
+        for (int p : profiles) {
+            if (userManager.getUserInfo(p).isManagedProfile()) {
+                return p;
+            }
+        }
+        return UserHandle.USER_SYSTEM;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index d405113..100d3ea 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -1773,57 +1773,75 @@
                 true);
     }
 
-    /**
-     * Test that when StatsProvider triggers limit reached, new limit will be calculated and
-     * re-armed.
-     */
-    @Test
-    public void testStatsProviderLimitReached() throws Exception {
-        final int CYCLE_DAY = 15;
-
-        final NetworkStats stats = new NetworkStats(0L, 1);
+    private void increaseMockedTotalBytes(NetworkStats stats, long rxBytes, long txBytes) {
         stats.insertEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE,
-                2999, 1, 2000, 1, 0);
+                rxBytes, 1, txBytes, 1, 0);
         when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
                 .thenReturn(stats.getTotalBytes());
         when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
                 .thenReturn(stats);
+    }
+
+    private void triggerOnStatsProviderWarningOrLimitReached() throws InterruptedException {
+        final NetworkPolicyManagerInternal npmi = LocalServices
+                .getService(NetworkPolicyManagerInternal.class);
+        npmi.onStatsProviderWarningOrLimitReached("TEST");
+        // Wait for processing of MSG_STATS_PROVIDER_WARNING_OR_LIMIT_REACHED.
+        postMsgAndWaitForCompletion();
+        verify(mStatsService).forceUpdate();
+        // Wait for processing of MSG_*_INTERFACE_QUOTAS.
+        postMsgAndWaitForCompletion();
+    }
+
+    /**
+     * Test that when StatsProvider triggers warning and limit reached, new quotas will be
+     * calculated and re-armed.
+     */
+    @Test
+    public void testStatsProviderWarningAndLimitReached() throws Exception {
+        final int CYCLE_DAY = 15;
+
+        final NetworkStats stats = new NetworkStats(0L, 1);
+        increaseMockedTotalBytes(stats, 2999, 2000);
 
         // Get active mobile network in place
         expectMobileDefaults();
         mService.updateNetworks();
-        verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, Long.MAX_VALUE);
+        verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE,
+                Long.MAX_VALUE);
 
-        // Set limit to 10KB.
+        // Set warning to 7KB and limit to 10KB.
         setNetworkPolicies(new NetworkPolicy(
-                sTemplateMobileAll, CYCLE_DAY, TIMEZONE_UTC, WARNING_DISABLED, 10000L,
-                true));
+                sTemplateMobileAll, CYCLE_DAY, TIMEZONE_UTC, 7000L, 10000L, true));
         postMsgAndWaitForCompletion();
 
-        // Verifies that remaining quota is set to providers.
-        verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, 10000L - 4999L);
-
+        // Verifies that remaining quotas are set to providers.
+        verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2001L, 5001L);
         reset(mStatsService);
 
-        // Increase the usage.
-        stats.insertEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE,
-                1000, 1, 999, 1, 0);
-        when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
-                .thenReturn(stats.getTotalBytes());
-        when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
-                .thenReturn(stats);
+        // Increase the usage and simulates that limit reached fires earlier by provider,
+        // but actually the quota is not yet reached. Verifies that the limit reached leads to
+        // a force update and new quotas should be set.
+        increaseMockedTotalBytes(stats, 1000, 999);
+        triggerOnStatsProviderWarningOrLimitReached();
+        verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, 2L, 3002L);
+        reset(mStatsService);
 
-        // Simulates that limit reached fires earlier by provider, but actually the quota is not
-        // yet reached.
-        final NetworkPolicyManagerInternal npmi = LocalServices
-                .getService(NetworkPolicyManagerInternal.class);
-        npmi.onStatsProviderWarningOrLimitReached("TEST");
+        // Increase the usage and simulate warning reached, the new warning should be unlimited
+        // since service will disable warning quota to stop lower layer from keep triggering
+        // warning reached event.
+        increaseMockedTotalBytes(stats, 1000L, 1000);
+        triggerOnStatsProviderWarningOrLimitReached();
+        verify(mStatsService).setStatsProviderWarningAndLimitAsync(
+                TEST_IFACE, Long.MAX_VALUE, 1002L);
+        reset(mStatsService);
 
-        // Verifies that the limit reached leads to a force update and new limit should be set.
-        postMsgAndWaitForCompletion();
-        verify(mStatsService).forceUpdate();
-        postMsgAndWaitForCompletion();
-        verify(mStatsService).setStatsProviderLimitAsync(TEST_IFACE, 10000L - 4999L - 1999L);
+        // Increase the usage that over the warning and limit, the new limit should set to 1 to
+        // block the network traffic.
+        increaseMockedTotalBytes(stats, 1000L, 1000);
+        triggerOnStatsProviderWarningOrLimitReached();
+        verify(mStatsService).setStatsProviderWarningAndLimitAsync(TEST_IFACE, Long.MAX_VALUE, 1L);
+        reset(mStatsService);
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/ArtStatsLogUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/ArtStatsLogUtilsTest.java
index 13d75a7..f014119 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/ArtStatsLogUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/ArtStatsLogUtilsTest.java
@@ -22,6 +22,7 @@
 import com.android.server.pm.dex.ArtStatsLogUtils.ArtStatsLogger;
 
 import org.junit.AfterClass;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -49,6 +50,9 @@
     private static final String COMPILER_FILTER = "space-profile";
     private static final String PROFILE_DEX_METADATA = "primary.prof";
     private static final String VDEX_DEX_METADATA = "primary.vdex";
+    private static final String INSTRUCTION_SET = "arm64";
+    private static final String BASE_APK = "base.apk";
+    private static final String SPLIT_APK = "split.apk";
     private static final byte[] DEX_CONTENT = "dexData".getBytes();
     private static final int COMPILATION_REASON = 1;
     private static final int RESULT_CODE = 222;
@@ -97,17 +101,18 @@
             ArtStatsLogUtils.writeStatsLog(
                     mockLogger,
                     SESSION_ID,
-                    apk.toString(),
                     COMPILER_FILTER,
                     UID,
                     COMPILE_TIME,
                     dexMetadataPath.toString(),
                     COMPILATION_REASON,
-                    RESULT_CODE);
+                    RESULT_CODE,
+                    ArtStatsLog.ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_BASE,
+                    INSTRUCTION_SET);
 
             // Assert
             verifyWrites(ArtStatsLog.
-                ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_PROFILE_AND_VDEX);
+                    ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_PROFILE_AND_VDEX);
         } finally {
             deleteSliently(dexMetadataPath);
             deleteSliently(apk);
@@ -127,17 +132,18 @@
             ArtStatsLogUtils.writeStatsLog(
                     mockLogger,
                     SESSION_ID,
-                    apk.toString(),
                     COMPILER_FILTER,
                     UID,
                     COMPILE_TIME,
                     dexMetadataPath.toString(),
                     COMPILATION_REASON,
-                    RESULT_CODE);
+                    RESULT_CODE,
+                    ArtStatsLog.ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_BASE,
+                    INSTRUCTION_SET);
 
             // Assert
             verifyWrites(ArtStatsLog.
-                ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_PROFILE);
+                    ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_PROFILE);
         } finally {
             deleteSliently(dexMetadataPath);
             deleteSliently(apk);
@@ -157,17 +163,18 @@
             ArtStatsLogUtils.writeStatsLog(
                     mockLogger,
                     SESSION_ID,
-                    apk.toString(),
                     COMPILER_FILTER,
                     UID,
                     COMPILE_TIME,
                     dexMetadataPath.toString(),
                     COMPILATION_REASON,
-                    RESULT_CODE);
+                    RESULT_CODE,
+                    ArtStatsLog.ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_BASE,
+                    INSTRUCTION_SET);
 
             // Assert
             verifyWrites(ArtStatsLog.
-                ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_VDEX);
+                    ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_VDEX);
         } finally {
             deleteSliently(dexMetadataPath);
             deleteSliently(apk);
@@ -185,17 +192,18 @@
             ArtStatsLogUtils.writeStatsLog(
                     mockLogger,
                     SESSION_ID,
-                    apk.toString(),
                     COMPILER_FILTER,
                     UID,
                     COMPILE_TIME,
                     /*dexMetadataPath=*/ null,
                     COMPILATION_REASON,
-                    RESULT_CODE);
+                    RESULT_CODE,
+                    ArtStatsLog.ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_BASE,
+                    INSTRUCTION_SET);
 
             // Assert
             verifyWrites(ArtStatsLog.
-                ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_NONE);
+                    ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_NONE);
         } finally {
             deleteSliently(apk);
         }
@@ -214,23 +222,35 @@
             ArtStatsLogUtils.writeStatsLog(
                     mockLogger,
                     SESSION_ID,
-                    apk.toString(),
                     COMPILER_FILTER,
                     UID,
                     COMPILE_TIME,
                     dexMetadataPath.toString(),
                     COMPILATION_REASON,
-                    RESULT_CODE);
+                    RESULT_CODE,
+                    ArtStatsLog.ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_BASE,
+                    INSTRUCTION_SET);
 
             // Assert
             verifyWrites(ArtStatsLog.
-                ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_UNKNOWN);
+                    ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_UNKNOWN);
         } finally {
             deleteSliently(dexMetadataPath);
             deleteSliently(apk);
         }
     }
 
+    @Test
+    public void testGetApkType() {
+        // Act
+        int result1 = ArtStatsLogUtils.getApkType(BASE_APK);
+        int result2 = ArtStatsLogUtils.getApkType(SPLIT_APK);
+
+        // Assert
+        Assert.assertEquals(result1, ArtStatsLog.ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_BASE);
+        Assert.assertEquals(result2, ArtStatsLog.ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_SPLIT);
+    }
+
     private void verifyWrites(int dexMetadataType) {
         InOrder inorder = inOrder(mockLogger);
         inorder.verify(mockLogger).write(
@@ -239,7 +259,9 @@
                 COMPILER_FILTER,
                 ArtStatsLog.ART_DATUM_REPORTED__KIND__ART_DATUM_DEX2OAT_RESULT_CODE,
                 RESULT_CODE,
-                dexMetadataType);
+                dexMetadataType,
+                ArtStatsLog.ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_BASE,
+                INSTRUCTION_SET);
         inorder.verify(mockLogger).write(
                 SESSION_ID,
                 UID,
@@ -247,7 +269,9 @@
                 COMPILER_FILTER,
                 ArtStatsLog.ART_DATUM_REPORTED__KIND__ART_DATUM_DEX2OAT_TOTAL_TIME,
                 COMPILE_TIME,
-                dexMetadataType);
+                dexMetadataType,
+                ArtStatsLog.ART_DATUM_REPORTED__APK_TYPE__ART_APK_TYPE_BASE,
+                INSTRUCTION_SET);
     }
 
     private Path zipFiles(String suffix, Path... files) throws IOException {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 7c39cf0..a2467f2 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -99,6 +99,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.telephony.CellNetworkScanResult;
 import com.android.internal.telephony.IBooleanConsumer;
 import com.android.internal.telephony.ICallForwardingInfoCallback;
@@ -128,6 +129,7 @@
 import java.util.Objects;
 import java.util.UUID;
 import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
@@ -417,6 +419,27 @@
     }
 
     /**
+     * Post a runnable to the BackgroundThread.
+     *
+     * Used to invoke user callbacks without calling into the caller's executor from the caller's
+     * calling thread context, for example to provide asynchronous error information that is
+     * generated locally (not over a binder thread).
+     *
+     * <p>This is not necessary unless you are invoking caller's code asynchronously from within
+     * the caller's thread context.
+     *
+     * @param r a runnable.
+     */
+    private static void runOnBackgroundThread(@NonNull Runnable r) {
+        try {
+            BackgroundThread.getExecutor().execute(r);
+        } catch (RejectedExecutionException e) {
+            throw new IllegalStateException(
+                    "Failed to post a callback from the caller's thread context.", e);
+        }
+    }
+
+    /**
      * Returns the multi SIM variant
      * Returns DSDS for Dual SIM Dual Standby
      * Returns DSDA for Dual SIM Dual Active
@@ -5875,7 +5898,7 @@
 
         /**
          * Error response to
-         * {@link android.telephony.TelephonyManager#requestCellInfoUpdate requestCellInfoUpdate()}.
+         * {@link TelephonyManager#requestCellInfoUpdate requestCellInfoUpdate()}.
          *
          * Invoked when an error condition prevents updated {@link CellInfo} from being fetched
          * and returned from the modem. Callers of requestCellInfoUpdate() should override this
@@ -5893,6 +5916,20 @@
     };
 
     /**
+     * Used for checking if the target SDK version for the current process is S or above.
+     *
+     * <p> Applies to the following methods:
+     * {@link #requestCellInfoUpdate},
+     * {@link #setPreferredOpportunisticDataSubscription},
+     * {@link #updateAvailableNetworks},
+     * requestNumberVerification(),
+     * setSimPowerStateForSlot(),
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+    private static final long NULL_TELEPHONY_THROW_NO_CB = 182185642L;
+
+    /**
      * Requests all available cell information from the current subscription for observed
      * camped/registered, serving, and neighboring cells.
      *
@@ -5912,7 +5949,14 @@
             @NonNull @CallbackExecutor Executor executor, @NonNull CellInfoCallback callback) {
         try {
             ITelephony telephony = getITelephony();
-            if (telephony == null) return;
+            if (telephony == null) {
+                if (Compatibility.isChangeEnabled(NULL_TELEPHONY_THROW_NO_CB)) {
+                    throw new IllegalStateException("Telephony is null");
+                } else {
+                    return;
+                }
+            }
+
             telephony.requestCellInfoUpdate(
                     getSubId(),
                     new ICellInfoCallback.Stub() {
@@ -5939,6 +5983,8 @@
                         }
                     }, getOpPackageName(), getAttributionTag());
         } catch (RemoteException ex) {
+            runOnBackgroundThread(() -> executor.execute(
+                    () -> callback.onError(CellInfoCallback.ERROR_MODEM_ERROR, ex)));
         }
     }
 
@@ -5966,7 +6012,14 @@
             @NonNull @CallbackExecutor Executor executor, @NonNull CellInfoCallback callback) {
         try {
             ITelephony telephony = getITelephony();
-            if (telephony == null) return;
+            if (telephony == null) {
+                if (Compatibility.isChangeEnabled(NULL_TELEPHONY_THROW_NO_CB)) {
+                    throw new IllegalStateException("Telephony is null");
+                } else {
+                    return;
+                }
+            }
+
             telephony.requestCellInfoUpdateWithWorkSource(
                     getSubId(),
                     new ICellInfoCallback.Stub() {
@@ -5994,6 +6047,8 @@
                         }
                     }, getOpPackageName(), getAttributionTag(), workSource);
         } catch (RemoteException ex) {
+            runOnBackgroundThread(() -> executor.execute(
+                    () -> callback.onError(CellInfoCallback.ERROR_MODEM_ERROR, ex)));
         }
     }
 
@@ -6960,14 +7015,21 @@
 
         try {
             ITelephony telephony = getITelephony();
-            if (telephony != null) {
-                telephony.requestNumberVerification(range, timeoutMillis, internalCallback,
-                        getOpPackageName());
+            if (telephony == null) {
+                if (Compatibility.isChangeEnabled(NULL_TELEPHONY_THROW_NO_CB)) {
+                    throw new IllegalStateException("Telephony is null");
+                } else {
+                    return;
+                }
             }
+
+            telephony.requestNumberVerification(range, timeoutMillis, internalCallback,
+                    getOpPackageName());
         } catch (RemoteException ex) {
             Rlog.e(TAG, "requestNumberVerification RemoteException", ex);
-            executor.execute(() ->
-                    callback.onVerificationFailed(NumberVerificationCallback.REASON_UNSPECIFIED));
+            runOnBackgroundThread(() -> executor.execute(
+                    () -> callback.onVerificationFailed(
+                            NumberVerificationCallback.REASON_UNSPECIFIED)));
         }
     }
 
@@ -10333,6 +10395,8 @@
         }
         try {
             ITelephony telephony = getITelephony();
+            if (telephony == null) throw new IllegalStateException("Telephony is null.");
+
             IIntegerConsumer internalCallback = new IIntegerConsumer.Stub() {
                 @Override
                 public void accept(int result) {
@@ -10340,11 +10404,18 @@
                             Binder.withCleanCallingIdentity(() -> callback.accept(result)));
                 }
             };
-            if (telephony != null) {
-                telephony.setSimPowerStateForSlotWithCallback(slotIndex, state, internalCallback);
+            if (telephony == null) {
+                if (Compatibility.isChangeEnabled(NULL_TELEPHONY_THROW_NO_CB)) {
+                    throw new IllegalStateException("Telephony is null");
+                } else {
+                    return;
+                }
             }
+            telephony.setSimPowerStateForSlotWithCallback(slotIndex, state, internalCallback);
         } catch (RemoteException e) {
             Log.e(TAG, "Error calling ITelephony#setSimPowerStateForSlot", e);
+            runOnBackgroundThread(() -> executor.execute(
+                    () -> callback.accept(SET_SIM_POWER_STATE_MODEM_ERROR)));
         } catch (SecurityException e) {
             Log.e(TAG, "Permission error calling ITelephony#setSimPowerStateForSlot",
                     e);
@@ -12774,22 +12845,12 @@
         try {
             IOns iOpportunisticNetworkService = getIOns();
             if (iOpportunisticNetworkService == null) {
-                if (executor == null || callback == null) {
-                    return;
+                if (Compatibility.isChangeEnabled(NULL_TELEPHONY_THROW_NO_CB)) {
+                    throw new IllegalStateException("Opportunistic Network Service is null");
+                } else {
+                    // Let the general remote exception handling catch this.
+                    throw new RemoteException("Null Opportunistic Network Service!");
                 }
-                final long identity = Binder.clearCallingIdentity();
-                try {
-                    executor.execute(() -> {
-                        if (Compatibility.isChangeEnabled(CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) {
-                            callback.accept(SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION);
-                        } else {
-                            callback.accept(SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION);
-                        }
-                    });
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
-                }
-                return;
             }
             ISetOpportunisticDataCallback callbackStub = new ISetOpportunisticDataCallback.Stub() {
                 @Override
@@ -12812,9 +12873,18 @@
                     .setPreferredDataSubscriptionId(subId, needValidation, callbackStub,
                             pkgForDebug);
         } catch (RemoteException ex) {
-            Rlog.e(TAG, "setPreferredDataSubscriptionId RemoteException", ex);
+            Rlog.e(TAG, "setPreferredOpportunisticDataSubscription RemoteException", ex);
+            if (executor == null || callback == null) {
+                return;
+            }
+            runOnBackgroundThread(() -> executor.execute(() -> {
+                if (Compatibility.isChangeEnabled(CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) {
+                    callback.accept(SET_OPPORTUNISTIC_SUB_REMOTE_SERVICE_EXCEPTION);
+                } else {
+                    callback.accept(SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION);
+                }
+            }));
         }
-        return;
     }
 
     /**
@@ -12871,37 +12941,18 @@
             @Nullable @CallbackExecutor Executor executor,
             @UpdateAvailableNetworksResult @Nullable Consumer<Integer> callback) {
         String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>";
+        Objects.requireNonNull(availableNetworks, "availableNetworks must not be null.");
         try {
             IOns iOpportunisticNetworkService = getIOns();
-            if (iOpportunisticNetworkService == null || availableNetworks == null) {
-                if (executor == null || callback == null) {
-                    return;
-                }
-                if (iOpportunisticNetworkService == null) {
-                    final long identity = Binder.clearCallingIdentity();
-                    try {
-                        executor.execute(() -> {
-                            if (Compatibility.isChangeEnabled(CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) {
-                                callback.accept(UPDATE_AVAILABLE_NETWORKS_REMOTE_SERVICE_EXCEPTION);
-                            } else {
-                                callback.accept(UPDATE_AVAILABLE_NETWORKS_UNKNOWN_FAILURE);
-                            }
-                        });
-                    } finally {
-                        Binder.restoreCallingIdentity(identity);
-                    }
+            if (iOpportunisticNetworkService == null) {
+                if (Compatibility.isChangeEnabled(NULL_TELEPHONY_THROW_NO_CB)) {
+                    throw new IllegalStateException("Opportunistic Network Service is null");
                 } else {
-                    final long identity = Binder.clearCallingIdentity();
-                    try {
-                        executor.execute(() -> {
-                            callback.accept(UPDATE_AVAILABLE_NETWORKS_INVALID_ARGUMENTS);
-                        });
-                    } finally {
-                        Binder.restoreCallingIdentity(identity);
-                    }
+                    // Let the general remote exception handling catch this.
+                    throw new RemoteException("Null Opportunistic Network Service!");
                 }
-                return;
             }
+
             IUpdateAvailableNetworksCallback callbackStub =
                     new IUpdateAvailableNetworksCallback.Stub() {
                         @Override
@@ -12909,20 +12960,25 @@
                             if (executor == null || callback == null) {
                                 return;
                             }
-                            final long identity = Binder.clearCallingIdentity();
-                            try {
-                                executor.execute(() -> {
-                                    callback.accept(result);
-                                });
-                            } finally {
-                                Binder.restoreCallingIdentity(identity);
-                            }
+                            Binder.withCleanCallingIdentity(() -> {
+                                executor.execute(() -> callback.accept(result));
+                            });
                         }
                     };
-            iOpportunisticNetworkService.updateAvailableNetworks(availableNetworks, callbackStub,
-                    pkgForDebug);
+            iOpportunisticNetworkService
+                    .updateAvailableNetworks(availableNetworks, callbackStub, pkgForDebug);
         } catch (RemoteException ex) {
             Rlog.e(TAG, "updateAvailableNetworks RemoteException", ex);
+            if (executor == null || callback == null) {
+                return;
+            }
+            runOnBackgroundThread(() -> executor.execute(() -> {
+                if (Compatibility.isChangeEnabled(CALLBACK_ON_MORE_ERROR_CODE_CHANGE)) {
+                    callback.accept(UPDATE_AVAILABLE_NETWORKS_REMOTE_SERVICE_EXCEPTION);
+                } else {
+                    callback.accept(UPDATE_AVAILABLE_NETWORKS_UNKNOWN_FAILURE);
+                }
+            }));
         }
     }
 
diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java
index 6315b24..b384e50 100644
--- a/telephony/java/android/telephony/ims/feature/RcsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java
@@ -391,6 +391,7 @@
      * event to the framework.
      * @return An instance of {@link RcsCapabilityExchangeImplBase} that implements capability
      * exchange if it is supported by the device.
+     * @hide
      */
     public @NonNull RcsCapabilityExchangeImplBase createCapabilityExchangeImpl(
             @NonNull Executor executor, @NonNull CapabilityExchangeEventListener listener) {
@@ -399,14 +400,45 @@
     }
 
     /**
+     * Retrieve the implementation of UCE for this {@link RcsFeature}, which can use either
+     * presence or OPTIONS for capability exchange.
+     *
+     * Will only be requested by the framework if capability exchange is configured
+     * as capable during a
+     * {@link #changeEnabledCapabilities(CapabilityChangeRequest, CapabilityCallbackProxy)}
+     * operation and the RcsFeature sets the status of the capability to true using
+     * {@link #notifyCapabilitiesStatusChanged(RcsImsCapabilities)}.
+     *
+     * @param listener A {@link CapabilityExchangeEventListener} to send the capability exchange
+     * event to the framework.
+     * @return An instance of {@link RcsCapabilityExchangeImplBase} that implements capability
+     * exchange if it is supported by the device.
+     */
+    public @NonNull RcsCapabilityExchangeImplBase createCapabilityExchangeImpl(
+            @NonNull CapabilityExchangeEventListener listener) {
+        // Base Implementation, override to implement functionality
+        return new RcsCapabilityExchangeImplBase();
+    }
+
+    /**
      * Remove the given CapabilityExchangeImplBase instance.
      * @param capExchangeImpl The {@link RcsCapabilityExchangeImplBase} instance to be removed.
+     * @hide
      */
     public void removeCapabilityExchangeImpl(
             @NonNull RcsCapabilityExchangeImplBase capExchangeImpl) {
         // Override to implement the process of removing RcsCapabilityExchangeImplBase instance.
     }
 
+    /**
+     * Remove the given CapabilityExchangeImplBase instance.
+     * @param capExchangeImpl The {@link RcsCapabilityExchangeImplBase} instance to be destroyed.
+     */
+    public void destroyCapabilityExchangeImpl(
+            @NonNull RcsCapabilityExchangeImplBase capExchangeImpl) {
+        // Override to implement the process of destroying RcsCapabilityExchangeImplBase instance.
+    }
+
     /**{@inheritDoc}*/
     @Override
     public void onFeatureRemoved() {
diff --git a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
index 25b9446..a117adc 100644
--- a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
@@ -356,12 +356,13 @@
         void onTerminated(@NonNull String reason, long retryAfterMilliseconds) throws ImsException;
     }
 
-    private final Executor mBinderExecutor;
+    private Executor mBinderExecutor;
 
     /**
      * Create a new RcsCapabilityExchangeImplBase instance.
      *
      * @param executor The executor that remote calls from the framework will be called on.
+     * @hide
      */
     public RcsCapabilityExchangeImplBase(@NonNull Executor executor) {
         if (executor == null) {
@@ -371,6 +372,12 @@
     }
 
     /**
+     * Create a new RcsCapabilityExchangeImplBase instance.
+     */
+    public RcsCapabilityExchangeImplBase() {
+    }
+
+    /**
      * The user capabilities of one or multiple contacts have been requested by the framework.
      * <p>
      * The implementer must follow up this call with an
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index e38773f..14c72f1 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -32,6 +32,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN;
 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
 import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
 import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
 import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
@@ -716,6 +717,9 @@
         private int mProbesSucceeded;
         private String mNmValidationRedirectUrl = null;
         private boolean mNmProvNotificationRequested = false;
+        private Runnable mCreatedCallback;
+        private Runnable mUnwantedCallback;
+        private Runnable mDisconnectedCallback;
 
         private final ConditionVariable mNetworkStatusReceived = new ConditionVariable();
         // Contains the redirectUrl from networkStatus(). Before reading, wait for
@@ -770,6 +774,24 @@
                     mRedirectUrl = redirectUrl;
                     mNetworkStatusReceived.open();
                 }
+
+                @Override
+                public void onNetworkCreated() {
+                    super.onNetworkCreated();
+                    if (mCreatedCallback != null) mCreatedCallback.run();
+                }
+
+                @Override
+                public void onNetworkUnwanted() {
+                    super.onNetworkUnwanted();
+                    if (mUnwantedCallback != null) mUnwantedCallback.run();
+                }
+
+                @Override
+                public void onNetworkDestroyed() {
+                    super.onNetworkDestroyed();
+                    if (mDisconnectedCallback != null) mDisconnectedCallback.run();
+                }
             };
 
             assertEquals(na.getNetwork().netId, nmNetworkCaptor.getValue().netId);
@@ -971,6 +993,18 @@
             p.timestampMillis = DATA_STALL_TIMESTAMP;
             mNmCallbacks.notifyDataStallSuspected(p);
         }
+
+        public void setCreatedCallback(Runnable r) {
+            mCreatedCallback = r;
+        }
+
+        public void setUnwantedCallback(Runnable r) {
+            mUnwantedCallback = r;
+        }
+
+        public void setDisconnectedCallback(Runnable r) {
+            mDisconnectedCallback = r;
+        }
     }
 
     /**
@@ -1374,10 +1408,21 @@
     }
 
     private void mockUidNetworkingBlocked() {
-        doAnswer(i -> NetworkPolicyManager.isUidBlocked(mBlockedReasons, i.getArgument(1))
+        doAnswer(i -> isUidBlocked(mBlockedReasons, i.getArgument(1))
         ).when(mNetworkPolicyManager).isUidNetworkingBlocked(anyInt(), anyBoolean());
     }
 
+    private boolean isUidBlocked(int blockedReasons, boolean meteredNetwork) {
+        final int blockedOnAllNetworksReason = (blockedReasons & ~BLOCKED_METERED_REASON_MASK);
+        if (blockedOnAllNetworksReason != BLOCKED_REASON_NONE) {
+            return true;
+        }
+        if (meteredNetwork) {
+            return blockedReasons != BLOCKED_REASON_NONE;
+        }
+        return false;
+    }
+
     private void setBlockedReasonChanged(int blockedReasons) {
         mBlockedReasons = blockedReasons;
         mPolicyCallback.onUidBlockedReasonChanged(Process.myUid(), blockedReasons);
@@ -2799,6 +2844,94 @@
     }
 
     @Test
+    public void testNetworkAgentCallbacks() throws Exception {
+        // Keeps track of the order of events that happen in this test.
+        final LinkedBlockingQueue<String> eventOrder = new LinkedBlockingQueue<>();
+
+        final NetworkRequest request = new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_WIFI).build();
+        final TestNetworkCallback callback = new TestNetworkCallback();
+        final AtomicReference<Network> wifiNetwork = new AtomicReference<>();
+        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+
+        // Expectations for state when various callbacks fire. These expectations run on the handler
+        // thread and not on the test thread because they need to prevent the handler thread from
+        // advancing while they examine state.
+
+        // 1. When onCreated fires, netd has been told to create the network.
+        mWiFiNetworkAgent.setCreatedCallback(() -> {
+            eventOrder.offer("onNetworkCreated");
+            wifiNetwork.set(mWiFiNetworkAgent.getNetwork());
+            assertNotNull(wifiNetwork.get());
+            try {
+                verify(mMockNetd).networkCreatePhysical(wifiNetwork.get().getNetId(),
+                        INetd.PERMISSION_NONE);
+            } catch (RemoteException impossible) {
+                fail();
+            }
+        });
+
+        // 2. onNetworkUnwanted isn't precisely ordered with respect to any particular events. Just
+        //    check that it is fired at some point after disconnect.
+        mWiFiNetworkAgent.setUnwantedCallback(() -> eventOrder.offer("onNetworkUnwanted"));
+
+        // 3. While the teardown timer is running, connectivity APIs report the network is gone, but
+        //    netd has not yet been told to destroy it.
+        final Runnable duringTeardown = () -> {
+            eventOrder.offer("timePasses");
+            assertNull(mCm.getLinkProperties(wifiNetwork.get()));
+            try {
+                verify(mMockNetd, never()).networkDestroy(wifiNetwork.get().getNetId());
+            } catch (RemoteException impossible) {
+                fail();
+            }
+        };
+
+        // 4. After onNetworkDisconnected is called, connectivity APIs report the network is gone,
+        // and netd has been told to destroy it.
+        mWiFiNetworkAgent.setDisconnectedCallback(() -> {
+            eventOrder.offer("onNetworkDisconnected");
+            assertNull(mCm.getLinkProperties(wifiNetwork.get()));
+            try {
+                verify(mMockNetd).networkDestroy(wifiNetwork.get().getNetId());
+            } catch (RemoteException impossible) {
+                fail();
+            }
+        });
+
+        // Connect a network, and file a request for it after it has come up, to ensure the nascent
+        // timer is cleared and the test does not have to wait for it. Filing the request after the
+        // network has come up is necessary because ConnectivityService does not appear to clear the
+        // nascent timer if the first request satisfied by the network was filed before the network
+        // connected.
+        // TODO: fix this bug, file the request before connecting, and remove the waitForIdle.
+        mWiFiNetworkAgent.connectWithoutInternet();
+        waitForIdle();
+        mCm.requestNetwork(request, callback);
+        callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+
+        // Set teardown delay and make sure CS has processed it.
+        mWiFiNetworkAgent.getNetworkAgent().setTeardownDelayMs(300);
+        waitForIdle();
+
+        // Post the duringTeardown lambda to the handler so it fires while teardown is in progress.
+        // The delay must be long enough it will run after the unregisterNetworkCallback has torn
+        // down the network and started the teardown timer, and short enough that the lambda is
+        // scheduled to run before the teardown timer.
+        final Handler h = new Handler(mCsHandlerThread.getLooper());
+        h.postDelayed(duringTeardown, 150);
+
+        // Disconnect the network and check that events happened in the right order.
+        mCm.unregisterNetworkCallback(callback);
+        assertEquals("onNetworkCreated", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals("onNetworkUnwanted", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals("timePasses", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals("onNetworkDisconnected", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        mCm.unregisterNetworkCallback(callback);
+    }
+
+    @Test
     public void testExplicitlySelected() throws Exception {
         NetworkRequest request = new NetworkRequest.Builder()
                 .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET)
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 8c5d1d6..8b072c4 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -22,7 +22,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
@@ -56,6 +58,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -80,6 +83,12 @@
 
     IpConnectivityMetrics mService;
     NetdEventListenerService mNetdListener;
+    private static final NetworkCapabilities CAPABILITIES_WIFI = new NetworkCapabilities.Builder()
+            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+            .build();
+    private static final NetworkCapabilities CAPABILITIES_CELL = new NetworkCapabilities.Builder()
+            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+            .build();
 
     @Before
     public void setUp() {
@@ -263,14 +272,6 @@
         // TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
         IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
 
-        NetworkCapabilities ncWifi = new NetworkCapabilities();
-        NetworkCapabilities ncCell = new NetworkCapabilities();
-        ncWifi.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
-        ncCell.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-
-        when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi);
-        when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell);
-
         ApfStats apfStats = new ApfStats.Builder()
                 .setDurationMs(45000)
                 .setReceivedRas(10)
@@ -584,11 +585,21 @@
         return buffer.toString();
     }
 
-    void connectEvent(int netid, int error, int latencyMs, String ipAddr) throws Exception {
-        mNetdListener.onConnectEvent(netid, error, latencyMs, ipAddr, 80, 1);
+    private void setCapabilities(int netId) {
+        final ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback =
+                ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+        verify(mCm).registerNetworkCallback(any(), networkCallback.capture());
+        networkCallback.getValue().onCapabilitiesChanged(new Network(netId),
+                netId == 100 ? CAPABILITIES_WIFI : CAPABILITIES_CELL);
+    }
+
+    void connectEvent(int netId, int error, int latencyMs, String ipAddr) throws Exception {
+        setCapabilities(netId);
+        mNetdListener.onConnectEvent(netId, error, latencyMs, ipAddr, 80, 1);
     }
 
     void dnsEvent(int netId, int type, int result, int latency) throws Exception {
+        setCapabilities(netId);
         mNetdListener.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
     }
 
diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 8ccea1a..50aaaee 100644
--- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -23,8 +23,9 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
 
 import android.content.Context;
 import android.net.ConnectivityManager;
@@ -42,6 +43,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 
 import java.io.FileOutputStream;
 import java.io.PrintWriter;
@@ -61,18 +63,16 @@
 
     NetdEventListenerService mService;
     ConnectivityManager mCm;
+    private static final NetworkCapabilities CAPABILITIES_WIFI = new NetworkCapabilities.Builder()
+            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+            .build();
+    private static final NetworkCapabilities CAPABILITIES_CELL = new NetworkCapabilities.Builder()
+            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+            .build();
 
     @Before
     public void setUp() {
-        NetworkCapabilities ncWifi = new NetworkCapabilities();
-        NetworkCapabilities ncCell = new NetworkCapabilities();
-        ncWifi.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
-        ncCell.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-
         mCm = mock(ConnectivityManager.class);
-        when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi);
-        when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell);
-
         mService = new NetdEventListenerService(mCm);
     }
 
@@ -470,7 +470,16 @@
         assertEquals(want, got);
     }
 
+    private void setCapabilities(int netId) {
+        final ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback =
+                ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+        verify(mCm).registerNetworkCallback(any(), networkCallback.capture());
+        networkCallback.getValue().onCapabilitiesChanged(new Network(netId),
+                netId == 100 ? CAPABILITIES_WIFI : CAPABILITIES_CELL);
+    }
+
     Thread connectEventAction(int netId, int error, int latencyMs, String ipAddr) {
+        setCapabilities(netId);
         return new Thread(() -> {
             try {
                 mService.onConnectEvent(netId, error, latencyMs, ipAddr, 80, 1);
@@ -481,6 +490,7 @@
     }
 
     void dnsEvent(int netId, int type, int result, int latency) throws Exception {
+        setCapabilities(netId);
         mService.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
     }
 
diff --git a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
index 1ef1a61..631c862 100644
--- a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
+++ b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java
@@ -328,7 +328,7 @@
     public void testRecordTrackerCallbackNotifiedForNetworkSuspended() {
         RouteSelectionCallback cb = verifyRegistrationOnAvailableAndGetCallback();
 
-        cb.onNetworkSuspended(mNetwork);
+        cb.onCapabilitiesChanged(mNetwork, SUSPENDED_NETWORK_CAPABILITIES);
 
         UnderlyingNetworkRecord expectedRecord =
                 new UnderlyingNetworkRecord(
@@ -336,7 +336,11 @@
                         SUSPENDED_NETWORK_CAPABILITIES,
                         INITIAL_LINK_PROPERTIES,
                         false /* isBlocked */);
-        verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+        verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+        // onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't
+        // change.
+        cb.onCapabilitiesChanged(mNetwork, SUSPENDED_NETWORK_CAPABILITIES);
+        verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
     }
 
     @Test
@@ -344,7 +348,7 @@
         RouteSelectionCallback cb =
                 verifyRegistrationOnAvailableAndGetCallback(SUSPENDED_NETWORK_CAPABILITIES);
 
-        cb.onNetworkResumed(mNetwork);
+        cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES);
 
         UnderlyingNetworkRecord expectedRecord =
                 new UnderlyingNetworkRecord(
@@ -352,7 +356,11 @@
                         INITIAL_NETWORK_CAPABILITIES,
                         INITIAL_LINK_PROPERTIES,
                         false /* isBlocked */);
-        verify(mNetworkTrackerCb).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+        verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
+        // onSelectedUnderlyingNetworkChanged() won't be fired twice if network capabilities doesn't
+        // change.
+        cb.onCapabilitiesChanged(mNetwork, INITIAL_NETWORK_CAPABILITIES);
+        verify(mNetworkTrackerCb, times(1)).onSelectedUnderlyingNetworkChanged(eq(expectedRecord));
     }
 
     @Test