Merge "Make StubVolumes visible for read"
diff --git a/apex/media/framework/java/android/media/MediaTranscodingManager.java b/apex/media/framework/java/android/media/MediaTranscodingManager.java
index 93d58d0..1a84929 100644
--- a/apex/media/framework/java/android/media/MediaTranscodingManager.java
+++ b/apex/media/framework/java/android/media/MediaTranscodingManager.java
@@ -952,6 +952,8 @@
              *
              * @return the video track format to be used if transcoding should be performed,
              *         and null otherwise.
+             * @throws IllegalArgumentException if the hinted source video format contains invalid
+             *         parameters.
              */
             @Nullable
             public MediaFormat resolveVideoFormat() {
@@ -962,20 +964,19 @@
                 MediaFormat videoTrackFormat = new MediaFormat(mSrcVideoFormatHint);
                 videoTrackFormat.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_VIDEO_AVC);
 
-                int width = mSrcVideoFormatHint.getInteger(MediaFormat.KEY_WIDTH);
-                int height = mSrcVideoFormatHint.getInteger(MediaFormat.KEY_HEIGHT);
+                int width = mSrcVideoFormatHint.getInteger(MediaFormat.KEY_WIDTH, -1);
+                int height = mSrcVideoFormatHint.getInteger(MediaFormat.KEY_HEIGHT, -1);
                 if (width <= 0 || height <= 0) {
                     throw new IllegalArgumentException(
                             "Source Width and height must be larger than 0");
                 }
 
-                float frameRate = 30.0f; // default to 30fps.
-                if (mSrcVideoFormatHint.containsKey(MediaFormat.KEY_FRAME_RATE)) {
-                    frameRate = mSrcVideoFormatHint.getFloat(MediaFormat.KEY_FRAME_RATE);
-                    if (frameRate <= 0) {
-                        throw new IllegalArgumentException(
-                                "frameRate must be larger than 0");
-                    }
+                float frameRate =
+                        mSrcVideoFormatHint.getNumber(MediaFormat.KEY_FRAME_RATE, 30.0)
+                        .floatValue();
+                if (frameRate <= 0) {
+                    throw new IllegalArgumentException(
+                            "frameRate must be larger than 0");
                 }
 
                 int bitrate = getAVCBitrate(width, height, frameRate);
diff --git a/core/api/current.txt b/core/api/current.txt
index a70a0a9..9922615 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9252,7 +9252,8 @@
     method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
     method public android.bluetooth.BluetoothGattService getService(java.util.UUID);
     method public java.util.List<android.bluetooth.BluetoothGattService> getServices();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean notifyCharacteristicChanged(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothGattCharacteristic, boolean);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean notifyCharacteristicChanged(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothGattCharacteristic, boolean);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int notifyCharacteristicChanged(@NonNull android.bluetooth.BluetoothDevice, @NonNull android.bluetooth.BluetoothGattCharacteristic, boolean, @NonNull byte[]);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void readPhy(android.bluetooth.BluetoothDevice);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean removeService(android.bluetooth.BluetoothGattService);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean sendResponse(android.bluetooth.BluetoothDevice, int, int, int, byte[]);
@@ -31513,6 +31514,7 @@
     method @Deprecated @Nullable public android.os.Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader);
     method @Nullable public <T> android.os.Parcelable.Creator<T> readParcelableCreator(@Nullable ClassLoader, @NonNull Class<T>);
     method @NonNull public <T extends android.os.Parcelable> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader);
+    method @NonNull public <T> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader, @NonNull Class<T>);
     method @Nullable public android.os.PersistableBundle readPersistableBundle();
     method @Nullable public android.os.PersistableBundle readPersistableBundle(@Nullable ClassLoader);
     method @Deprecated @Nullable public java.io.Serializable readSerializable();
diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java
index 3e799de..08e0178 100644
--- a/core/java/android/bluetooth/BluetoothGattServer.java
+++ b/core/java/android/bluetooth/BluetoothGattServer.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.RequiresNoPermission;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
@@ -26,6 +28,8 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.UUID;
@@ -709,33 +713,85 @@
      * notification
      * @return true, if the notification has been triggered successfully
      * @throws IllegalArgumentException
+     *
+     * @deprecated Use {@link BluetoothGattServer#notifyCharacteristicChanged(BluetoothDevice,
+     * BluetoothGattCharacteristic, boolean, byte[])}  as this is not memory safe.
      */
+    @Deprecated
     @RequiresLegacyBluetoothPermission
     @RequiresBluetoothConnectPermission
     @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
     public boolean notifyCharacteristicChanged(BluetoothDevice device,
             BluetoothGattCharacteristic characteristic, boolean confirm) {
+        return notifyCharacteristicChanged(device, characteristic, confirm,
+                characteristic.getValue()) == BluetoothStatusCodes.SUCCESS;
+    }
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {
+            BluetoothStatusCodes.SUCCESS,
+            BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION,
+            BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_PRIVILEGED_PERMISSION,
+            BluetoothStatusCodes.ERROR_DEVICE_NOT_CONNECTED,
+            BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND,
+            BluetoothStatusCodes.ERROR_GATT_WRITE_NOT_ALLOWED,
+            BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY,
+            BluetoothStatusCodes.ERROR_UNKNOWN
+    })
+    public @interface NotifyCharacteristicReturnValues{}
+
+    /**
+     * Send a notification or indication that a local characteristic has been
+     * updated.
+     *
+     * <p>A notification or indication is sent to the remote device to signal
+     * that the characteristic has been updated. This function should be invoked
+     * for every client that requests notifications/indications by writing
+     * to the "Client Configuration" descriptor for the given characteristic.
+     *
+     * @param device the remote device to receive the notification/indication
+     * @param characteristic the local characteristic that has been updated
+     * @param confirm {@code true} to request confirmation from the client (indication) or
+     * {@code false} to send a notification
+     * @param value the characteristic value
+     * @return whether the notification has been triggered successfully
+     * @throws IllegalArgumentException if the characteristic value or service is null
+     */
+    @RequiresLegacyBluetoothPermission
+    @RequiresBluetoothConnectPermission
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
+    @NotifyCharacteristicReturnValues
+    public int notifyCharacteristicChanged(@NonNull BluetoothDevice device,
+            @NonNull BluetoothGattCharacteristic characteristic, boolean confirm,
+            @NonNull byte[] value) {
         if (VDBG) Log.d(TAG, "notifyCharacteristicChanged() - device: " + device.getAddress());
-        if (mService == null || mServerIf == 0) return false;
+        if (mService == null || mServerIf == 0) {
+            return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND;
+        }
 
+        if (characteristic == null) {
+            throw new IllegalArgumentException("characteristic must not be null");
+        }
+        if (device == null) {
+            throw new IllegalArgumentException("device must not be null");
+        }
         BluetoothGattService service = characteristic.getService();
-        if (service == null) return false;
-
-        if (characteristic.getValue() == null) {
-            throw new IllegalArgumentException("Chracteristic value is empty. Use "
-                    + "BluetoothGattCharacteristic#setvalue to update");
+        if (service == null) {
+            throw new IllegalArgumentException("Characteristic must have a non-null service");
+        }
+        if (value == null) {
+            throw new IllegalArgumentException("Characteristic value must not be null");
         }
 
         try {
-            mService.sendNotification(mServerIf, device.getAddress(),
+            return mService.sendNotification(mServerIf, device.getAddress(),
                     characteristic.getInstanceId(), confirm,
-                    characteristic.getValue(), mAttributionSource);
+                    value, mAttributionSource);
         } catch (RemoteException e) {
             Log.e(TAG, "", e);
-            return false;
+            throw e.rethrowFromSystemServer();
         }
-
-        return true;
     }
 
     /**
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index 1655b62..db5b751 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -18,7 +18,6 @@
 
 import android.annotation.RequiresNoPermission;
 import android.annotation.RequiresPermission;
-import android.annotation.SuppressLint;
 import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.net.LocalSocket;
@@ -266,7 +265,7 @@
             throw new IOException("bt socket acept failed");
         }
 
-        as.mPfd = new ParcelFileDescriptor(fds[0]);
+        as.mPfd = ParcelFileDescriptor.dup(fds[0]);
         as.mSocket = LocalSocket.createConnectedLocalSocket(fds[0]);
         as.mSocketIS = as.mSocket.getInputStream();
         as.mSocketOS = as.mSocket.getOutputStream();
diff --git a/core/java/android/hardware/OWNERS b/core/java/android/hardware/OWNERS
index 2b4e4a1..95f13b5 100644
--- a/core/java/android/hardware/OWNERS
+++ b/core/java/android/hardware/OWNERS
@@ -6,3 +6,7 @@
 
 # Sensors framework
 per-file *Sensor*,*Trigger* = file:platform/frameworks/native:/services/sensorservice/OWNERS
+
+# Buffers
+per-file HardwareBuffer* = file:/graphics/java/android/graphics/OWNERS
+per-file DataSpace* = file:/graphics/java/android/graphics/OWNERS
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index ee24084..c906a13 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -70,9 +70,17 @@
     private static final String TAG = "NetworkTemplate";
 
     /**
+     * Initial Version of the backup serializer.
+     */
+    public static final int BACKUP_VERSION_1_INIT = 1;
+    /**
+     * Version of the backup serializer that added carrier template support.
+     */
+    public static final int BACKUP_VERSION_2_SUPPORT_CARRIER_TEMPLATE = 2;
+    /**
      * Current Version of the Backup Serializer.
      */
-    private static final int BACKUP_VERSION = 1;
+    private static final int BACKUP_VERSION = BACKUP_VERSION_2_SUPPORT_CARRIER_TEMPLATE;
 
     public static final int MATCH_MOBILE = 1;
     public static final int MATCH_WIFI = 4;
@@ -285,6 +293,10 @@
     private final int mRoaming;
     private final int mDefaultNetwork;
     private final int mSubType;
+    /**
+     * The subscriber Id match rule defines how the template should match networks with
+     * specific subscriberId(s). See NetworkTemplate#SUBSCRIBER_ID_MATCH_RULE_* for more detail.
+     */
     private final int mSubscriberIdMatchRule;
 
     // Bitfield containing OEM network properties{@code NetworkIdentity#OEM_*}.
@@ -348,7 +360,7 @@
         mSubscriberIdMatchRule = subscriberIdMatchRule;
         checkValidSubscriberIdMatchRule();
         if (!isKnownMatchRule(matchRule)) {
-            Log.e(TAG, "Unknown network template rule " + matchRule
+            throw new IllegalArgumentException("Unknown network template rule " + matchRule
                     + " will not match any identity.");
         }
     }
@@ -842,11 +854,17 @@
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         DataOutputStream out = new DataOutputStream(baos);
 
+        if (!isPersistable()) {
+            Log.wtf(TAG, "Trying to backup non-persistable template: " + this);
+        }
+
         out.writeInt(BACKUP_VERSION);
 
         out.writeInt(mMatchRule);
         BackupUtils.writeString(out, mSubscriberId);
         BackupUtils.writeString(out, mNetworkId);
+        out.writeInt(mMetered);
+        out.writeInt(mSubscriberIdMatchRule);
 
         return baos.toByteArray();
     }
