Merge "Add top-level OWNERS coverage for APAC."
diff --git a/core/api/current.txt b/core/api/current.txt
index 2e821b3..252c33c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8909,6 +8909,8 @@
     method public int getConnectionState(android.bluetooth.BluetoothDevice);
     method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
     method public boolean isAudioConnected(android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isNoiseReductionSupported(@NonNull android.bluetooth.BluetoothDevice);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isVoiceRecognitionSupported(@NonNull android.bluetooth.BluetoothDevice);
     method public boolean sendVendorSpecificResultCode(android.bluetooth.BluetoothDevice, String, String);
     method public boolean startVoiceRecognition(android.bluetooth.BluetoothDevice);
     method public boolean stopVoiceRecognition(android.bluetooth.BluetoothDevice);
@@ -41015,6 +41017,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean doesSwitchMultiSimConfigTriggerReboot();
     method public int getActiveModemCount();
     method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public java.util.List<android.telephony.CellInfo> getAllCellInfo();
+    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public int getCallComposerStatus();
     method public int getCallState();
     method public int getCardIdForDefaultEuicc();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @WorkerThread public android.os.PersistableBundle getCarrierConfig();
@@ -41109,6 +41112,7 @@
     method @Deprecated public String sendEnvelopeWithStatus(String);
     method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler);
     method public void sendVisualVoicemailSms(String, int, String, android.app.PendingIntent);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallComposerStatus(int);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabledForReason(int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setForbiddenPlmns(@NonNull java.util.List<java.lang.String>);
@@ -41143,6 +41147,8 @@
     field public static final int APPTYPE_USIM = 2; // 0x2
     field public static final int AUTHTYPE_EAP_AKA = 129; // 0x81
     field public static final int AUTHTYPE_EAP_SIM = 128; // 0x80
+    field public static final int CALL_COMPOSER_STATUS_OFF = 0; // 0x0
+    field public static final int CALL_COMPOSER_STATUS_ON = 1; // 0x1
     field public static final int CALL_STATE_IDLE = 0; // 0x0
     field public static final int CALL_STATE_OFFHOOK = 2; // 0x2
     field public static final int CALL_STATE_RINGING = 1; // 0x1
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index adb7e2f7..f59ae33 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -682,6 +682,48 @@
     }
 
     /**
+     * Checks whether the headset supports some form of noise reduction
+     *
+     * @param device Bluetooth device
+     * @return true if echo cancellation and/or noise reduction is supported, false otherwise
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public boolean isNoiseReductionSupported(@NonNull BluetoothDevice device) {
+        if (DBG) log("isNoiseReductionSupported()");
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.isNoiseReductionSupported(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Checks whether the headset supports voice recognition
+     *
+     * @param device Bluetooth device
+     * @return true if voice recognition is supported, false otherwise
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public boolean isVoiceRecognitionSupported(@NonNull BluetoothDevice device) {
+        if (DBG) log("isVoiceRecognitionSupported()");
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.isVoiceRecognitionSupported(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
      * Start Bluetooth voice recognition. This methods sends the voice
      * recognition AT command to the headset and establishes the
      * audio connection.
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 3d724f0..5bdd521 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -943,7 +943,7 @@
 
     /** @hide */
     @Override
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @UnsupportedAppUsage(trackingBug = 175981568)
     public Context createApplicationContext(ApplicationInfo application,
             int flags) throws PackageManager.NameNotFoundException {
         return mBase.createApplicationContext(application, flags);
diff --git a/core/java/android/content/om/IOverlayManager.aidl b/core/java/android/content/om/IOverlayManager.aidl
index 44b5c44..0b950b4 100644
--- a/core/java/android/content/om/IOverlayManager.aidl
+++ b/core/java/android/content/om/IOverlayManager.aidl
@@ -17,6 +17,7 @@
 package android.content.om;
 
 import android.content.om.OverlayInfo;
+import android.content.om.OverlayManagerTransaction;
 
 /**
  * Api for getting information about overlay packages.
@@ -163,4 +164,18 @@
      * @param packageName The name of the overlay package whose idmap should be deleted.
      */
     void invalidateCachesForOverlay(in String packageName, in int userIs);
+
+    /**
+     * Perform a series of requests related to overlay packages. This is an
+     * atomic operation: either all requests were performed successfully and
+     * the changes were propagated to the rest of the system, or at least one
+     * request could not be performed successfully and nothing is changed and
+     * nothing is propagated to the rest of the system.
+     *
+     * @see OverlayManagerTransaction
+     *
+     * @param transaction the series of overlay related requests to perform
+     * @throws SecurityException if the transaction failed
+     */
+    void commit(in OverlayManagerTransaction transaction);
 }
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 217f637c..7c14c28 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -254,6 +254,29 @@
     }
 
     /**
+     * Perform a series of requests related to overlay packages. This is an
+     * atomic operation: either all requests were performed successfully and
+     * the changes were propagated to the rest of the system, or at least one
+     * request could not be performed successfully and nothing is changed and
+     * nothing is propagated to the rest of the system.
+     *
+     * @see OverlayManagerTransaction
+     *
+     * @param transaction the series of overlay related requests to perform
+     * @throws Exception if not all the requests could be successfully and
+     *         atomically executed
+     *
+     * @hide
+     */
+    public void commit(@NonNull final OverlayManagerTransaction transaction) {
+        try {
+            mService.commit(transaction);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Starting on R, actor enforcement and app visibility changes introduce additional failure
      * cases, but the SecurityException thrown with these checks is unexpected for existing
      * consumers of the API.
diff --git a/core/java/android/content/om/OverlayManagerTransaction.aidl b/core/java/android/content/om/OverlayManagerTransaction.aidl
new file mode 100644
index 0000000..6715c82
--- /dev/null
+++ b/core/java/android/content/om/OverlayManagerTransaction.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.om;
+
+parcelable OverlayManagerTransaction;
diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java
new file mode 100644
index 0000000..1fa8973
--- /dev/null
+++ b/core/java/android/content/om/OverlayManagerTransaction.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.om;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Container for a batch of requests to the OverlayManagerService.
+ *
+ * Transactions are created using a builder interface. Example usage:
+ *
+ * final OverlayManager om = ctx.getSystemService(OverlayManager.class);
+ * final OverlayManagerTransaction t = new OverlayManagerTransaction.Builder()
+ *     .setEnabled(...)
+ *     .setEnabled(...)
+ *     .build();
+ * om.commit(t);
+ *
+ * @hide
+ */
+public class OverlayManagerTransaction
+        implements Iterable<OverlayManagerTransaction.Request>, Parcelable {
+    // TODO: remove @hide from this class when OverlayManager is added to the
+    // SDK, but keep OverlayManagerTransaction.Request @hidden
+    private final List<Request> mRequests;
+
+    OverlayManagerTransaction(@NonNull final List<Request> requests) {
+        checkNotNull(requests);
+        if (requests.contains(null)) {
+            throw new IllegalArgumentException("null request");
+        }
+        mRequests = requests;
+    }
+
+    private OverlayManagerTransaction(@NonNull final Parcel source) {
+        final int size = source.readInt();
+        mRequests = new ArrayList<Request>(size);
+        for (int i = 0; i < size; i++) {
+            final int request = source.readInt();
+            final String packageName = source.readString();
+            final int userId = source.readInt();
+            mRequests.add(new Request(request, packageName, userId));
+        }
+    }
+
+    @Override
+    public Iterator<Request> iterator() {
+        return mRequests.iterator();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("OverlayManagerTransaction { mRequests = %s }", mRequests);
+    }
+
+    /**
+     * A single unit of the transaction, such as a request to enable an
+     * overlay, or to disable an overlay.
+     *
+     * @hide
+     */
+    public static class Request {
+        @IntDef(prefix = "TYPE_", value = {
+                TYPE_SET_ENABLED,
+                TYPE_SET_DISABLED,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        @interface RequestType {}
+
+        public static final int TYPE_SET_ENABLED = 0;
+        public static final int TYPE_SET_DISABLED = 1;
+
+        @RequestType public final int type;
+        public final String packageName;
+        public final int userId;
+
+        public Request(@RequestType final int type, @NonNull final String packageName,
+                final int userId) {
+            this.type = type;
+            this.packageName = packageName;
+            this.userId = userId;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("Request{type=0x%02x (%s), packageName=%s, userId=%d}",
+                    type, typeToString(), packageName, userId);
+        }
+
+        /**
+         * Translate the request type into a human readable string. Only
+         * intended for debugging.
+         *
+         * @hide
+         */
+        public String typeToString() {
+            switch (type) {
+                case TYPE_SET_ENABLED: return "TYPE_SET_ENABLED";
+                case TYPE_SET_DISABLED: return "TYPE_SET_DISABLED";
+                default: return String.format("TYPE_UNKNOWN (0x%02x)", type);
+            }
+        }
+    }
+
+    /**
+     * Builder class for OverlayManagerTransaction objects.
+     *
+     * @hide
+     */
+    public static class Builder {
+        private final List<Request> mRequests = new ArrayList<>();
+
+        /**
+         * Request that an overlay package be enabled and change its loading
+         * order to the last package to be loaded, or disabled
+         *
+         * If the caller has the correct permissions, it is always possible to
+         * disable an overlay. Due to technical and security reasons it may not
+         * always be possible to enable an overlay, for instance if the overlay
+         * does not successfully overlay any target resources due to
+         * overlayable policy restrictions.
+         *
+         * An enabled overlay is a part of target package's resources, i.e. it will
+         * be part of any lookups performed via {@link android.content.res.Resources}
+         * and {@link android.content.res.AssetManager}. A disabled overlay will no
+         * longer affect the resources of the target package. If the target is
+         * currently running, its outdated resources will be replaced by new ones.
+         *
+         * @param packageName The name of the overlay package.
+         * @param enable true to enable the overlay, false to disable it.
+         * @return this Builder object, so you can chain additional requests
+         */
+        public Builder setEnabled(@NonNull String packageName, boolean enable) {
+            return setEnabled(packageName, enable, UserHandle.myUserId());
+        }
+
+        /**
+         * @hide
+         */
+        public Builder setEnabled(@NonNull String packageName, boolean enable, int userId) {
+            checkNotNull(packageName);
+            @Request.RequestType final int type =
+                enable ? Request.TYPE_SET_ENABLED : Request.TYPE_SET_DISABLED;
+            mRequests.add(new Request(type, packageName, userId));
+            return this;
+        }
+
+        /**
+         * Create a new transaction out of the requests added so far. Execute
+         * the transaction by calling OverlayManager#commit.
+         *
+         * @see OverlayManager#commit
+         * @return a new transaction
+         */
+        public OverlayManagerTransaction build() {
+            return new OverlayManagerTransaction(mRequests);
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        final int size = mRequests.size();
+        dest.writeInt(size);
+        for (int i = 0; i < size; i++) {
+            final Request req = mRequests.get(i);
+            dest.writeInt(req.type);
+            dest.writeString(req.packageName);
+            dest.writeInt(req.userId);
+        }
+    }
+
+    public static final Parcelable.Creator<OverlayManagerTransaction> CREATOR =
+            new Parcelable.Creator<OverlayManagerTransaction>() {
+
+        @Override
+        public OverlayManagerTransaction createFromParcel(Parcel source) {
+            return new OverlayManagerTransaction(source);
+        }
+
+        @Override
+        public OverlayManagerTransaction[] newArray(int size) {
+            return new OverlayManagerTransaction[size];
+        }
+    };
+}
diff --git a/core/java/android/content/pm/OWNERS b/core/java/android/content/pm/OWNERS
index 24872e8..f88df95 100644
--- a/core/java/android/content/pm/OWNERS
+++ b/core/java/android/content/pm/OWNERS
@@ -5,4 +5,5 @@
 patb@google.com
 
 per-file PackageParser.java = chiuwinson@google.com
-per-file *Shortcut* = omakoto@google.com, yamasani@google.com, sunnygoyal@google.com, mett@google.com, pinyaoting@google.com
+per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
+per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 6c911f6..63c58d2 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -2057,7 +2057,7 @@
     }
 
     /** @hide */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @UnsupportedAppUsage(trackingBug = 176190631)
     public DisplayAdjustments getDisplayAdjustments() {
         final DisplayAdjustments overrideDisplayAdjustments = mOverrideDisplayAdjustments;
         if (overrideDisplayAdjustments != null) {
diff --git a/core/java/android/hardware/biometrics/OWNERS b/core/java/android/hardware/biometrics/OWNERS
index 33527f8..2065ffa 100644
--- a/core/java/android/hardware/biometrics/OWNERS
+++ b/core/java/android/hardware/biometrics/OWNERS
@@ -1,3 +1,8 @@
 # Bug component: 879035
 
+curtislb@google.com
+ilyamaty@google.com
 jaggies@google.com
+joshmccloskey@google.com
+kchyn@google.com
+
diff --git a/core/java/android/hardware/fingerprint/OWNERS b/core/java/android/hardware/fingerprint/OWNERS
index dcead40..e55b8c56 100644
--- a/core/java/android/hardware/fingerprint/OWNERS
+++ b/core/java/android/hardware/fingerprint/OWNERS
@@ -1,3 +1,8 @@
 # Bug component: 114777
 
+curtislb@google.com
+ilyamaty@google.com
 jaggies@google.com
+joshmccloskey@google.com
+kchyn@google.com
+
diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java
index 704f31d..5234494 100644
--- a/core/java/android/net/ConnectivityDiagnosticsManager.java
+++ b/core/java/android/net/ConnectivityDiagnosticsManager.java
@@ -623,32 +623,41 @@
         /** @hide */
         @VisibleForTesting
         public void onConnectivityReportAvailable(@NonNull ConnectivityReport report) {
-            Binder.withCleanCallingIdentity(() -> {
+            final long token = Binder.clearCallingIdentity();
+            try {
                 mExecutor.execute(() -> {
                     mCb.onConnectivityReportAvailable(report);
                 });
-            });
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         /** @hide */
         @VisibleForTesting
         public void onDataStallSuspected(@NonNull DataStallReport report) {
-            Binder.withCleanCallingIdentity(() -> {
+            final long token = Binder.clearCallingIdentity();
+            try {
                 mExecutor.execute(() -> {
                     mCb.onDataStallSuspected(report);
                 });
-            });
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         /** @hide */
         @VisibleForTesting
         public void onNetworkConnectivityReported(
                 @NonNull Network network, boolean hasConnectivity) {
-            Binder.withCleanCallingIdentity(() -> {
+            final long token = Binder.clearCallingIdentity();
+            try {
                 mExecutor.execute(() -> {
                     mCb.onNetworkConnectivityReported(network, hasConnectivity);
                 });
-            });
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
     }
 
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 6cef73d..06c15980 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -59,6 +59,7 @@
 import android.telephony.TelephonyManager;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.Range;
 import android.util.SparseIntArray;
 
 import com.android.connectivity.aidl.INetworkAgent;
@@ -73,10 +74,12 @@
 import java.io.UncheckedIOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.net.DatagramSocket;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 import java.net.Socket;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -1163,6 +1166,55 @@
     }
 
     /**
+     * Adds or removes a requirement for given UID ranges to use the VPN.
+     *
+     * If set to {@code true}, informs the system that the UIDs in the specified ranges must not
+     * have any connectivity except if a VPN is connected and applies to the UIDs, or if the UIDs
+     * otherwise have permission to bypass the VPN (e.g., because they have the
+     * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission, or when
+     * using a socket protected by a method such as {@link VpnService#protect(DatagramSocket)}. If
+     * set to {@code false}, a previously-added restriction is removed.
+     * <p>
+     * Each of the UID ranges specified by this method is added and removed as is, and no processing
+     * is performed on the ranges to de-duplicate, merge, split, or intersect them. In order to
+     * remove a previously-added range, the exact range must be removed as is.
+     * <p>
+     * The changes are applied asynchronously and may not have been applied by the time the method
+     * returns. Apps will be notified about any changes that apply to them via
+     * {@link NetworkCallback#onBlockedStatusChanged} callbacks called after the changes take
+     * effect.
+     * <p>
+     * This method should be called only by the VPN code.
+     *
+     * @param ranges the UID ranges to restrict
+     * @param requireVpn whether the specified UID ranges must use a VPN
+     *
+     * TODO: expose as @SystemApi.
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
+            android.Manifest.permission.NETWORK_STACK})
+    public void setRequireVpnForUids(boolean requireVpn,
+            @NonNull Collection<Range<Integer>> ranges) {
+        Objects.requireNonNull(ranges);
+        // The Range class is not parcelable. Convert to UidRange, which is what is used internally.
+        // This method is not necessarily expected to be used outside the system server, so
+        // parceling may not be necessary, but it could be used out-of-process, e.g., by the network
+        // stack process, or by tests.
+        UidRange[] rangesArray = new UidRange[ranges.size()];
+        int index = 0;
+        for (Range<Integer> range : ranges) {
+            rangesArray[index++] = new UidRange(range.getLower(), range.getUpper());
+        }
+        try {
+            mService.setRequireVpnForUids(requireVpn, rangesArray);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns details about the currently active default data network
      * for a given uid.  This is for internal use only to avoid spying
      * other apps.
@@ -1833,30 +1885,42 @@
             mCallback = new ISocketKeepaliveCallback.Stub() {
                 @Override
                 public void onStarted(int slot) {
-                    Binder.withCleanCallingIdentity(() ->
-                            mExecutor.execute(() -> {
-                                mSlot = slot;
-                                callback.onStarted();
-                            }));
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        mExecutor.execute(() -> {
+                            mSlot = slot;
+                            callback.onStarted();
+                        });
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
                 }
 
                 @Override
                 public void onStopped() {
-                    Binder.withCleanCallingIdentity(() ->
-                            mExecutor.execute(() -> {
-                                mSlot = null;
-                                callback.onStopped();
-                            }));
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        mExecutor.execute(() -> {
+                            mSlot = null;
+                            callback.onStopped();
+                        });
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
                     mExecutor.shutdown();
                 }
 
                 @Override
                 public void onError(int error) {
-                    Binder.withCleanCallingIdentity(() ->
-                            mExecutor.execute(() -> {
-                                mSlot = null;
-                                callback.onError(error);
-                            }));
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        mExecutor.execute(() -> {
+                            mSlot = null;
+                            callback.onError(error);
+                        });
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
                     mExecutor.shutdown();
                 }
 
@@ -3120,39 +3184,6 @@
     }
 
     /**
-     * Check mobile provisioning.
-     *
-     * @param suggestedTimeOutMs, timeout in milliseconds
-     *
-     * @return time out that will be used, maybe less that suggestedTimeOutMs
-     * -1 if an error.
-     *
-     * {@hide}
-     */
-    public int checkMobileProvisioning(int suggestedTimeOutMs) {
-        int timeOutMs = -1;
-        try {
-            timeOutMs = mService.checkMobileProvisioning(suggestedTimeOutMs);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-        return timeOutMs;
-    }
-
-    /**
-     * Get the mobile provisioning url.
-     * {@hide}
-     */
-    @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
-    public String getMobileProvisioningUrl() {
-        try {
-            return mService.getMobileProvisioningUrl();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Set sign in error notification to visible or invisible
      *
      * @hide
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index cbb1197..b32c98b 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -29,6 +29,7 @@
 import android.net.NetworkState;
 import android.net.ISocketKeepaliveCallback;
 import android.net.ProxyInfo;
+import android.net.UidRange;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.INetworkActivityListener;
@@ -146,10 +147,7 @@
     String getAlwaysOnVpnPackage(int userId);
     boolean isVpnLockdownEnabled(int userId);
     List<String> getVpnLockdownWhitelist(int userId);
-
-    int checkMobileProvisioning(int suggestedTimeOutMs);
-
-    String getMobileProvisioningUrl();
+    void setRequireVpnForUids(boolean requireVpn, in UidRange[] ranges);
 
     void setProvisioningNotificationVisible(boolean visible, int networkType, in String action);
 
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index bf25602..f413063 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -45,8 +45,8 @@
 import libcore.util.EmptyArray;
 
 import java.io.CharArrayWriter;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
+import java.io.DataInput;
+import java.io.DataOutput;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.ProtocolException;
@@ -162,7 +162,7 @@
         out.writeLong(totalBytes);
     }
 
-    public NetworkStatsHistory(DataInputStream in) throws IOException {
+    public NetworkStatsHistory(DataInput in) throws IOException {
         final int version = in.readInt();
         switch (version) {
             case VERSION_INIT: {
@@ -204,7 +204,7 @@
         }
     }
 
-    public void writeToStream(DataOutputStream out) throws IOException {
+    public void writeToStream(DataOutput out) throws IOException {
         out.writeInt(VERSION_ADD_ACTIVE);
         out.writeLong(bucketDuration);
         writeVarLongArray(out, bucketStart, bucketCount);
@@ -768,7 +768,7 @@
      */
     public static class DataStreamUtils {
         @Deprecated
-        public static long[] readFullLongArray(DataInputStream in) throws IOException {
+        public static long[] readFullLongArray(DataInput in) throws IOException {
             final int size = in.readInt();
             if (size < 0) throw new ProtocolException("negative array size");
             final long[] values = new long[size];
@@ -781,7 +781,7 @@
         /**
          * Read variable-length {@link Long} using protobuf-style approach.
          */
-        public static long readVarLong(DataInputStream in) throws IOException {
+        public static long readVarLong(DataInput in) throws IOException {
             int shift = 0;
             long result = 0;
             while (shift < 64) {
@@ -797,7 +797,7 @@
         /**
          * Write variable-length {@link Long} using protobuf-style approach.
          */
-        public static void writeVarLong(DataOutputStream out, long value) throws IOException {
+        public static void writeVarLong(DataOutput out, long value) throws IOException {
             while (true) {
                 if ((value & ~0x7FL) == 0) {
                     out.writeByte((int) value);
@@ -809,7 +809,7 @@
             }
         }
 
-        public static long[] readVarLongArray(DataInputStream in) throws IOException {
+        public static long[] readVarLongArray(DataInput in) throws IOException {
             final int size = in.readInt();
             if (size == -1) return null;
             if (size < 0) throw new ProtocolException("negative array size");
@@ -820,7 +820,7 @@
             return values;
         }
 
-        public static void writeVarLongArray(DataOutputStream out, long[] values, int size)
+        public static void writeVarLongArray(DataOutput out, long[] values, int size)
                 throws IOException {
             if (values == null) {
                 out.writeInt(-1);
diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java
index a7dce18..d007a95 100644
--- a/core/java/android/net/SocketKeepalive.java
+++ b/core/java/android/net/SocketKeepalive.java
@@ -187,38 +187,54 @@
         mCallback = new ISocketKeepaliveCallback.Stub() {
             @Override
             public void onStarted(int slot) {
-                Binder.withCleanCallingIdentity(() ->
-                        mExecutor.execute(() -> {
-                            mSlot = slot;
-                            callback.onStarted();
-                        }));
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    mExecutor.execute(() -> {
+                        mSlot = slot;
+                        callback.onStarted();
+                    });
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
             }
 
             @Override
             public void onStopped() {
-                Binder.withCleanCallingIdentity(() ->
-                        executor.execute(() -> {
-                            mSlot = null;
-                            callback.onStopped();
-                        }));
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(() -> {
+                        mSlot = null;
+                        callback.onStopped();
+                    });
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
             }
 
             @Override
             public void onError(int error) {
-                Binder.withCleanCallingIdentity(() ->
-                        executor.execute(() -> {
-                            mSlot = null;
-                            callback.onError(error);
-                        }));
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(() -> {
+                        mSlot = null;
+                        callback.onError(error);
+                    });
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
             }
 
             @Override
             public void onDataReceived() {
-                Binder.withCleanCallingIdentity(() ->
-                        executor.execute(() -> {
-                            mSlot = null;
-                            callback.onDataReceived();
-                        }));
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(() -> {
+                        mSlot = null;
+                        callback.onDataReceived();
+                    });
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
             }
         };
     }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 300bb760..1e14e3a 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -13283,6 +13283,16 @@
         @TestApi
         public static final String HIDDEN_API_POLICY = "hidden_api_policy";
 
+         /**
+         * Flag for forcing {@link com.android.server.compat.OverrideValidatorImpl}
+         * to consider this a non-debuggable build.
+         *
+         * @hide
+         */
+        public static final String FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT =
+                "force_non_debuggable_final_build_for_compat";
+
+
         /**
          * Current version of signed configuration applied.
          *
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index cf0e0d1..5e74381 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -73,7 +73,7 @@
 /**
  * <p>Displays a vertically-scrollable collection of views, where each view is positioned
  * immediatelybelow the previous view in the list.  For a more modern, flexible, and performant
- * approach to displaying lists, use {@link android.support.v7.widget.RecyclerView}.</p>
+ * approach to displaying lists, use {@link androidx.recyclerview.widget.RecyclerView}.</p>
  *
  * <p>To display a list, you can include a list view in your layout XML file:</p>
  *
diff --git a/core/java/com/android/internal/compat/OverrideAllowedState.java b/core/java/com/android/internal/compat/OverrideAllowedState.java
index 9a78ad2..c0bbe50 100644
--- a/core/java/com/android/internal/compat/OverrideAllowedState.java
+++ b/core/java/com/android/internal/compat/OverrideAllowedState.java
@@ -33,7 +33,7 @@
             DISABLED_NOT_DEBUGGABLE,
             DISABLED_NON_TARGET_SDK,
             DISABLED_TARGET_SDK_TOO_HIGH,
-            PACKAGE_DOES_NOT_EXIST,
+            DEFERRED_VERIFICATION,
             LOGGING_ONLY_CHANGE
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -57,10 +57,10 @@
      * Change cannot be overridden, due to the app's targetSdk being above the change's targetSdk.
      */
     public static final int DISABLED_TARGET_SDK_TOO_HIGH = 3;
-    /**
-     * Package does not exist.
+     /**
+     * Change override decision is currently being deferred, due to the app not being installed yet.
      */
-    public static final int PACKAGE_DOES_NOT_EXIST = 4;
+    public static final int DEFERRED_VERIFICATION = 4;
     /**
      * Change is marked as logging only, and cannot be toggled.
      */
@@ -106,6 +106,7 @@
             throws SecurityException {
         switch (state) {
             case ALLOWED:
+            case DEFERRED_VERIFICATION:
                 return;
             case DISABLED_NOT_DEBUGGABLE:
                 throw new SecurityException(
@@ -118,11 +119,6 @@
                         "Cannot override %1$d for %2$s because the app's targetSdk (%3$d) is "
                                 + "above the change's targetSdk threshold (%4$d)",
                         changeId, packageName, appTargetSdk, changeIdTargetSdk));
-            case PACKAGE_DOES_NOT_EXIST:
-                throw new SecurityException(String.format(
-                        "Cannot override %1$d for %2$s because the package does not exist, and "
-                                + "the change is targetSdk gated.",
-                        changeId, packageName));
             case LOGGING_ONLY_CHANGE:
                 throw new SecurityException(String.format(
                         "Cannot override %1$d because it is marked as a logging-only change.",
@@ -170,8 +166,8 @@
                 return "DISABLED_NON_TARGET_SDK";
             case DISABLED_TARGET_SDK_TOO_HIGH:
                 return "DISABLED_TARGET_SDK_TOO_HIGH";
-            case PACKAGE_DOES_NOT_EXIST:
-                return "PACKAGE_DOES_NOT_EXIST";
+            case DEFERRED_VERIFICATION:
+                return "DEFERRED_VERIFICATION";
             case LOGGING_ONLY_CHANGE:
                 return "LOGGING_ONLY_CHANGE";
         }
diff --git a/core/proto/android/app/OWNERS b/core/proto/android/app/OWNERS
new file mode 100644
index 0000000..296abd1
--- /dev/null
+++ b/core/proto/android/app/OWNERS
@@ -0,0 +1 @@
+per-file location_time_zone_manager.proto = nfuller@google.com, mingaleev@google.com
diff --git a/core/proto/android/app/location_time_zone_manager.proto b/core/proto/android/app/location_time_zone_manager.proto
new file mode 100644
index 0000000..f44d549
--- /dev/null
+++ b/core/proto/android/app/location_time_zone_manager.proto
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+package android.app.time;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+
+option java_multiple_files = true;
+option java_outer_classname = "LocationTimeZoneManagerProto";
+
+// Represents the state of the LocationTimeZoneManagerService for use in tests.
+message LocationTimeZoneManagerServiceStateProto {
+  option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+  optional GeolocationTimeZoneSuggestionProto last_suggestion = 1;
+  repeated TimeZoneProviderStateProto primary_provider_states = 2;
+  repeated TimeZoneProviderStateProto secondary_provider_states = 3;
+}
+
+// Represents a GeolocationTimeZoneSuggestion that can be / has been passed to the time zone
+// detector.
+message GeolocationTimeZoneSuggestionProto {
+  option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+  repeated string zone_ids = 1;
+  repeated string debug_info = 2;
+}
+
+// The state tracked for a LocationTimeZoneProvider.
+message TimeZoneProviderStateProto {
+  option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+  optional TimeZoneProviderStateEnum state = 1;
+}
+
+// The state enum for LocationTimeZoneProviders.
+enum TimeZoneProviderStateEnum {
+  TIME_ZONE_PROVIDER_STATE_UNKNOWN = 0;
+  TIME_ZONE_PROVIDER_STATE_INITIALIZING = 1;
+  TIME_ZONE_PROVIDER_STATE_CERTAIN = 2;
+  TIME_ZONE_PROVIDER_STATE_UNCERTAIN = 3;
+  TIME_ZONE_PROVIDER_STATE_DISABLED = 4;
+  TIME_ZONE_PROVIDER_STATE_PERM_FAILED = 5;
+  TIME_ZONE_PROVIDER_STATE_DESTROYED = 6;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a9fe5d5..714a09d 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2206,7 +2206,9 @@
     <!-- Allows to query ongoing call details and manage ongoing calls
      <p>Protection level: signature|appop -->
     <permission android:name="android.permission.MANAGE_ONGOING_CALLS"
-        android:protectionLevel="signature|appop" />
+        android:protectionLevel="signature|appop"
+        android:label="@string/permlab_manageOngoingCalls"
+        android:description="@string/permdesc_manageOngoingCalls" />
 
     <!-- Allows the app to request network scans from telephony.
          <p>Not for use by third-party applications.
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index c1f3028..1143467 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -914,6 +914,15 @@
         interfere with the performance or operation of your device when an
         emergency cell broadcast is received.</string>
 
+    <!-- Title for an application which grants an app the ability to see and manage calls on
+         the user's device. Usually reserved for apps associated with wearable devices that
+         can show information about calls. -->
+    <string name="permlab_manageOngoingCalls">Manage ongoing calls</string>
+    <!-- Description of an application permission, listed so the user can choose whether they
+         want to allow the application to do this. -->
+    <string name="permdesc_manageOngoingCalls">Allows an app to see details about ongoing calls
+         on your device and to control these calls.</string>
+
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_readCellBroadcasts">read cell broadcast messages</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
diff --git a/core/tests/coretests/src/android/content/OWNERS b/core/tests/coretests/src/android/content/OWNERS
index 911efb2..912db1e 100644
--- a/core/tests/coretests/src/android/content/OWNERS
+++ b/core/tests/coretests/src/android/content/OWNERS
@@ -1 +1,3 @@
 per-file ContextTest.java = file:/services/core/java/com/android/server/wm/OWNERS
+per-file *Shortcut* = file:/core/java/android/content/pm/SHORTCUT_OWNERS
+per-file *Launcher* = file:/core/java/android/content/pm/LAUNCHER_OWNERS
diff --git a/core/tests/coretests/src/android/view/inputmethod/OWNERS b/core/tests/coretests/src/android/view/inputmethod/OWNERS
new file mode 100644
index 0000000..eb06b78
--- /dev/null
+++ b/core/tests/coretests/src/android/view/inputmethod/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 34867
+
+include /core/java/android/view/inputmethod/OWNERS
diff --git a/core/tests/overlaytests/device/Android.bp b/core/tests/overlaytests/device/Android.bp
index 12a2b08..f86ac9c 100644
--- a/core/tests/overlaytests/device/Android.bp
+++ b/core/tests/overlaytests/device/Android.bp
@@ -16,7 +16,11 @@
     name: "OverlayDeviceTests",
     srcs: ["src/**/*.java"],
     platform_apis: true,
-    static_libs: ["androidx.test.rules"],
+    certificate: "platform",
+    static_libs: [
+        "androidx.test.rules",
+        "testng",
+    ],
     test_suites: ["device-tests"],
     data: [
         ":OverlayDeviceTests_AppOverlayOne",
diff --git a/core/tests/overlaytests/device/AndroidManifest.xml b/core/tests/overlaytests/device/AndroidManifest.xml
index 4881636..a69911f 100644
--- a/core/tests/overlaytests/device/AndroidManifest.xml
+++ b/core/tests/overlaytests/device/AndroidManifest.xml
@@ -19,6 +19,8 @@
 
     <uses-sdk android:minSdkVersion="21" />
 
+    <uses-permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES" />
+
     <application>
         <uses-library android:name="android.test.runner"/>
     </application>
diff --git a/core/tests/overlaytests/device/AndroidTest.xml b/core/tests/overlaytests/device/AndroidTest.xml
index 6507839..db45750 100644
--- a/core/tests/overlaytests/device/AndroidTest.xml
+++ b/core/tests/overlaytests/device/AndroidTest.xml
@@ -19,9 +19,20 @@
     <option name="test-suite-tag" value="apct" />
     <option name="test-suite-tag" value="apct-instrumentation" />
 
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="remount-system" value="true" />
+        <option name="push" value="OverlayDeviceTests.apk->/system/app/OverlayDeviceTests.apk" />
+    </target_preparer>
+  
+    <!-- Reboot to have the test APK scanned by PM and reboot after to remove the test APK. -->
+    <target_preparer class="com.android.tradefed.targetprep.RebootTargetPreparer">
+      <option name="pre-reboot" value="true" />
+      <option name="post-reboot" value="true" />
+    </target_preparer>
+
     <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
         <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="OverlayDeviceTests.apk" />
         <option name="test-file-name" value="OverlayDeviceTests_AppOverlayOne.apk" />
         <option name="test-file-name" value="OverlayDeviceTests_AppOverlayTwo.apk" />
         <option name="test-file-name" value="OverlayDeviceTests_FrameworkOverlay.apk" />
diff --git a/core/tests/overlaytests/device/TEST_MAPPING b/core/tests/overlaytests/device/TEST_MAPPING
new file mode 100644
index 0000000..43ee00f
--- /dev/null
+++ b/core/tests/overlaytests/device/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name" : "OverlayDeviceTests"
+    }
+  ]
+}
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/LocalOverlayManager.java b/core/tests/overlaytests/device/src/com/android/overlaytest/LocalOverlayManager.java
index 390bb76..76c01a7 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/LocalOverlayManager.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/LocalOverlayManager.java
@@ -18,60 +18,76 @@
 
 import static java.util.concurrent.TimeUnit.SECONDS;
 
-import android.app.UiAutomation;
-import android.content.res.Resources;
-import android.os.ParcelFileDescriptor;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.om.OverlayManager;
+import android.content.om.OverlayManagerTransaction;
+import android.os.UserHandle;
 
 import androidx.test.InstrumentationRegistry;
 
-import java.io.BufferedReader;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
 import java.util.concurrent.Executor;
 import java.util.concurrent.FutureTask;
 
 class LocalOverlayManager {
     private static final long TIMEOUT = 30;
 
-    public static void setEnabledAndWait(Executor executor, final String packageName,
-            boolean enable) throws Exception {
-        final String pattern = (enable ? "[x]" : "[ ]") + " " + packageName;
-        if (executeShellCommand("cmd overlay list").contains(pattern)) {
-            // nothing to do, overlay already in the requested state
-            return;
+    public static void toggleOverlaysAndWait(@NonNull final String[] overlaysToEnable,
+            @NonNull final String[] overlaysToDisable) throws Exception {
+        final int userId = UserHandle.myUserId();
+        OverlayManagerTransaction.Builder builder = new OverlayManagerTransaction.Builder();
+        for (String pkg : overlaysToEnable) {
+            builder.setEnabled(pkg, true, userId);
         }
+        for (String pkg : overlaysToDisable) {
+            builder.setEnabled(pkg, false, userId);
+        }
+        OverlayManagerTransaction transaction = builder.build();
 
-        final Resources res = InstrumentationRegistry.getContext().getResources();
-        final String[] oldApkPaths = res.getAssets().getApkPaths();
+        final Context ctx = InstrumentationRegistry.getTargetContext();
         FutureTask<Boolean> task = new FutureTask<>(() -> {
             while (true) {
-                if (!Arrays.equals(oldApkPaths, res.getAssets().getApkPaths())) {
+                final String[] paths = ctx.getResources().getAssets().getApkPaths();
+                if (arrayTailContains(paths, overlaysToEnable)
+                        && arrayDoesNotContain(paths, overlaysToDisable)) {
                     return true;
                 }
                 Thread.sleep(10);
             }
         });
+
+        OverlayManager om = ctx.getSystemService(OverlayManager.class);
+        om.commit(transaction);
+
+        Executor executor = (cmd) -> new Thread(cmd).start();
         executor.execute(task);
-        executeShellCommand("cmd overlay " + (enable ? "enable " : "disable ") + packageName);
         task.get(TIMEOUT, SECONDS);
     }
 
-    private static String executeShellCommand(final String command)
-            throws Exception {
-        final UiAutomation uiAutomation =
-                InstrumentationRegistry.getInstrumentation().getUiAutomation();
-        final ParcelFileDescriptor pfd = uiAutomation.executeShellCommand(command);
-        try (InputStream in = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
-            final BufferedReader reader = new BufferedReader(
-                    new InputStreamReader(in, StandardCharsets.UTF_8));
-            StringBuilder str = new StringBuilder();
-            String line;
-            while ((line = reader.readLine()) != null) {
-                str.append(line);
-            }
-            return str.toString();
+    private static boolean arrayTailContains(@NonNull final String[] array,
+            @NonNull final String[] substrings) {
+        if (array.length < substrings.length) {
+            return false;
         }
+        for (int i = 0; i < substrings.length; i++) {
+            String a = array[array.length - substrings.length + i];
+            String s = substrings[i];
+            if (!a.contains(s)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static boolean arrayDoesNotContain(@NonNull final String[] array,
+            @NonNull final String[] substrings) {
+        for (String s : substrings) {
+            for (String a : array) {
+                if (a.contains(s)) {
+                    return false;
+                }
+            }
+        }
+        return true;
     }
 }
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/TransactionTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/TransactionTest.java
new file mode 100644
index 0000000..0b4f5e2
--- /dev/null
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/TransactionTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.overlaytest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
+import android.content.om.OverlayInfo;
+import android.content.om.OverlayManager;
+import android.content.om.OverlayManagerTransaction;
+import android.content.res.Resources;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.List;
+
+@RunWith(JUnit4.class)
+@MediumTest
+public class TransactionTest {
+    static final String APP_OVERLAY_ONE_PKG = "com.android.overlaytest.app_overlay_one";
+    static final String APP_OVERLAY_TWO_PKG = "com.android.overlaytest.app_overlay_two";
+
+    private Context mContext;
+    private Resources mResources;
+    private OverlayManager mOverlayManager;
+    private int mUserId;
+    private UserHandle mUserHandle;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        mResources = mContext.getResources();
+        mOverlayManager = mContext.getSystemService(OverlayManager.class);
+        mUserId = UserHandle.myUserId();
+        mUserHandle = UserHandle.of(mUserId);
+
+        LocalOverlayManager.toggleOverlaysAndWait(
+                new String[]{},
+                new String[]{APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG});
+    }
+
+    @Test
+    public void testValidTransaction() throws Exception {
+        assertOverlayIsEnabled(APP_OVERLAY_ONE_PKG, false, mUserId);
+        assertOverlayIsEnabled(APP_OVERLAY_TWO_PKG, false, mUserId);
+
+        OverlayManagerTransaction t = new OverlayManagerTransaction.Builder()
+                .setEnabled(APP_OVERLAY_ONE_PKG, true)
+                .setEnabled(APP_OVERLAY_TWO_PKG, true)
+                .build();
+        mOverlayManager.commit(t);
+
+        assertOverlayIsEnabled(APP_OVERLAY_ONE_PKG, true, mUserId);
+        assertOverlayIsEnabled(APP_OVERLAY_TWO_PKG, true, mUserId);
+        List<OverlayInfo> ois =
+                mOverlayManager.getOverlayInfosForTarget("com.android.overlaytest", mUserHandle);
+        assertEquals(ois.size(), 2);
+        assertEquals(ois.get(0).packageName, APP_OVERLAY_ONE_PKG);
+        assertEquals(ois.get(1).packageName, APP_OVERLAY_TWO_PKG);
+
+        OverlayManagerTransaction t2 = new OverlayManagerTransaction.Builder()
+                .setEnabled(APP_OVERLAY_TWO_PKG, true)
+                .setEnabled(APP_OVERLAY_ONE_PKG, true)
+                .build();
+        mOverlayManager.commit(t2);
+
+        assertOverlayIsEnabled(APP_OVERLAY_ONE_PKG, true, mUserId);
+        assertOverlayIsEnabled(APP_OVERLAY_TWO_PKG, true, mUserId);
+        List<OverlayInfo> ois2 =
+                mOverlayManager.getOverlayInfosForTarget("com.android.overlaytest", mUserHandle);
+        assertEquals(ois2.size(), 2);
+        assertEquals(ois2.get(0).packageName, APP_OVERLAY_TWO_PKG);
+        assertEquals(ois2.get(1).packageName, APP_OVERLAY_ONE_PKG);
+
+        OverlayManagerTransaction t3 = new OverlayManagerTransaction.Builder()
+                .setEnabled(APP_OVERLAY_TWO_PKG, false)
+                .build();
+        mOverlayManager.commit(t3);
+
+        assertOverlayIsEnabled(APP_OVERLAY_ONE_PKG, true, mUserId);
+        assertOverlayIsEnabled(APP_OVERLAY_TWO_PKG, false, mUserId);
+        List<OverlayInfo> ois3 =
+                mOverlayManager.getOverlayInfosForTarget("com.android.overlaytest", mUserHandle);
+        assertEquals(ois3.size(), 2);
+        assertEquals(ois3.get(0).packageName, APP_OVERLAY_TWO_PKG);
+        assertEquals(ois3.get(1).packageName, APP_OVERLAY_ONE_PKG);
+    }
+
+    @Test
+    public void testInvalidRequestHasNoEffect() {
+        assertOverlayIsEnabled(APP_OVERLAY_ONE_PKG, false, mUserId);
+        assertOverlayIsEnabled(APP_OVERLAY_TWO_PKG, false, mUserId);
+
+        OverlayManagerTransaction t = new OverlayManagerTransaction.Builder()
+                .setEnabled(APP_OVERLAY_ONE_PKG, true)
+                .setEnabled("does-not-exist", true)
+                .setEnabled(APP_OVERLAY_TWO_PKG, true)
+                .build();
+        assertThrows(SecurityException.class, () -> mOverlayManager.commit(t));
+
+        assertOverlayIsEnabled(APP_OVERLAY_ONE_PKG, false, mUserId);
+        assertOverlayIsEnabled(APP_OVERLAY_TWO_PKG, false, mUserId);
+    }
+
+    private void assertOverlayIsEnabled(final String packageName, boolean enabled, int userId) {
+        final OverlayInfo oi = mOverlayManager.getOverlayInfo(packageName, UserHandle.of(userId));
+        assertNotNull(oi);
+        assertEquals(oi.isEnabled(), enabled);
+    }
+}
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java
index d28c47d..420f755 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java
@@ -22,8 +22,6 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-import java.util.concurrent.Executor;
-
 @RunWith(JUnit4.class)
 @MediumTest
 public class WithMultipleOverlaysTest extends OverlayBaseTest {
@@ -33,9 +31,8 @@
 
     @BeforeClass
     public static void enableOverlay() throws Exception {
-        Executor executor = (cmd) -> new Thread(cmd).start();
-        LocalOverlayManager.setEnabledAndWait(executor, APP_OVERLAY_ONE_PKG, true);
-        LocalOverlayManager.setEnabledAndWait(executor, APP_OVERLAY_TWO_PKG, true);
-        LocalOverlayManager.setEnabledAndWait(executor, FRAMEWORK_OVERLAY_PKG, true);
+        LocalOverlayManager.toggleOverlaysAndWait(
+                new String[]{FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG},
+                new String[]{});
     }
 }
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java
index 6566ad3..a86255e 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java
@@ -22,8 +22,6 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-import java.util.concurrent.Executor;
-
 @RunWith(JUnit4.class)
 @MediumTest
 public class WithOverlayTest extends OverlayBaseTest {
@@ -32,10 +30,9 @@
     }
 
     @BeforeClass
-    public static void enableOverlay() throws Exception {
-        Executor executor = (cmd) -> new Thread(cmd).start();
-        LocalOverlayManager.setEnabledAndWait(executor, APP_OVERLAY_ONE_PKG, true);
-        LocalOverlayManager.setEnabledAndWait(executor, APP_OVERLAY_TWO_PKG, false);
-        LocalOverlayManager.setEnabledAndWait(executor, FRAMEWORK_OVERLAY_PKG, true);
+    public static void enableOverlays() throws Exception {
+        LocalOverlayManager.toggleOverlaysAndWait(
+                new String[]{FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG},
+                new String[]{APP_OVERLAY_TWO_PKG});
     }
 }
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java
index 48cfeab..51c4118 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java
@@ -22,8 +22,6 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-import java.util.concurrent.Executor;
-
 @RunWith(JUnit4.class)
 @MediumTest
 public class WithoutOverlayTest extends OverlayBaseTest {
@@ -33,9 +31,8 @@
 
     @BeforeClass
     public static void disableOverlays() throws Exception {
-        Executor executor = (cmd) -> new Thread(cmd).start();
-        LocalOverlayManager.setEnabledAndWait(executor, APP_OVERLAY_ONE_PKG, false);
-        LocalOverlayManager.setEnabledAndWait(executor, APP_OVERLAY_TWO_PKG, false);
-        LocalOverlayManager.setEnabledAndWait(executor, FRAMEWORK_OVERLAY_PKG, false);
+        LocalOverlayManager.toggleOverlaysAndWait(
+                new String[]{},
+                new String[]{FRAMEWORK_OVERLAY_PKG, APP_OVERLAY_ONE_PKG, APP_OVERLAY_TWO_PKG});
     }
 }
diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayOne/Android.bp b/core/tests/overlaytests/device/test-apps/AppOverlayOne/Android.bp
index da3aa00..847b491 100644
--- a/core/tests/overlaytests/device/test-apps/AppOverlayOne/Android.bp
+++ b/core/tests/overlaytests/device/test-apps/AppOverlayOne/Android.bp
@@ -15,6 +15,6 @@
 android_test {
     name: "OverlayDeviceTests_AppOverlayOne",
     sdk_version: "current",
-
+    certificate: "platform",
     aaptflags: ["--no-resource-removal"],
 }
diff --git a/core/tests/overlaytests/device/test-apps/AppOverlayTwo/Android.bp b/core/tests/overlaytests/device/test-apps/AppOverlayTwo/Android.bp
index 215b66da3..7d5f82a 100644
--- a/core/tests/overlaytests/device/test-apps/AppOverlayTwo/Android.bp
+++ b/core/tests/overlaytests/device/test-apps/AppOverlayTwo/Android.bp
@@ -15,6 +15,6 @@
 android_test {
     name: "OverlayDeviceTests_AppOverlayTwo",
     sdk_version: "current",
-
+    certificate: "platform",
     aaptflags: ["--no-resource-removal"],
 }
diff --git a/framework-jarjar-rules.txt b/framework-jarjar-rules.txt
index d8af726..52ee63a 100644
--- a/framework-jarjar-rules.txt
+++ b/framework-jarjar-rules.txt
@@ -1,2 +1,3 @@
 rule android.hidl.** android.internal.hidl.@1
 rule android.net.wifi.WifiAnnotations* android.internal.wifi.WifiAnnotations@1
+rule com.android.server.vcn.util.** com.android.server.vcn.repackaged.util.@1
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index 7a2e584..9b2effc 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -76,9 +76,9 @@
     /**
      * These fields are used by native code, do not access or modify.
      */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @UnsupportedAppUsage(trackingBug = 176388660)
     private long mSurfaceTexture;
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @UnsupportedAppUsage(trackingBug = 176388660)
     private long mProducer;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private long mFrameAvailableListener;
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 635f6c6..b3c3355 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -122,7 +122,7 @@
     private final Rect mDirtyBounds = new Rect();
 
     /** Mirrors mLayerState with some extra information. */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
+    @UnsupportedAppUsage(trackingBug = 175939224)
     private RippleState mState;
 
     /** The masking layer, e.g. the layer with id R.id.mask. */
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
index 334900b..766d603 100644
--- a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
+++ b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
@@ -43,6 +43,10 @@
             FrontendCapabilities frontendCap) {
         mId = id;
         mType = type;
+        // if max Frequency is negative, we set it as max value of the Integer.
+        if (maxFrequency < 0) {
+            maxFrequency = Integer.MAX_VALUE;
+        }
         mFrequencyRange = new Range<>(minFrequency, maxFrequency);
         mSymbolRateRange = new Range<>(minSymbolRate, maxSymbolRate);
         mAcquireRange = acquireRange;
diff --git a/media/java/android/mtp/MtpStorageManager.java b/media/java/android/mtp/MtpStorageManager.java
index c0eb5e8..0bede0d 100644
--- a/media/java/android/mtp/MtpStorageManager.java
+++ b/media/java/android/mtp/MtpStorageManager.java
@@ -958,7 +958,7 @@
         MtpObject parent = obj.getParent();
         MtpObject oldObj = parent.getChild(oldName);
         if (!success) {
-            // If the rename failed, we want oldObj to be the original and obj to be the dummy.
+            // If the rename failed, we want oldObj to be the original and obj to be the stand-in.
             // Switch the objects, except for their name and state.
             MtpObject temp = oldObj;
             MtpObjectState oldState = oldObj.getState();
@@ -1034,7 +1034,7 @@
             return generalBeginRemoveObject(obj, MtpOperation.RENAME)
                     && generalBeginCopyObject(newObj, false);
         }
-        // Move obj to new parent, create a dummy object in the old parent.
+        // Move obj to new parent, create a fake object in the old parent.
         MtpObject oldObj = obj.copy(false);
         obj.setParent(newParent);
         oldObj.getParent().addChild(oldObj);
@@ -1063,7 +1063,7 @@
             return generalEndCopyObject(newObj, success, true) && ret;
         }
         if (!success) {
-            // If the rename failed, we want oldObj to be the original and obj to be the dummy.
+            // If the rename failed, we want oldObj to be the original and obj to be the stand-in.
             // Switch the objects, except for their parent and state.
             MtpObject temp = oldObj;
             MtpObjectState oldState = oldObj.getState();
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index e90bb36..5ecb1710 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -300,6 +300,7 @@
                     Settings.Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED,
                     Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
                     Settings.Global.HIDDEN_API_POLICY,
+                    Settings.Global.FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT,
                     Settings.Global.HIDE_ERROR_DIALOGS,
                     Settings.Global.HTTP_PROXY,
                     HYBRID_SYSUI_BATTERY_WARNING_FLAGS,
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 88eef6a..741a680 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -120,6 +120,7 @@
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
     <uses-permission android:name="android.permission.CREATE_USERS" />
     <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" />
+    <uses-permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS" />
     <uses-permission android:name="android.permission.ACCESS_LOWPAN_STATE"/>
     <uses-permission android:name="android.permission.CHANGE_LOWPAN_STATE"/>
     <uses-permission android:name="android.permission.READ_LOWPAN_CREDENTIAL"/>
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 8b73c5e..7b4cf0f 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -2146,8 +2146,10 @@
             name = in.readString();
             initialName = in.readString();
             title = in.readString();
+            shareTitle = in.readString();
             description = in.readString();
             progress.set(in.readInt());
+            lastProgress.set(in.readInt());
             lastUpdate.set(in.readLong());
             formattedLastUpdate = in.readString();
             bugreportFile = readFile(in);
@@ -2158,9 +2160,10 @@
             }
 
             finished.set(in.readInt() == 1);
+            addingDetailsToZip = in.readBoolean();
+            addedDetailsToZip = in.readBoolean();
             screenshotCounter = in.readInt();
             shareDescription = in.readString();
-            shareTitle = in.readString();
             type = in.readInt();
         }
 
@@ -2171,8 +2174,10 @@
             dest.writeString(name);
             dest.writeString(initialName);
             dest.writeString(title);
+            dest.writeString(shareTitle);
             dest.writeString(description);
             dest.writeInt(progress.intValue());
+            dest.writeInt(lastProgress.intValue());
             dest.writeLong(lastUpdate.longValue());
             dest.writeString(getFormattedLastUpdate());
             writeFile(dest, bugreportFile);
@@ -2183,9 +2188,10 @@
             }
 
             dest.writeInt(finished.get() ? 1 : 0);
+            dest.writeBoolean(addingDetailsToZip);
+            dest.writeBoolean(addedDetailsToZip);
             dest.writeInt(screenshotCounter);
             dest.writeString(shareDescription);
-            dest.writeString(shareTitle);
             dest.writeInt(type);
         }
 
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 1378776..397eeb2 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -74,7 +74,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.net.CaptivePortal;
 import android.net.CaptivePortalData;
@@ -90,12 +89,10 @@
 import android.net.IDnsResolver;
 import android.net.IIpConnectivityMetrics;
 import android.net.INetd;
-import android.net.INetdEventCallback;
 import android.net.INetworkManagementEventObserver;
 import android.net.INetworkMonitor;
 import android.net.INetworkMonitorCallbacks;
 import android.net.INetworkPolicyListener;
-import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
 import android.net.ISocketKeepaliveCallback;
 import android.net.InetAddresses;
@@ -133,6 +130,7 @@
 import android.net.Uri;
 import android.net.VpnManager;
 import android.net.VpnService;
+import android.net.metrics.INetdEventListener;
 import android.net.metrics.IpConnectivityLog;
 import android.net.metrics.NetworkEvent;
 import android.net.netlink.InetDiagMessage;
@@ -173,7 +171,6 @@
 import android.util.Pair;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
-import android.util.Xml;
 
 import com.android.connectivity.aidl.INetworkAgent;
 import com.android.internal.R;
@@ -190,7 +187,6 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.LocationPermissionChecker;
 import com.android.internal.util.MessageUtils;
-import com.android.internal.util.XmlUtils;
 import com.android.modules.utils.BasicShellCommandHandler;
 import com.android.net.module.util.LinkPropertiesUtils.CompareOrUpdateResult;
 import com.android.net.module.util.LinkPropertiesUtils.CompareResult;
@@ -211,7 +207,6 @@
 import com.android.server.connectivity.PermissionMonitor;
 import com.android.server.connectivity.ProxyTracker;
 import com.android.server.connectivity.Vpn;
-import com.android.server.net.BaseNetdEventCallback;
 import com.android.server.net.BaseNetworkObserver;
 import com.android.server.net.LockdownVpnTracker;
 import com.android.server.net.NetworkPolicyManagerInternal;
@@ -221,14 +216,7 @@
 
 import libcore.io.IoUtils;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.File;
 import java.io.FileDescriptor;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.Inet4Address;
 import java.net.InetAddress;
@@ -337,7 +325,7 @@
     @VisibleForTesting
     protected INetd mNetd;
     private INetworkStatsService mStatsService;
-    private INetworkPolicyManager mPolicyManager;
+    private NetworkPolicyManager mPolicyManager;
     private NetworkPolicyManagerInternal mPolicyManagerInternal;
 
     /**
@@ -558,6 +546,13 @@
     private static final int EVENT_CAPPORT_DATA_CHANGED = 46;
 
     /**
+     * Used by setRequireVpnForUids.
+     * arg1 = whether the specified UID ranges are required to use a VPN.
+     * obj  = Array of UidRange objects.
+     */
+    private static final int EVENT_SET_REQUIRE_VPN_FOR_UIDS = 47;
+
+    /**
      * Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
      * should be shown.
      */
@@ -946,15 +941,15 @@
     }
 
     public ConnectivityService(Context context, INetworkManagementService netManager,
-            INetworkStatsService statsService, INetworkPolicyManager policyManager) {
-        this(context, netManager, statsService, policyManager, getDnsResolver(context),
-                new IpConnectivityLog(), NetdService.getInstance(), new Dependencies());
+            INetworkStatsService statsService) {
+        this(context, netManager, statsService, getDnsResolver(context), new IpConnectivityLog(),
+                NetdService.getInstance(), new Dependencies());
     }
 
     @VisibleForTesting
     protected ConnectivityService(Context context, INetworkManagementService netManager,
-            INetworkStatsService statsService, INetworkPolicyManager policyManager,
-            IDnsResolver dnsresolver, IpConnectivityLog logger, INetd netd, Dependencies deps) {
+            INetworkStatsService statsService, IDnsResolver dnsresolver, IpConnectivityLog logger,
+            INetd netd, Dependencies deps) {
         if (DBG) log("ConnectivityService starting up");
 
         mDeps = Objects.requireNonNull(deps, "missing Dependencies");
@@ -992,7 +987,7 @@
 
         mNMS = Objects.requireNonNull(netManager, "missing INetworkManagementService");
         mStatsService = Objects.requireNonNull(statsService, "missing INetworkStatsService");
-        mPolicyManager = Objects.requireNonNull(policyManager, "missing INetworkPolicyManager");
+        mPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);
         mPolicyManagerInternal = Objects.requireNonNull(
                 LocalServices.getService(NetworkPolicyManagerInternal.class),
                 "missing NetworkPolicyManagerInternal");
@@ -1008,12 +1003,7 @@
         // To ensure uid rules are synchronized with Network Policy, register for
         // NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService
         // reading existing policy from disk.
-        try {
-            mPolicyManager.registerListener(mPolicyListener);
-        } catch (RemoteException e) {
-            // ouch, no rules updates means some processes may never get network
-            loge("unable to register INetworkPolicyListener" + e);
-        }
+        mPolicyManager.registerListener(mPolicyListener);
 
         final PowerManager powerManager = (PowerManager) context.getSystemService(
                 Context.POWER_SERVICE);
@@ -1290,19 +1280,28 @@
         }
     }
 
-    private Network[] getVpnUnderlyingNetworks(int uid) {
-        synchronized (mVpns) {
-            if (!mLockdownEnabled) {
-                int user = UserHandle.getUserId(uid);
-                Vpn vpn = mVpns.get(user);
-                if (vpn != null && vpn.appliesToUid(uid)) {
-                    return vpn.getUnderlyingNetworks();
+    // TODO: determine what to do when more than one VPN applies to |uid|.
+    private NetworkAgentInfo getVpnForUid(int uid) {
+        synchronized (mNetworkForNetId) {
+            for (int i = 0; i < mNetworkForNetId.size(); i++) {
+                final NetworkAgentInfo nai = mNetworkForNetId.valueAt(i);
+                if (nai.isVPN() && nai.everConnected && nai.networkCapabilities.appliesToUid(uid)) {
+                    return nai;
                 }
             }
         }
         return null;
     }
 
+    private Network[] getVpnUnderlyingNetworks(int uid) {
+        synchronized (mVpns) {
+            if (mLockdownEnabled) return null;
+        }
+        final NetworkAgentInfo nai = getVpnForUid(uid);
+        if (nai != null) return nai.declaredUnderlyingNetworks;
+        return null;
+    }
+
     private NetworkState getUnfilteredActiveNetworkState(int uid) {
         NetworkAgentInfo nai = getDefaultNetwork();
 
@@ -1328,7 +1327,7 @@
     }
 
     /**
-     * Check if UID should be blocked from using the network with the given LinkProperties.
+     * Check if UID should be blocked from using the specified network.
      */
     private boolean isNetworkWithLinkPropertiesBlocked(LinkProperties lp, int uid,
             boolean ignoreBlocked) {
@@ -1336,12 +1335,7 @@
         if (ignoreBlocked) {
             return false;
         }
-        synchronized (mVpns) {
-            final Vpn vpn = mVpns.get(UserHandle.getUserId(uid));
-            if (vpn != null && vpn.getLockdown() && vpn.isBlockingUid(uid)) {
-                return true;
-            }
-        }
+        if (isUidBlockedByVpn(uid, mVpnBlockedUidRanges)) return true;
         final String iface = (lp == null ? "" : lp.getInterfaceName());
         return mPolicyManagerInternal.isUidNetworkingBlocked(uid, iface);
     }
@@ -1567,22 +1561,14 @@
                             nc, mDeps.getCallingUid(), callingPackageName));
         }
 
-        synchronized (mVpns) {
-            if (!mLockdownEnabled) {
-                Vpn vpn = mVpns.get(userId);
-                if (vpn != null) {
-                    Network[] networks = vpn.getUnderlyingNetworks();
-                    if (networks != null) {
-                        for (Network network : networks) {
-                            nc = getNetworkCapabilitiesInternal(network);
-                            if (nc != null) {
-                                result.put(
-                                        network,
-                                        maybeSanitizeLocationInfoForCaller(
-                                                nc, mDeps.getCallingUid(), callingPackageName));
-                            }
-                        }
-                    }
+        // No need to check mLockdownEnabled. If it's true, getVpnUnderlyingNetworks returns null.
+        final Network[] networks = getVpnUnderlyingNetworks(Binder.getCallingUid());
+        if (networks != null) {
+            for (Network network : networks) {
+                nc = getNetworkCapabilitiesInternal(network);
+                if (nc != null) {
+                    result.put(network, maybeSanitizeLocationInfoForCaller(
+                                    nc, mDeps.getCallingUid(), callingPackageName));
                 }
             }
         }
@@ -1920,8 +1906,7 @@
         return true;
     }
 
-    @VisibleForTesting
-    protected final INetdEventCallback mNetdEventCallback = new BaseNetdEventCallback() {
+    private class NetdEventCallback extends INetdEventListener.Stub {
         @Override
         public void onPrivateDnsValidationEvent(int netId, String ipAddress,
                 String hostname, boolean validated) {
@@ -1937,8 +1922,8 @@
         }
 
         @Override
-        public void onDnsEvent(int netId, int eventType, int returnCode, String hostname,
-                String[] ipAddresses, int ipAddressesCount, long timestamp, int uid) {
+        public void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs,
+                String hostname,  String[] ipAddresses, int ipAddressesCount, int uid) {
             NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
             // Netd event only allow registrants from system. Each NetworkMonitor thread is under
             // the caller thread of registerNetworkAgent. Thus, it's not allowed to register netd
@@ -1957,21 +1942,42 @@
                                        String prefixString, int prefixLength) {
             mHandler.post(() -> handleNat64PrefixEvent(netId, added, prefixString, prefixLength));
         }
-    };
 
-    private void registerNetdEventCallback() {
-        final IIpConnectivityMetrics ipConnectivityMetrics = mDeps.getIpConnectivityMetrics();
-        if (ipConnectivityMetrics == null) {
-            Log.wtf(TAG, "Missing IIpConnectivityMetrics");
-            return;
+        @Override
+        public void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port,
+                int uid) {
         }
 
+        @Override
+        public void onWakeupEvent(String prefix, int uid, int ethertype, int ipNextHeader,
+                byte[] dstHw, String srcIp, String dstIp, int srcPort, int dstPort,
+                long timestampNs) {
+        }
+
+        @Override
+        public void onTcpSocketStatsEvent(int[] networkIds, int[] sentPackets, int[] lostPackets,
+                int[] rttsUs, int[] sentAckDiffsMs) {
+        }
+
+        @Override
+        public int getInterfaceVersion() throws RemoteException {
+            return this.VERSION;
+        }
+
+        @Override
+        public String getInterfaceHash() {
+            return this.HASH;
+        }
+    };
+
+    @VisibleForTesting
+    protected final INetdEventListener mNetdEventCallback = new NetdEventCallback();
+
+    private void registerNetdEventCallback() {
         try {
-            ipConnectivityMetrics.addNetdEventCallback(
-                    INetdEventCallback.CALLBACK_CALLER_CONNECTIVITY_SERVICE,
-                    mNetdEventCallback);
+            mDnsResolver.registerEventListener(mNetdEventCallback);
         } catch (Exception e) {
-            loge("Error registering netd callback: " + e);
+            loge("Error registering DnsResolver callback: " + e);
         }
     }
 
@@ -2008,29 +2014,18 @@
     void handleRestrictBackgroundChanged(boolean restrictBackground) {
         if (mRestrictBackground == restrictBackground) return;
 
+        final List<UidRange> blockedRanges = mVpnBlockedUidRanges;
         for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
             final boolean curMetered = nai.networkCapabilities.isMetered();
             maybeNotifyNetworkBlocked(nai, curMetered, curMetered, mRestrictBackground,
-                    restrictBackground);
+                    restrictBackground, blockedRanges, blockedRanges);
         }
 
         mRestrictBackground = restrictBackground;
     }
 
-    private boolean isUidNetworkingWithVpnBlocked(int uid, int uidRules, boolean isNetworkMetered,
+    private boolean isUidBlockedByRules(int uid, int uidRules, boolean isNetworkMetered,
             boolean isBackgroundRestricted) {
-        synchronized (mVpns) {
-            final Vpn vpn = mVpns.get(UserHandle.getUserId(uid));
-            // Because the return value of this function depends on the list of UIDs the
-            // always-on VPN blocks when in lockdown mode, when the always-on VPN changes that
-            // list all state depending on the return value of this function has to be recomputed.
-            // TODO: add a trigger when the always-on VPN sets its blocked UIDs to reevaluate and
-            // send the necessary onBlockedStatusChanged callbacks.
-            if (vpn != null && vpn.getLockdown() && vpn.isBlockingUid(uid)) {
-                return true;
-            }
-        }
-
         return NetworkPolicyManagerInternal.isUidNetworkingBlocked(uid, uidRules,
                 isNetworkMetered, isBackgroundRestricted);
     }
@@ -4305,6 +4300,9 @@
                 case EVENT_DATA_SAVER_CHANGED:
                     handleRestrictBackgroundChanged(toBool(msg.arg1));
                     break;
+                case EVENT_SET_REQUIRE_VPN_FOR_UIDS:
+                    handleSetRequireVpnForUids(toBool(msg.arg1), (UidRange[]) msg.obj);
+                    break;
             }
         }
     }
@@ -4473,8 +4471,7 @@
         if (!nai.everConnected) {
             return;
         }
-        LinkProperties lp = getLinkProperties(nai);
-        if (isNetworkWithLinkPropertiesBlocked(lp, uid, false)) {
+        if (isNetworkWithLinkPropertiesBlocked(nai.linkProperties, uid, false)) {
             return;
         }
         nai.networkMonitor().forceReevaluation(uid);
@@ -4901,6 +4898,56 @@
         }
     }
 
+    private boolean isUidBlockedByVpn(int uid, List<UidRange> blockedUidRanges) {
+        // Determine whether this UID is blocked because of always-on VPN lockdown. If a VPN applies
+        // to the UID, then the UID is not blocked because always-on VPN lockdown applies only when
+        // a VPN is not up.
+        final NetworkAgentInfo vpnNai = getVpnForUid(uid);
+        if (vpnNai != null && !vpnNai.networkAgentConfig.allowBypass) return false;
+        for (UidRange range : blockedUidRanges) {
+            if (range.contains(uid)) return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void setRequireVpnForUids(boolean requireVpn, UidRange[] ranges) {
+        NetworkStack.checkNetworkStackPermission(mContext);
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_REQUIRE_VPN_FOR_UIDS,
+                encodeBool(requireVpn), 0 /* arg2 */, ranges));
+    }
+
+    private void handleSetRequireVpnForUids(boolean requireVpn, UidRange[] ranges) {
+        if (DBG) {
+            Log.d(TAG, "Setting VPN " + (requireVpn ? "" : "not ") + "required for UIDs: "
+                    + Arrays.toString(ranges));
+        }
+        // Cannot use a Set since the list of UID ranges might contain duplicates.
+        final List<UidRange> newVpnBlockedUidRanges = new ArrayList(mVpnBlockedUidRanges);
+        for (int i = 0; i < ranges.length; i++) {
+            if (requireVpn) {
+                newVpnBlockedUidRanges.add(ranges[i]);
+            } else {
+                newVpnBlockedUidRanges.remove(ranges[i]);
+            }
+        }
+
+        try {
+            mNetd.networkRejectNonSecureVpn(requireVpn, toUidRangeStableParcels(ranges));
+        } catch (RemoteException | ServiceSpecificException e) {
+            Log.e(TAG, "setRequireVpnForUids(" + requireVpn + ", "
+                    + Arrays.toString(ranges) + "): netd command failed: " + e);
+        }
+
+        for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
+            final boolean curMetered = nai.networkCapabilities.isMetered();
+            maybeNotifyNetworkBlocked(nai, curMetered, curMetered, mRestrictBackground,
+                    mRestrictBackground, mVpnBlockedUidRanges, newVpnBlockedUidRanges);
+        }
+
+        mVpnBlockedUidRanges = newVpnBlockedUidRanges;
+    }
+
     @Override
     public boolean updateLockdownVpn() {
         if (mDeps.getCallingUid() != Process.SYSTEM_UID) {
@@ -5085,101 +5132,6 @@
     }
 
     @Override
-    public int checkMobileProvisioning(int suggestedTimeOutMs) {
-        // TODO: Remove?  Any reason to trigger a provisioning check?
-        return -1;
-    }
-
-    /** Location to an updatable file listing carrier provisioning urls.
-     *  An example:
-     *
-     * <?xml version="1.0" encoding="utf-8"?>
-     *  <provisioningUrls>
-     *   <provisioningUrl mcc="310" mnc="4">http://myserver.com/foo?mdn=%3$s&amp;iccid=%1$s&amp;imei=%2$s</provisioningUrl>
-     *  </provisioningUrls>
-     */
-    private static final String PROVISIONING_URL_PATH =
-            "/data/misc/radio/provisioning_urls.xml";
-    private final File mProvisioningUrlFile = new File(PROVISIONING_URL_PATH);
-
-    /** XML tag for root element. */
-    private static final String TAG_PROVISIONING_URLS = "provisioningUrls";
-    /** XML tag for individual url */
-    private static final String TAG_PROVISIONING_URL = "provisioningUrl";
-    /** XML attribute for mcc */
-    private static final String ATTR_MCC = "mcc";
-    /** XML attribute for mnc */
-    private static final String ATTR_MNC = "mnc";
-
-    private String getProvisioningUrlBaseFromFile() {
-        XmlPullParser parser;
-        Configuration config = mContext.getResources().getConfiguration();
-
-        try (FileReader fileReader = new FileReader(mProvisioningUrlFile)) {
-            parser = Xml.newPullParser();
-            parser.setInput(fileReader);
-            XmlUtils.beginDocument(parser, TAG_PROVISIONING_URLS);
-
-            while (true) {
-                XmlUtils.nextElement(parser);
-
-                String element = parser.getName();
-                if (element == null) break;
-
-                if (element.equals(TAG_PROVISIONING_URL)) {
-                    String mcc = parser.getAttributeValue(null, ATTR_MCC);
-                    try {
-                        if (mcc != null && Integer.parseInt(mcc) == config.mcc) {
-                            String mnc = parser.getAttributeValue(null, ATTR_MNC);
-                            if (mnc != null && Integer.parseInt(mnc) == config.mnc) {
-                                parser.next();
-                                if (parser.getEventType() == XmlPullParser.TEXT) {
-                                    return parser.getText();
-                                }
-                            }
-                        }
-                    } catch (NumberFormatException e) {
-                        loge("NumberFormatException in getProvisioningUrlBaseFromFile: " + e);
-                    }
-                }
-            }
-            return null;
-        } catch (FileNotFoundException e) {
-            loge("Carrier Provisioning Urls file not found");
-        } catch (XmlPullParserException e) {
-            loge("Xml parser exception reading Carrier Provisioning Urls file: " + e);
-        } catch (IOException e) {
-            loge("I/O exception reading Carrier Provisioning Urls file: " + e);
-        }
-        return null;
-    }
-
-    @Override
-    public String getMobileProvisioningUrl() {
-        enforceSettingsPermission();
-        String url = getProvisioningUrlBaseFromFile();
-        if (TextUtils.isEmpty(url)) {
-            url = mContext.getResources().getString(R.string.mobile_provisioning_url);
-            log("getMobileProvisioningUrl: mobile_provisioining_url from resource =" + url);
-        } else {
-            log("getMobileProvisioningUrl: mobile_provisioning_url from File =" + url);
-        }
-        // populate the iccid, imei and phone number in the provisioning url.
-        if (!TextUtils.isEmpty(url)) {
-            String phoneNumber = mTelephonyManager.getLine1Number();
-            if (TextUtils.isEmpty(phoneNumber)) {
-                phoneNumber = "0000000000";
-            }
-            url = String.format(url,
-                    mTelephonyManager.getSimSerialNumber() /* ICCID */,
-                    mTelephonyManager.getDeviceId() /* IMEI */,
-                    phoneNumber /* Phone number */);
-        }
-
-        return url;
-    }
-
-    @Override
     public void setProvisioningNotificationVisible(boolean visible, int networkType,
             String action) {
         enforceSettingsPermission();
@@ -5981,6 +5933,12 @@
     // NOTE: Only should be accessed on ConnectivityServiceThread, except dump().
     private final ArraySet<NetworkAgentInfo> mNetworkAgentInfos = new ArraySet<>();
 
+    // UID ranges for users that are currently blocked by VPNs.
+    // This array is accessed and iterated on multiple threads without holding locks, so its
+    // contents must never be mutated. When the ranges change, the array is replaced with a new one
+    // (on the handler thread).
+    private volatile List<UidRange> mVpnBlockedUidRanges = new ArrayList<>();
+
     @GuardedBy("mBlockedAppUids")
     private final HashSet<Integer> mBlockedAppUids = new HashSet<>();
 
@@ -6635,7 +6593,7 @@
 
             if (meteredChanged) {
                 maybeNotifyNetworkBlocked(nai, oldMetered, newMetered, mRestrictBackground,
-                        mRestrictBackground);
+                        mRestrictBackground, mVpnBlockedUidRanges, mVpnBlockedUidRanges);
             }
 
             final boolean roamingChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING) !=
@@ -6700,6 +6658,15 @@
         return stableRanges;
     }
 
+    private static UidRangeParcel[] toUidRangeStableParcels(UidRange[] ranges) {
+        final UidRangeParcel[] stableRanges = new UidRangeParcel[ranges.length];
+        for (int i = 0; i < ranges.length; i++) {
+            stableRanges[i] = new UidRangeParcel(ranges[i].start, ranges[i].stop);
+        }
+        return stableRanges;
+    }
+
+
     private void updateUids(NetworkAgentInfo nai, NetworkCapabilities prevNc,
             NetworkCapabilities newNc) {
         Set<UidRange> prevRanges = null == prevNc ? null : prevNc.getUids();
@@ -7527,7 +7494,9 @@
         }
 
         final boolean metered = nai.networkCapabilities.isMetered();
-        final boolean blocked = isUidNetworkingWithVpnBlocked(nri.mUid, mUidRules.get(nri.mUid),
+        boolean blocked;
+        blocked = isUidBlockedByVpn(nri.mUid, mVpnBlockedUidRanges);
+        blocked |= isUidBlockedByRules(nri.mUid, mUidRules.get(nri.mUid),
                 metered, mRestrictBackground);
         callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, blocked ? 1 : 0);
     }
@@ -7549,21 +7518,25 @@
      * @param newRestrictBackground True if data saver is enabled.
      */
     private void maybeNotifyNetworkBlocked(NetworkAgentInfo nai, boolean oldMetered,
-            boolean newMetered, boolean oldRestrictBackground, boolean newRestrictBackground) {
+            boolean newMetered, boolean oldRestrictBackground, boolean newRestrictBackground,
+            List<UidRange> oldBlockedUidRanges, List<UidRange> newBlockedUidRanges) {
 
         for (int i = 0; i < nai.numNetworkRequests(); i++) {
             NetworkRequest nr = nai.requestAt(i);
             NetworkRequestInfo nri = mNetworkRequests.get(nr);
             final int uidRules = mUidRules.get(nri.mUid);
-            final boolean oldBlocked, newBlocked;
-            // mVpns lock needs to be hold here to ensure that the active VPN cannot be changed
-            // between these two calls.
-            synchronized (mVpns) {
-                oldBlocked = isUidNetworkingWithVpnBlocked(nri.mUid, uidRules, oldMetered,
-                        oldRestrictBackground);
-                newBlocked = isUidNetworkingWithVpnBlocked(nri.mUid, uidRules, newMetered,
-                        newRestrictBackground);
-            }
+            final boolean oldBlocked, newBlocked, oldVpnBlocked, newVpnBlocked;
+
+            oldVpnBlocked = isUidBlockedByVpn(nri.mUid, oldBlockedUidRanges);
+            newVpnBlocked = (oldBlockedUidRanges != newBlockedUidRanges)
+                    ? isUidBlockedByVpn(nri.mUid, newBlockedUidRanges)
+                    : oldVpnBlocked;
+
+            oldBlocked = oldVpnBlocked || isUidBlockedByRules(nri.mUid, uidRules, oldMetered,
+                    oldRestrictBackground);
+            newBlocked = newVpnBlocked || isUidBlockedByRules(nri.mUid, uidRules, newMetered,
+                    newRestrictBackground);
+
             if (oldBlocked != newBlocked) {
                 callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED,
                         encodeBool(newBlocked));
@@ -7579,17 +7552,12 @@
     private void maybeNotifyNetworkBlockedForNewUidRules(int uid, int newRules) {
         for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
             final boolean metered = nai.networkCapabilities.isMetered();
+            final boolean vpnBlocked = isUidBlockedByVpn(uid, mVpnBlockedUidRanges);
             final boolean oldBlocked, newBlocked;
-            // TODO: Consider that doze mode or turn on/off battery saver would deliver lots of uid
-            // rules changed event. And this function actually loop through all connected nai and
-            // its requests. It seems that mVpns lock will be grabbed frequently in this case.
-            // Reduce the number of locking or optimize the use of lock are likely needed in future.
-            synchronized (mVpns) {
-                oldBlocked = isUidNetworkingWithVpnBlocked(
-                        uid, mUidRules.get(uid), metered, mRestrictBackground);
-                newBlocked = isUidNetworkingWithVpnBlocked(
-                        uid, newRules, metered, mRestrictBackground);
-            }
+            oldBlocked = vpnBlocked || isUidBlockedByRules(
+                    uid, mUidRules.get(uid), metered, mRestrictBackground);
+            newBlocked = vpnBlocked || isUidBlockedByRules(
+                    uid, newRules, metered, mRestrictBackground);
             if (oldBlocked == newBlocked) {
                 continue;
             }
diff --git a/services/core/java/com/android/server/ConnectivityServiceInitializer.java b/services/core/java/com/android/server/ConnectivityServiceInitializer.java
index 2bc8925..f701688 100644
--- a/services/core/java/com/android/server/ConnectivityServiceInitializer.java
+++ b/services/core/java/com/android/server/ConnectivityServiceInitializer.java
@@ -20,7 +20,6 @@
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
 
 import android.content.Context;
-import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
 import android.os.INetworkManagementService;
 import android.os.ServiceManager;
@@ -38,7 +37,7 @@
         super(context);
         // TODO: Define formal APIs to get the needed services.
         mConnectivity = new ConnectivityService(context, getNetworkManagementService(),
-                getNetworkStatsService(), getNetworkPolicyManager());
+                getNetworkStatsService());
     }
 
     @Override
@@ -57,10 +56,4 @@
         return INetworkStatsService.Stub.asInterface(
                 ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
     }
-
-    private INetworkPolicyManager getNetworkPolicyManager() {
-        return INetworkPolicyManager.Stub.asInterface(
-                ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
-    }
-
 }
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index bf53f28..f6b72d6 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -1,5 +1,5 @@
 # Connectivity / Networking
-per-file ConnectivityService.java,NetworkManagementService.java,NsdService.java = codewiz@google.com, ek@google.com, jchalard@google.com, junyulai@google.com, lorenzo@google.com, reminv@google.com, satk@google.com
+per-file ConnectivityService.java,ConnectivityServiceInitializer.java,NetworkManagementService.java,NsdService.java = file:/services/core/java/com/android/server/net/OWNERS
 
 # Vibrator / Threads
 per-file VibratorService.java, DisplayThread.java = michaelwr@google.com
@@ -25,7 +25,6 @@
 per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS
 per-file *Storage* = file:/core/java/android/os/storage/OWNERS
 per-file *TimeUpdate* = file:/core/java/android/app/timezone/OWNERS
-per-file ConnectivityService.java = file:/services/core/java/com/android/server/net/OWNERS
 per-file DynamicSystemService.java = file:/packages/DynamicSystemInstallationService/OWNERS
 per-file GestureLauncherService.java = file:platform/packages/apps/EmergencyInfo:/OWNERS
 per-file IpSecService.java = file:/services/core/java/com/android/server/net/OWNERS
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index eb1123e..5f6e8df 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -304,7 +304,7 @@
 
     private final LocalLog mLocalLog = new LocalLog(200);
 
-    private final LocalLog mListenLog = new LocalLog(00);
+    private final LocalLog mListenLog = new LocalLog(200);
 
     /**
      * Per-phone map of precise data connection state. The key of the map is the pair of transport
@@ -2316,7 +2316,9 @@
             pw.println("local logs:");
             pw.increaseIndent();
             mLocalLog.dump(fd, pw, args);
+            pw.decreaseIndent();
             pw.println("listen logs:");
+            pw.increaseIndent();
             mListenLog.dump(fd, pw, args);
             pw.decreaseIndent();
             pw.println("registrations: count=" + recordCount);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7e67f97..928ddab 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -9481,6 +9481,7 @@
         final long waitForNetworkTimeoutMs = Settings.Global.getLong(resolver,
                 NETWORK_ACCESS_TIMEOUT_MS, NETWORK_ACCESS_TIMEOUT_DEFAULT_MS);
         mHiddenApiBlacklist.registerObserver();
+        mPlatformCompat.registerContentObserver();
 
         final long pssDeferralMs = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                 ACTIVITY_START_PSS_DEFER_CONFIG, 0L);
diff --git a/services/core/java/com/android/server/compat/CompatChange.java b/services/core/java/com/android/server/compat/CompatChange.java
index a8aa9aa..c4ff99b 100644
--- a/services/core/java/com/android/server/compat/CompatChange.java
+++ b/services/core/java/com/android/server/compat/CompatChange.java
@@ -61,6 +61,7 @@
     ChangeListener mListener = null;
 
     private Map<String, Boolean> mPackageOverrides;
+    private Map<String, Boolean> mDeferredOverrides;
 
     public CompatChange(long changeId) {
         this(changeId, null, -1, -1, false, false, null);
@@ -121,6 +122,56 @@
     }
 
     /**
+     * Tentatively set the state of this change for a given package name.
+     * The override will only take effect after that package is installed, if applicable.
+     *
+     * <p>Note, this method is not thread safe so callers must ensure thread safety.
+     *
+     * @param packageName Package name to tentatively enable the change for.
+     * @param enabled Whether or not to enable the change.
+     */
+    void addPackageDeferredOverride(String packageName, boolean enabled) {
+        if (getLoggingOnly()) {
+            throw new IllegalArgumentException(
+                    "Can't add overrides for a logging only change " + toString());
+        }
+        if (mDeferredOverrides == null) {
+            mDeferredOverrides = new HashMap<>();
+        }
+        mDeferredOverrides.put(packageName, enabled);
+    }
+
+    /**
+     * Rechecks an existing (and possibly deferred) override.
+     *
+     * <p>For deferred overrides, check if they can be promoted to a regular override. For regular
+     * overrides, check if they need to be demoted to deferred.</p>
+     *
+     * @param packageName Package name to apply deferred overrides for.
+     * @param allowed Whether the override is allowed.
+     *
+     * @return {@code true} if the recheck yielded a result that requires invalidating caches
+     *         (a deferred override was consolidated or a regular override was removed).
+     */
+    boolean recheckOverride(String packageName, boolean allowed) {
+        // A deferred override now is allowed by the policy, so promote it to a regular override.
+        if (hasDeferredOverride(packageName) && allowed) {
+            boolean overrideValue = mDeferredOverrides.remove(packageName);
+            addPackageOverride(packageName, overrideValue);
+            return true;
+        }
+        // A previously set override is no longer allowed by the policy, so make it deferred.
+        if (hasOverride(packageName) && !allowed) {
+            boolean overrideValue = mPackageOverrides.remove(packageName);
+            addPackageDeferredOverride(packageName, overrideValue);
+            // Notify because the override was removed.
+            notifyListener(packageName);
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Remove any package override for the given package name, restoring the default behaviour.
      *
      * <p>Note, this method is not thread safe so callers must ensure thread safety.
@@ -133,6 +184,9 @@
                 notifyListener(pname);
             }
         }
+        if (mDeferredOverrides != null) {
+            mDeferredOverrides.remove(pname);
+        }
     }
 
     /**
@@ -176,6 +230,15 @@
         return mPackageOverrides != null && mPackageOverrides.containsKey(packageName);
     }
 
+    /**
+     * Checks whether a change has a deferred override for a package.
+     * @param packageName name of the package
+     * @return true if there is such a deferred override
+     */
+    boolean hasDeferredOverride(String packageName) {
+        return mDeferredOverrides != null && mDeferredOverrides.containsKey(packageName);
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder("ChangeId(")
@@ -195,6 +258,9 @@
         if (mPackageOverrides != null && mPackageOverrides.size() > 0) {
             sb.append("; packageOverrides=").append(mPackageOverrides);
         }
+        if (mDeferredOverrides != null && mDeferredOverrides.size() > 0) {
+            sb.append("; deferredOverrides=").append(mDeferredOverrides);
+        }
         return sb.append(")").toString();
     }
 
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 8511118..f03a608 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -65,7 +65,7 @@
     @GuardedBy("mChanges")
     private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>();
 
-    private IOverrideValidator mOverrideValidator;
+    private OverrideValidatorImpl mOverrideValidator;
 
     @VisibleForTesting
     CompatConfig(AndroidBuildClassifier androidBuildClassifier, Context context) {
@@ -161,7 +161,7 @@
      * @return {@code true} if the change existed before adding the override.
      */
     boolean addOverride(long changeId, String packageName, boolean enabled)
-            throws RemoteException, SecurityException {
+            throws SecurityException {
         boolean alreadyKnown = true;
         OverrideAllowedState allowedState =
                 mOverrideValidator.getOverrideAllowedState(changeId, packageName);
@@ -173,7 +173,17 @@
                 c = new CompatChange(changeId);
                 addChange(c);
             }
-            c.addPackageOverride(packageName, enabled);
+            switch (allowedState.state) {
+                case OverrideAllowedState.ALLOWED:
+                    c.addPackageOverride(packageName, enabled);
+                    break;
+                case OverrideAllowedState.DEFERRED_VERIFICATION:
+                    c.addPackageDeferredOverride(packageName, enabled);
+                    break;
+                default:
+                    throw new IllegalStateException("Should only be able to override changes that "
+                                                    + "are allowed or can be deferred.");
+            }
             invalidateCache();
         }
         return alreadyKnown;
@@ -244,26 +254,26 @@
      * @return {@code true} if an override existed;
      */
     boolean removeOverride(long changeId, String packageName)
-            throws RemoteException, SecurityException {
+            throws SecurityException {
         boolean overrideExists = false;
         synchronized (mChanges) {
             CompatChange c = mChanges.get(changeId);
-            try {
-                if (c != null) {
-                    overrideExists = c.hasOverride(packageName);
-                    if (overrideExists) {
-                        OverrideAllowedState allowedState =
-                                mOverrideValidator.getOverrideAllowedState(changeId, packageName);
-                        allowedState.enforce(changeId, packageName);
-                        c.removePackageOverride(packageName);
-                    }
+            if (c != null) {
+                // Always allow removing a deferred override.
+                if (c.hasDeferredOverride(packageName)) {
+                    c.removePackageOverride(packageName);
+                    overrideExists = true;
+                } else if (c.hasOverride(packageName)) {
+                    // Regular overrides need to pass the policy.
+                    overrideExists = true;
+                    OverrideAllowedState allowedState =
+                            mOverrideValidator.getOverrideAllowedState(changeId, packageName);
+                    allowedState.enforce(changeId, packageName);
+                    c.removePackageOverride(packageName);
                 }
-            } catch (RemoteException e) {
-                // Should never occur, since validator is in the same process.
-                throw new RuntimeException("Unable to call override validator!", e);
             }
-            invalidateCache();
         }
+        invalidateCache();
         return overrideExists;
     }
 
@@ -293,29 +303,15 @@
      * Removes all overrides previously added via {@link #addOverride(long, String, boolean)} or
      * {@link #addOverrides(CompatibilityChangeConfig, String)} for a certain package.
      *
-     * <p>This restores the default behaviour for the given change and app, once any app
-     * processes have been restarted.
+     * <p>This restores the default behaviour for the given app.
      *
      * @param packageName The package for which the overrides should be purged.
      */
-    void removePackageOverrides(String packageName) throws RemoteException, SecurityException {
+    void removePackageOverrides(String packageName) throws SecurityException {
         synchronized (mChanges) {
             for (int i = 0; i < mChanges.size(); ++i) {
-                try {
-                    CompatChange change = mChanges.valueAt(i);
-                    if (change.hasOverride(packageName)) {
-                        OverrideAllowedState allowedState =
-                                mOverrideValidator.getOverrideAllowedState(change.getId(),
-                                        packageName);
-                        allowedState.enforce(change.getId(), packageName);
-                        if (change != null) {
-                            mChanges.valueAt(i).removePackageOverride(packageName);
-                        }
-                    }
-                } catch (RemoteException e) {
-                    // Should never occur, since validator is in the same process.
-                    throw new RuntimeException("Unable to call override validator!", e);
-                }
+                CompatChange change = mChanges.valueAt(i);
+                removeOverride(change.getId(), packageName);
             }
             invalidateCache();
         }
@@ -327,20 +323,15 @@
         LongArray allowed = new LongArray();
         synchronized (mChanges) {
             for (int i = 0; i < mChanges.size(); ++i) {
-                try {
-                    CompatChange change = mChanges.valueAt(i);
-                    if (change.getEnableSinceTargetSdk() != targetSdkVersion) {
-                        continue;
-                    }
-                    OverrideAllowedState allowedState =
-                            mOverrideValidator.getOverrideAllowedState(change.getId(),
-                                                                       packageName);
-                    if (allowedState.state == OverrideAllowedState.ALLOWED) {
-                        allowed.add(change.getId());
-                    }
-                } catch (RemoteException e) {
-                    // Should never occur, since validator is in the same process.
-                    throw new RuntimeException("Unable to call override validator!", e);
+                CompatChange change = mChanges.valueAt(i);
+                if (change.getEnableSinceTargetSdk() != targetSdkVersion) {
+                    continue;
+                }
+                OverrideAllowedState allowedState =
+                        mOverrideValidator.getOverrideAllowedState(change.getId(),
+                                                                    packageName);
+                if (allowedState.state == OverrideAllowedState.ALLOWED) {
+                    allowed.add(change.getId());
                 }
             }
         }
@@ -401,6 +392,11 @@
     }
 
     @VisibleForTesting
+    void forceNonDebuggableFinalForTest(boolean value) {
+        mOverrideValidator.forceNonDebuggableFinalForTest(value);
+    }
+
+    @VisibleForTesting
     void clearChanges() {
         synchronized (mChanges) {
             mChanges.clear();
@@ -511,4 +507,26 @@
     private void invalidateCache() {
         ChangeIdStateCache.invalidate();
     }
+    /**
+     * Rechecks all the existing overrides for a package.
+     */
+    void recheckOverrides(String packageName) {
+        synchronized (mChanges) {
+            boolean shouldInvalidateCache = false;
+            for (int idx = 0; idx < mChanges.size(); ++idx) {
+                CompatChange c = mChanges.valueAt(idx);
+                OverrideAllowedState allowedState =
+                        mOverrideValidator.getOverrideAllowedState(c.getId(), packageName);
+                boolean allowedOverride = (allowedState.state == OverrideAllowedState.ALLOWED);
+                shouldInvalidateCache |= c.recheckOverride(packageName, allowedOverride);
+            }
+            if (shouldInvalidateCache) {
+                invalidateCache();
+            }
+        }
+    }
+
+    void registerContentObserver() {
+        mOverrideValidator.registerContentObserver();
+    }
 }
diff --git a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
index 79a13ca..fe5b4a9 100644
--- a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
+++ b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
@@ -17,16 +17,19 @@
 package com.android.server.compat;
 
 import static com.android.internal.compat.OverrideAllowedState.ALLOWED;
+import static com.android.internal.compat.OverrideAllowedState.DEFERRED_VERIFICATION;
 import static com.android.internal.compat.OverrideAllowedState.DISABLED_NON_TARGET_SDK;
 import static com.android.internal.compat.OverrideAllowedState.DISABLED_NOT_DEBUGGABLE;
 import static com.android.internal.compat.OverrideAllowedState.DISABLED_TARGET_SDK_TOO_HIGH;
 import static com.android.internal.compat.OverrideAllowedState.LOGGING_ONLY_CHANGE;
-import static com.android.internal.compat.OverrideAllowedState.PACKAGE_DOES_NOT_EXIST;
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.provider.Settings;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.compat.AndroidBuildClassifier;
@@ -41,6 +44,20 @@
     private AndroidBuildClassifier mAndroidBuildClassifier;
     private Context mContext;
     private CompatConfig mCompatConfig;
+    private boolean mForceNonDebuggableFinalBuild;
+
+    private class SettingsObserver extends ContentObserver {
+        SettingsObserver() {
+            super(new Handler());
+        }
+        @Override
+        public void onChange(boolean selfChange) {
+            mForceNonDebuggableFinalBuild = Settings.Global.getInt(
+                mContext.getContentResolver(),
+                Settings.Global.FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT,
+                0) == 1;
+        }
+    }
 
     @VisibleForTesting
     OverrideValidatorImpl(AndroidBuildClassifier androidBuildClassifier,
@@ -48,6 +65,7 @@
         mAndroidBuildClassifier = androidBuildClassifier;
         mContext = context;
         mCompatConfig = config;
+        mForceNonDebuggableFinalBuild = false;
     }
 
     @Override
@@ -56,8 +74,10 @@
             return new OverrideAllowedState(LOGGING_ONLY_CHANGE, -1, -1);
         }
 
-        boolean debuggableBuild = mAndroidBuildClassifier.isDebuggableBuild();
-        boolean finalBuild = mAndroidBuildClassifier.isFinalBuild();
+        boolean debuggableBuild = mAndroidBuildClassifier.isDebuggableBuild()
+                                    && !mForceNonDebuggableFinalBuild;
+        boolean finalBuild = mAndroidBuildClassifier.isFinalBuild()
+                                || mForceNonDebuggableFinalBuild;
         int maxTargetSdk = mCompatConfig.maxTargetSdkForChangeIdOptIn(changeId);
         boolean disabled = mCompatConfig.isDisabled(changeId);
 
@@ -73,7 +93,7 @@
         try {
             applicationInfo = packageManager.getApplicationInfo(packageName, 0);
         } catch (NameNotFoundException e) {
-            return new OverrideAllowedState(PACKAGE_DOES_NOT_EXIST, -1, -1);
+            return new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1);
         }
         int appTargetSdk = applicationInfo.targetSdkVersion;
         // Only allow overriding debuggable apps.
@@ -94,4 +114,17 @@
         }
         return new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, appTargetSdk, maxTargetSdk);
     }
+
+    void registerContentObserver() {
+        mContext.getContentResolver().registerContentObserver(
+                Settings.Global.getUriFor(
+                    Settings.Global.FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT),
+                false,
+                new SettingsObserver());
+    }
+
+    void forceNonDebuggableFinalForTest(boolean value) {
+        mForceNonDebuggableFinalBuild = value;
+    }
+
 }
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index aa85f7f..283dba7 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -25,9 +25,13 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.IActivityManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManagerInternal;
+import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
 import android.os.RemoteException;
@@ -74,6 +78,7 @@
         mChangeReporter = new ChangeReporter(
                 ChangeReporter.SOURCE_SYSTEM_SERVER);
         mCompatConfig = compatConfig;
+        registerPackageReceiver(context);
     }
 
     @Override
@@ -389,4 +394,42 @@
         }
         return true;
     }
+
+    /**
+     * Registers a broadcast receiver that listens for package install, replace or remove.
+     * @param context the context where the receiver should be registered.
+     */
+    public void registerPackageReceiver(Context context) {
+        final BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (intent == null) {
+                    return;
+                }
+                final Uri packageData = intent.getData();
+                if (packageData == null) {
+                    return;
+                }
+                final String packageName = packageData.getSchemeSpecificPart();
+                if (packageName == null) {
+                    return;
+                }
+                mCompatConfig.recheckOverrides(packageName);
+            }
+        };
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addDataScheme("package");
+        context.registerReceiver(receiver, filter);
+    }
+
+    /**
+     * Register the observer for
+     * {@link android.provider.Settings.Global#FORCE_NON_DEBUGGABLE_FINAL_BUILD_FOR_COMPAT}
+     */
+    public void registerContentObserver() {
+        mCompatConfig.registerContentObserver();
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 841c970..7bde4d5 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -146,7 +146,11 @@
     // Underlying networks declared by the agent. Only set if supportsUnderlyingNetworks is true.
     // The networks in this list might be declared by a VPN app using setUnderlyingNetworks and are
     // not guaranteed to be current or correct, or even to exist.
-    public @Nullable Network[] declaredUnderlyingNetworks;
+    //
+    // This array is read and iterated on multiple threads with no locking so its contents must
+    // never be modified. When the list of networks changes, replace with a new array, on the
+    // handler thread.
+    public @Nullable volatile Network[] declaredUnderlyingNetworks;
 
     // The capabilities originally announced by the NetworkAgent, regardless of any capabilities
     // that were added or removed due to this network's underlying networks.
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 228ad588..07a4b89 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -101,6 +101,7 @@
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Range;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -223,7 +224,7 @@
     private @NonNull List<String> mLockdownAllowlist = Collections.emptyList();
 
      /**
-     * A memory of what UIDs this class told netd to block for the lockdown feature.
+     * A memory of what UIDs this class told ConnectivityService to block for the lockdown feature.
      *
      * Netd maintains ranges of UIDs for which network should be restricted to using only the VPN
      * for the lockdown feature. This class manages these UIDs and sends this information to netd.
@@ -237,7 +238,7 @@
      * @see mLockdown
      */
     @GuardedBy("this")
-    private final Set<UidRangeParcel> mBlockedUidsAsToldToNetd = new ArraySet<>();
+    private final Set<UidRangeParcel> mBlockedUidsAsToldToConnectivity = new ArraySet<>();
 
     // The user id of initiating VPN.
     private final int mUserId;
@@ -1588,7 +1589,7 @@
      *                {@link Vpn} goes through a VPN connection or is blocked until one is
      *                available, {@code false} to lift the requirement.
      *
-     * @see #mBlockedUidsAsToldToNetd
+     * @see #mBlockedUidsAsToldToConnectivity
      */
     @GuardedBy("this")
     private void setVpnForcedLocked(boolean enforce) {
@@ -1599,10 +1600,8 @@
             exemptedPackages = new ArrayList<>(mLockdownAllowlist);
             exemptedPackages.add(mPackage);
         }
-        final Set<UidRangeParcel> rangesToTellNetdToRemove =
-                new ArraySet<>(mBlockedUidsAsToldToNetd);
-
-        final Set<UidRangeParcel> rangesToTellNetdToAdd;
+        final Set<UidRangeParcel> rangesToRemove = new ArraySet<>(mBlockedUidsAsToldToConnectivity);
+        final Set<UidRangeParcel> rangesToAdd;
         if (enforce) {
             final Set<UidRange> restrictedProfilesRanges =
                     createUserAndRestrictedProfilesRanges(mUserId,
@@ -1621,26 +1620,27 @@
                 }
             }
 
-            rangesToTellNetdToRemove.removeAll(rangesThatShouldBeBlocked);
-            rangesToTellNetdToAdd = rangesThatShouldBeBlocked;
-            // The ranges to tell netd to add are the ones that should be blocked minus the
-            // ones it already knows to block. Note that this will change the contents of
+            rangesToRemove.removeAll(rangesThatShouldBeBlocked);
+            rangesToAdd = rangesThatShouldBeBlocked;
+            // The ranges to tell ConnectivityService to add are the ones that should be blocked
+            // minus the ones it already knows to block. Note that this will change the contents of
             // rangesThatShouldBeBlocked, but the list of ranges that should be blocked is
             // not used after this so it's fine to destroy it.
-            rangesToTellNetdToAdd.removeAll(mBlockedUidsAsToldToNetd);
+            rangesToAdd.removeAll(mBlockedUidsAsToldToConnectivity);
         } else {
-            rangesToTellNetdToAdd = Collections.emptySet();
+            rangesToAdd = Collections.emptySet();
         }
 
         // If mBlockedUidsAsToldToNetd used to be empty, this will always be a no-op.
-        setAllowOnlyVpnForUids(false, rangesToTellNetdToRemove);
+        setAllowOnlyVpnForUids(false, rangesToRemove);
         // If nothing should be blocked now, this will now be a no-op.
-        setAllowOnlyVpnForUids(true, rangesToTellNetdToAdd);
+        setAllowOnlyVpnForUids(true, rangesToAdd);
     }
 
     /**
-     * Tell netd to add or remove a list of {@link UidRange}s to the list of UIDs that are only
-     * allowed to make connections through sockets that have had {@code protect()} called on them.
+     * Tell ConnectivityService to add or remove a list of {@link UidRange}s to the list of UIDs
+     * that are only allowed to make connections through sockets that have had {@code protect()}
+     * called on them.
      *
      * @param enforce {@code true} to add to the denylist, {@code false} to remove.
      * @param ranges {@link Collection} of {@link UidRange}s to add (if {@param enforce} is
@@ -1653,18 +1653,22 @@
         if (ranges.size() == 0) {
             return true;
         }
-        final UidRangeParcel[] stableRanges = ranges.toArray(new UidRangeParcel[ranges.size()]);
+        // Convert to Collection<Range> which is what the ConnectivityManager API takes.
+        ArrayList<Range<Integer>> integerRanges = new ArrayList<>(ranges.size());
+        for (UidRangeParcel uidRange : ranges) {
+            integerRanges.add(new Range<>(uidRange.start, uidRange.stop));
+        }
         try {
-            mNetd.networkRejectNonSecureVpn(enforce, stableRanges);
-        } catch (RemoteException | RuntimeException e) {
+            mConnectivityManager.setRequireVpnForUids(enforce, integerRanges);
+        } catch (RuntimeException e) {
             Log.e(TAG, "Updating blocked=" + enforce
                     + " for UIDs " + Arrays.toString(ranges.toArray()) + " failed", e);
             return false;
         }
         if (enforce) {
-            mBlockedUidsAsToldToNetd.addAll(ranges);
+            mBlockedUidsAsToldToConnectivity.addAll(ranges);
         } else {
-            mBlockedUidsAsToldToNetd.removeAll(ranges);
+            mBlockedUidsAsToldToConnectivity.removeAll(ranges);
         }
         return true;
     }
@@ -1783,9 +1787,6 @@
 
     /**
      * Updates underlying network set.
-     *
-     * <p>Note: Does not updates capabilities. Call {@link #updateCapabilities} from
-     * ConnectivityService thread to get updated capabilities.
      */
     public synchronized boolean setUnderlyingNetworks(Network[] networks) {
         if (!isCallerEstablishedOwnerLocked()) {
@@ -1808,13 +1809,6 @@
         return true;
     }
 
-    public synchronized Network[] getUnderlyingNetworks() {
-        if (!isRunningLocked()) {
-            return null;
-        }
-        return mConfig.underlyingNetworks;
-    }
-
     /**
      * This method should only be called by ConnectivityService because it doesn't
      * have enough data to fill VpnInfo.primaryUnderlyingIface field.
@@ -1864,13 +1858,13 @@
      * the {@code uid}.
      *
      * @apiNote This method don't check VPN lockdown status.
-     * @see #mBlockedUidsAsToldToNetd
+     * @see #mBlockedUidsAsToldToConnectivity
      */
     public synchronized boolean isBlockingUid(int uid) {
         if (mNetworkInfo.isConnected()) {
             return !appliesToUid(uid);
         } else {
-            return containsUid(mBlockedUidsAsToldToNetd, uid);
+            return containsUid(mBlockedUidsAsToldToConnectivity, uid);
         }
     }
 
diff --git a/services/core/java/com/android/server/net/NetworkIdentitySet.java b/services/core/java/com/android/server/net/NetworkIdentitySet.java
index 2326ad3..bce8069 100644
--- a/services/core/java/com/android/server/net/NetworkIdentitySet.java
+++ b/services/core/java/com/android/server/net/NetworkIdentitySet.java
@@ -20,8 +20,8 @@
 import android.service.NetworkIdentitySetProto;
 import android.util.proto.ProtoOutputStream;
 
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
+import java.io.DataInput;
+import java.io.DataOutput;
 import java.io.IOException;
 import java.util.HashSet;
 
@@ -44,7 +44,7 @@
     public NetworkIdentitySet() {
     }
 
-    public NetworkIdentitySet(DataInputStream in) throws IOException {
+    public NetworkIdentitySet(DataInput in) throws IOException {
         final int version = in.readInt();
         final int size = in.readInt();
         for (int i = 0; i < size; i++) {
@@ -89,7 +89,7 @@
         }
     }
 
-    public void writeToStream(DataOutputStream out) throws IOException {
+    public void writeToStream(DataOutput out) throws IOException {
         out.writeInt(VERSION_ADD_DEFAULT_NETWORK);
         out.writeInt(size());
         for (NetworkIdentity ident : this) {
@@ -143,7 +143,7 @@
         return true;
     }
 
-    private static void writeOptionalString(DataOutputStream out, String value) throws IOException {
+    private static void writeOptionalString(DataOutput out, String value) throws IOException {
         if (value != null) {
             out.writeByte(1);
             out.writeUTF(value);
@@ -152,7 +152,7 @@
         }
     }
 
-    private static String readOptionalString(DataInputStream in) throws IOException {
+    private static String readOptionalString(DataInput in) throws IOException {
         if (in.readByte() != 0) {
             return in.readUTF();
         } else {
diff --git a/services/core/java/com/android/server/net/NetworkStatsCollection.java b/services/core/java/com/android/server/net/NetworkStatsCollection.java
index c4beddd4..6aefe41 100644
--- a/services/core/java/com/android/server/net/NetworkStatsCollection.java
+++ b/services/core/java/com/android/server/net/NetworkStatsCollection.java
@@ -63,12 +63,15 @@
 import com.google.android.collect.Maps;
 
 import java.io.BufferedInputStream;
+import java.io.DataInput;
 import java.io.DataInputStream;
+import java.io.DataOutput;
 import java.io.DataOutputStream;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.net.ProtocolException;
 import java.time.ZonedDateTime;
@@ -82,7 +85,7 @@
  * Collection of {@link NetworkStatsHistory}, stored based on combined key of
  * {@link NetworkIdentitySet}, UID, set, and tag. Knows how to persist itself.
  */
-public class NetworkStatsCollection implements FileRotator.Reader {
+public class NetworkStatsCollection implements FileRotator.Reader, FileRotator.Writer {
     /** File header magic number: "ANET" */
     private static final int FILE_MAGIC = 0x414E4554;
 
@@ -431,10 +434,10 @@
 
     @Override
     public void read(InputStream in) throws IOException {
-        read(new DataInputStream(in));
+        read((DataInput) new DataInputStream(in));
     }
 
-    public void read(DataInputStream in) throws IOException {
+    private void read(DataInput in) throws IOException {
         // verify file magic header intact
         final int magic = in.readInt();
         if (magic != FILE_MAGIC) {
@@ -468,7 +471,13 @@
         }
     }
 
-    public void write(DataOutputStream out) throws IOException {
+    @Override
+    public void write(OutputStream out) throws IOException {
+        write((DataOutput) new DataOutputStream(out));
+        out.flush();
+    }
+
+    private void write(DataOutput out) throws IOException {
         // cluster key lists grouped by ident
         final HashMap<NetworkIdentitySet, ArrayList<Key>> keysByIdent = Maps.newHashMap();
         for (Key key : mStats.keySet()) {
@@ -497,8 +506,6 @@
                 history.writeToStream(out);
             }
         }
-
-        out.flush();
     }
 
     @Deprecated
diff --git a/services/core/java/com/android/server/net/NetworkStatsRecorder.java b/services/core/java/com/android/server/net/NetworkStatsRecorder.java
index ce74169..978ae87 100644
--- a/services/core/java/com/android/server/net/NetworkStatsRecorder.java
+++ b/services/core/java/com/android/server/net/NetworkStatsRecorder.java
@@ -42,7 +42,6 @@
 import libcore.io.IoUtils;
 
 import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -375,7 +374,7 @@
 
         @Override
         public void write(OutputStream out) throws IOException {
-            mCollection.write(new DataOutputStream(out));
+            mCollection.write(out);
             mCollection.reset();
         }
     }
@@ -412,7 +411,7 @@
 
         @Override
         public void write(OutputStream out) throws IOException {
-            mTemp.write(new DataOutputStream(out));
+            mTemp.write(out);
         }
     }
 
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 0f8c9c7..7a6792c 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -24,11 +24,15 @@
 import static android.content.Intent.ACTION_USER_ADDED;
 import static android.content.Intent.ACTION_USER_REMOVED;
 import static android.content.Intent.EXTRA_REASON;
+import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_DISABLED;
+import static android.content.om.OverlayManagerTransaction.Request.TYPE_SET_ENABLED;
 import static android.content.pm.PackageManager.SIGNATURE_MATCH;
 import static android.os.Trace.TRACE_TAG_RRO;
 import static android.os.Trace.traceBegin;
 import static android.os.Trace.traceEnd;
 
+import static com.android.server.om.OverlayManagerServiceImpl.OperationFailedException;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -39,6 +43,7 @@
 import android.content.IntentFilter;
 import android.content.om.IOverlayManager;
 import android.content.om.OverlayInfo;
+import android.content.om.OverlayManagerTransaction;
 import android.content.om.OverlayableInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
@@ -48,6 +53,7 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Environment;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -64,7 +70,6 @@
 
 import com.android.internal.content.om.OverlayConfig;
 import com.android.server.FgThread;
-import com.android.server.IoThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 import com.android.server.SystemService;
@@ -82,12 +87,15 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Consumer;
 
 /**
  * Service to manage asset overlays.
@@ -236,7 +244,14 @@
 
     private final OverlayActorEnforcer mActorEnforcer;
 
-    private final AtomicBoolean mPersistSettingsScheduled = new AtomicBoolean(false);
+    private final Consumer<PackageAndUser> mPropagateOverlayChange = (pair) -> {
+        persistSettings();
+        FgThread.getHandler().post(() -> {
+            List<String> affectedTargets = updatePackageManager(pair.packageName, pair.userId);
+            updateActivityManager(affectedTargets, pair.userId);
+            broadcastActionOverlayChanged(affectedTargets, pair.userId);
+        });
+    };
 
     public OverlayManagerService(@NonNull final Context context) {
         super(context);
@@ -249,17 +264,19 @@
             IdmapManager im = new IdmapManager(IdmapDaemon.getInstance(), mPackageManager);
             mSettings = new OverlayManagerSettings();
             mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings,
-                    OverlayConfig.getSystemInstance(), getDefaultOverlayPackages(),
-                    new OverlayChangeListener());
+                    OverlayConfig.getSystemInstance(), getDefaultOverlayPackages());
             mActorEnforcer = new OverlayActorEnforcer(mPackageManager);
 
+            HandlerThread packageReceiverThread = new HandlerThread(TAG);
+            packageReceiverThread.start();
+
             final IntentFilter packageFilter = new IntentFilter();
             packageFilter.addAction(ACTION_PACKAGE_ADDED);
             packageFilter.addAction(ACTION_PACKAGE_CHANGED);
             packageFilter.addAction(ACTION_PACKAGE_REMOVED);
             packageFilter.addDataScheme("package");
             getContext().registerReceiverAsUser(new PackageReceiver(), UserHandle.ALL,
-                    packageFilter, null, null);
+                    packageFilter, null, packageReceiverThread.getThreadHandler());
 
             final IntentFilter userFilter = new IntentFilter();
             userFilter.addAction(ACTION_USER_ADDED);
@@ -292,11 +309,11 @@
             for (int i = 0; i < userCount; i++) {
                 final UserInfo userInfo = users.get(i);
                 if (!userInfo.supportsSwitchTo() && userInfo.id != UserHandle.USER_SYSTEM) {
-                    // Initialize any users that can't be switched to, as there state would
+                    // Initialize any users that can't be switched to, as their state would
                     // never be setup in onSwitchUser(). We will switch to the system user right
                     // after this, and its state will be setup there.
                     final List<String> targets = mImpl.updateOverlaysForUser(users.get(i).id);
-                    updateOverlayPaths(users.get(i).id, targets);
+                    updatePackageManager(targets, users.get(i).id);
                 }
             }
         }
@@ -310,9 +327,10 @@
             // any asset changes to the rest of the system
             synchronized (mLock) {
                 final List<String> targets = mImpl.updateOverlaysForUser(newUserId);
-                updateAssets(newUserId, targets);
+                final List<String> affectedTargets = updatePackageManager(targets, newUserId);
+                updateActivityManager(affectedTargets, newUserId);
             }
-            schedulePersistSettings();
+            persistSettings();
         } finally {
             traceEnd(TRACE_TAG_RRO);
         }
@@ -396,10 +414,17 @@
                                 false);
                         if (pi != null && !pi.applicationInfo.isInstantApp()) {
                             mPackageManager.cachePackageInfo(packageName, userId, pi);
-                            if (pi.isOverlayPackage()) {
-                                mImpl.onOverlayPackageAdded(packageName, userId);
-                            } else {
-                                mImpl.onTargetPackageAdded(packageName, userId);
+
+                            try {
+                                if (pi.isOverlayPackage()) {
+                                    mImpl.onOverlayPackageAdded(packageName, userId)
+                                        .ifPresent(mPropagateOverlayChange);
+                                } else {
+                                    mImpl.onTargetPackageAdded(packageName, userId)
+                                        .ifPresent(mPropagateOverlayChange);
+                                }
+                            } catch (OperationFailedException e) {
+                                Slog.e(TAG, "onPackageAdded internal error", e);
                             }
                         }
                     }
@@ -419,10 +444,17 @@
                                 false);
                         if (pi != null && pi.applicationInfo.isInstantApp()) {
                             mPackageManager.cachePackageInfo(packageName, userId, pi);
-                            if (pi.isOverlayPackage()) {
-                                mImpl.onOverlayPackageChanged(packageName, userId);
-                            }  else {
-                                mImpl.onTargetPackageChanged(packageName, userId);
+
+                            try {
+                                if (pi.isOverlayPackage()) {
+                                    mImpl.onOverlayPackageChanged(packageName, userId)
+                                        .ifPresent(mPropagateOverlayChange);
+                                }  else {
+                                    mImpl.onTargetPackageChanged(packageName, userId)
+                                        .ifPresent(mPropagateOverlayChange);
+                                }
+                            } catch (OperationFailedException e) {
+                                Slog.e(TAG, "onPackageChanged internal error", e);
                             }
                         }
                     }
@@ -441,7 +473,12 @@
                         mPackageManager.forgetPackageInfo(packageName, userId);
                         final OverlayInfo oi = mImpl.getOverlayInfo(packageName, userId);
                         if (oi != null) {
-                            mImpl.onOverlayPackageReplacing(packageName, userId);
+                            try {
+                                mImpl.onOverlayPackageReplacing(packageName, userId)
+                                    .ifPresent(mPropagateOverlayChange);
+                            } catch (OperationFailedException e) {
+                                Slog.e(TAG, "onPackageReplacing internal error", e);
+                            }
                         }
                     }
                 }
@@ -460,10 +497,16 @@
                                 false);
                         if (pi != null && !pi.applicationInfo.isInstantApp()) {
                             mPackageManager.cachePackageInfo(packageName, userId, pi);
-                            if (pi.isOverlayPackage()) {
-                                mImpl.onOverlayPackageReplaced(packageName, userId);
-                            } else {
-                                mImpl.onTargetPackageReplaced(packageName, userId);
+                            try {
+                                if (pi.isOverlayPackage()) {
+                                    mImpl.onOverlayPackageReplaced(packageName, userId)
+                                        .ifPresent(mPropagateOverlayChange);
+                                } else {
+                                    mImpl.onTargetPackageReplaced(packageName, userId)
+                                        .ifPresent(mPropagateOverlayChange);
+                                }
+                            } catch (OperationFailedException e) {
+                                Slog.e(TAG, "onPackageReplaced internal error", e);
                             }
                         }
                     }
@@ -481,10 +524,17 @@
                     synchronized (mLock) {
                         mPackageManager.forgetPackageInfo(packageName, userId);
                         final OverlayInfo oi = mImpl.getOverlayInfo(packageName, userId);
-                        if (oi != null) {
-                            mImpl.onOverlayPackageRemoved(packageName, userId);
-                        } else {
-                            mImpl.onTargetPackageRemoved(packageName, userId);
+
+                        try {
+                            if (oi != null) {
+                                mImpl.onOverlayPackageRemoved(packageName, userId)
+                                    .ifPresent(mPropagateOverlayChange);
+                            } else {
+                                mImpl.onTargetPackageRemoved(packageName, userId)
+                                    .ifPresent(mPropagateOverlayChange);
+                            }
+                        } catch (OperationFailedException e) {
+                            Slog.e(TAG, "onPackageRemoved internal error", e);
                         }
                     }
                 }
@@ -507,7 +557,7 @@
                             synchronized (mLock) {
                                 targets = mImpl.updateOverlaysForUser(userId);
                             }
-                            updateOverlayPaths(userId, targets);
+                            updatePackageManager(targets, userId);
                         } finally {
                             traceEnd(TRACE_TAG_RRO);
                         }
@@ -602,7 +652,13 @@
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     synchronized (mLock) {
-                        return mImpl.setEnabled(packageName, enable, realUserId);
+                        try {
+                            mImpl.setEnabled(packageName, enable, realUserId)
+                                .ifPresent(mPropagateOverlayChange);
+                            return true;
+                        } catch (OperationFailedException e) {
+                            return false;
+                        }
                     }
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -627,8 +683,14 @@
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     synchronized (mLock) {
-                        return mImpl.setEnabledExclusive(packageName, false /* withinCategory */,
-                                realUserId);
+                        try {
+                            mImpl.setEnabledExclusive(packageName,
+                                    false /* withinCategory */, realUserId)
+                                .ifPresent(mPropagateOverlayChange);
+                            return true;
+                        } catch (OperationFailedException e) {
+                            return false;
+                        }
                     }
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -654,8 +716,14 @@
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     synchronized (mLock) {
-                        return mImpl.setEnabledExclusive(packageName, true /* withinCategory */,
-                                realUserId);
+                        try {
+                            mImpl.setEnabledExclusive(packageName,
+                                    true /* withinCategory */, realUserId)
+                                .ifPresent(mPropagateOverlayChange);
+                            return true;
+                        } catch (OperationFailedException e) {
+                            return false;
+                        }
                     }
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -681,7 +749,13 @@
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     synchronized (mLock) {
-                        return mImpl.setPriority(packageName, parentPackageName, realUserId);
+                        try {
+                            mImpl.setPriority(packageName, parentPackageName, realUserId)
+                                .ifPresent(mPropagateOverlayChange);
+                            return true;
+                        } catch (OperationFailedException e) {
+                            return false;
+                        }
                     }
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -705,7 +779,13 @@
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     synchronized (mLock) {
-                        return mImpl.setHighestPriority(packageName, realUserId);
+                        try {
+                            mImpl.setHighestPriority(packageName, realUserId)
+                                .ifPresent(mPropagateOverlayChange);
+                            return true;
+                        } catch (OperationFailedException e) {
+                            return false;
+                        }
                     }
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -729,7 +809,13 @@
                 final long ident = Binder.clearCallingIdentity();
                 try {
                     synchronized (mLock) {
-                        return mImpl.setLowestPriority(packageName, realUserId);
+                        try {
+                            mImpl.setLowestPriority(packageName, realUserId)
+                                .ifPresent(mPropagateOverlayChange);
+                            return true;
+                        } catch (OperationFailedException e) {
+                            return false;
+                        }
                     }
                 } finally {
                     Binder.restoreCallingIdentity(ident);
@@ -778,6 +864,120 @@
         }
 
         @Override
+        public void commit(@NonNull final OverlayManagerTransaction transaction)
+                throws RemoteException {
+            try {
+                traceBegin(TRACE_TAG_RRO, "OMS#commit " + transaction);
+                try {
+                    executeAllRequests(transaction);
+                } catch (Exception e) {
+                    final long ident = Binder.clearCallingIdentity();
+                    try {
+                        restoreSettings();
+                    } finally {
+                        Binder.restoreCallingIdentity(ident);
+                    }
+                    Slog.d(TAG, "commit failed: " + e.getMessage(), e);
+                    throw new SecurityException("commit failed"
+                            + (DEBUG ? ": " + e.getMessage() : ""));
+                }
+            } finally {
+                traceEnd(TRACE_TAG_RRO);
+            }
+        }
+
+        private Optional<PackageAndUser> executeRequest(
+                @NonNull final OverlayManagerTransaction.Request request) throws Exception {
+            final int realUserId = handleIncomingUser(request.userId, request.typeToString());
+            enforceActor(request.packageName, request.typeToString(), realUserId);
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                switch (request.type) {
+                    case TYPE_SET_ENABLED:
+                        Optional<PackageAndUser> opt1 =
+                                mImpl.setEnabled(request.packageName, true, request.userId);
+                        Optional<PackageAndUser> opt2 =
+                                mImpl.setHighestPriority(request.packageName, request.userId);
+                        // Both setEnabled and setHighestPriority affected the same
+                        // target package and user: if both return non-empty
+                        // Optionals, they are identical
+                        return opt1.isPresent() ? opt1 : opt2;
+                    case TYPE_SET_DISABLED:
+                        return mImpl.setEnabled(request.packageName, false, request.userId);
+                    default:
+                        throw new IllegalArgumentException("unsupported request: " + request);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        private void executeAllRequests(@NonNull final OverlayManagerTransaction transaction)
+                throws Exception {
+            if (DEBUG) {
+                Slog.d(TAG, "commit " + transaction);
+            }
+            if (transaction == null) {
+                throw new IllegalArgumentException("null transaction");
+            }
+
+            // map: userId -> list<targetPackageName>
+            SparseArray<List<String>> affectedTargetsToUpdate = new SparseArray<>();
+
+            synchronized (mLock) {
+                // map: userId -> set<targetPackageName>
+                SparseArray<Set<String>> targetsToUpdate = new SparseArray<>();
+
+                // execute the requests (as calling user)
+                for (final OverlayManagerTransaction.Request request : transaction) {
+                    executeRequest(request).ifPresent(target -> {
+                        Set<String> userTargets = targetsToUpdate.get(target.userId);
+                        if (userTargets == null) {
+                            userTargets = new ArraySet<String>();
+                            targetsToUpdate.put(target.userId, userTargets);
+                        }
+                        userTargets.add(target.packageName);
+                    });
+                }
+
+                // past the point of no return: the entire transaction has been
+                // processed successfully, we can no longer fail: continue as
+                // system_server
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    persistSettings();
+
+                    // inform the package manager about the new paths
+                    for (int index = 0; index < targetsToUpdate.size(); index++) {
+                        final int userId = targetsToUpdate.keyAt(index);
+                        final List<String> affectedTargets =
+                                updatePackageManager(targetsToUpdate.valueAt(index), userId);
+                        affectedTargetsToUpdate.put(userId, affectedTargets);
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            } // synchronized (mLock)
+
+            FgThread.getHandler().post(() -> {
+                final long ident = Binder.clearCallingIdentity();
+                try {
+                    // schedule apps to refresh + broadcast the ACTION_OVERLAY_CHANGED intents
+                    for (int index = 0; index < affectedTargetsToUpdate.size(); index++) {
+                        final int userId = affectedTargetsToUpdate.keyAt(index);
+                        final List<String> packageNames = affectedTargetsToUpdate.valueAt(index);
+
+                        updateActivityManager(packageNames, userId);
+                        broadcastActionOverlayChanged(packageNames, userId);
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(ident);
+                }
+            });
+        }
+
+        @Override
         public void onShellCommand(@NonNull final FileDescriptor in,
                 @NonNull final FileDescriptor out, @NonNull final FileDescriptor err,
                 @NonNull final String[] args, @NonNull final ShellCallback callback,
@@ -898,162 +1098,7 @@
         }
     };
 
-    private final class OverlayChangeListener
-            implements OverlayManagerServiceImpl.OverlayChangeListener {
-        @Override
-        public void onOverlaysChanged(@NonNull final String targetPackageName, final int userId) {
-            schedulePersistSettings();
-            FgThread.getHandler().post(() -> {
-                updateAssets(userId, targetPackageName);
-
-                final Intent intent = new Intent(ACTION_OVERLAY_CHANGED,
-                        Uri.fromParts("package", targetPackageName, null));
-                intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-
-                if (DEBUG) {
-                    Slog.d(TAG, "send broadcast " + intent);
-                }
-
-                try {
-                    ActivityManager.getService().broadcastIntentWithFeature(null, null, intent,
-                            null, null, 0, null, null, null, android.app.AppOpsManager.OP_NONE,
-                            null, false, false, userId);
-                } catch (RemoteException e) {
-                    // Intentionally left empty.
-                }
-            });
-        }
-    }
-
-    /**
-     * Updates the target packages' set of enabled overlays in PackageManager.
-     */
-    private ArrayList<String> updateOverlayPaths(int userId, List<String> targetPackageNames) {
-        try {
-            traceBegin(TRACE_TAG_RRO, "OMS#updateOverlayPaths " + targetPackageNames);
-            if (DEBUG) {
-                Slog.d(TAG, "Updating overlay assets");
-            }
-            final PackageManagerInternal pm =
-                    LocalServices.getService(PackageManagerInternal.class);
-            final boolean updateFrameworkRes = targetPackageNames.contains("android");
-            if (updateFrameworkRes) {
-                targetPackageNames = pm.getTargetPackageNames(userId);
-            }
-
-            final Map<String, List<String>> pendingChanges =
-                    new ArrayMap<>(targetPackageNames.size());
-            synchronized (mLock) {
-                final List<String> frameworkOverlays =
-                        mImpl.getEnabledOverlayPackageNames("android", userId);
-                final int n = targetPackageNames.size();
-                for (int i = 0; i < n; i++) {
-                    final String targetPackageName = targetPackageNames.get(i);
-                    List<String> list = new ArrayList<>();
-                    if (!"android".equals(targetPackageName)) {
-                        list.addAll(frameworkOverlays);
-                    }
-                    list.addAll(mImpl.getEnabledOverlayPackageNames(targetPackageName, userId));
-                    pendingChanges.put(targetPackageName, list);
-                }
-            }
-
-            final HashSet<String> updatedPackages = new HashSet<>();
-            final int n = targetPackageNames.size();
-            for (int i = 0; i < n; i++) {
-                final String targetPackageName = targetPackageNames.get(i);
-                if (DEBUG) {
-                    Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=["
-                            + TextUtils.join(",", pendingChanges.get(targetPackageName))
-                            + "] userId=" + userId);
-                }
-
-                if (!pm.setEnabledOverlayPackages(
-                        userId, targetPackageName, pendingChanges.get(targetPackageName),
-                        updatedPackages)) {
-                    Slog.e(TAG, String.format("Failed to change enabled overlays for %s user %d",
-                            targetPackageName, userId));
-                }
-            }
-            return new ArrayList<>(updatedPackages);
-        } finally {
-            traceEnd(TRACE_TAG_RRO);
-        }
-    }
-
-    private void updateAssets(final int userId, final String targetPackageName) {
-        updateAssets(userId, Collections.singletonList(targetPackageName));
-    }
-
-    private void updateAssets(final int userId, List<String> targetPackageNames) {
-        final IActivityManager am = ActivityManager.getService();
-        try {
-            final ArrayList<String> updatedPaths = updateOverlayPaths(userId, targetPackageNames);
-            am.scheduleApplicationInfoChanged(updatedPaths, userId);
-        } catch (RemoteException e) {
-            // Intentionally left empty.
-        }
-    }
-
-    private void schedulePersistSettings() {
-        if (mPersistSettingsScheduled.getAndSet(true)) {
-            return;
-        }
-        IoThread.getHandler().post(() -> {
-            mPersistSettingsScheduled.set(false);
-            if (DEBUG) {
-                Slog.d(TAG, "Writing overlay settings");
-            }
-            synchronized (mLock) {
-                FileOutputStream stream = null;
-                try {
-                    stream = mSettingsFile.startWrite();
-                    mSettings.persist(stream);
-                    mSettingsFile.finishWrite(stream);
-                } catch (IOException | XmlPullParserException e) {
-                    mSettingsFile.failWrite(stream);
-                    Slog.e(TAG, "failed to persist overlay state", e);
-                }
-            }
-        });
-    }
-
-    private void restoreSettings() {
-        try {
-            traceBegin(TRACE_TAG_RRO, "OMS#restoreSettings");
-            synchronized (mLock) {
-                if (!mSettingsFile.getBaseFile().exists()) {
-                    return;
-                }
-                try (FileInputStream stream = mSettingsFile.openRead()) {
-                    mSettings.restore(stream);
-
-                    // We might have data for dying users if the device was
-                    // restarted before we received USER_REMOVED. Remove data for
-                    // users that will not exist after the system is ready.
-
-                    final List<UserInfo> liveUsers = mUserManager.getUsers(true /*excludeDying*/);
-                    final int[] liveUserIds = new int[liveUsers.size()];
-                    for (int i = 0; i < liveUsers.size(); i++) {
-                        liveUserIds[i] = liveUsers.get(i).getUserHandle().getIdentifier();
-                    }
-                    Arrays.sort(liveUserIds);
-
-                    for (int userId : mSettings.getUsers()) {
-                        if (Arrays.binarySearch(liveUserIds, userId) < 0) {
-                            mSettings.removeUser(userId);
-                        }
-                    }
-                } catch (IOException | XmlPullParserException e) {
-                    Slog.e(TAG, "failed to restore overlay state", e);
-                }
-            }
-        } finally {
-            traceEnd(TRACE_TAG_RRO);
-        }
-    }
-
-    private static final class PackageManagerHelperImpl implements PackageManagerHelper  {
+    private static final class PackageManagerHelperImpl implements PackageManagerHelper {
 
         private final Context mContext;
         private final IPackageManager mPackageManager;
@@ -1263,4 +1308,151 @@
             }
         }
     }
+
+    // Helper methods to update other parts of the system or read/write
+    // settings: these methods should never call into each other!
+
+    private void broadcastActionOverlayChanged(@NonNull final Collection<String> packageNames,
+            final int userId) {
+        for (final String packageName : packageNames) {
+            broadcastActionOverlayChanged(packageName, userId);
+        }
+    }
+
+    private void broadcastActionOverlayChanged(@NonNull final String targetPackageName,
+            final int userId) {
+        final Intent intent = new Intent(ACTION_OVERLAY_CHANGED,
+                Uri.fromParts("package", targetPackageName, null));
+        intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+        try {
+            ActivityManager.getService().broadcastIntent(null, intent, null, null, 0, null, null,
+                    null, android.app.AppOpsManager.OP_NONE, null, false, false, userId);
+        } catch (RemoteException e) {
+            // Intentionally left empty.
+        }
+    }
+
+    /**
+     * Tell the activity manager to tell a set of packages to reload their
+     * resources.
+     */
+    private void updateActivityManager(List<String> targetPackageNames, final int userId) {
+        final IActivityManager am = ActivityManager.getService();
+        try {
+            am.scheduleApplicationInfoChanged(targetPackageNames, userId);
+        } catch (RemoteException e) {
+            // Intentionally left empty.
+        }
+    }
+
+    private ArrayList<String> updatePackageManager(String targetPackageNames, final int userId) {
+        return updatePackageManager(Collections.singletonList(targetPackageNames), userId);
+    }
+
+    /**
+     * Updates the target packages' set of enabled overlays in PackageManager.
+     * @return the package names of affected targets (a superset of
+     *         targetPackageNames: the target themserlves and shared libraries)
+     */
+    private ArrayList<String> updatePackageManager(@NonNull Collection<String> targetPackageNames,
+            final int userId) {
+        try {
+            traceBegin(TRACE_TAG_RRO, "OMS#updatePackageManager " + targetPackageNames);
+            if (DEBUG) {
+                Slog.d(TAG, "Update package manager about changed overlays");
+            }
+            final PackageManagerInternal pm =
+                    LocalServices.getService(PackageManagerInternal.class);
+            final boolean updateFrameworkRes = targetPackageNames.contains("android");
+            if (updateFrameworkRes) {
+                targetPackageNames = pm.getTargetPackageNames(userId);
+            }
+
+            final Map<String, List<String>> pendingChanges =
+                    new ArrayMap<>(targetPackageNames.size());
+            synchronized (mLock) {
+                final List<String> frameworkOverlays =
+                        mImpl.getEnabledOverlayPackageNames("android", userId);
+                for (final String targetPackageName : targetPackageNames) {
+                    List<String> list = new ArrayList<>();
+                    if (!"android".equals(targetPackageName)) {
+                        list.addAll(frameworkOverlays);
+                    }
+                    list.addAll(mImpl.getEnabledOverlayPackageNames(targetPackageName, userId));
+                    pendingChanges.put(targetPackageName, list);
+                }
+            }
+
+            final HashSet<String> updatedPackages = new HashSet<>();
+            for (final String targetPackageName : targetPackageNames) {
+                if (DEBUG) {
+                    Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=["
+                            + TextUtils.join(",", pendingChanges.get(targetPackageName))
+                            + "] userId=" + userId);
+                }
+
+                if (!pm.setEnabledOverlayPackages(
+                        userId, targetPackageName, pendingChanges.get(targetPackageName),
+                        updatedPackages)) {
+                    Slog.e(TAG, String.format("Failed to change enabled overlays for %s user %d",
+                            targetPackageName, userId));
+                }
+            }
+            return new ArrayList<>(updatedPackages);
+        } finally {
+            traceEnd(TRACE_TAG_RRO);
+        }
+    }
+
+    private void persistSettings() {
+        if (DEBUG) {
+            Slog.d(TAG, "Writing overlay settings");
+        }
+        synchronized (mLock) {
+            FileOutputStream stream = null;
+            try {
+                stream = mSettingsFile.startWrite();
+                mSettings.persist(stream);
+                mSettingsFile.finishWrite(stream);
+            } catch (IOException | XmlPullParserException e) {
+                mSettingsFile.failWrite(stream);
+                Slog.e(TAG, "failed to persist overlay state", e);
+            }
+        }
+    }
+
+    private void restoreSettings() {
+        try {
+            traceBegin(TRACE_TAG_RRO, "OMS#restoreSettings");
+            synchronized (mLock) {
+                if (!mSettingsFile.getBaseFile().exists()) {
+                    return;
+                }
+                try (FileInputStream stream = mSettingsFile.openRead()) {
+                    mSettings.restore(stream);
+
+                    // We might have data for dying users if the device was
+                    // restarted before we received USER_REMOVED. Remove data for
+                    // users that will not exist after the system is ready.
+
+                    final List<UserInfo> liveUsers = mUserManager.getUsers(true /*excludeDying*/);
+                    final int[] liveUserIds = new int[liveUsers.size()];
+                    for (int i = 0; i < liveUsers.size(); i++) {
+                        liveUserIds[i] = liveUsers.get(i).getUserHandle().getIdentifier();
+                    }
+                    Arrays.sort(liveUserIds);
+
+                    for (int userId : mSettings.getUsers()) {
+                        if (Arrays.binarySearch(liveUserIds, userId) < 0) {
+                            mSettings.removeUser(userId);
+                        }
+                    }
+                } catch (IOException | XmlPullParserException e) {
+                    Slog.e(TAG, "failed to restore overlay state", e);
+                }
+            }
+        } finally {
+            traceEnd(TRACE_TAG_RRO);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 05a4a38..e60411bb 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -45,6 +45,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 
 /**
@@ -71,7 +72,6 @@
     private final OverlayManagerSettings mSettings;
     private final OverlayConfig mOverlayConfig;
     private final String[] mDefaultOverlays;
-    private final OverlayChangeListener mListener;
 
     /**
      * Helper method to merge the overlay manager's (as read from overlays.xml)
@@ -114,14 +114,12 @@
             @NonNull final IdmapManager idmapManager,
             @NonNull final OverlayManagerSettings settings,
             @NonNull final OverlayConfig overlayConfig,
-            @NonNull final String[] defaultOverlays,
-            @NonNull final OverlayChangeListener listener) {
+            @NonNull final String[] defaultOverlays) {
         mPackageManager = packageManager;
         mIdmapManager = idmapManager;
         mSettings = settings;
         mOverlayConfig = overlayConfig;
         mDefaultOverlays = defaultOverlays;
-        mListener = listener;
     }
 
     /**
@@ -259,52 +257,58 @@
         mSettings.removeUser(userId);
     }
 
-    void onTargetPackageAdded(@NonNull final String packageName, final int userId) {
+    Optional<PackageAndUser> onTargetPackageAdded(@NonNull final String packageName,
+            final int userId) throws OperationFailedException {
         if (DEBUG) {
             Slog.d(TAG, "onTargetPackageAdded packageName=" + packageName + " userId=" + userId);
         }
 
-        updateAndRefreshOverlaysForTarget(packageName, userId, 0);
+        return updateAndRefreshOverlaysForTarget(packageName, userId, 0);
     }
 
-    void onTargetPackageChanged(@NonNull final String packageName, final int userId) {
+    Optional<PackageAndUser> onTargetPackageChanged(@NonNull final String packageName,
+            final int userId) throws OperationFailedException {
         if (DEBUG) {
             Slog.d(TAG, "onTargetPackageChanged packageName=" + packageName + " userId=" + userId);
         }
 
-        updateAndRefreshOverlaysForTarget(packageName, userId, 0);
+        return updateAndRefreshOverlaysForTarget(packageName, userId, 0);
     }
 
-    void onTargetPackageReplacing(@NonNull final String packageName, final int userId) {
+    Optional<PackageAndUser> onTargetPackageReplacing(@NonNull final String packageName,
+            final int userId) throws OperationFailedException {
         if (DEBUG) {
             Slog.d(TAG, "onTargetPackageReplacing packageName=" + packageName + " userId="
                     + userId);
         }
 
-        updateAndRefreshOverlaysForTarget(packageName, userId, 0);
+        return updateAndRefreshOverlaysForTarget(packageName, userId, 0);
     }
 
-    void onTargetPackageReplaced(@NonNull final String packageName, final int userId) {
+    Optional<PackageAndUser> onTargetPackageReplaced(@NonNull final String packageName,
+            final int userId) throws OperationFailedException {
         if (DEBUG) {
             Slog.d(TAG, "onTargetPackageReplaced packageName=" + packageName + " userId=" + userId);
         }
 
-        updateAndRefreshOverlaysForTarget(packageName, userId, 0);
+        return updateAndRefreshOverlaysForTarget(packageName, userId, 0);
     }
 
-    void onTargetPackageRemoved(@NonNull final String packageName, final int userId) {
+    Optional<PackageAndUser> onTargetPackageRemoved(@NonNull final String packageName,
+            final int userId) throws OperationFailedException {
         if (DEBUG) {
             Slog.d(TAG, "onTargetPackageRemoved packageName=" + packageName + " userId=" + userId);
         }
 
-        updateAndRefreshOverlaysForTarget(packageName, userId, 0);
+        return updateAndRefreshOverlaysForTarget(packageName, userId, 0);
     }
 
     /**
      * Update the state of any overlays for this target.
      */
-    private void updateAndRefreshOverlaysForTarget(@NonNull final String targetPackageName,
-            final int userId, final int flags) {
+    private Optional<PackageAndUser> updateAndRefreshOverlaysForTarget(
+            @NonNull final String targetPackageName, final int userId, final int flags)
+            throws OperationFailedException {
         final List<OverlayInfo> targetOverlays = mSettings.getOverlaysForTarget(targetPackageName,
                 userId);
 
@@ -364,11 +368,13 @@
         }
 
         if (modified) {
-            mListener.onOverlaysChanged(targetPackageName, userId);
+            return Optional.of(new PackageAndUser(targetPackageName, userId));
         }
+        return Optional.empty();
     }
 
-    void onOverlayPackageAdded(@NonNull final String packageName, final int userId) {
+    Optional<PackageAndUser> onOverlayPackageAdded(@NonNull final String packageName,
+            final int userId) throws OperationFailedException {
         if (DEBUG) {
             Slog.d(TAG, "onOverlayPackageAdded packageName=" + packageName + " userId=" + userId);
         }
@@ -376,8 +382,7 @@
         final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
         if (overlayPackage == null) {
             Slog.w(TAG, "overlay package " + packageName + " was added, but couldn't be found");
-            onOverlayPackageRemoved(packageName, userId);
-            return;
+            return onOverlayPackageRemoved(packageName, userId);
         }
 
         mSettings.init(packageName, userId, overlayPackage.overlayTarget,
@@ -389,15 +394,17 @@
                 overlayPackage.overlayCategory);
         try {
             if (updateState(overlayPackage.overlayTarget, packageName, userId, 0)) {
-                mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId);
+                return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId));
             }
+            return Optional.empty();
         } catch (OverlayManagerSettings.BadKeyException e) {
-            Slog.e(TAG, "failed to update settings", e);
             mSettings.remove(packageName, userId);
+            throw new OperationFailedException("failed to update settings", e);
         }
     }
 
-    void onOverlayPackageChanged(@NonNull final String packageName, final int userId) {
+    Optional<PackageAndUser> onOverlayPackageChanged(@NonNull final String packageName,
+            final int userId) throws OperationFailedException {
         if (DEBUG) {
             Slog.d(TAG, "onOverlayPackageChanged packageName=" + packageName + " userId=" + userId);
         }
@@ -405,14 +412,16 @@
         try {
             final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
             if (updateState(oi.targetPackageName, packageName, userId, 0)) {
-                mListener.onOverlaysChanged(oi.targetPackageName, userId);
+                return Optional.of(new PackageAndUser(oi.targetPackageName, userId));
             }
+            return Optional.empty();
         } catch (OverlayManagerSettings.BadKeyException e) {
-            Slog.e(TAG, "failed to update settings", e);
+            throw new OperationFailedException("failed to update settings", e);
         }
     }
 
-    void onOverlayPackageReplacing(@NonNull final String packageName, final int userId) {
+    Optional<PackageAndUser> onOverlayPackageReplacing(@NonNull final String packageName,
+            final int userId) throws OperationFailedException {
         if (DEBUG) {
             Slog.d(TAG, "onOverlayPackageReplacing packageName=" + packageName + " userId="
                     + userId);
@@ -423,14 +432,16 @@
             if (updateState(oi.targetPackageName, packageName, userId,
                         FLAG_OVERLAY_IS_BEING_REPLACED)) {
                 removeIdmapIfPossible(oi);
-                mListener.onOverlaysChanged(oi.targetPackageName, userId);
+                return Optional.of(new PackageAndUser(oi.targetPackageName, userId));
             }
+            return Optional.empty();
         } catch (OverlayManagerSettings.BadKeyException e) {
-            Slog.e(TAG, "failed to update settings", e);
+            throw new OperationFailedException("failed to update settings", e);
         }
     }
 
-    void onOverlayPackageReplaced(@NonNull final String packageName, final int userId) {
+    Optional<PackageAndUser> onOverlayPackageReplaced(@NonNull final String packageName,
+            final int userId) throws OperationFailedException {
         if (DEBUG) {
             Slog.d(TAG, "onOverlayPackageReplaced packageName=" + packageName + " userId="
                     + userId);
@@ -439,16 +450,12 @@
         final PackageInfo pkg = mPackageManager.getPackageInfo(packageName, userId);
         if (pkg == null) {
             Slog.w(TAG, "overlay package " + packageName + " was replaced, but couldn't be found");
-            onOverlayPackageRemoved(packageName, userId);
-            return;
+            return onOverlayPackageRemoved(packageName, userId);
         }
 
         try {
             final OverlayInfo oldOi = mSettings.getOverlayInfo(packageName, userId);
             if (mustReinitializeOverlay(pkg, oldOi)) {
-                if (oldOi != null && !oldOi.targetPackageName.equals(pkg.overlayTarget)) {
-                    mListener.onOverlaysChanged(pkg.overlayTarget, userId);
-                }
                 mSettings.init(packageName, userId, pkg.overlayTarget, pkg.targetOverlayableName,
                         pkg.applicationInfo.getBaseCodePath(),
                         isPackageConfiguredMutable(pkg.packageName),
@@ -457,22 +464,25 @@
             }
 
             if (updateState(pkg.overlayTarget, packageName, userId, 0)) {
-                mListener.onOverlaysChanged(pkg.overlayTarget, userId);
+                return Optional.of(new PackageAndUser(pkg.overlayTarget, userId));
             }
+            return Optional.empty();
         } catch (OverlayManagerSettings.BadKeyException e) {
-            Slog.e(TAG, "failed to update settings", e);
+            throw new OperationFailedException("failed to update settings", e);
         }
     }
 
-    void onOverlayPackageRemoved(@NonNull final String packageName, final int userId) {
+    Optional<PackageAndUser> onOverlayPackageRemoved(@NonNull final String packageName,
+            final int userId) throws OperationFailedException {
         try {
             final OverlayInfo overlayInfo = mSettings.getOverlayInfo(packageName, userId);
             if (mSettings.remove(packageName, userId)) {
                 removeIdmapIfPossible(overlayInfo);
-                mListener.onOverlaysChanged(overlayInfo.targetPackageName, userId);
+                return Optional.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
             }
+            return Optional.empty();
         } catch (OverlayManagerSettings.BadKeyException e) {
-            Slog.e(TAG, "failed to remove overlay", e);
+            throw new OperationFailedException("failed to remove overlay", e);
         }
     }
 
@@ -493,8 +503,8 @@
         return mSettings.getOverlaysForUser(userId);
     }
 
-    boolean setEnabled(@NonNull final String packageName, final boolean enable,
-            final int userId) {
+    Optional<PackageAndUser> setEnabled(@NonNull final String packageName, final boolean enable,
+            final int userId) throws OperationFailedException {
         if (DEBUG) {
             Slog.d(TAG, String.format("setEnabled packageName=%s enable=%s userId=%d",
                         packageName, enable, userId));
@@ -502,30 +512,33 @@
 
         final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
         if (overlayPackage == null) {
-            return false;
+            throw new OperationFailedException(
+                    String.format("failed to find overlay package %s for user %d",
+                        packageName, userId));
         }
 
         try {
             final OverlayInfo oi = mSettings.getOverlayInfo(packageName, userId);
             if (!oi.isMutable) {
                 // Ignore immutable overlays.
-                return false;
+                throw new OperationFailedException(
+                        "cannot enable immutable overlay packages in runtime");
             }
 
             boolean modified = mSettings.setEnabled(packageName, userId, enable);
             modified |= updateState(oi.targetPackageName, oi.packageName, userId, 0);
 
             if (modified) {
-                mListener.onOverlaysChanged(oi.targetPackageName, userId);
+                return Optional.of(new PackageAndUser(oi.targetPackageName, userId));
             }
-            return true;
+            return Optional.empty();
         } catch (OverlayManagerSettings.BadKeyException e) {
-            return false;
+            throw new OperationFailedException("failed to update settings", e);
         }
     }
 
-    boolean setEnabledExclusive(@NonNull final String packageName, boolean withinCategory,
-            final int userId) {
+    Optional<PackageAndUser> setEnabledExclusive(@NonNull final String packageName,
+            boolean withinCategory, final int userId) throws OperationFailedException {
         if (DEBUG) {
             Slog.d(TAG, String.format("setEnabledExclusive packageName=%s"
                     + " withinCategory=%s userId=%d", packageName, withinCategory, userId));
@@ -533,7 +546,8 @@
 
         final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
         if (overlayPackage == null) {
-            return false;
+            throw new OperationFailedException(String.format(
+                        "failed to find overlay package %s for user %d", packageName, userId));
         }
 
         try {
@@ -576,11 +590,11 @@
             modified |= updateState(targetPackageName, packageName, userId, 0);
 
             if (modified) {
-                mListener.onOverlaysChanged(targetPackageName, userId);
+                return Optional.of(new PackageAndUser(targetPackageName, userId));
             }
-            return true;
+            return Optional.empty();
         } catch (OverlayManagerSettings.BadKeyException e) {
-            return false;
+            throw new OperationFailedException("failed to update settings", e);
         }
     }
 
@@ -596,66 +610,75 @@
         return mOverlayConfig.isEnabled(packageName);
     }
 
-    boolean setPriority(@NonNull final String packageName,
-            @NonNull final String newParentPackageName, final int userId) {
+    Optional<PackageAndUser> setPriority(@NonNull final String packageName,
+            @NonNull final String newParentPackageName, final int userId)
+            throws OperationFailedException {
         if (DEBUG) {
             Slog.d(TAG, "setPriority packageName=" + packageName + " newParentPackageName="
                     + newParentPackageName + " userId=" + userId);
         }
 
         if (!isPackageConfiguredMutable(packageName)) {
-            return false;
+            throw new OperationFailedException(String.format(
+                        "overlay package %s user %d is not updatable", packageName, userId));
         }
 
         final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
         if (overlayPackage == null) {
-            return false;
+            throw new OperationFailedException(String.format(
+                        "failed to find overlay package %s for user %d", packageName, userId));
         }
 
         if (mSettings.setPriority(packageName, newParentPackageName, userId)) {
-            mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId);
+            return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId));
         }
-        return true;
+        return Optional.empty();
     }
 
-    boolean setHighestPriority(@NonNull final String packageName, final int userId) {
+    Optional<PackageAndUser> setHighestPriority(@NonNull final String packageName,
+            final int userId) throws OperationFailedException {
         if (DEBUG) {
             Slog.d(TAG, "setHighestPriority packageName=" + packageName + " userId=" + userId);
         }
 
         if (!isPackageConfiguredMutable(packageName)) {
-            return false;
+            throw new OperationFailedException(String.format(
+                        "overlay package %s user %d is not updatable", packageName, userId));
         }
 
         final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
         if (overlayPackage == null) {
-            return false;
+            throw new OperationFailedException(String.format(
+                        "failed to find overlay package %s for user %d", packageName, userId));
         }
 
         if (mSettings.setHighestPriority(packageName, userId)) {
-            mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId);
+            return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId));
         }
-        return true;
+        return Optional.empty();
     }
 
-    boolean setLowestPriority(@NonNull final String packageName, final int userId) {
+    Optional<PackageAndUser> setLowestPriority(@NonNull final String packageName, final int userId)
+            throws OperationFailedException {
         if (DEBUG) {
             Slog.d(TAG, "setLowestPriority packageName=" + packageName + " userId=" + userId);
         }
 
         if (!isPackageConfiguredMutable(packageName)) {
-            return false;
+            throw new OperationFailedException(String.format(
+                        "overlay package %s user %d is not updatable", packageName, userId));
         }
 
         final PackageInfo overlayPackage = mPackageManager.getPackageInfo(packageName, userId);
         if (overlayPackage == null) {
-            return false;
+            throw new OperationFailedException(String.format(
+                        "failed to find overlay package %s for user %d", packageName, userId));
         }
 
         if (mSettings.setLowestPriority(packageName, userId)) {
-            mListener.onOverlaysChanged(overlayPackage.overlayTarget, userId);
+            return Optional.of(new PackageAndUser(overlayPackage.overlayTarget, userId));
         }
-        return true;
+        return Optional.empty();
     }
 
     void dump(@NonNull final PrintWriter pw, @NonNull DumpState dumpState) {
@@ -797,12 +820,13 @@
         mIdmapManager.removeIdmap(oi, oi.userId);
     }
 
-    interface OverlayChangeListener {
+    static final class OperationFailedException extends Exception {
+        OperationFailedException(@NonNull final String message) {
+            super(message);
+        }
 
-        /**
-         * An event triggered by changes made to overlay state or settings as well as changes that
-         * add or remove target packages of overlays.
-         **/
-        void onOverlaysChanged(@NonNull String targetPackage, int userId);
+        OperationFailedException(@NonNull final String message, @NonNull Throwable cause) {
+            super(message, cause);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/om/PackageAndUser.java b/services/core/java/com/android/server/om/PackageAndUser.java
new file mode 100644
index 0000000..5c38ba7
--- /dev/null
+++ b/services/core/java/com/android/server/om/PackageAndUser.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.om;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+
+final class PackageAndUser {
+    public final @NonNull String packageName;
+    public final @UserIdInt int userId;
+
+    PackageAndUser(@NonNull String packageName, @UserIdInt int userId) {
+        this.packageName = packageName;
+        this.userId = userId;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof PackageAndUser)) {
+            return false;
+        }
+        PackageAndUser other = (PackageAndUser) obj;
+        return packageName.equals(other.packageName) && userId == other.userId;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + packageName.hashCode();
+        result = prime * result + userId;
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("PackageAndUser{packageName=%s, userId=%d}", packageName, userId);
+    }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b5c5bb5..59b24f8 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -910,6 +910,9 @@
         mActivityManagerService.setSystemProcess();
         t.traceEnd();
 
+        // The package receiver depends on the activity service in order to get registered.
+        platformCompat.registerPackageReceiver(mSystemContext);
+
         // Complete the watchdog setup with an ActivityManager instance and listen for reboots
         // Do this only after the ActivityManagerService is properly started as a system process
         t.traceBegin("InitWatchdog");
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java
index 870fe4a..4f4aa3f 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java
@@ -117,6 +117,7 @@
 
     CompatConfig build() {
         CompatConfig config = new CompatConfig(mBuildClassifier, mContext);
+        config.forceNonDebuggableFinalForTest(false);
         for (CompatChange change : mChanges) {
             config.addChange(change);
         }
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index 8c63bfc..ac8dc34 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -28,6 +28,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -81,6 +82,8 @@
     @Test
     public void testUnknownChangeEnabled() throws Exception {
         CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
+        compatConfig.forceNonDebuggableFinalForTest(false);
+
         assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create().build()))
             .isTrue();
     }
@@ -180,6 +183,8 @@
     @Test
     public void testPackageOverrideUnknownPackage() throws Exception {
         CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
+        compatConfig.forceNonDebuggableFinalForTest(false);
+
 
         compatConfig.addOverride(1234L, "com.some.package", false);
 
@@ -230,6 +235,83 @@
     }
 
     @Test
+    public void testApplyDeferredOverridesAfterInstallingApp() throws Exception {
+        ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+                .withPackageName("com.notinstalled.foo")
+                .debuggable().build();
+        when(mPackageManager.getApplicationInfo(eq("com.notinstalled.foo"), anyInt()))
+                .thenThrow(new NameNotFoundException());
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1234L).build();
+        when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
+        when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+
+        // Add override before the app is available.
+        compatConfig.addOverride(1234L, "com.notinstalled.foo", true);
+        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse();
+
+        // Pretend the app is now installed.
+        when(mPackageManager.getApplicationInfo(eq("com.notinstalled.foo"), anyInt()))
+                .thenReturn(applicationInfo);
+
+        compatConfig.recheckOverrides("com.notinstalled.foo");
+        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue();
+    }
+
+    @Test
+    public void testApplyDeferredOverrideClearsOverrideAfterUninstall() throws Exception {
+        ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+                .withPackageName("com.installedapp.foo")
+                .debuggable().build();
+        when(mPackageManager.getApplicationInfo(eq("com.installedapp.foo"), anyInt()))
+                .thenReturn(applicationInfo);
+
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1234L).build();
+        when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
+        when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+
+        // Add override when app is installed.
+        compatConfig.addOverride(1234L, "com.installedapp.foo", true);
+        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue();
+
+        // Pretend the app is now uninstalled.
+        when(mPackageManager.getApplicationInfo(eq("com.installedapp.foo"), anyInt()))
+                .thenThrow(new NameNotFoundException());
+
+        compatConfig.recheckOverrides("com.installedapp.foo");
+        assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse();
+    }
+
+    @Test
+    public void testApplyDeferredOverrideClearsOverrideAfterChange() throws Exception {
+        ApplicationInfo debuggableApp = ApplicationInfoBuilder.create()
+                .withPackageName("com.installedapp.foo")
+                .debuggable().build();
+        ApplicationInfo releaseApp = ApplicationInfoBuilder.create()
+                .withPackageName("com.installedapp.foo")
+                .build();
+        when(mPackageManager.getApplicationInfo(eq("com.installedapp.foo"), anyInt()))
+                .thenReturn(debuggableApp);
+
+        CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+                .addDisabledChangeWithId(1234L).build();
+        when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
+        when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+
+        // Add override for debuggable app.
+        compatConfig.addOverride(1234L, "com.installedapp.foo", true);
+        assertThat(compatConfig.isChangeEnabled(1234L, debuggableApp)).isTrue();
+
+        // Pretend the app now is no longer debuggable, but has the same package.
+        when(mPackageManager.getApplicationInfo(eq("com.installedapp.foo"), anyInt()))
+                .thenReturn(releaseApp);
+
+        compatConfig.recheckOverrides("com.installedapp.foo");
+        assertThat(compatConfig.isChangeEnabled(1234L, releaseApp)).isFalse();
+    }
+
+    @Test
     public void testLoggingOnlyChangePreventAddOverride() throws Exception {
         CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
                 .addLoggingOnlyChangeWithId(1234L)
@@ -259,7 +341,7 @@
         // Reject all override attempts.
         // Force the validator to prevent overriding the change by using a user build.
         when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
-        when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+        when(mBuildClassifier.isFinalBuild()).thenReturn(false);
         // Try to turn off change, but validator prevents it.
         assertThrows(SecurityException.class,
                 () -> compatConfig.removeOverride(1234L, "com.some.package"));
@@ -360,6 +442,8 @@
     @Test
     public void testLookupChangeIdNotPresent() throws Exception {
         CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
+        compatConfig.forceNonDebuggableFinalForTest(false);
+
         assertThat(compatConfig.lookupChangeId("MY_CHANGE")).isEqualTo(-1L);
     }
 
@@ -374,6 +458,8 @@
         File dir = createTempDir();
         writeToFile(dir, "platform_compat_config.xml", configXml);
         CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
+        compatConfig.forceNonDebuggableFinalForTest(false);
+
         compatConfig.initConfigFromLib(dir);
 
         assertThat(compatConfig.isChangeEnabled(1234L,
@@ -400,6 +486,8 @@
         writeToFile(dir, "libcore_platform_compat_config.xml", configXml1);
         writeToFile(dir, "frameworks_platform_compat_config.xml", configXml2);
         CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
+        compatConfig.forceNonDebuggableFinalForTest(false);
+
         compatConfig.initConfigFromLib(dir);
 
         assertThat(compatConfig.isChangeEnabled(1234L,
diff --git a/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java
index c53b29a..0fd6445 100644
--- a/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java
@@ -17,6 +17,7 @@
 package com.android.server.compat;
 
 import static com.android.internal.compat.OverrideAllowedState.ALLOWED;
+import static com.android.internal.compat.OverrideAllowedState.DEFERRED_VERIFICATION;
 import static com.android.internal.compat.OverrideAllowedState.DISABLED_NON_TARGET_SDK;
 import static com.android.internal.compat.OverrideAllowedState.DISABLED_NOT_DEBUGGABLE;
 import static com.android.internal.compat.OverrideAllowedState.DISABLED_TARGET_SDK_TOO_HIGH;
@@ -31,6 +32,7 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -409,4 +411,216 @@
         assertThat(stateDLoggingOnlyChange)
                 .isEqualTo(new OverrideAllowedState(LOGGING_ONLY_CHANGE, -1, -1));
     }
+    @Test
+    public void getOverrideAllowedState_finalBuildAnyChangeNotInstalledApp_deferOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext)
+                        .addEnableAfterSdkChangeWithId(TARGET_SDK_BEFORE, 1)
+                        .addEnableAfterSdkChangeWithId(TARGET_SDK, 2)
+                        .addEnableAfterSdkChangeWithId(TARGET_SDK_AFTER, 3)
+                        .addEnabledChangeWithId(4)
+                        .addDisabledChangeWithId(5)
+                        .addLoggingOnlyChangeWithId(6).build();
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenThrow(new NameNotFoundException());
+
+        OverrideAllowedState stateTargetSdkLessChange =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkEqualChange =
+                overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkAfterChange =
+                overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME);
+        OverrideAllowedState stateEnabledChange =
+                overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME);
+        OverrideAllowedState stateDisabledChange =
+                overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME);
+        OverrideAllowedState stateDLoggingOnlyChange =
+                overrideValidator.getOverrideAllowedState(6, PACKAGE_NAME);
+
+        assertThat(stateTargetSdkLessChange)
+                .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1));
+        assertThat(stateTargetSdkEqualChange)
+                .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1));
+        assertThat(stateTargetSdkAfterChange)
+                .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1));
+        assertThat(stateEnabledChange)
+                .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1));
+        assertThat(stateDisabledChange)
+                .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1));
+        assertThat(stateDLoggingOnlyChange)
+                .isEqualTo(new OverrideAllowedState(LOGGING_ONLY_CHANGE, -1, -1));
+    }
+
+    @Test
+    public void getOverrideAllowedState_forceFinalBuildTargetSdkChangeDebugAppOptin_allowOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(debuggableBuild(), mContext)
+                        .addEnableAfterSdkChangeWithId(TARGET_SDK_AFTER, 1)
+                        .addEnableAfterSdkChangeWithId(TARGET_SDK, 2).build();
+        config.forceNonDebuggableFinalForTest(true);
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .debuggable()
+                        .withTargetSdk(TARGET_SDK)
+                        .withPackageName(PACKAGE_NAME).build());
+
+        OverrideAllowedState stateTargetSdkGreaterChange =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkEqualChange =
+                overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
+
+        assertThat(stateTargetSdkGreaterChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK_AFTER));
+
+        assertThat(stateTargetSdkEqualChange)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK));
+    }
+
+    @Test
+    public void getOverrideAllowedState_forceFinalBldTargetSdkChangeDebugAppOptout_rejectOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(debuggableBuild(), mContext)
+                        .addEnableAfterSdkChangeWithId(TARGET_SDK_BEFORE, 1).build();
+        config.forceNonDebuggableFinalForTest(true);
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .withPackageName(PACKAGE_NAME)
+                        .withTargetSdk(TARGET_SDK)
+                        .debuggable()
+                        .build());
+
+        OverrideAllowedState stateTargetSdkLessChange =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+
+        assertThat(stateTargetSdkLessChange).isEqualTo(
+                new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, TARGET_SDK,
+                                         TARGET_SDK_BEFORE));
+    }
+
+    @Test
+    public void getOverrideAllowedState_forceFinalBuildEnabledChangeDebugApp_rejectOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(debuggableBuild(), mContext)
+                        .addEnabledChangeWithId(1).build();
+        config.forceNonDebuggableFinalForTest(true);
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .withPackageName(PACKAGE_NAME)
+                        .debuggable().build());
+
+        OverrideAllowedState allowedState =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+
+        assertThat(allowedState)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NON_TARGET_SDK, -1, -1));
+    }
+
+    @Test
+    public void getOverrideAllowedState_forceFinalBuildDisabledChangeDebugApp_allowOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(debuggableBuild(), mContext)
+                .addDisabledChangeWithId(1).build();
+        config.forceNonDebuggableFinalForTest(true);
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .withPackageName(PACKAGE_NAME)
+                        .withTargetSdk(TARGET_SDK)
+                        .debuggable().build());
+
+        OverrideAllowedState allowedState =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+
+        assertThat(allowedState)
+                .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, -1));
+    }
+
+    @Test
+    public void getOverrideAllowedState_forceFinalBuildAnyChangeReleaseApp_rejectOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(debuggableBuild(), mContext)
+                        .addEnableAfterSdkChangeWithId(TARGET_SDK_BEFORE, 1)
+                        .addEnableAfterSdkChangeWithId(TARGET_SDK, 2)
+                        .addEnableAfterSdkChangeWithId(TARGET_SDK_AFTER, 3)
+                        .addEnabledChangeWithId(4)
+                        .addDisabledChangeWithId(5)
+                        .addLoggingOnlyChangeWithId(6).build();
+        config.forceNonDebuggableFinalForTest(true);
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenReturn(ApplicationInfoBuilder.create()
+                        .withPackageName(PACKAGE_NAME)
+                        .withTargetSdk(TARGET_SDK).build());
+
+        OverrideAllowedState stateTargetSdkLessChange =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkEqualChange =
+                overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkAfterChange =
+                overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME);
+        OverrideAllowedState stateEnabledChange =
+                overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME);
+        OverrideAllowedState stateDisabledChange =
+                overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME);
+        OverrideAllowedState stateDLoggingOnlyChange =
+                overrideValidator.getOverrideAllowedState(6, PACKAGE_NAME);
+
+        assertThat(stateTargetSdkLessChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+        assertThat(stateTargetSdkEqualChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+        assertThat(stateTargetSdkAfterChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+        assertThat(stateEnabledChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+        assertThat(stateDisabledChange)
+                .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+        assertThat(stateDLoggingOnlyChange)
+                .isEqualTo(new OverrideAllowedState(LOGGING_ONLY_CHANGE, -1, -1));
+    }
+    @Test
+    public void getOverrideAllowedState_forceFinalBuildAnyChangeNotInstalledApp_deferOverride()
+            throws Exception {
+        CompatConfig config = CompatConfigBuilder.create(debuggableBuild(), mContext)
+                        .addEnableAfterSdkChangeWithId(TARGET_SDK_BEFORE, 1)
+                        .addEnableAfterSdkChangeWithId(TARGET_SDK, 2)
+                        .addEnableAfterSdkChangeWithId(TARGET_SDK_AFTER, 3)
+                        .addEnabledChangeWithId(4)
+                        .addDisabledChangeWithId(5)
+                        .addLoggingOnlyChangeWithId(6).build();
+        config.forceNonDebuggableFinalForTest(true);
+        IOverrideValidator overrideValidator = config.getOverrideValidator();
+        when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+                .thenThrow(new NameNotFoundException());
+
+        OverrideAllowedState stateTargetSdkLessChange =
+                overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkEqualChange =
+                overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
+        OverrideAllowedState stateTargetSdkAfterChange =
+                overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME);
+        OverrideAllowedState stateEnabledChange =
+                overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME);
+        OverrideAllowedState stateDisabledChange =
+                overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME);
+        OverrideAllowedState stateDLoggingOnlyChange =
+                overrideValidator.getOverrideAllowedState(6, PACKAGE_NAME);
+
+        assertThat(stateTargetSdkLessChange)
+                .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1));
+        assertThat(stateTargetSdkEqualChange)
+                .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1));
+        assertThat(stateTargetSdkAfterChange)
+                .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1));
+        assertThat(stateEnabledChange)
+                .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1));
+        assertThat(stateDisabledChange)
+                .isEqualTo(new OverrideAllowedState(DEFERRED_VERIFICATION, -1, -1));
+        assertThat(stateDLoggingOnlyChange)
+                .isEqualTo(new OverrideAllowedState(LOGGING_ONLY_CHANGE, -1, -1));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
index 1d3b643..3f65a46 100644
--- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
@@ -73,6 +73,7 @@
         mCompatConfig = new CompatConfig(mBuildClassifier, mContext);
         mPlatformCompat = new PlatformCompat(mContext, mCompatConfig);
         // Assume userdebug/eng non-final build