@@ -854,7 +872,7 @@
     public static NetworkTemplate getNetworkTemplateFromBackup(DataInputStream in)
             throws IOException, BackupUtils.BadVersionException {
         int version = in.readInt();
-        if (version < 1 || version > BACKUP_VERSION) {
+        if (version < BACKUP_VERSION_1_INIT || version > BACKUP_VERSION) {
             throw new BackupUtils.BadVersionException("Unknown Backup Serialization Version");
         }
 
@@ -862,11 +880,27 @@
         String subscriberId = BackupUtils.readString(in);
         String networkId = BackupUtils.readString(in);
 
-        if (!isKnownMatchRule(matchRule)) {
-            throw new BackupUtils.BadVersionException(
-                    "Restored network template contains unknown match rule " + matchRule);
+        final int metered;
+        final int subscriberIdMatchRule;
+        if (version >= BACKUP_VERSION_2_SUPPORT_CARRIER_TEMPLATE) {
+            metered = in.readInt();
+            subscriberIdMatchRule = in.readInt();
+        } else {
+            // For backward compatibility, fill the missing filters from match rules.
+            metered = (matchRule == MATCH_MOBILE || matchRule == MATCH_MOBILE_WILDCARD
+                    || matchRule == MATCH_CARRIER) ? METERED_YES : METERED_ALL;
+            subscriberIdMatchRule = SUBSCRIBER_ID_MATCH_RULE_EXACT;
         }
 
-        return new NetworkTemplate(matchRule, subscriberId, networkId);
+        try {
+            return new NetworkTemplate(matchRule,
+                    subscriberId, new String[] { subscriberId },
+                    networkId, metered, NetworkStats.ROAMING_ALL,
+                    NetworkStats.DEFAULT_NETWORK_ALL, NetworkTemplate.NETWORK_TYPE_ALL,
+                    NetworkTemplate.OEM_MANAGED_ALL, subscriberIdMatchRule);
+        } catch (IllegalArgumentException e) {
+            throw new BackupUtils.BadVersionException(
+                    "Restored network template contains unknown match rule " + matchRule, e);
+        }
     }
 }
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 92861fb..1e424d1 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -1,18 +1,6 @@
 # Haptics
-per-file CombinedVibrationEffect.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file CombinedVibrationEffect.java = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file ExternalVibration.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file ExternalVibration.java = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file IExternalVibrationController.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file IExternalVibratorService.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file IVibratorManagerService.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file NullVibrator.java = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file SystemVibrator.java = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file SystemVibratorManager.java = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file VibrationEffect.aidl = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file VibrationEffect.java = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file Vibrator.java = file:/services/core/java/com/android/server/vibrator/OWNERS
-per-file VibratorManager.java = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file *Vibration* = file:/services/core/java/com/android/server/vibrator/OWNERS
+per-file *Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS
 
 # PowerManager
 per-file IPowerManager.aidl = michaelwr@google.com, santoscordon@google.com
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 7bdb6b9..afd0ff7 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -3650,22 +3650,47 @@
     @NonNull
     public final <T extends Parcelable> List<T> readParcelableList(@NonNull List<T> list,
             @Nullable ClassLoader cl) {
-        final int N = readInt();
-        if (N == -1) {
+        return readParcelableListInternal(list, cl, /*clazz*/ null);
+    }
+
+    /**
+     * Same as {@link #readParcelableList(List, ClassLoader)} but accepts {@code clazz} parameter as
+     * the type required for each item.
+     *
+     * @throws BadParcelableException Throws BadParcelableException if the item to be deserialized
+     * is not an instance of that class or any of its children classes or there was an error
+     * trying to instantiate an element.
+     */
+    @NonNull
+    public <T> List<T> readParcelableList(@NonNull List<T> list,
+            @Nullable ClassLoader cl, @NonNull Class<T> clazz) {
+        Objects.requireNonNull(list);
+        Objects.requireNonNull(clazz);
+        return readParcelableListInternal(list, cl, clazz);
+    }
+
+    /**
+     * @param clazz The type of the object expected or {@code null} for performing no checks.
+     */
+    @NonNull
+    private <T> List<T> readParcelableListInternal(@NonNull List<T> list,
+            @Nullable ClassLoader cl, @Nullable Class<T> clazz) {
+        final int n = readInt();
+        if (n == -1) {
             list.clear();
             return list;
         }
 
-        final int M = list.size();
+        final int m = list.size();
         int i = 0;
-        for (; i < M && i < N; i++) {
-            list.set(i, (T) readParcelable(cl));
+        for (; i < m && i < n; i++) {
+            list.set(i, (T) readParcelableInternal(cl, clazz));
         }
-        for (; i<N; i++) {
-            list.add((T) readParcelable(cl));
+        for (; i < n; i++) {
+            list.add((T) readParcelableInternal(cl, clazz));
         }
-        for (; i<M; i++) {
-            list.remove(N);
+        for (; i < m; i++) {
+            list.remove(n);
         }
         return list;
     }
diff --git a/core/java/android/os/vibrator/OWNERS b/core/java/android/os/vibrator/OWNERS
new file mode 100644
index 0000000..b54d6bf
--- /dev/null
+++ b/core/java/android/os/vibrator/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS
\ No newline at end of file
diff --git a/core/java/android/util/BackupUtils.java b/core/java/android/util/BackupUtils.java
index 474ceda..4fcb13c 100644
--- a/core/java/android/util/BackupUtils.java
+++ b/core/java/android/util/BackupUtils.java
@@ -37,6 +37,10 @@
         public BadVersionException(String message) {
             super(message);
         }
+
+        public BadVersionException(String message, Throwable throwable) {
+            super(message, throwable);
+        }
     }
 
     public static String readString(DataInputStream in) throws IOException {
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 92d5a47..6d4b8c5 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -724,9 +724,6 @@
         DataOutputStream usapOutputStream = null;
         ZygoteArguments args = null;
 
-        // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool.
-        blockSigTerm();
-
         LocalSocket sessionSocket = null;
         if (argBuffer == null) {
             // Read arguments from usapPoolSocket instead.
@@ -742,6 +739,10 @@
                 ZygoteCommandBuffer tmpArgBuffer = null;
                 try {
                     sessionSocket = usapPoolSocket.accept();
+                    // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool.
+                    // This is safe from a race condition because the pool is only flushed after
+                    // the SystemServer changes its internal state to stop using the USAP pool.
+                    blockSigTerm();
 
                     usapOutputStream =
                             new DataOutputStream(sessionSocket.getOutputStream());
@@ -759,9 +760,10 @@
                 unblockSigTerm();
                 IoUtils.closeQuietly(sessionSocket);
                 IoUtils.closeQuietly(tmpArgBuffer);
-                blockSigTerm();
             }
         } else {
+            // Block SIGTERM so we won't be killed if the Zygote flushes the USAP pool.
+            blockSigTerm();
             try {
                 args = ZygoteArguments.getInstance(argBuffer);
             } catch (Exception ex) {
diff --git a/media/java/android/media/Image.java b/media/java/android/media/Image.java
index 1616c03..54e19db 100644
--- a/media/java/android/media/Image.java
+++ b/media/java/android/media/Image.java
@@ -163,6 +163,14 @@
      *      {@link android.graphics.BitmapFactory#decodeByteArray BitmapFactory#decodeByteArray}.
      *   </td>
      * </tr>
+     * <tr>
+     *   <td>{@link android.graphics.ImageFormat#YCBCR_P010 YCBCR_P010}</td>
+     *   <td>1</td>
+     *   <td>P010 is a 4:2:0 YCbCr semiplanar format comprised of a WxH Y plane
+     *     followed by a Wx(H/2) CbCr plane. Each sample is represented by a 16-bit
+     *     little-endian value, with the lower 6 bits set to zero.
+     *   </td>
+     * </tr>
      * </table>
      *
      * @see android.graphics.ImageFormat
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index de31a7f..72dd2bd 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -5107,7 +5107,6 @@
         public MediaImage(
                 @NonNull ByteBuffer buffer, @NonNull ByteBuffer info, boolean readOnly,
                 long timestamp, int xOffset, int yOffset, @Nullable Rect cropRect) {
-            mFormat = ImageFormat.YUV_420_888;
             mTimestamp = timestamp;
             mIsImageValid = true;
             mIsReadOnly = buffer.isReadOnly();
@@ -5120,6 +5119,11 @@
 
             mBufferContext = 0;
 
+            int cbPlaneOffset = -1;
+            int crPlaneOffset = -1;
+            int planeOffsetInc = -1;
+            int pixelStride = -1;
+
             // read media-info.  See MediaImage2
             if (info.remaining() == 104) {
                 int type = info.getInt();
@@ -5137,14 +5141,27 @@
                             "unsupported size: " + mWidth + "x" + mHeight);
                 }
                 int bitDepth = info.getInt();
-                if (bitDepth != 8) {
+                if (bitDepth != 8 && bitDepth != 10) {
                     throw new UnsupportedOperationException("unsupported bit depth: " + bitDepth);
                 }
                 int bitDepthAllocated = info.getInt();
-                if (bitDepthAllocated != 8) {
+                if (bitDepthAllocated != 8 && bitDepthAllocated != 16) {
                     throw new UnsupportedOperationException(
                             "unsupported allocated bit depth: " + bitDepthAllocated);
                 }
+                if (bitDepth == 8 && bitDepthAllocated == 8) {
+                    mFormat = ImageFormat.YUV_420_888;
+                    planeOffsetInc = 1;
+                    pixelStride = 2;
+                } else if (bitDepth == 10 && bitDepthAllocated == 16) {
+                    mFormat = ImageFormat.YCBCR_P010;
+                    planeOffsetInc = 2;
+                    pixelStride = 4;
+                } else {
+                    throw new UnsupportedOperationException("couldn't infer ImageFormat"
+                      + " bitDepth: " + bitDepth + " bitDepthAllocated: " + bitDepthAllocated);
+                }
+
                 mPlanes = new MediaPlane[numPlanes];
                 for (int ix = 0; ix < numPlanes; ix++) {
                     int planeOffset = info.getInt();
@@ -5166,12 +5183,31 @@
                     buffer.limit(buffer.position() + Utils.divUp(bitDepth, 8)
                             + (mHeight / vert - 1) * rowInc + (mWidth / horiz - 1) * colInc);
                     mPlanes[ix] = new MediaPlane(buffer.slice(), rowInc, colInc);
+                    if ((mFormat == ImageFormat.YUV_420_888 || mFormat == ImageFormat.YCBCR_P010)
+                            && ix == 1) {
+                        cbPlaneOffset = planeOffset;
+                    } else if ((mFormat == ImageFormat.YUV_420_888
+                            || mFormat == ImageFormat.YCBCR_P010) && ix == 2) {
+                        crPlaneOffset = planeOffset;
+                    }
                 }
             } else {
                 throw new UnsupportedOperationException(
                         "unsupported info length: " + info.remaining());
             }
 
+            // Validate chroma semiplanerness.
+            if (mFormat == ImageFormat.YCBCR_P010) {
+                if (crPlaneOffset != cbPlaneOffset + planeOffsetInc) {
+                    throw new UnsupportedOperationException("Invalid plane offsets"
+                    + " cbPlaneOffset: " + cbPlaneOffset + " crPlaneOffset: " + crPlaneOffset);
+                }
+                if (mPlanes[1].getPixelStride() != pixelStride
+                        || mPlanes[2].getPixelStride() != pixelStride) {
+                    throw new UnsupportedOperationException("Invalid pixelStride");
+                }
+            }
+
             if (cropRect == null) {
                 cropRect = new Rect(0, 0, mWidth, mHeight);
             }
diff --git a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementService.aidl b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementService.aidl
index ae63462..0c8e431 100644
--- a/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementService.aidl
+++ b/omapi/aidl/aidl_api/android.se.omapi/current/android/se/omapi/ISecureElementService.aidl
@@ -41,5 +41,5 @@
 interface ISecureElementService {
   String[] getReaders();
   android.se.omapi.ISecureElementReader getReader(in String reader);
-  boolean[] isNFCEventAllowed(in String reader, in byte[] aid, in String[] packageNames);
+  boolean[] isNfcEventAllowed(in String reader, in byte[] aid, in String[] packageNames, in int userId);
 }
diff --git a/omapi/aidl/android/se/omapi/ISecureElementService.aidl b/omapi/aidl/android/se/omapi/ISecureElementService.aidl
index 61ae481..13707ec 100644
--- a/omapi/aidl/android/se/omapi/ISecureElementService.aidl
+++ b/omapi/aidl/android/se/omapi/ISecureElementService.aidl
@@ -52,7 +52,7 @@
      * Checks if the application defined by the package name is allowed to
      * receive NFC transaction events for the defined AID.
      */
-    boolean[] isNFCEventAllowed(in String reader, in byte[] aid,
-            in String[] packageNames);
+    boolean[] isNfcEventAllowed(in String reader, in byte[] aid,
+            in String[] packageNames, in int userId);
 
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 021ba224..ddee433 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -21,7 +21,6 @@
 import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHearingAid;
-import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.content.Context;
@@ -191,7 +190,7 @@
     void onProfileStateChanged(LocalBluetoothProfile profile, int newProfileState) {
         if (BluetoothUtils.D) {
             Log.d(TAG, "onProfileStateChanged: profile " + profile + ", device "
-                    + mDevice.getAlias() + ", newProfileState " + newProfileState);
+                    + mDevice.getAnonymizedAddress() + ", newProfileState " + newProfileState);
         }
         if (mLocalAdapter.getState() == BluetoothAdapter.STATE_TURNING_OFF)
         {
@@ -741,7 +740,7 @@
         }
 
         if (BluetoothUtils.D) {
-            Log.d(TAG, "updating profiles for " + mDevice.getAlias());
+            Log.d(TAG, "updating profiles for " + mDevice.getAnonymizedAddress());
             BluetoothClass bluetoothClass = mDevice.getBluetoothClass();
 
             if (bluetoothClass != null) Log.v(TAG, "Class: " + bluetoothClass.toString());
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 7d75b73..0aa50bd 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -407,7 +407,7 @@
     @Override
     public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId) {
         synchronized (mLock) {
-            if (mSecurityPolicy.canPerformGestures(this)) {
+            if (mServiceInterface != null && mSecurityPolicy.canPerformGestures(this)) {
                 MotionEventInjector motionEventInjector =
                         mSystemSupport.getMotionEventInjectorForDisplayLocked(displayId);
                 if (motionEventInjector != null
diff --git a/services/core/java/com/android/server/audio/TEST_MAPPING b/services/core/java/com/android/server/audio/TEST_MAPPING
index 90246f8..5a6c6a5 100644
--- a/services/core/java/com/android/server/audio/TEST_MAPPING
+++ b/services/core/java/com/android/server/audio/TEST_MAPPING
@@ -1,13 +1,13 @@
 {
     "presubmit-large": [
         {
-            "name": "CtsMediaTestCases",
+            "name": "CtsMediaAudioTestCases",
             "options": [
                 {
-                    "include-filter": "android.media.cts.AudioManagerTest"
+                    "include-filter": "android.media.audio.cts.AudioManagerTest"
                 },
                 {
-                    "include-filter": "android.media.cts.AudioFocusTest"
+                    "include-filter": "android.media.audio.cts.AudioFocusTest"
                 }
             ]
         }
diff --git a/services/core/java/com/android/server/net/IpConfigStore.java b/services/core/java/com/android/server/net/IpConfigStore.java
index df1eb6d..d17dbde 100644
--- a/services/core/java/com/android/server/net/IpConfigStore.java
+++ b/services/core/java/com/android/server/net/IpConfigStore.java
@@ -322,8 +322,11 @@
                                     gateway = InetAddresses.parseNumericAddress(in.readUTF());
                                 }
                                 // If the destination is a default IPv4 route, use the gateway
-                                // address unless already set.
-                                if (dest.getAddress() instanceof Inet4Address
+                                // address unless already set. If there is no destination, assume
+                                // it is default route and use the gateway address in all cases.
+                                if (dest == null) {
+                                    gatewayAddress = gateway;
+                                } else if (dest.getAddress() instanceof Inet4Address
                                         && dest.getPrefixLength() == 0 && gatewayAddress == null) {
                                     gatewayAddress = gateway;
                                 } else {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index 654b17f..b45d87f 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -39,6 +39,7 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.RingBuffer;
 import com.android.server.am.ProcessList;
+import com.android.server.net.NetworkPolicyManagerService.UidBlockedState;
 
 import java.text.SimpleDateFormat;
 import java.util.Arrays;
@@ -72,16 +73,6 @@
     private static final int EVENT_UPDATE_METERED_RESTRICTED_PKGS = 13;
     private static final int EVENT_APP_IDLE_WL_CHANGED = 14;
 
-    static final int NTWK_BLOCKED_POWER = 0;
-    static final int NTWK_ALLOWED_NON_METERED = 1;
-    static final int NTWK_BLOCKED_DENYLIST = 2;
-    static final int NTWK_ALLOWED_ALLOWLIST = 3;
-    static final int NTWK_ALLOWED_TMP_ALLOWLIST = 4;
-    static final int NTWK_BLOCKED_BG_RESTRICT = 5;
-    static final int NTWK_ALLOWED_DEFAULT = 6;
-    static final int NTWK_ALLOWED_SYSTEM = 7;
-    static final int NTWK_BLOCKED_RESTRICTED_MODE = 8;
-
     private final LogBuffer mNetworkBlockedBuffer = new LogBuffer(MAX_NETWORK_BLOCKED_LOG_SIZE);
     private final LogBuffer mUidStateChangeBuffer = new LogBuffer(MAX_LOG_SIZE);
     private final LogBuffer mEventsBuffer = new LogBuffer(MAX_LOG_SIZE);
@@ -90,12 +81,13 @@
 
     private final Object mLock = new Object();
 
-    void networkBlocked(int uid, int reason) {
+    void networkBlocked(int uid, UidBlockedState uidBlockedState) {
         synchronized (mLock) {
             if (LOGD || uid == mDebugUid) {
-                Slog.d(TAG, uid + " is " + getBlockedReason(reason));
+                Slog.d(TAG, "Blocked state of uid: " + uidBlockedState.toString());
             }
-            mNetworkBlockedBuffer.networkBlocked(uid, reason);
+            mNetworkBlockedBuffer.networkBlocked(uid, uidBlockedState.blockedReasons,
+                    uidBlockedState.allowedReasons, uidBlockedState.effectiveBlockedReasons);
         }
     }
 
@@ -269,29 +261,6 @@
         }
     }
 
-    private static String getBlockedReason(int reason) {
-        switch (reason) {
-            case NTWK_BLOCKED_POWER:
-                return "blocked by power restrictions";
-            case NTWK_ALLOWED_NON_METERED:
-                return "allowed on unmetered network";
-            case NTWK_BLOCKED_DENYLIST:
-                return "denylisted on metered network";
-            case NTWK_ALLOWED_ALLOWLIST:
-                return "allowlisted on metered network";
-            case NTWK_ALLOWED_TMP_ALLOWLIST:
-                return "temporary allowlisted on metered network";
-            case NTWK_BLOCKED_BG_RESTRICT:
-                return "blocked when background is restricted";
-            case NTWK_ALLOWED_DEFAULT:
-                return "allowed by default";
-            case NTWK_BLOCKED_RESTRICTED_MODE:
-                return "blocked by restricted networking mode";
-            default:
-                return String.valueOf(reason);
-        }
-    }
-
     private static String getPolicyChangedLog(int uid, int oldPolicy, int newPolicy) {
         return "Policy for " + uid + " changed from "
                 + NetworkPolicyManager.uidPoliciesToString(oldPolicy) + " to "
@@ -402,14 +371,17 @@
             data.timeStamp = System.currentTimeMillis();
         }
 
-        public void networkBlocked(int uid, int reason) {
+        public void networkBlocked(int uid, int blockedReasons, int allowedReasons,
+                int effectiveBlockedReasons) {
             final Data data = getNextSlot();
             if (data == null) return;
 
             data.reset();
             data.type = EVENT_NETWORK_BLOCKED;
             data.ifield1 = uid;
-            data.ifield2 = reason;
+            data.ifield2 = blockedReasons;
+            data.ifield3 = allowedReasons;
+            data.ifield4 = effectiveBlockedReasons;
             data.timeStamp = System.currentTimeMillis();
         }
 
@@ -554,7 +526,8 @@
                 case EVENT_TYPE_GENERIC:
                     return data.sfield1;
                 case EVENT_NETWORK_BLOCKED:
-                    return data.ifield1 + "-" + getBlockedReason(data.ifield2);
+                    return data.ifield1 + "-" + UidBlockedState.toString(
+                            data.ifield2, data.ifield3, data.ifield4);
                 case EVENT_UID_STATE_CHANGED:
                     return data.ifield1 + ":" + ProcessList.makeProcStateString(data.ifield2)
                             + ":" + ActivityManager.getCapabilitiesSummary(data.ifield3)
@@ -593,17 +566,18 @@
         }
     }
 
-    public final static class Data {
-        int type;
-        long timeStamp;
+    private static final class Data {
+        public int type;
+        public long timeStamp;
 
-        int ifield1;
-        int ifield2;
-        int ifield3;
-        long lfield1;
-        boolean bfield1;
-        boolean bfield2;
-        String sfield1;
+        public int ifield1;
+        public int ifield2;
+        public int ifield3;
+        public int ifield4;
+        public long lfield1;
+        public boolean bfield1;
+        public boolean bfield2;
+        public String sfield1;
 
         public void reset(){
             sfield1 = null;
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 20687c6..5de5fd3 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -79,14 +79,10 @@
 import static android.net.NetworkPolicyManager.ALLOWED_REASON_SYSTEM;
 import static android.net.NetworkPolicyManager.EXTRA_NETWORK_TEMPLATE;
 import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
-import static android.net.NetworkPolicyManager.MASK_ALL_NETWORKS;
-import static android.net.NetworkPolicyManager.MASK_METERED_NETWORKS;
-import static android.net.NetworkPolicyManager.MASK_RESTRICTED_MODE_NETWORKS;
 import static android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.POLICY_NONE;
 import static android.net.NetworkPolicyManager.POLICY_REJECT_METERED_BACKGROUND;
 import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
-import static android.net.NetworkPolicyManager.RULE_ALLOW_METERED;
 import static android.net.NetworkPolicyManager.RULE_NONE;
 import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
 import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
@@ -135,15 +131,6 @@
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
 import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_ALLOWLIST;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_DEFAULT;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_NON_METERED;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_SYSTEM;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_TMP_ALLOWLIST;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BG_RESTRICT;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_DENYLIST;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_POWER;
-import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_RESTRICTED_MODE;
 import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
 
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -518,8 +505,6 @@
 
     /** Defined UID policies. */
     @GuardedBy("mUidRulesFirstLock") final SparseIntArray mUidPolicy = new SparseIntArray();
-    /** Currently derived rules for each UID. */
-    @GuardedBy("mUidRulesFirstLock") final SparseIntArray mUidRules = new SparseIntArray();
 
     @GuardedBy("mUidRulesFirstLock")
     final SparseIntArray mUidFirewallStandbyRules = new SparseIntArray();
@@ -598,6 +583,10 @@
     @GuardedBy("mUidRulesFirstLock")
     private final SparseArray<UidBlockedState> mUidBlockedState = new SparseArray<>();
 
+    /** Objects used temporarily while computing the new blocked state for each uid. */
+    @GuardedBy("mUidRulesFirstLock")
+    private final SparseArray<UidBlockedState> mTmpUidBlockedState = new SparseArray<>();
+
     /** Map from network ID to last observed meteredness state */
     @GuardedBy("mNetworkPoliciesSecondLock")
     private final SparseBooleanArray mNetworkMetered = new SparseBooleanArray();
@@ -3825,7 +3814,7 @@
 
                 final SparseBooleanArray knownUids = new SparseBooleanArray();
                 collectKeys(mUidState, knownUids);
-                collectKeys(mUidRules, knownUids);
+                collectKeys(mUidBlockedState, knownUids);
 
                 fout.println("Status for all known UIDs:");
                 fout.increaseIndent();
@@ -3843,23 +3832,13 @@
                         fout.print(uidState.toString());
                     }
 
-                    final int uidRules = mUidRules.get(uid, RULE_NONE);
-                    fout.print(" rules=");
-                    fout.print(uidRulesToString(uidRules));
-                    fout.println();
-                }
-                fout.decreaseIndent();
-
-                fout.println("Status for just UIDs with rules:");
-                fout.increaseIndent();
-                size = mUidRules.size();
-                for (int i = 0; i < size; i++) {
-                    final int uid = mUidRules.keyAt(i);
-                    fout.print("UID=");
-                    fout.print(uid);
-                    final int uidRules = mUidRules.get(uid, RULE_NONE);
-                    fout.print(" rules=");
-                    fout.print(uidRulesToString(uidRules));
+                    final UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
+                    if (uidBlockedState == null) {
+                        fout.print(" blocked_state={null}");
+                    } else {
+                        fout.print(" blocked_state=");
+                        fout.print(uidBlockedState.toString());
+                    }
                     fout.println();
                 }
                 fout.decreaseIndent();
@@ -4010,22 +3989,17 @@
     void updateRestrictedModeAllowlistUL() {
         mUidFirewallRestrictedModeRules.clear();
         forEachUid("updateRestrictedModeAllowlist", uid -> {
-            final int oldUidRule = mUidRules.get(uid);
-            final int newUidRule = getNewRestrictedModeUidRule(uid, oldUidRule);
-            final boolean hasUidRuleChanged = oldUidRule != newUidRule;
-            final int newFirewallRule = getRestrictedModeFirewallRule(newUidRule);
+            synchronized (mUidRulesFirstLock) {
+                final UidBlockedState uidBlockedState = updateBlockedReasonsForRestrictedModeUL(
+                        uid);
+                final int newFirewallRule = getRestrictedModeFirewallRule(uidBlockedState);
 
-            // setUidFirewallRulesUL will allowlist all uids that are passed to it, so only add
-            // non-default rules.
-            if (newFirewallRule != FIREWALL_RULE_DEFAULT) {
-                mUidFirewallRestrictedModeRules.append(uid, newFirewallRule);
+                // setUidFirewallRulesUL will allowlist all uids that are passed to it, so only add
+                // non-default rules.
+                if (newFirewallRule != FIREWALL_RULE_DEFAULT) {
+                    mUidFirewallRestrictedModeRules.append(uid, newFirewallRule);
+                }
             }
-
-            if (hasUidRuleChanged) {
-                mUidRules.put(uid, newUidRule);
-                mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRule).sendToTarget();
-            }
-            updateBlockedReasonsForRestrictedModeUL(uid);
         });
         if (mRestrictedNetworkingMode) {
             // firewall rules only need to be set when this mode is being enabled.
@@ -4038,15 +4012,7 @@
     @VisibleForTesting
     @GuardedBy("mUidRulesFirstLock")
     void updateRestrictedModeForUidUL(int uid) {
-        final int oldUidRule = mUidRules.get(uid);
-        final int newUidRule = getNewRestrictedModeUidRule(uid, oldUidRule);
-        final boolean hasUidRuleChanged = oldUidRule != newUidRule;
-
-        if (hasUidRuleChanged) {
-            mUidRules.put(uid, newUidRule);
-            mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRule).sendToTarget();
-        }
-        updateBlockedReasonsForRestrictedModeUL(uid);
+        final UidBlockedState uidBlockedState = updateBlockedReasonsForRestrictedModeUL(uid);
 
         // if restricted networking mode is on, and the app has an access exemption, the uid rule
         // will not change, but the firewall rule will have to be updated.
@@ -4054,16 +4020,14 @@
             // Note: setUidFirewallRule also updates mUidFirewallRestrictedModeRules.
             // In this case, default firewall rules can also be added.
             setUidFirewallRule(FIREWALL_CHAIN_RESTRICTED, uid,
-                    getRestrictedModeFirewallRule(newUidRule));
+                    getRestrictedModeFirewallRule(uidBlockedState));
         }
     }
 
-    private void updateBlockedReasonsForRestrictedModeUL(int uid) {
-        UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
-        if (uidBlockedState == null) {
-            uidBlockedState = new UidBlockedState();
-            mUidBlockedState.put(uid, uidBlockedState);
-        }
+    @GuardedBy("mUidRulesFirstLock")
+    private UidBlockedState updateBlockedReasonsForRestrictedModeUL(int uid) {
+        final UidBlockedState uidBlockedState = getOrCreateUidBlockedStateForUid(
+                mUidBlockedState, uid);
         final int oldEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
         if (mRestrictedNetworkingMode) {
             uidBlockedState.blockedReasons |= BLOCKED_REASON_RESTRICTED_MODE;
@@ -4077,23 +4041,16 @@
         }
         uidBlockedState.updateEffectiveBlockedReasons();
         if (oldEffectiveBlockedReasons != uidBlockedState.effectiveBlockedReasons) {
-            mHandler.obtainMessage(MSG_BLOCKED_REASON_CHANGED, uid,
-                    uidBlockedState.effectiveBlockedReasons, oldEffectiveBlockedReasons)
-                    .sendToTarget();
+            postBlockedReasonsChangedMsg(uid,
+                    uidBlockedState.effectiveBlockedReasons, oldEffectiveBlockedReasons);
+
+            postUidRulesChangedMsg(uid, uidBlockedState.deriveUidRules());
         }
+        return uidBlockedState;
     }
 
-    private int getNewRestrictedModeUidRule(int uid, int oldUidRule) {
-        int newRule = oldUidRule;
-        newRule &= ~MASK_RESTRICTED_MODE_NETWORKS;
-        if (mRestrictedNetworkingMode && !hasRestrictedModeAccess(uid)) {
-            newRule |= RULE_REJECT_RESTRICTED_MODE;
-        }
-        return newRule;
-    }
-
-    private static int getRestrictedModeFirewallRule(int uidRule) {
-        if ((uidRule & RULE_REJECT_RESTRICTED_MODE) != 0) {
+    private static int getRestrictedModeFirewallRule(UidBlockedState uidBlockedState) {
+        if ((uidBlockedState.effectiveBlockedReasons & BLOCKED_REASON_RESTRICTED_MODE) != 0) {
             // rejected in restricted mode, this is the default behavior.
             return FIREWALL_RULE_DEFAULT;
         } else {
@@ -4301,16 +4258,12 @@
             if (!isUidValidForDenylistRulesUL(uid)) {
                 continue;
             }
-            int oldRules = mUidRules.get(uid);
-            if (enableChain) {
-                // Chain wasn't enabled before and the other power-related
-                // chains are allowlists, so we can clear the
-                // MASK_ALL_NETWORKS part of the rules and re-inform listeners if
-                // the effective rules result in blocking network access.
-                oldRules &= MASK_METERED_NETWORKS;
-            } else {
-                // Skip if it had no restrictions to begin with
-                if ((oldRules & MASK_ALL_NETWORKS) == 0) continue;
+            final UidBlockedState uidBlockedState = getOrCreateUidBlockedStateForUid(
+                    mUidBlockedState, uid);
+            if (!enableChain && (uidBlockedState.blockedReasons & ~BLOCKED_METERED_REASON_MASK)
+                    == BLOCKED_REASON_NONE) {
+                // Chain isn't enabled and the uid had no restrictions to begin with.
+                continue;
             }
             final boolean isUidIdle = !paroled && isUidIdle(uid);
             if (isUidIdle && !mPowerSaveTempWhitelistAppIds.get(UserHandle.getAppId(uid))
@@ -4320,13 +4273,7 @@
             } else {
                 mUidFirewallStandbyRules.put(uid, FIREWALL_RULE_DEFAULT);
             }
-            final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldRules,
-                    isUidIdle);
-            if (newUidRules == RULE_NONE) {
-                mUidRules.delete(uid);
-            } else {
-                mUidRules.put(uid, newUidRules);
-            }
+            updateRulesForPowerRestrictionsUL(uid, isUidIdle);
         }
         setUidFirewallRulesUL(FIREWALL_CHAIN_STANDBY, blockedUids,
                 enableChain ? CHAIN_TOGGLE_ENABLE : CHAIN_TOGGLE_DISABLE);
@@ -4544,6 +4491,7 @@
             mInternetPermissionMap.put(uid, hasPermission);
             return hasPermission;
         } catch (RemoteException e) {
+            // ignored; service lives in system_server
         }
         return true;
     }
@@ -4554,7 +4502,7 @@
     @GuardedBy("mUidRulesFirstLock")
     private void onUidDeletedUL(int uid) {
         // First cleanup in-memory state synchronously...
-        mUidRules.delete(uid);
+        mUidBlockedState.delete(uid);
         mUidPolicy.delete(uid);
         mUidFirewallStandbyRules.delete(uid);
         mUidFirewallDozableRules.delete(uid);
@@ -4640,7 +4588,7 @@
      * permission, since there is no need to change the {@code iptables} rule if the app does not
      * have permission to use the internet.
      *
-     * <p>The {@link #mUidRules} map is used to define the transtion of states of an UID.
+     * <p>The {@link #mUidBlockedState} map is used to define the transition of states of an UID.
      *
      */
     private void updateRulesForDataUsageRestrictionsUL(int uid) {
@@ -4655,6 +4603,7 @@
         }
     }
 
+    @GuardedBy("mUidRulesFirstLock")
     private void updateRulesForDataUsageRestrictionsULInner(int uid) {
         if (!isUidValidForAllowlistRulesUL(uid)) {
             if (LOGD) Slog.d(TAG, "no need to update restrict data rules for uid " + uid);
@@ -4662,38 +4611,17 @@
         }
 
         final int uidPolicy = mUidPolicy.get(uid, POLICY_NONE);
-        final int oldUidRules = mUidRules.get(uid, RULE_NONE);
         final boolean isForeground = isUidForegroundOnRestrictBackgroundUL(uid);
         final boolean isRestrictedByAdmin = isRestrictedByAdminUL(uid);
-        UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
-        if (uidBlockedState == null) {
-            uidBlockedState = new UidBlockedState();
-            mUidBlockedState.put(uid, uidBlockedState);
-        }
+        final UidBlockedState uidBlockedState = getOrCreateUidBlockedStateForUid(
+                mUidBlockedState, uid);
+        final UidBlockedState previousUidBlockedState = getOrCreateUidBlockedStateForUid(
+                mTmpUidBlockedState, uid);
+        previousUidBlockedState.copyFrom(uidBlockedState);
 
         final boolean isDenied = (uidPolicy & POLICY_REJECT_METERED_BACKGROUND) != 0;
         final boolean isAllowed = (uidPolicy & POLICY_ALLOW_METERED_BACKGROUND) != 0;
 
-        // copy oldUidRules and clear out METERED_NETWORKS rules.
-        int newUidRules = oldUidRules & (~MASK_METERED_NETWORKS);
-
-        // First step: define the new rule based on user restrictions and foreground state.
-        if (isRestrictedByAdmin) {
-            newUidRules |= RULE_REJECT_METERED;
-        } else if (isForeground) {
-            if (isDenied || (mRestrictBackground && !isAllowed)) {
-                newUidRules |= RULE_TEMPORARY_ALLOW_METERED;
-            } else if (isAllowed) {
-                newUidRules |= RULE_ALLOW_METERED;
-            }
-        } else {
-            if (isDenied) {
-                newUidRules |= RULE_REJECT_METERED;
-            } else if (mRestrictBackground && isAllowed) {
-                newUidRules |= RULE_ALLOW_METERED;
-            }
-        }
-
         int newBlockedReasons = BLOCKED_REASON_NONE;
         int newAllowedReasons = ALLOWED_REASON_NONE;
         newBlockedReasons |= (isRestrictedByAdmin ? BLOCKED_METERED_REASON_ADMIN_DISABLED : 0);
@@ -4704,16 +4632,48 @@
         newAllowedReasons |= (isForeground ? ALLOWED_METERED_REASON_FOREGROUND : 0);
         newAllowedReasons |= (isAllowed ? ALLOWED_METERED_REASON_USER_EXEMPTED : 0);
 
+        uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
+                & ~BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
+        uidBlockedState.allowedReasons = (uidBlockedState.allowedReasons
+                & ~ALLOWED_METERED_REASON_MASK) | newAllowedReasons;
+        uidBlockedState.updateEffectiveBlockedReasons();
+        final int oldEffectiveBlockedReasons = previousUidBlockedState.effectiveBlockedReasons;
+        final int newEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
+        if (oldEffectiveBlockedReasons != newEffectiveBlockedReasons) {
+            postBlockedReasonsChangedMsg(uid,
+                    newEffectiveBlockedReasons, oldEffectiveBlockedReasons);
+
+            postUidRulesChangedMsg(uid, uidBlockedState.deriveUidRules());
+        }
+
+        // Note that the conditionals below are for avoiding unnecessary calls to netd.
+        // TODO: Measure the performance for doing a no-op call to netd so that we can
+        // remove the conditionals to simplify the logic below. We can also further reduce
+        // some calls to netd if they turn out to be costly.
+        final int denylistReasons = BLOCKED_METERED_REASON_ADMIN_DISABLED
+                | BLOCKED_METERED_REASON_USER_RESTRICTED;
+        if ((oldEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE
+                || (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE) {
+            setMeteredNetworkDenylist(uid,
+                    (newEffectiveBlockedReasons & denylistReasons) != BLOCKED_REASON_NONE);
+        }
+        final int allowlistReasons = ALLOWED_METERED_REASON_FOREGROUND
+                | ALLOWED_METERED_REASON_USER_EXEMPTED;
+        final int oldAllowedReasons = previousUidBlockedState.allowedReasons;
+        if ((oldAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE
+                || (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE) {
+            setMeteredNetworkAllowlist(uid,
+                    (newAllowedReasons & allowlistReasons) != ALLOWED_REASON_NONE);
+        }
+
         if (LOGV) {
             Log.v(TAG, "updateRuleForRestrictBackgroundUL(" + uid + ")"
                     + ": isForeground=" +isForeground
                     + ", isDenied=" + isDenied
                     + ", isAllowed=" + isAllowed
                     + ", isRestrictedByAdmin=" + isRestrictedByAdmin
-                    + ", oldRule=" + uidRulesToString(oldUidRules & MASK_METERED_NETWORKS)
-                    + ", newRule=" + uidRulesToString(newUidRules & MASK_METERED_NETWORKS)
-                    + ", newUidRules=" + uidRulesToString(newUidRules)
-                    + ", oldUidRules=" + uidRulesToString(oldUidRules)
+                    + ", oldBlockedState=" + previousUidBlockedState.toString()
+                    + ", newBlockedState="
                     + ", oldBlockedMeteredReasons=" + NetworkPolicyManager.blockedReasonsToString(
                     uidBlockedState.blockedReasons & BLOCKED_METERED_REASON_MASK)
                     + ", oldBlockedMeteredEffectiveReasons="
@@ -4722,84 +4682,11 @@
                     + ", oldAllowedMeteredReasons=" + NetworkPolicyManager.blockedReasonsToString(
                     uidBlockedState.allowedReasons & BLOCKED_METERED_REASON_MASK));
         }
-
-        if (newUidRules == RULE_NONE) {
-            mUidRules.delete(uid);
-        } else {
-            mUidRules.put(uid, newUidRules);
-        }
-
-        // Second step: apply bw changes based on change of state.
-        if (newUidRules != oldUidRules) {
-            if (hasRule(newUidRules, RULE_TEMPORARY_ALLOW_METERED)) {
-                // Temporarily allow foreground app, removing from denylist if necessary
-                // (since bw_penalty_box prevails over bw_happy_box).
-
-                setMeteredNetworkAllowlist(uid, true);
-                // TODO: if statement below is used to avoid an unnecessary call to netd / iptables,
-                // but ideally it should be just:
-                //    setMeteredNetworkDenylist(uid, isDenied);
-                if (isDenied) {
-                    setMeteredNetworkDenylist(uid, false);
-                }
-            } else if (hasRule(oldUidRules, RULE_TEMPORARY_ALLOW_METERED)) {
-                // Remove temporary exemption from app that is not on foreground anymore.
-
-                // TODO: if statements below are used to avoid unnecessary calls to netd / iptables,
-                // but ideally they should be just:
-                //    setMeteredNetworkAllowlist(uid, isAllowed);
-                //    setMeteredNetworkDenylist(uid, isDenied);
-                if (!isAllowed) {
-                    setMeteredNetworkAllowlist(uid, false);
-                }
-                if (isDenied || isRestrictedByAdmin) {
-                    setMeteredNetworkDenylist(uid, true);
-                }
-            } else if (hasRule(newUidRules, RULE_REJECT_METERED)
-                    || hasRule(oldUidRules, RULE_REJECT_METERED)) {
-                // Flip state because app was explicitly added or removed to denylist.
-                setMeteredNetworkDenylist(uid, (isDenied || isRestrictedByAdmin));
-                if (hasRule(oldUidRules, RULE_REJECT_METERED) && isAllowed) {
-                    // Since denial prevails over allowance, we need to handle the special case
-                    // where app is allowed and denied at the same time (although such
-                    // scenario should be blocked by the UI), then it is removed from the denylist.
-                    setMeteredNetworkAllowlist(uid, isAllowed);
-                }
-            } else if (hasRule(newUidRules, RULE_ALLOW_METERED)
-                    || hasRule(oldUidRules, RULE_ALLOW_METERED)) {
-                // Flip state because app was explicitly added or removed to allowlist.
-                setMeteredNetworkAllowlist(uid, isAllowed);
-            } else {
-                // All scenarios should have been covered above.
-                Log.wtf(TAG, "Unexpected change of metered UID state for " + uid
-                        + ": foreground=" + isForeground
-                        + ", allowlisted=" + isAllowed
-                        + ", denylisted=" + isDenied
-                        + ", isRestrictedByAdmin=" + isRestrictedByAdmin
-                        + ", newRule=" + uidRulesToString(newUidRules)
-                        + ", oldRule=" + uidRulesToString(oldUidRules));
-            }
-
-            // Dispatch changed rule to existing listeners.
-            mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRules).sendToTarget();
-        }
-
-        final int oldEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
-        uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
-                & ~BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
-        uidBlockedState.allowedReasons = (uidBlockedState.allowedReasons
-                & ~ALLOWED_METERED_REASON_MASK) | newAllowedReasons;
-        uidBlockedState.updateEffectiveBlockedReasons();
-        if (oldEffectiveBlockedReasons != uidBlockedState.effectiveBlockedReasons) {
-            mHandler.obtainMessage(MSG_BLOCKED_REASON_CHANGED, uid,
-                    uidBlockedState.effectiveBlockedReasons, oldEffectiveBlockedReasons)
-                    .sendToTarget();
-        }
     }
 
     /**
-     * Updates the power-related part of the {@link #mUidRules} for a given map, and notify external
-     * listeners in case of change.
+     * Updates the power-related part of the {@link #mUidBlockedState} for a given map, and
+     * notify external listeners in case of change.
      * <p>
      * There are 3 power-related rules that affects whether an app has background access on
      * non-metered networks, and when the condition applies and the UID is not allowed for power
@@ -4810,23 +4697,15 @@
      * <li>Battery Saver Mode is on: {@code fw_powersave} firewall chain.
      * </ul>
      * <p>
-     * This method updates the power-related part of the {@link #mUidRules} for a given uid based on
-     * these modes, the UID process state (foreground or not), and the UID allowlist state.
+     * This method updates the power-related part of the {@link #mUidBlockedState} for a given
+     * uid based on these modes, the UID process state (foreground or not), and the UID
+     * allowlist state.
      * <p>
      * <strong>NOTE: </strong>This method does not update the firewall rules on {@code netd}.
      */
     @GuardedBy("mUidRulesFirstLock")
     private void updateRulesForPowerRestrictionsUL(int uid) {
-        final int oldUidRules = mUidRules.get(uid, RULE_NONE);
-
-        final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldUidRules,
-                isUidIdle(uid));
-
-        if (newUidRules == RULE_NONE) {
-            mUidRules.delete(uid);
-        } else {
-            mUidRules.put(uid, newUidRules);
-        }
+        updateRulesForPowerRestrictionsUL(uid, isUidIdle(uid));
     }
 
     /**
@@ -4835,56 +4714,37 @@
      * @param uid the uid of the app to update rules for
      * @param oldUidRules the current rules for the uid, in order to determine if there's a change
      * @param isUidIdle whether uid is idle or not
-     *
-     * @return the new computed rules for the uid
      */
     @GuardedBy("mUidRulesFirstLock")
-    private int updateRulesForPowerRestrictionsUL(int uid, int oldUidRules, boolean isUidIdle) {
+    private void updateRulesForPowerRestrictionsUL(int uid, boolean isUidIdle) {
         if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
             Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
-                    "updateRulesForPowerRestrictionsUL: " + uid + "/" + oldUidRules + "/"
+                    "updateRulesForPowerRestrictionsUL: " + uid + "/"
                             + (isUidIdle ? "I" : "-"));
         }
         try {
-            return updateRulesForPowerRestrictionsULInner(uid, oldUidRules, isUidIdle);
+            updateRulesForPowerRestrictionsULInner(uid, isUidIdle);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
         }
     }
 
     @GuardedBy("mUidRulesFirstLock")
-    private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules,
-            boolean isUidIdle) {
+    private void updateRulesForPowerRestrictionsULInner(int uid, boolean isUidIdle) {
         if (!isUidValidForDenylistRulesUL(uid)) {
             if (LOGD) Slog.d(TAG, "no need to update restrict power rules for uid " + uid);
-            return RULE_NONE;
+            return;
         }
 
-        final boolean restrictMode = isUidIdle || mRestrictPower || mDeviceIdleMode;
         final boolean isForeground = isUidForegroundOnRestrictPowerUL(uid);
 
         final boolean isWhitelisted = isWhitelistedFromPowerSaveUL(uid, mDeviceIdleMode);
 
-        // Copy existing uid rules and clear ALL_NETWORK rules.
-        int newUidRules = oldUidRules & (~MASK_ALL_NETWORKS);
-
-        UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
-        if (uidBlockedState == null) {
-            uidBlockedState = new UidBlockedState();
-            mUidBlockedState.put(uid, uidBlockedState);
-        }
-
-        // First step: define the new rule based on user restrictions and foreground state.
-
-        // NOTE: if statements below could be inlined, but it's easier to understand the logic
-        // by considering the foreground and non-foreground states.
-        if (isForeground) {
-            if (restrictMode) {
-                newUidRules |= RULE_ALLOW_ALL;
-            }
-        } else if (restrictMode) {
-            newUidRules |= isWhitelisted ? RULE_ALLOW_ALL : RULE_REJECT_ALL;
-        }
+        final UidBlockedState uidBlockedState = getOrCreateUidBlockedStateForUid(
+                mUidBlockedState, uid);
+        final UidBlockedState previousUidBlockedState = getOrCreateUidBlockedStateForUid(
+                mTmpUidBlockedState, uid);
+        previousUidBlockedState.copyFrom(uidBlockedState);
 
         int newBlockedReasons = BLOCKED_REASON_NONE;
         int newAllowedReasons = ALLOWED_REASON_NONE;
@@ -4899,6 +4759,22 @@
                 ? ALLOWED_REASON_POWER_SAVE_ALLOWLIST : 0);
         newAllowedReasons |= (isWhitelistedFromPowerSaveExceptIdleUL(uid)
                 ? ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST : 0);
+        newAllowedReasons |= (uidBlockedState.allowedReasons
+                & ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS);
+
+        uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
+                & BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
+        uidBlockedState.allowedReasons = (uidBlockedState.allowedReasons
+                & ALLOWED_METERED_REASON_MASK) | newAllowedReasons;
+        uidBlockedState.updateEffectiveBlockedReasons();
+        if (previousUidBlockedState.effectiveBlockedReasons
+                != uidBlockedState.effectiveBlockedReasons) {
+            postBlockedReasonsChangedMsg(uid,
+                    uidBlockedState.effectiveBlockedReasons,
+                    previousUidBlockedState.effectiveBlockedReasons);
+
+            postUidRulesChangedMsg(uid, uidBlockedState.deriveUidRules());
+        }
 
         if (LOGV) {
             Log.v(TAG, "updateRulesForPowerRestrictionsUL(" + uid + ")"
@@ -4907,43 +4783,9 @@
                     + ", mDeviceIdleMode: " + mDeviceIdleMode
                     + ", isForeground=" + isForeground
                     + ", isWhitelisted=" + isWhitelisted
-                    + ", oldRule=" + uidRulesToString(oldUidRules & MASK_ALL_NETWORKS)
-                    + ", newRule=" + uidRulesToString(newUidRules & MASK_ALL_NETWORKS)
-                    + ", newUidRules=" + uidRulesToString(newUidRules)
-                    + ", oldUidRules=" + uidRulesToString(oldUidRules));
+                    + ", oldUidBlockedState=" + previousUidBlockedState.toString()
+                    + ", newUidBlockedState=" + uidBlockedState.toString());
         }
-
-        // Second step: notify listeners if state changed.
-        if (newUidRules != oldUidRules) {
-            if ((newUidRules & MASK_ALL_NETWORKS) == RULE_NONE || hasRule(newUidRules,
-                    RULE_ALLOW_ALL)) {
-                if (LOGV) Log.v(TAG, "Allowing non-metered access for UID " + uid);
-            } else if (hasRule(newUidRules, RULE_REJECT_ALL)) {
-                if (LOGV) Log.v(TAG, "Rejecting non-metered access for UID " + uid);
-            } else {
-                // All scenarios should have been covered above
-                Log.wtf(TAG, "Unexpected change of non-metered UID state for " + uid
-                        + ": foreground=" + isForeground
-                        + ", whitelisted=" + isWhitelisted
-                        + ", newRule=" + uidRulesToString(newUidRules)
-                        + ", oldRule=" + uidRulesToString(oldUidRules));
-            }
-            mHandler.obtainMessage(MSG_RULES_CHANGED, uid, newUidRules).sendToTarget();
-        }
-
-        final int oldEffectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
-        uidBlockedState.blockedReasons = (uidBlockedState.blockedReasons
-                & BLOCKED_METERED_REASON_MASK) | newBlockedReasons;
-        uidBlockedState.allowedReasons = (uidBlockedState.allowedReasons
-                & ALLOWED_METERED_REASON_MASK) | newAllowedReasons;
-        uidBlockedState.updateEffectiveBlockedReasons();
-        if (oldEffectiveBlockedReasons != uidBlockedState.effectiveBlockedReasons) {
-            mHandler.obtainMessage(MSG_BLOCKED_REASON_CHANGED, uid,
-                    uidBlockedState.effectiveBlockedReasons, oldEffectiveBlockedReasons)
-                    .sendToTarget();
-        }
-
-        return newUidRules;
     }
 
     private class NetPolicyAppIdleStateChangeListener extends AppIdleStateChangeListener {
@@ -4971,10 +4813,23 @@
         }
     }
 
+    private void postBlockedReasonsChangedMsg(int uid, int newEffectiveBlockedReasons,
+            int oldEffectiveBlockedReasons) {
+        mHandler.obtainMessage(MSG_BLOCKED_REASON_CHANGED, uid,
+                newEffectiveBlockedReasons, oldEffectiveBlockedReasons)
+                .sendToTarget();
+    }
+
+    private void postUidRulesChangedMsg(int uid, int uidRules) {
+        mHandler.obtainMessage(MSG_RULES_CHANGED, uid, uidRules)
+                .sendToTarget();
+    }
+
     private void dispatchUidRulesChanged(INetworkPolicyListener listener, int uid, int uidRules) {
         try {
             listener.onUidRulesChanged(uid, uidRules);
         } catch (RemoteException ignored) {
+            // Ignore if there is an error sending the callback to the client.
         }
     }
 
@@ -4983,6 +4838,7 @@
         try {
             listener.onMeteredIfacesChanged(meteredIfaces);
         } catch (RemoteException ignored) {
+            // Ignore if there is an error sending the callback to the client.
         }
     }
 
@@ -4991,6 +4847,7 @@
         try {
             listener.onRestrictBackgroundChanged(restrictBackground);
         } catch (RemoteException ignored) {
+            // Ignore if there is an error sending the callback to the client.
         }
     }
 
@@ -4999,6 +4856,7 @@
         try {
             listener.onUidPoliciesChanged(uid, uidPolicies);
         } catch (RemoteException ignored) {
+            // Ignore if there is an error sending the callback to the client.
         }
     }
 
@@ -5007,6 +4865,7 @@
         try {
             listener.onSubscriptionOverride(subId, overrideMask, overrideValue, networkTypes);
         } catch (RemoteException ignored) {
+            // Ignore if there is an error sending the callback to the client.
         }
     }
 
@@ -5015,6 +4874,7 @@
         try {
             listener.onSubscriptionPlansChanged(subId, plans);
         } catch (RemoteException ignored) {
+            // Ignore if there is an error sending the callback to the client.
         }
     }
 
@@ -5023,6 +4883,7 @@
         try {
             listener.onBlockedReasonChanged(uid, oldBlockedReasons, newBlockedReasons);
         } catch (RemoteException ignored) {
+            // Ignore if there is an error sending the callback to the client.
         }
     }
 
@@ -5033,6 +4894,10 @@
                 case MSG_RULES_CHANGED: {
                     final int uid = msg.arg1;
                     final int uidRules = msg.arg2;
+                    if (LOGV) {
+                        Slog.v(TAG, "Dispatching rules=" + uidRulesToString(uidRules)
+                                + " for uid=" + uid);
+                    }
                     final int length = mListeners.beginBroadcast();
                     for (int i = 0; i < length; i++) {
                         final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
@@ -5605,7 +5470,7 @@
         }
     }
 
-    private static void collectKeys(SparseArray<UidState> source, SparseBooleanArray target) {
+    private static <T> void collectKeys(SparseArray<T> source, SparseBooleanArray target) {
         final int size = source.size();
         for (int i = 0; i < size; i++) {
             target.put(source.keyAt(i), true);
@@ -5653,90 +5518,38 @@
         final long startTime = mStatLogger.getTime();
 
         mContext.enforceCallingOrSelfPermission(OBSERVE_NETWORK_POLICY, TAG);
-        final int uidRules;
-        final boolean isBackgroundRestricted;
+        int blockedReasons;
         synchronized (mUidRulesFirstLock) {
-            uidRules = mUidRules.get(uid, RULE_NONE);
-            isBackgroundRestricted = mRestrictBackground;
+            final UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
+            blockedReasons = uidBlockedState == null
+                    ? BLOCKED_REASON_NONE : uidBlockedState.effectiveBlockedReasons;
+            if (!isNetworkMetered) {
+                blockedReasons &= ~BLOCKED_METERED_REASON_MASK;
+            }
+            mLogger.networkBlocked(uid, uidBlockedState);
         }
-        final boolean ret = isUidNetworkingBlockedInternal(uid, uidRules, isNetworkMetered,
-                isBackgroundRestricted, mLogger);
 
         mStatLogger.logDurationStat(Stats.IS_UID_NETWORKING_BLOCKED, startTime);
 
-        return ret;
+        return blockedReasons != BLOCKED_REASON_NONE;
     }
 
     @Override
     public boolean isUidRestrictedOnMeteredNetworks(int uid) {
         mContext.enforceCallingOrSelfPermission(OBSERVE_NETWORK_POLICY, TAG);
-        final int uidRules;
-        final boolean isBackgroundRestricted;
         synchronized (mUidRulesFirstLock) {
-            uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
-            isBackgroundRestricted = mRestrictBackground;
+            final UidBlockedState uidBlockedState = mUidBlockedState.get(uid);
+            int blockedReasons = uidBlockedState == null
+                    ? BLOCKED_REASON_NONE : uidBlockedState.effectiveBlockedReasons;
+            blockedReasons &= BLOCKED_METERED_REASON_MASK;
+            return blockedReasons != BLOCKED_REASON_NONE;
         }
-        // TODO(b/177490332): The logic here might not be correct because it doesn't consider
-        //  RULE_REJECT_METERED condition. And it could be replaced by
-        //  isUidNetworkingBlockedInternal().
-        return isBackgroundRestricted
-                && !hasRule(uidRules, RULE_ALLOW_METERED)
-                && !hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED);
     }
 
     private static boolean isSystem(int uid) {
         return uid < Process.FIRST_APPLICATION_UID;
     }
 
-    static boolean isUidNetworkingBlockedInternal(int uid, int uidRules, boolean isNetworkMetered,
-            boolean isBackgroundRestricted, @Nullable NetworkPolicyLogger logger) {
-        final int reason;
-        // Networks are never blocked for system components
-        if (isSystem(uid)) {
-            reason = NTWK_ALLOWED_SYSTEM;
-        } else if (hasRule(uidRules, RULE_REJECT_RESTRICTED_MODE)) {
-            reason = NTWK_BLOCKED_RESTRICTED_MODE;
-        } else if (hasRule(uidRules, RULE_REJECT_ALL)) {
-            reason = NTWK_BLOCKED_POWER;
-        } else if (!isNetworkMetered) {
-            reason = NTWK_ALLOWED_NON_METERED;
-        } else if (hasRule(uidRules, RULE_REJECT_METERED)) {
-            reason = NTWK_BLOCKED_DENYLIST;
-        } else if (hasRule(uidRules, RULE_ALLOW_METERED)) {
-            reason = NTWK_ALLOWED_ALLOWLIST;
-        } else if (hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED)) {
-            reason = NTWK_ALLOWED_TMP_ALLOWLIST;
-        } else if (isBackgroundRestricted) {
-            reason = NTWK_BLOCKED_BG_RESTRICT;
-        } else {
-            reason = NTWK_ALLOWED_DEFAULT;
-        }
-
-        final boolean blocked;
-        switch(reason) {
-            case NTWK_ALLOWED_DEFAULT:
-            case NTWK_ALLOWED_NON_METERED:
-            case NTWK_ALLOWED_TMP_ALLOWLIST:
-            case NTWK_ALLOWED_ALLOWLIST:
-            case NTWK_ALLOWED_SYSTEM:
-                blocked = false;
-                break;
-            case NTWK_BLOCKED_RESTRICTED_MODE:
-            case NTWK_BLOCKED_POWER:
-            case NTWK_BLOCKED_DENYLIST:
-            case NTWK_BLOCKED_BG_RESTRICT:
-                blocked = true;
-                break;
-            default:
-                throw new IllegalArgumentException();
-        }
-        if (logger != null) {
-            logger.networkBlocked(uid, reason);
-        }
-
-        return blocked;
-    }
-
     private class NetworkPolicyManagerInternalImpl extends NetworkPolicyManagerInternal {
 
         @Override
@@ -5945,6 +5758,16 @@
         return (bundle != null) ? bundle.getBoolean(key, defaultValue) : defaultValue;
     }
 
+    private static UidBlockedState getOrCreateUidBlockedStateForUid(
+            SparseArray<UidBlockedState> uidBlockedStates, int uid) {
+        UidBlockedState uidBlockedState = uidBlockedStates.get(uid);
+        if (uidBlockedState == null) {
+            uidBlockedState = new UidBlockedState();
+            uidBlockedStates.put(uid, uidBlockedState);
+        }
+        return uidBlockedState;
+    }
+
     @VisibleForTesting
     static final class UidBlockedState {
         public int blockedReasons;
@@ -6008,9 +5831,180 @@
             }
             return effectiveBlockedReasons;
         }
+
+        @Override
+        public String toString() {
+            return toString(blockedReasons, allowedReasons, effectiveBlockedReasons);
+        }
+
+        public static String toString(int blockedReasons, int allowedReasons,
+                int effectiveBlockedReasons) {
+            final StringBuilder sb = new StringBuilder();
+            sb.append("{");
+            sb.append("blocked=").append(blockedReasonsToString(blockedReasons)).append(",");
+            sb.append("allowed=").append(allowedReasonsToString(allowedReasons)).append(",");
+            sb.append("effective=").append(blockedReasonsToString(effectiveBlockedReasons));
+            sb.append("}");
+            return sb.toString();
+        }
+
+        private static final int[] BLOCKED_REASONS = {
+                BLOCKED_REASON_BATTERY_SAVER,
+                BLOCKED_REASON_DOZE,
+                BLOCKED_REASON_APP_STANDBY,
+                BLOCKED_REASON_RESTRICTED_MODE,
+                BLOCKED_METERED_REASON_DATA_SAVER,
+                BLOCKED_METERED_REASON_USER_RESTRICTED,
+                BLOCKED_METERED_REASON_ADMIN_DISABLED,
+        };
+
+        private static final int[] ALLOWED_REASONS = {
+                ALLOWED_REASON_SYSTEM,
+                ALLOWED_REASON_FOREGROUND,
+                ALLOWED_REASON_POWER_SAVE_ALLOWLIST,
+                ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST,
+                ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS,
+                ALLOWED_METERED_REASON_USER_EXEMPTED,
+                ALLOWED_METERED_REASON_SYSTEM,
+                ALLOWED_METERED_REASON_FOREGROUND,
+        };
+
+        private static String blockedReasonToString(int blockedReason) {
+            switch (blockedReason) {
+                case BLOCKED_REASON_NONE:
+                    return "NONE";
+                case BLOCKED_REASON_BATTERY_SAVER:
+                    return "BATTERY_SAVER";
+                case BLOCKED_REASON_DOZE:
+                    return "DOZE";
+                case BLOCKED_REASON_APP_STANDBY:
+                    return "APP_STANDBY";
+                case BLOCKED_REASON_RESTRICTED_MODE:
+                    return "RESTRICTED_MODE";
+                case BLOCKED_METERED_REASON_DATA_SAVER:
+                    return "DATA_SAVER";
+                case BLOCKED_METERED_REASON_USER_RESTRICTED:
+                    return "METERED_USER_RESTRICTED";
+                case BLOCKED_METERED_REASON_ADMIN_DISABLED:
+                    return "METERED_ADMIN_DISABLED";
+                default:
+                    Slog.wtfStack(TAG, "Unknown blockedReason: " + blockedReason);
+                    return String.valueOf(blockedReason);
+            }
+        }
+
+        private static String allowedReasonToString(int allowedReason) {
+            switch (allowedReason) {
+                case ALLOWED_REASON_NONE:
+                    return "NONE";
+                case ALLOWED_REASON_SYSTEM:
+                    return "SYSTEM";
+                case ALLOWED_REASON_FOREGROUND:
+                    return "FOREGROUND";
+                case ALLOWED_REASON_POWER_SAVE_ALLOWLIST:
+                    return "POWER_SAVE_ALLOWLIST";
+                case ALLOWED_REASON_POWER_SAVE_EXCEPT_IDLE_ALLOWLIST:
+                    return "POWER_SAVE_EXCEPT_IDLE_ALLOWLIST";
+                case ALLOWED_REASON_RESTRICTED_MODE_PERMISSIONS:
+                    return "RESTRICTED_MODE_PERMISSIONS";
+                case ALLOWED_METERED_REASON_USER_EXEMPTED:
+                    return "METERED_USER_EXEMPTED";
+                case ALLOWED_METERED_REASON_SYSTEM:
+                    return "METERED_SYSTEM";
+                case ALLOWED_METERED_REASON_FOREGROUND:
+                    return "METERED_FOREGROUND";
+                default:
+                    Slog.wtfStack(TAG, "Unknown allowedReason: " + allowedReason);
+                    return String.valueOf(allowedReason);
+            }
+        }
+
+        public static String blockedReasonsToString(int blockedReasons) {
+            if (blockedReasons == BLOCKED_REASON_NONE) {
+                return blockedReasonToString(BLOCKED_REASON_NONE);
+            }
+            final StringBuilder sb = new StringBuilder();
+            for (int reason : BLOCKED_REASONS) {
+                if ((blockedReasons & reason) != 0) {
+                    sb.append(sb.length() == 0 ? "" : "|");
+                    sb.append(blockedReasonToString(reason));
+                    blockedReasons &= ~reason;
+                }
+            }
+            if (blockedReasons != 0) {
+                sb.append(sb.length() == 0 ? "" : "|");
+                sb.append(String.valueOf(blockedReasons));
+                Slog.wtfStack(TAG, "Unknown blockedReasons: " + blockedReasons);
+            }
+            return sb.toString();
+        }
+
+        public static String allowedReasonsToString(int allowedReasons) {
+            if (allowedReasons == ALLOWED_REASON_NONE) {
+                return allowedReasonToString(ALLOWED_REASON_NONE);
+            }
+            final StringBuilder sb = new StringBuilder();
+            for (int reason : ALLOWED_REASONS) {
+                if ((allowedReasons & reason) != 0) {
+                    sb.append(sb.length() == 0 ? "" : "|");
+                    sb.append(allowedReasonToString(reason));
+                    allowedReasons &= ~reason;
+                }
+            }
+            if (allowedReasons != 0) {
+                sb.append(sb.length() == 0 ? "" : "|");
+                sb.append(String.valueOf(allowedReasons));
+                Slog.wtfStack(TAG, "Unknown allowedReasons: " + allowedReasons);
+            }
+            return sb.toString();
+        }
+
+        public void copyFrom(UidBlockedState uidBlockedState) {
+            blockedReasons = uidBlockedState.blockedReasons;
+            allowedReasons = uidBlockedState.allowedReasons;
+            effectiveBlockedReasons = uidBlockedState.effectiveBlockedReasons;
+        }
+
+        public int deriveUidRules() {
+            int uidRule = RULE_NONE;
+            if ((effectiveBlockedReasons & BLOCKED_REASON_RESTRICTED_MODE) != 0) {
+                uidRule |= RULE_REJECT_RESTRICTED_MODE;
+            }
+
+            int powerBlockedReasons = BLOCKED_REASON_APP_STANDBY
+                    | BLOCKED_REASON_DOZE
+                    | BLOCKED_REASON_BATTERY_SAVER;
+            if ((effectiveBlockedReasons & powerBlockedReasons) != 0) {
+                uidRule |= RULE_REJECT_ALL;
+            } else if ((blockedReasons & powerBlockedReasons) != 0) {
+                uidRule |= RULE_ALLOW_ALL;
+            }
+
+            // UidRule doesn't include RestrictBackground (DataSaver) state, so not including in
+            // metered blocked reasons below.
+            int meteredBlockedReasons = BLOCKED_METERED_REASON_ADMIN_DISABLED
+                    | BLOCKED_METERED_REASON_USER_RESTRICTED;
+            if ((effectiveBlockedReasons & meteredBlockedReasons) != 0) {
+                uidRule |= RULE_REJECT_METERED;
+            } else if ((blockedReasons & BLOCKED_METERED_REASON_USER_RESTRICTED) != 0
+                    && (allowedReasons & ALLOWED_METERED_REASON_FOREGROUND) != 0) {
+                uidRule |= RULE_TEMPORARY_ALLOW_METERED;
+            } else if ((blockedReasons & BLOCKED_METERED_REASON_DATA_SAVER) != 0) {
+                if ((allowedReasons & ALLOWED_METERED_REASON_USER_EXEMPTED) != 0) {
+                    uidRule |= RULE_ALLOW_ALL;
+                } else if ((allowedReasons & ALLOWED_METERED_REASON_FOREGROUND) != 0) {
+                    uidRule |= RULE_TEMPORARY_ALLOW_METERED;
+                }
+            }
+            if (LOGV) {
+                Slog.v(TAG, "uidBlockedState=" + this.toString()
+                        + " -> uidRule=" + uidRulesToString(uidRule));
+            }
+            return uidRule;
+        }
     }
 
-    private class NotificationId {
+    private static class NotificationId {
         private final String mTag;
         private final int mId;
 
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index f4b72a1..c876d41 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -150,6 +150,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FileRotator;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.net.module.util.BinderUtils;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
 
@@ -2097,14 +2098,18 @@
 
         @Override
         public void notifyAlertReached() throws RemoteException {
-            mAlertObserver.limitReached(LIMIT_GLOBAL_ALERT, null /* unused */);
+            // This binder object can only have been obtained by a process that holds
+            // NETWORK_STATS_PROVIDER. Thus, no additional permission check is required.
+            BinderUtils.withCleanCallingIdentity(() ->
+                    mAlertObserver.limitReached(LIMIT_GLOBAL_ALERT, null /* unused */));
         }
 
         @Override
         public void notifyWarningOrLimitReached() {
             Log.d(TAG, mTag + ": notifyWarningOrLimitReached");
-            LocalServices.getService(NetworkPolicyManagerInternal.class)
-                    .onStatsProviderWarningOrLimitReached(mTag);
+            BinderUtils.withCleanCallingIdentity(() ->
+                    LocalServices.getService(NetworkPolicyManagerInternal.class)
+                            .onStatsProviderWarningOrLimitReached(mTag));
         }
 
         @Override
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 0bb6198..22320fd 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -27,9 +27,8 @@
 
 java_sdk_library {
     name: "android.test.mock",
-
-    srcs: [
-        ":android-test-mock-sources",
+    srcs: [":android-test-mock-sources"],
+    api_srcs: [
         // Note: Below are NOT APIs of this library. We only take APIs under
         // the android.test.mock package. They however provide private APIs that
         // android.test.mock APIs references to. We need to have the classes in
@@ -44,15 +43,9 @@
         "app-compat-annotations",
         "unsupportedappusage",
     ],
-
     api_packages: [
         "android.test.mock",
     ],
-    // Only include android.test.mock.* classes. Jarjar rules below removes
-    // classes in other packages like android.content. In order to keep the
-    // list up-to-date, permitted_packages ensures that the library contains
-    // clases under android.test.mock after the jarjar rules are applied.
-    jarjar_rules: "jarjar-rules.txt",
     permitted_packages: [
         "android.test.mock",
     ],
diff --git a/test-mock/jarjar-rules.txt b/test-mock/jarjar-rules.txt
deleted file mode 100644
index 4420a44..0000000
--- a/test-mock/jarjar-rules.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-zap android.accounts.**
-zap android.app.**
-zap android.content.**
-zap android.database.**
-zap android.os.**
-zap android.util.**
-zap android.view.**