+        mCompatConfig.forceNonDebuggableFinalForTest(false);
         when(mBuildClassifier.isDebuggableBuild()).thenReturn(true);
         when(mBuildClassifier.isFinalBuild()).thenReturn(false);
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
index 391611b..5468fba 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
@@ -78,7 +78,7 @@
     }
 
     @Test
-    public void testImmutableEnabledChange() {
+    public void testImmutableEnabledChange() throws Exception {
         final OverlayManagerServiceImpl impl = getImpl();
         installNewPackage(target(TARGET), USER);
         installNewPackage(overlay(OVERLAY, TARGET), USER);
@@ -106,7 +106,7 @@
     }
 
     @Test
-    public void testMutableEnabledChangeHasNoEffect() {
+    public void testMutableEnabledChangeHasNoEffect() throws Exception {
         final OverlayManagerServiceImpl impl = getImpl();
         installNewPackage(target(TARGET), USER);
         installNewPackage(overlay(OVERLAY, TARGET), USER);
@@ -134,7 +134,7 @@
     }
 
     @Test
-    public void testMutableEnabledToImmutableEnabled() {
+    public void testMutableEnabledToImmutableEnabled() throws Exception {
         final OverlayManagerServiceImpl impl = getImpl();
         installNewPackage(target(TARGET), USER);
         installNewPackage(overlay(OVERLAY, TARGET), USER);
@@ -178,7 +178,7 @@
     }
 
     @Test
-    public void testMutablePriorityChange() {
+    public void testMutablePriorityChange() throws Exception {
         final OverlayManagerServiceImpl impl = getImpl();
         installNewPackage(target(TARGET), USER);
         installNewPackage(overlay(OVERLAY, TARGET), USER);
@@ -218,7 +218,7 @@
     }
 
     @Test
-    public void testImmutablePriorityChange() {
+    public void testImmutablePriorityChange() throws Exception {
         final OverlayManagerServiceImpl impl = getImpl();
         installNewPackage(target(TARGET), USER);
         installNewPackage(overlay(OVERLAY, TARGET), USER);
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
index 4f882ce..33dbcc0 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
@@ -22,11 +22,14 @@
 import static android.os.OverlayablePolicy.CONFIG_SIGNATURE;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertThrows;
 
 import android.content.om.OverlayInfo;
+import android.util.Pair;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -35,6 +38,7 @@
 
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 @RunWith(AndroidJUnit4.class)
 public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTestsBase {
@@ -55,7 +59,7 @@
     private static final String CERT_CONFIG_NOK = "config_certificate_nok";
 
     @Test
-    public void testGetOverlayInfo() {
+    public void testGetOverlayInfo() throws Exception {
         installNewPackage(overlay(OVERLAY, TARGET), USER);
 
         final OverlayManagerServiceImpl impl = getImpl();
@@ -67,7 +71,7 @@
     }
 
     @Test
-    public void testGetOverlayInfosForTarget() {
+    public void testGetOverlayInfosForTarget() throws Exception {
         installNewPackage(overlay(OVERLAY, TARGET), USER);
         installNewPackage(overlay(OVERLAY2, TARGET), USER);
         installNewPackage(overlay(OVERLAY3, TARGET), USER2);
@@ -92,7 +96,7 @@
     }
 
     @Test
-    public void testGetOverlayInfosForUser() {
+    public void testGetOverlayInfosForUser() throws Exception {
         installNewPackage(target(TARGET), USER);
         installNewPackage(overlay(OVERLAY, TARGET), USER);
         installNewPackage(overlay(OVERLAY2, TARGET), USER);
@@ -119,7 +123,7 @@
     }
 
     @Test
-    public void testPriority() {
+    public void testPriority() throws Exception {
         installNewPackage(overlay(OVERLAY, TARGET), USER);
         installNewPackage(overlay(OVERLAY2, TARGET), USER);
         installNewPackage(overlay(OVERLAY3, TARGET), USER);
@@ -131,18 +135,21 @@
 
         assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
 
-        assertTrue(impl.setLowestPriority(OVERLAY3, USER));
+        assertEquals(impl.setLowestPriority(OVERLAY3, USER),
+                Optional.of(new PackageAndUser(TARGET, USER)));
         assertOverlayInfoForTarget(TARGET, USER, o3, o1, o2);
 
-        assertTrue(impl.setHighestPriority(OVERLAY3, USER));
+        assertEquals(impl.setHighestPriority(OVERLAY3, USER),
+                Optional.of(new PackageAndUser(TARGET, USER)));
         assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
 
-        assertTrue(impl.setPriority(OVERLAY, OVERLAY2, USER));
+        assertEquals(impl.setPriority(OVERLAY, OVERLAY2, USER),
+                Optional.of(new PackageAndUser(TARGET, USER)));
         assertOverlayInfoForTarget(TARGET, USER, o2, o1, o3);
     }
 
     @Test
-    public void testOverlayInfoStateTransitions() {
+    public void testOverlayInfoStateTransitions() throws Exception {
         final OverlayManagerServiceImpl impl = getImpl();
         assertNull(impl.getOverlayInfo(OVERLAY, USER));
 
@@ -153,7 +160,8 @@
         installNewPackage(target, USER);
         assertState(STATE_DISABLED, OVERLAY, USER);
 
-        impl.setEnabled(OVERLAY, true, USER);
+        assertEquals(impl.setEnabled(OVERLAY, true, USER),
+                Optional.of(new PackageAndUser(TARGET, USER)));
         assertState(STATE_ENABLED, OVERLAY, USER);
 
         // target upgrades do not change the state of the overlay
@@ -168,50 +176,40 @@
     }
 
     @Test
-    public void testOnOverlayPackageUpgraded() {
-        final FakeListener listener = getListener();
+    public void testOnOverlayPackageUpgraded() throws Exception {
         final FakeDeviceState.PackageBuilder target = target(TARGET);
         final FakeDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET);
         installNewPackage(target, USER);
         installNewPackage(overlay, USER);
-        listener.count = 0;
         upgradePackage(overlay, USER);
-        assertEquals(2, listener.count);
 
         // upgrade to a version where the overlay has changed its target
-        // expect once for the old target package, once for the new target package
-        listener.count = 0;
         final FakeDeviceState.PackageBuilder overlay2 = overlay(OVERLAY, "some.other.target");
-        upgradePackage(overlay2, USER);
-        assertEquals(3, listener.count);
-
-        listener.count = 0;
-        upgradePackage(overlay2, USER);
-        assertEquals(2, listener.count);
+        final Pair<Optional<PackageAndUser>, Optional<PackageAndUser>> pair =
+                upgradePackage(overlay2, USER);
+        assertEquals(pair.first, Optional.of(new PackageAndUser(TARGET, USER)));
+        assertEquals(pair.second, Optional.of(new PackageAndUser("some.other.target", USER)));
     }
 
     @Test
-    public void testListener() {
+    public void testSetEnabledAtVariousConditions() throws Exception {
         final OverlayManagerServiceImpl impl = getImpl();
-        final FakeListener listener = getListener();
-        installNewPackage(overlay(OVERLAY, TARGET), USER);
-        assertEquals(1, listener.count);
-        listener.count = 0;
+        assertThrows(OverlayManagerServiceImpl.OperationFailedException.class,
+                () -> impl.setEnabled(OVERLAY, true, USER));
 
+        // request succeeded, and there was a change that needs to be
+        // propagated to the rest of the system
         installNewPackage(target(TARGET), USER);
-        assertEquals(1, listener.count);
-        listener.count = 0;
+        installNewPackage(overlay(OVERLAY, TARGET), USER);
+        assertEquals(impl.setEnabled(OVERLAY, true, USER),
+                Optional.of(new PackageAndUser(TARGET, USER)));
 
-        impl.setEnabled(OVERLAY, true, USER);
-        assertEquals(1, listener.count);
-        listener.count = 0;
-
-        impl.setEnabled(OVERLAY, true, USER);
-        assertEquals(0, listener.count);
+        // request succeeded, but nothing changed
+        assertFalse(impl.setEnabled(OVERLAY, true, USER).isPresent());
     }
 
     @Test
-    public void testConfigSignaturePolicyOk() {
+    public void testConfigSignaturePolicyOk() throws Exception {
         setConfigSignaturePackageName(CONFIG_SIGNATURE_REFERENCE_PKG);
         reinitializeImpl();
 
@@ -229,7 +227,7 @@
     }
 
     @Test
-    public void testConfigSignaturePolicyCertNok() {
+    public void testConfigSignaturePolicyCertNok() throws Exception {
         setConfigSignaturePackageName(CONFIG_SIGNATURE_REFERENCE_PKG);
         reinitializeImpl();
 
@@ -247,7 +245,7 @@
     }
 
     @Test
-    public void testConfigSignaturePolicyNoConfig() {
+    public void testConfigSignaturePolicyNoConfig() throws Exception {
         addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
         installNewPackage(target(TARGET), USER);
         installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
@@ -262,7 +260,7 @@
     }
 
     @Test
-    public void testConfigSignaturePolicyNoRefPkg() {
+    public void testConfigSignaturePolicyNoRefPkg() throws Exception {
         installNewPackage(target(TARGET), USER);
         installNewPackage(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER);
 
@@ -276,7 +274,7 @@
     }
 
     @Test
-    public void testConfigSignaturePolicyRefPkgNotSystem() {
+    public void testConfigSignaturePolicyRefPkgNotSystem() throws Exception {
         setConfigSignaturePackageName(CONFIG_SIGNATURE_REFERENCE_PKG);
         reinitializeImpl();
 
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
index 006dda0..2c477c8 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
@@ -16,6 +16,8 @@
 
 package com.android.server.om;
 
+import static com.android.server.om.OverlayManagerServiceImpl.OperationFailedException;
+
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
@@ -30,6 +32,7 @@
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Pair;
 
 import androidx.annotation.Nullable;
 
@@ -43,13 +46,13 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.stream.Collectors;
 
 /** Base class for creating {@link OverlayManagerServiceImplTests} tests. */
 class OverlayManagerServiceImplTestsBase {
     private OverlayManagerServiceImpl mImpl;
     private FakeDeviceState mState;
-    private FakeListener mListener;
     private FakePackageManagerHelper mPackageManager;
     private FakeIdmapDaemon mIdmapDaemon;
     private OverlayConfig mOverlayConfig;
@@ -58,7 +61,6 @@
     @Before
     public void setUp() {
         mState = new FakeDeviceState();
-        mListener = new FakeListener();
         mPackageManager = new FakePackageManagerHelper(mState);
         mIdmapDaemon = new FakeIdmapDaemon(mState);
         mOverlayConfig = mock(OverlayConfig.class);
@@ -73,18 +75,13 @@
                 new IdmapManager(mIdmapDaemon, mPackageManager),
                 new OverlayManagerSettings(),
                 mOverlayConfig,
-                new String[0],
-                mListener);
+                new String[0]);
     }
 
     OverlayManagerServiceImpl getImpl() {
         return mImpl;
     }
 
-    FakeListener getListener() {
-        return mListener;
-    }
-
     FakeIdmapDaemon getIdmapd() {
         return mIdmapDaemon;
     }
@@ -155,7 +152,8 @@
      *
      * @throws IllegalStateException if the package is currently installed
      */
-    void installNewPackage(FakeDeviceState.PackageBuilder pkg, int userId) {
+    void installNewPackage(FakeDeviceState.PackageBuilder pkg, int userId)
+            throws OperationFailedException {
         if (mState.select(pkg.packageName, userId) != null) {
             throw new IllegalStateException("package " + pkg.packageName + " already installed");
         }
@@ -176,23 +174,30 @@
      * {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast with the
      * {@link android.content.Intent#EXTRA_REPLACING} extra.
      *
+     * @return the two Optional<PackageAndUser> objects from starting and finishing the upgrade
+     *
      * @throws IllegalStateException if the package is not currently installed
      */
-    void upgradePackage(FakeDeviceState.PackageBuilder pkg, int userId) {
+    Pair<Optional<PackageAndUser>, Optional<PackageAndUser>> upgradePackage(
+            FakeDeviceState.PackageBuilder pkg, int userId) throws OperationFailedException {
         final FakeDeviceState.Package replacedPackage = mState.select(pkg.packageName, userId);
         if (replacedPackage == null) {
             throw new IllegalStateException("package " + pkg.packageName + " not installed");
         }
+        Optional<PackageAndUser> opt1 = Optional.empty();
         if (replacedPackage.targetPackageName != null) {
-            mImpl.onOverlayPackageReplacing(pkg.packageName, userId);
+            opt1 = mImpl.onOverlayPackageReplacing(pkg.packageName, userId);
         }
 
         mState.add(pkg, userId);
+        Optional<PackageAndUser> opt2;
         if (pkg.targetPackage == null) {
-            mImpl.onTargetPackageReplaced(pkg.packageName, userId);
+            opt2 = mImpl.onTargetPackageReplaced(pkg.packageName, userId);
         } else {
-            mImpl.onOverlayPackageReplaced(pkg.packageName, userId);
+            opt2 = mImpl.onOverlayPackageReplaced(pkg.packageName, userId);
         }
+
+        return Pair.create(opt1, opt2);
     }
 
     /**
@@ -203,7 +208,7 @@
      *
      * @throws IllegalStateException if the package is not currently installed
      */
-    void uninstallPackage(String packageName, int userId) {
+    void uninstallPackage(String packageName, int userId) throws OperationFailedException {
         final FakeDeviceState.Package pkg = mState.select(packageName, userId);
         if (pkg == null) {
             throw new IllegalStateException("package " + packageName+ " not installed");
@@ -485,12 +490,4 @@
             }
         }
     }
-
-    static class FakeListener implements OverlayManagerServiceImpl.OverlayChangeListener {
-        public int count;
-
-        public void onOverlaysChanged(@NonNull String targetPackage, int userId) {
-            count++;
-        }
-    }
 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 7d3cef5..c05e90b 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -8679,6 +8679,77 @@
         return Collections.EMPTY_LIST;
     }
 
+    /**
+     * Call composer status OFF from user setting.
+     */
+    public static final int CALL_COMPOSER_STATUS_OFF = 0;
+
+    /**
+     * Call composer status ON from user setting.
+     */
+    public static final int CALL_COMPOSER_STATUS_ON = 1;
+
+    /** @hide */
+    @IntDef(prefix = {"CALL_COMPOSER_STATUS_"},
+            value = {
+                CALL_COMPOSER_STATUS_ON,
+                CALL_COMPOSER_STATUS_OFF,
+            })
+    public @interface CallComposerStatus {}
+
+    /**
+     * Set the user-set status for enriched calling with call composer.
+     *
+     * @param status user-set status for enriched calling with call composer;
+     *               it must be a value of either {@link #CALL_COMPOSER_STATUS_ON}
+     *               or {@link #CALL_COMPOSER_STATUS_OFF}.
+     *
+     * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
+     * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
+     *
+     * @throws IllegalArgumentException if requested state is invalid.
+     * @throws SecurityException if the caller does not have the permission.
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    public void setCallComposerStatus(@CallComposerStatus int status) {
+        if (status != CALL_COMPOSER_STATUS_ON && status != CALL_COMPOSER_STATUS_OFF) {
+            throw new IllegalArgumentException("requested status is invalid");
+        }
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                telephony.setCallComposerStatus(getSubId(), status);
+            }
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Error calling ITelephony#setCallComposerStatus", ex);
+            ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the user-set status for enriched calling with call composer.
+     *
+     * <p>If this object has been created with {@link #createForSubscriptionId}, applies to the
+     * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}
+     *
+     * @throws SecurityException if the caller does not have the permission.
+     *
+     * @return the user-set status for enriched calling with call composer either
+     * {@link #CALL_COMPOSER_STATUS_ON} or {@link #CALL_COMPOSER_STATUS_OFF}.
+     */
+    @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    public @CallComposerStatus int getCallComposerStatus() {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getCallComposerStatus(getSubId());
+            }
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Error calling ITelephony#getCallComposerStatus", ex);
+            ex.rethrowFromSystemServer();
+        }
+        return CALL_COMPOSER_STATUS_OFF;
+    }
 
     /** @hide */
     @SystemApi
diff --git a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
index b0aaa92..e4d20e9 100644
--- a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
+++ b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
@@ -39,6 +39,10 @@
     /** The service id of the MMTEL */
     public static final String SERVICE_ID_MMTEL = "org.3gpp.urn:urn-7:3gpp-service.ims.icsi.mmtel";
 
+    /** The service id of the Call Composer */
+    public static final String SERVICE_ID_CALL_COMPOSER =
+            "org.3gpp.urn:urn-7:3gppservice.ims.icsi.gsma.callcomposer";
+
     /** The service capabilities is available. */
     public static final String TUPLE_BASIC_STATUS_OPEN = "open";
 
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 33acc15..c60a44c 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -128,6 +128,15 @@
      */
     boolean isRadioOnForSubscriberWithFeature(int subId, String callingPackage, String callingFeatureId);
 
+    /**
+     * Set the user-set status for enriched calling with call composer.
+     */
+    void setCallComposerStatus(int subId, int status);
+
+    /**
+     * Get the user-set status for enriched calling with call composer.
+     */
+    int getCallComposerStatus(int subId);
 
     /**
      * Supply a pin to unlock the SIM for particular subId.
diff --git a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
index 70f6386..8e18751 100644
--- a/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
+++ b/tests/net/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt
@@ -25,7 +25,6 @@
 import android.net.ConnectivityManager
 import android.net.IDnsResolver
 import android.net.INetd
-import android.net.INetworkPolicyManager
 import android.net.INetworkStatsService
 import android.net.LinkProperties
 import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
@@ -88,8 +87,6 @@
     @Mock
     private lateinit var statsService: INetworkStatsService
     @Mock
-    private lateinit var policyManager: INetworkPolicyManager
-    @Mock
     private lateinit var log: IpConnectivityLog
     @Mock
     private lateinit var netd: INetd
@@ -171,7 +168,7 @@
     }
 
     private inner class TestConnectivityService(deps: Dependencies) : ConnectivityService(
-            context, netManager, statsService, policyManager, dnsResolver, log, netd, deps)
+            context, netManager, statsService, dnsResolver, log, netd, deps)
 
     private fun makeDependencies(): ConnectivityService.Dependencies {
         val deps = spy(ConnectivityService.Dependencies())
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 5b6f637..9421acd 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -166,7 +166,6 @@
 import android.net.INetworkMonitor;
 import android.net.INetworkMonitorCallbacks;
 import android.net.INetworkPolicyListener;
-import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
 import android.net.InetAddresses;
 import android.net.InterfaceConfigurationParcel;
@@ -183,6 +182,7 @@
 import android.net.NetworkFactory;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkPolicyManager;
 import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
 import android.net.NetworkStack;
@@ -299,6 +299,7 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 import kotlin.reflect.KClass;
 
@@ -365,7 +366,6 @@
     @Mock INetworkManagementService mNetworkManagementService;
     @Mock INetworkStatsService mStatsService;
     @Mock IBatteryStats mBatteryStatsService;
-    @Mock INetworkPolicyManager mNpm;
     @Mock IDnsResolver mMockDnsResolver;
     @Mock INetd mMockNetd;
     @Mock NetworkStackClient mNetworkStack;
@@ -380,6 +380,7 @@
     @Mock TelephonyManager mTelephonyManager;
     @Mock MockableSystemProperties mSystemProperties;
     @Mock EthernetManager mEthernetManager;
+    @Mock NetworkPolicyManager mNetworkPolicyManager;
 
     private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
             ArgumentCaptor.forClass(ResolverParamsParcel.class);
@@ -412,6 +413,7 @@
 
         @Spy private Resources mResources;
         private final LinkedBlockingQueue<Intent> mStartedActivities = new LinkedBlockingQueue<>();
+
         // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
         private final HashMap<String, Integer> mMockedPermissions = new HashMap<>();
 
@@ -477,6 +479,7 @@
             if (Context.APP_OPS_SERVICE.equals(name)) return mAppOpsManager;
             if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager;
             if (Context.ETHERNET_SERVICE.equals(name)) return mEthernetManager;
+            if (Context.NETWORK_POLICY_SERVICE.equals(name)) return mNetworkPolicyManager;
             return super.getSystemService(name);
         }
 
@@ -1326,7 +1329,6 @@
         mService = new ConnectivityService(mServiceContext,
                 mNetworkManagementService,
                 mStatsService,
-                mNpm,
                 mMockDnsResolver,
                 mock(IpConnectivityLog.class),
                 mMockNetd,
@@ -1336,7 +1338,7 @@
 
         final ArgumentCaptor<INetworkPolicyListener> policyListenerCaptor =
                 ArgumentCaptor.forClass(INetworkPolicyListener.class);
-        verify(mNpm).registerListener(policyListenerCaptor.capture());
+        verify(mNetworkPolicyManager).registerListener(policyListenerCaptor.capture());
         mPolicyListener = policyListenerCaptor.getValue();
 
         // Create local CM before sending system ready so that we can answer
@@ -6509,6 +6511,26 @@
         checkNetworkInfo(mCm.getNetworkInfo(type), type, state);
     }
 
+    // Checks that each of the |agents| receive a blocked status change callback with the specified
+    // |blocked| value, in any order. This is needed because when an event affects multiple
+    // networks, ConnectivityService does not guarantee the order in which callbacks are fired.
+    private void assertBlockedCallbackInAnyOrder(TestNetworkCallback callback, boolean blocked,
+            TestNetworkAgentWrapper... agents) {
+        final List<Network> expectedNetworks = Arrays.asList(agents).stream()
+                .map((agent) -> agent.getNetwork())
+                .collect(Collectors.toList());
+
+        // Expect exactly one blocked callback for each agent.
+        for (int i = 0; i < agents.length; i++) {
+            CallbackEntry e = callback.expectCallbackThat(TIMEOUT_MS, (c) ->
+                    c instanceof CallbackEntry.BlockedStatus
+                            && ((CallbackEntry.BlockedStatus) c).getBlocked() == blocked);
+            Network network = e.getNetwork();
+            assertTrue("Received unexpected blocked callback for network " + network,
+                    expectedNetworks.remove(network));
+        }
+    }
+
     @Test
     public void testNetworkBlockedStatusAlwaysOnVpn() throws Exception {
         mServiceContext.setPermission(
@@ -6555,9 +6577,10 @@
         assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED);
 
         // Disable lockdown, expect to see the network unblocked.
-        // There are no callbacks because they are not implemented yet.
         mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
         expectNetworkRejectNonSecureVpn(inOrder, false, firstHalf, secondHalf);
+        callback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
+        defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
         vpnUidCallback.assertNoCallback();
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -6605,6 +6628,8 @@
         allowList.clear();
         mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
         expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf);
+        defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent);
+        assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent);
         vpnUidCallback.assertNoCallback();
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertNull(mCm.getActiveNetwork());
@@ -6614,6 +6639,8 @@
 
         // Disable lockdown. Everything is unblocked.
         mService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList);
+        defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent);
+        assertBlockedCallbackInAnyOrder(callback, false, mWiFiNetworkAgent, mCellNetworkAgent);
         vpnUidCallback.assertNoCallback();
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
@@ -6647,6 +6674,8 @@
 
         // Enable lockdown and connect a VPN. The VPN is not blocked.
         mService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, allowList);
+        defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent);
+        assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent);
         vpnUidCallback.assertNoCallback();
         assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertNull(mCm.getActiveNetwork());
@@ -6658,7 +6687,7 @@
         defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn);
         vpnUidCallback.assertNoCallback();  // vpnUidCallback has NOT_VPN capability.
         assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork());
-        assertEquals(null, mCm.getActiveNetworkForUid(VPN_UID));  // BUG?
+        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID));
         assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED);
         assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED);
         assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED);
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index cc47317..3648c4d 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -27,7 +27,6 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-import static org.mockito.AdditionalMatchers.aryEq;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -89,6 +88,7 @@
 import android.security.KeyStore;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Range;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -350,7 +350,7 @@
 
         // Set always-on with lockdown.
         assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null, mKeyStore));
-        verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(new UidRangeParcel[] {
+        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1),
                 new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop)
         }));
@@ -361,12 +361,11 @@
 
         // Switch to another app.
         assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore));
-        verify(mNetd).networkRejectNonSecureVpn(eq(false), aryEq(new UidRangeParcel[] {
+        verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1),
                 new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop)
         }));
-
-        verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(new UidRangeParcel[] {
+        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start, user.start + PKG_UIDS[3] - 1),
                 new UidRangeParcel(user.start + PKG_UIDS[3] + 1, user.stop)
         }));
@@ -383,7 +382,7 @@
         // Set always-on with lockdown and allow app PKGS[2] from lockdown.
         assertTrue(vpn.setAlwaysOnPackage(
                 PKGS[1], true, Collections.singletonList(PKGS[2]), mKeyStore));
-        verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(new UidRangeParcel[] {
+        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1),
                 new UidRangeParcel(user.start + PKG_UIDS[2] + 1, user.stop)
         }));
@@ -392,10 +391,10 @@
         // Change allowed app list to PKGS[3].
         assertTrue(vpn.setAlwaysOnPackage(
                 PKGS[1], true, Collections.singletonList(PKGS[3]), mKeyStore));
-        verify(mNetd).networkRejectNonSecureVpn(eq(false), aryEq(new UidRangeParcel[] {
+        verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start + PKG_UIDS[2] + 1, user.stop)
         }));
-        verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(new UidRangeParcel[] {
+        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1),
                 new UidRangeParcel(user.start + PKG_UIDS[3] + 1, user.stop)
         }));
@@ -405,11 +404,11 @@
         // Change the VPN app.
         assertTrue(vpn.setAlwaysOnPackage(
                 PKGS[0], true, Collections.singletonList(PKGS[3]), mKeyStore));
-        verify(mNetd).networkRejectNonSecureVpn(eq(false), aryEq(new UidRangeParcel[] {
+        verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start, user.start + PKG_UIDS[1] - 1),
                 new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1)
         }));
-        verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(new UidRangeParcel[] {
+        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start, user.start + PKG_UIDS[0] - 1),
                 new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1)
         }));
@@ -418,11 +417,11 @@
 
         // Remove the list of allowed packages.
         assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null, mKeyStore));
-        verify(mNetd).networkRejectNonSecureVpn(eq(false), aryEq(new UidRangeParcel[] {
+        verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1),
                 new UidRangeParcel(user.start + PKG_UIDS[3] + 1, user.stop)
         }));
-        verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(new UidRangeParcel[] {
+        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.stop),
         }));
         assertBlocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2],
@@ -432,10 +431,10 @@
         // Add the list of allowed packages.
         assertTrue(vpn.setAlwaysOnPackage(
                 PKGS[0], true, Collections.singletonList(PKGS[1]), mKeyStore));
-        verify(mNetd).networkRejectNonSecureVpn(eq(false), aryEq(new UidRangeParcel[] {
+        verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.stop)
         }));
-        verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(new UidRangeParcel[] {
+        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1),
                 new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop)
         }));
@@ -450,11 +449,11 @@
         // allowed package should change from PGKS[1] to PKGS[2].
         assertTrue(vpn.setAlwaysOnPackage(
                 PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app"), mKeyStore));
-        verify(mNetd).networkRejectNonSecureVpn(eq(false), aryEq(new UidRangeParcel[]{
+        verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1),
                 new UidRangeParcel(user.start + PKG_UIDS[1] + 1, user.stop)
         }));
-        verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(new UidRangeParcel[]{
+        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[2] - 1),
                 new UidRangeParcel(user.start + PKG_UIDS[2] + 1, user.stop)
         }));
@@ -475,7 +474,7 @@
 
         // Set lockdown.
         assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore));
-        verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(new UidRangeParcel[] {
+        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(user.start, user.start + PKG_UIDS[3] - 1),
                 new UidRangeParcel(user.start + PKG_UIDS[3] + 1, user.stop)
         }));
@@ -485,7 +484,7 @@
         // Add the restricted user.
         setMockedUsers(primaryUser, tempProfile);
         vpn.onUserAdded(tempProfile.id);
-        verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(new UidRangeParcel[] {
+        verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(profile.start, profile.start + PKG_UIDS[3] - 1),
                 new UidRangeParcel(profile.start + PKG_UIDS[3] + 1, profile.stop)
         }));
@@ -493,7 +492,7 @@
         // Remove the restricted user.
         tempProfile.partial = true;
         vpn.onUserRemoved(tempProfile.id);
-        verify(mNetd).networkRejectNonSecureVpn(eq(false), aryEq(new UidRangeParcel[] {
+        verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(new UidRangeParcel[] {
                 new UidRangeParcel(profile.start, profile.start + PKG_UIDS[3] - 1),
                 new UidRangeParcel(profile.start + PKG_UIDS[3] + 1, profile.stop)
         }));
@@ -506,22 +505,29 @@
                 new UidRangeParcel(PRI_USER_RANGE.start, PRI_USER_RANGE.stop)};
         // Given legacy lockdown is already enabled,
         vpn.setLockdown(true);
-
-        verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(primaryUserRangeParcel));
+        verify(mConnectivityManager, times(1)).setRequireVpnForUids(true,
+                toRanges(primaryUserRangeParcel));
 
         // Enabling legacy lockdown twice should do nothing.
         vpn.setLockdown(true);
-        verify(mNetd, times(1))
-                .networkRejectNonSecureVpn(anyBoolean(), any(UidRangeParcel[].class));
+        verify(mConnectivityManager, times(1)).setRequireVpnForUids(anyBoolean(), any());
 
         // And disabling should remove the rules exactly once.
         vpn.setLockdown(false);
-        verify(mNetd).networkRejectNonSecureVpn(eq(false), aryEq(primaryUserRangeParcel));
+        verify(mConnectivityManager, times(1)).setRequireVpnForUids(false,
+                toRanges(primaryUserRangeParcel));
 
         // Removing the lockdown again should have no effect.
         vpn.setLockdown(false);
-        verify(mNetd, times(2)).networkRejectNonSecureVpn(
-                anyBoolean(), any(UidRangeParcel[].class));
+        verify(mConnectivityManager, times(2)).setRequireVpnForUids(anyBoolean(), any());
+    }
+
+    private ArrayList<Range<Integer>> toRanges(UidRangeParcel[] ranges) {
+        ArrayList<Range<Integer>> rangesArray = new ArrayList<>(ranges.length);
+        for (int i = 0; i < ranges.length; i++) {
+            rangesArray.add(new Range<>(ranges[i].start, ranges[i].stop));
+        }
+        return rangesArray;
     }
 
     @Test
@@ -535,21 +541,21 @@
             new UidRangeParcel(entireUser[0].start + PKG_UIDS[0] + 1, entireUser[0].stop)
         };
 
-        final InOrder order = inOrder(mNetd);
+        final InOrder order = inOrder(mConnectivityManager);
 
         // Given lockdown is enabled with no package (legacy VPN),
         vpn.setLockdown(true);
-        order.verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(entireUser));
+        order.verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(entireUser));
 
         // When a new VPN package is set the rules should change to cover that package.
         vpn.prepare(null, PKGS[0], VpnManager.TYPE_VPN_SERVICE);
-        order.verify(mNetd).networkRejectNonSecureVpn(eq(false), aryEq(entireUser));
-        order.verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(exceptPkg0));
+        order.verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(entireUser));
+        order.verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(exceptPkg0));
 
         // When that VPN package is unset, everything should be undone again in reverse.
         vpn.prepare(null, VpnConfig.LEGACY_VPN, VpnManager.TYPE_VPN_SERVICE);
-        order.verify(mNetd).networkRejectNonSecureVpn(eq(false), aryEq(exceptPkg0));
-        order.verify(mNetd).networkRejectNonSecureVpn(eq(true), aryEq(entireUser));
+        order.verify(mConnectivityManager).setRequireVpnForUids(false, toRanges(exceptPkg0));
+        order.verify(mConnectivityManager).setRequireVpnForUids(true, toRanges(entireUser));
     }
 
     @Test
diff --git a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
index 89146f9..435c3c0 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsCollectionTest.java
@@ -64,7 +64,6 @@
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.io.DataOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.InputStream;
@@ -124,7 +123,7 @@
 
         // now export into a unified format
         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        collection.write(new DataOutputStream(bos));
+        collection.write(bos);
 
         // clear structure completely
         collection.reset();
@@ -152,7 +151,7 @@
 
         // now export into a unified format
         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        collection.write(new DataOutputStream(bos));
+        collection.write(bos);
 
         // clear structure completely
         collection.reset();
@@ -180,7 +179,7 @@
 
         // now export into a unified format
         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        collection.write(new DataOutputStream(bos));
+        collection.write(bos);
 
         // clear structure completely
         collection.reset();