Merge "Update @UsesReflection annotation" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 98b326f..ce7c0440 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5317,7 +5317,6 @@
     ctor @Deprecated public AutomaticZenRule(String, android.content.ComponentName, android.net.Uri, int, boolean);
     ctor public AutomaticZenRule(@NonNull String, @Nullable android.content.ComponentName, @Nullable android.content.ComponentName, @NonNull android.net.Uri, @Nullable android.service.notification.ZenPolicy, int, boolean);
     ctor public AutomaticZenRule(android.os.Parcel);
-    method @FlaggedApi("android.app.modes_api") public boolean canUpdate();
     method public int describeContents();
     method public android.net.Uri getConditionId();
     method @Nullable public android.content.ComponentName getConfigurationActivity();
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b8c9fc66..5296712 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4221,11 +4221,14 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.UserProperties> CREATOR;
     field public static final int CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT = 1; // 0x1
     field public static final int CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION = 0; // 0x0
+    field public static final int CROSS_PROFILE_CONTENT_SHARING_UNKNOWN = -1; // 0xffffffff
     field public static final int SHOW_IN_QUIET_MODE_DEFAULT = 2; // 0x2
     field public static final int SHOW_IN_QUIET_MODE_HIDDEN = 1; // 0x1
     field public static final int SHOW_IN_QUIET_MODE_PAUSED = 0; // 0x0
+    field public static final int SHOW_IN_QUIET_MODE_UNKNOWN = -1; // 0xffffffff
     field public static final int SHOW_IN_SHARING_SURFACES_NO = 2; // 0x2
     field public static final int SHOW_IN_SHARING_SURFACES_SEPARATE = 1; // 0x1
+    field public static final int SHOW_IN_SHARING_SURFACES_UNKNOWN = -1; // 0xffffffff
     field public static final int SHOW_IN_SHARING_SURFACES_WITH_PARENT = 0; // 0x0
   }
 
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index bbe03a3..0d1d8d7 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -284,16 +284,6 @@
     method public default void onOpActiveChanged(@NonNull String, int, @NonNull String, @Nullable String, boolean, int, int);
   }
 
-  public final class AutomaticZenRule implements android.os.Parcelable {
-    method @FlaggedApi("android.app.modes_api") public int getUserModifiedFields();
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_INTERRUPTION_FILTER = 2; // 0x2
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_NAME = 1; // 0x1
-  }
-
-  @FlaggedApi("android.app.modes_api") public static final class AutomaticZenRule.Builder {
-    method @FlaggedApi("android.app.modes_api") @NonNull public android.app.AutomaticZenRule.Builder setUserModifiedFields(int);
-  }
-
   public class BroadcastOptions extends android.app.ComponentOptions {
     ctor public BroadcastOptions();
     ctor public BroadcastOptions(@NonNull android.os.Bundle);
@@ -1177,6 +1167,7 @@
     method public int getShowInLauncher();
     field public static final int SHOW_IN_LAUNCHER_NO = 2; // 0x2
     field public static final int SHOW_IN_LAUNCHER_SEPARATE = 1; // 0x1
+    field public static final int SHOW_IN_LAUNCHER_UNKNOWN = -1; // 0xffffffff
     field public static final int SHOW_IN_LAUNCHER_WITH_PARENT = 0; // 0x0
   }
 
@@ -3021,47 +3012,8 @@
     method @Deprecated public boolean isBound();
   }
 
-  @FlaggedApi("android.app.modes_api") public final class ZenDeviceEffects implements android.os.Parcelable {
-    method public int getUserModifiedFields();
-    field public static final int FIELD_DIM_WALLPAPER = 4; // 0x4
-    field public static final int FIELD_DISABLE_AUTO_BRIGHTNESS = 16; // 0x10
-    field public static final int FIELD_DISABLE_TAP_TO_WAKE = 32; // 0x20
-    field public static final int FIELD_DISABLE_TILT_TO_WAKE = 64; // 0x40
-    field public static final int FIELD_DISABLE_TOUCH = 128; // 0x80
-    field public static final int FIELD_GRAYSCALE = 1; // 0x1
-    field public static final int FIELD_MAXIMIZE_DOZE = 512; // 0x200
-    field public static final int FIELD_MINIMIZE_RADIO_USAGE = 256; // 0x100
-    field public static final int FIELD_NIGHT_MODE = 8; // 0x8
-    field public static final int FIELD_SUPPRESS_AMBIENT_DISPLAY = 2; // 0x2
-  }
-
-  @FlaggedApi("android.app.modes_api") public static final class ZenDeviceEffects.Builder {
-    method @NonNull public android.service.notification.ZenDeviceEffects.Builder setUserModifiedFields(int);
-  }
-
-  public final class ZenPolicy implements android.os.Parcelable {
-    method @FlaggedApi("android.app.modes_api") public int getUserModifiedFields();
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_ALLOW_CHANNELS = 8; // 0x8
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_CALLS = 2; // 0x2
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_CONVERSATIONS = 4; // 0x4
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_MESSAGES = 1; // 0x1
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_ALARMS = 128; // 0x80
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_EVENTS = 32; // 0x20
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_MEDIA = 256; // 0x100
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS = 64; // 0x40
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_SYSTEM = 512; // 0x200
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_AMBIENT = 32768; // 0x8000
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_BADGE = 16384; // 0x4000
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT = 1024; // 0x400
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_LIGHTS = 2048; // 0x800
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_NOTIFICATION_LIST = 65536; // 0x10000
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_PEEK = 4096; // 0x1000
-    field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_STATUS_BAR = 8192; // 0x2000
-  }
-
   public static final class ZenPolicy.Builder {
     ctor public ZenPolicy.Builder(@Nullable android.service.notification.ZenPolicy);
-    method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy.Builder setUserModifiedFields(int);
   }
 
 }
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index 5b354fc..d57a4e5 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -23,7 +23,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.TestApi;
 import android.app.NotificationManager.InterruptionFilter;
 import android.content.ComponentName;
 import android.net.Uri;
@@ -113,8 +112,8 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface Type {}
 
-    /** Used to track which rule variables have been modified by the user.
-     * Should be checked against the bitmask {@link #getUserModifiedFields()}.
+    /**
+     * Enum for the user-modifiable fields in this object.
      * @hide
      */
     @IntDef(flag = true, prefix = { "FIELD_" }, value = {
@@ -128,13 +127,11 @@
      * @hide
      */
     @FlaggedApi(Flags.FLAG_MODES_API)
-    @TestApi
     public static final int FIELD_NAME = 1 << 0;
     /**
      * @hide
      */
     @FlaggedApi(Flags.FLAG_MODES_API)
-    @TestApi
     public static final int FIELD_INTERRUPTION_FILTER = 1 << 1;
 
     private boolean enabled;
@@ -153,7 +150,6 @@
     private int mIconResId;
     private String mTriggerDescription;
     private boolean mAllowManualInvocation;
-    private @ModifiableField int mUserModifiedFields; // Bitwise representation
 
     /**
      * The maximum string length for any string contained in this automatic zen rule. This pertains
@@ -256,7 +252,6 @@
             mIconResId = source.readInt();
             mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH);
             mType = source.readInt();
-            mUserModifiedFields = source.readInt();
         }
     }
 
@@ -307,8 +302,7 @@
      * Returns whether this rule's name has been modified by the user.
      * @hide
      */
-    // TODO: b/310620812 - Replace with mUserModifiedFields & FIELD_NAME once
-    //  FLAG_MODES_API is inlined.
+    // TODO: b/310620812 - Consider removing completely. Seems not be used anywhere except tests.
     public boolean isModified() {
         return mModified;
     }
@@ -506,32 +500,6 @@
         return type;
     }
 
-    /**
-     * Gets the bitmask representing which fields are user modified. Bits are set using
-     * {@link ModifiableField}.
-     * @hide
-     */
-    @FlaggedApi(Flags.FLAG_MODES_API)
-    @TestApi
-    public @ModifiableField int getUserModifiedFields() {
-        return mUserModifiedFields;
-    }
-
-    /**
-     * Returns {@code true} if the {@link AutomaticZenRule} can be updated.
-     * When this returns {@code false}, calls to
-     * {@link NotificationManager#updateAutomaticZenRule(String, AutomaticZenRule)}) with this rule
-     * will ignore changes to user-configurable fields.
-     */
-    @FlaggedApi(Flags.FLAG_MODES_API)
-    public boolean canUpdate() {
-        // The rule is considered updateable if its bitmask has no user modifications, and
-        // the bitmasks of the policy and device effects have no modification.
-        return mUserModifiedFields == 0
-                && (mZenPolicy == null || mZenPolicy.getUserModifiedFields() == 0)
-                && (mDeviceEffects == null || mDeviceEffects.getUserModifiedFields() == 0);
-    }
-
     @Override
     public int describeContents() {
         return 0;
@@ -560,7 +528,6 @@
             dest.writeInt(mIconResId);
             dest.writeString(mTriggerDescription);
             dest.writeInt(mType);
-            dest.writeInt(mUserModifiedFields);
         }
     }
 
@@ -582,16 +549,14 @@
                     .append(",allowManualInvocation=").append(mAllowManualInvocation)
                     .append(",iconResId=").append(mIconResId)
                     .append(",triggerDescription=").append(mTriggerDescription)
-                    .append(",type=").append(mType)
-                    .append(",userModifiedFields=")
-                    .append(modifiedFieldsToString(mUserModifiedFields));
+                    .append(",type=").append(mType);
         }
 
         return sb.append(']').toString();
     }
 
-    @FlaggedApi(Flags.FLAG_MODES_API)
-    private String modifiedFieldsToString(int bitmask) {
+    /** @hide */
+    public static String fieldsToString(@ModifiableField int bitmask) {
         ArrayList<String> modified = new ArrayList<>();
         if ((bitmask & FIELD_NAME) != 0) {
             modified.add("FIELD_NAME");
@@ -623,8 +588,7 @@
                     && other.mAllowManualInvocation == mAllowManualInvocation
                     && other.mIconResId == mIconResId
                     && Objects.equals(other.mTriggerDescription, mTriggerDescription)
-                    && other.mType == mType
-                    && other.mUserModifiedFields == mUserModifiedFields;
+                    && other.mType == mType;
         }
         return finalEquals;
     }
@@ -634,8 +598,7 @@
         if (Flags.modesApi()) {
             return Objects.hash(enabled, name, interruptionFilter, conditionId, owner,
                     configurationActivity, mZenPolicy, mDeviceEffects, mModified, creationTime,
-                    mPkg, mAllowManualInvocation, mIconResId, mTriggerDescription, mType,
-                    mUserModifiedFields);
+                    mPkg, mAllowManualInvocation, mIconResId, mTriggerDescription, mType);
         }
         return Objects.hash(enabled, name, interruptionFilter, conditionId, owner,
                 configurationActivity, mZenPolicy, mModified, creationTime, mPkg);
@@ -704,7 +667,6 @@
         private boolean mAllowManualInvocation;
         private long mCreationTime;
         private String mPkg;
-        private @ModifiableField int mUserModifiedFields;
 
         public Builder(@NonNull AutomaticZenRule rule) {
             mName = rule.getName();
@@ -721,7 +683,6 @@
             mAllowManualInvocation = rule.isManualInvocationAllowed();
             mCreationTime = rule.getCreationTime();
             mPkg = rule.getPackageName();
-            mUserModifiedFields = rule.mUserModifiedFields;
         }
 
         public Builder(@NonNull String name, @NonNull Uri conditionId) {
@@ -848,19 +809,6 @@
             return this;
         }
 
-        /**
-         * Sets the bitmask representing which fields have been user-modified.
-         * This method should not be used outside of tests. The value of userModifiedFields
-         * should be set based on what values are changed when a rule is populated or updated..
-         * @hide
-         */
-        @FlaggedApi(Flags.FLAG_MODES_API)
-        @TestApi
-        public @NonNull Builder setUserModifiedFields(@ModifiableField int userModifiedFields) {
-            mUserModifiedFields = userModifiedFields;
-            return this;
-        }
-
         public @NonNull AutomaticZenRule build() {
             AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConfigurationActivity,
                     mConditionId, mPolicy, mInterruptionFilter, mEnabled);
@@ -871,7 +819,6 @@
             rule.mIconResId = mIconResId;
             rule.mAllowManualInvocation = mAllowManualInvocation;
             rule.setPackageName(mPkg);
-            rule.mUserModifiedFields = mUserModifiedFields;
 
             return rule;
         }
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 57749d4..269c6c2 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -123,6 +123,7 @@
      * @hide
      */
     @IntDef(prefix = "SHOW_IN_LAUNCHER_", value = {
+            SHOW_IN_LAUNCHER_UNKNOWN,
             SHOW_IN_LAUNCHER_WITH_PARENT,
             SHOW_IN_LAUNCHER_SEPARATE,
             SHOW_IN_LAUNCHER_NO,
@@ -131,6 +132,13 @@
     public @interface ShowInLauncher {
     }
     /**
+     * Indicates that the show in launcher value for this profile is unknown or unsupported.
+     * @hide
+     */
+    @TestApi
+    @SuppressLint("UnflaggedApi") // b/306636213
+    public static final int SHOW_IN_LAUNCHER_UNKNOWN = -1;
+    /**
      * Suggests that the launcher should show this user's apps in the main tab.
      * That is, either this user is a full user, so its apps should be presented accordingly, or, if
      * this user is a profile, then its apps should be shown alongside its parent's apps.
@@ -157,6 +165,7 @@
      * @hide
      */
     @IntDef(prefix = "SHOW_IN_SETTINGS_", value = {
+            SHOW_IN_SETTINGS_UNKNOWN,
             SHOW_IN_SETTINGS_WITH_PARENT,
             SHOW_IN_SETTINGS_SEPARATE,
             SHOW_IN_SETTINGS_NO,
@@ -165,6 +174,12 @@
     public @interface ShowInSettings {
     }
     /**
+     * Indicates that the show in settings value for this profile is unknown or unsupported.
+     * @hide
+     */
+    @SuppressLint("UnflaggedApi") // b/306636213
+    public static final int SHOW_IN_SETTINGS_UNKNOWN = -1;
+    /**
      * Suggests that the Settings app should show this user's apps in the main tab.
      * That is, either this user is a full user, so its apps should be presented accordingly, or, if
      * this user is a profile, then its apps should be shown alongside its parent's apps.
@@ -309,6 +324,7 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "SHOW_IN_QUIET_MODE_",
             value = {
+                    SHOW_IN_QUIET_MODE_UNKNOWN,
                     SHOW_IN_QUIET_MODE_PAUSED,
                     SHOW_IN_QUIET_MODE_HIDDEN,
                     SHOW_IN_QUIET_MODE_DEFAULT,
@@ -318,6 +334,12 @@
     }
 
     /**
+     * Indicates that the show in quiet mode value for this profile is unknown.
+     */
+    @SuppressLint("UnflaggedApi") // b/306636213
+    public static final int SHOW_IN_QUIET_MODE_UNKNOWN = -1;
+
+    /**
      * Indicates that the profile should still be visible in quiet mode but should be shown as
      * paused (e.g. by greying out its icons).
      */
@@ -347,6 +369,7 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "SHOW_IN_SHARING_SURFACES_",
             value = {
+                    SHOW_IN_SHARING_SURFACES_UNKNOWN,
                     SHOW_IN_SHARING_SURFACES_SEPARATE,
                     SHOW_IN_SHARING_SURFACES_WITH_PARENT,
                     SHOW_IN_SHARING_SURFACES_NO,
@@ -356,6 +379,12 @@
     }
 
     /**
+     * Indicates that the show in launcher value for this profile is unknown or unsupported.
+     */
+    @SuppressLint("UnflaggedApi") // b/306636213
+    public static final int SHOW_IN_SHARING_SURFACES_UNKNOWN = SHOW_IN_LAUNCHER_UNKNOWN;
+
+    /**
      * Indicates that the profile data and apps should be shown in sharing surfaces intermixed with
      * parent user's data and apps.
      */
@@ -379,7 +408,8 @@
      *
      * @hide
      */
-    @IntDef(prefix = {"CROSS_PROFILE_CONTENT_SHARING_STRATEGY_"}, value = {
+    @IntDef(prefix = {"CROSS_PROFILE_CONTENT_SHARING_"}, value = {
+            CROSS_PROFILE_CONTENT_SHARING_UNKNOWN,
             CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION,
             CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT
     })
@@ -388,6 +418,13 @@
     }
 
     /**
+     * Signifies that cross-profile content sharing strategy, both to and from this profile, is
+     * unknown/unsupported.
+     */
+    @SuppressLint("UnflaggedApi") // b/306636213
+    public static final int CROSS_PROFILE_CONTENT_SHARING_UNKNOWN = -1;
+
+    /**
      * Signifies that cross-profile content sharing strategy, both to and from this profile, should
      * not be delegated to any other user/profile.
      * For ex:
diff --git a/core/java/android/service/notification/ZenDeviceEffects.java b/core/java/android/service/notification/ZenDeviceEffects.java
index 03ebae5..90049e6 100644
--- a/core/java/android/service/notification/ZenDeviceEffects.java
+++ b/core/java/android/service/notification/ZenDeviceEffects.java
@@ -20,7 +20,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.TestApi;
 import android.app.Flags;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -37,8 +36,8 @@
 @FlaggedApi(Flags.FLAG_MODES_API)
 public final class ZenDeviceEffects implements Parcelable {
 
-    /** Used to track which rule variables have been modified by the user.
-     * Should be checked against the bitmask {@link #getUserModifiedFields()}.
+    /**
+     * Enum for the user-modifiable fields in this object.
      * @hide
      */
     @IntDef(flag = true, prefix = { "FIELD_" }, value = {
@@ -59,52 +58,42 @@
     /**
      * @hide
      */
-    @TestApi
     public static final int FIELD_GRAYSCALE = 1 << 0;
     /**
      * @hide
      */
-    @TestApi
     public static final int FIELD_SUPPRESS_AMBIENT_DISPLAY = 1 << 1;
     /**
      * @hide
      */
-    @TestApi
     public static final int FIELD_DIM_WALLPAPER = 1 << 2;
     /**
      * @hide
      */
-    @TestApi
     public static final int FIELD_NIGHT_MODE = 1 << 3;
     /**
      * @hide
      */
-    @TestApi
     public static final int FIELD_DISABLE_AUTO_BRIGHTNESS = 1 << 4;
     /**
      * @hide
      */
-    @TestApi
     public static final int FIELD_DISABLE_TAP_TO_WAKE = 1 << 5;
     /**
      * @hide
      */
-    @TestApi
     public static final int FIELD_DISABLE_TILT_TO_WAKE = 1 << 6;
     /**
      * @hide
      */
-    @TestApi
     public static final int FIELD_DISABLE_TOUCH = 1 << 7;
     /**
      * @hide
      */
-    @TestApi
     public static final int FIELD_MINIMIZE_RADIO_USAGE = 1 << 8;
     /**
      * @hide
      */
-    @TestApi
     public static final int FIELD_MAXIMIZE_DOZE = 1 << 9;
 
     private final boolean mGrayscale;
@@ -119,13 +108,10 @@
     private final boolean mMinimizeRadioUsage;
     private final boolean mMaximizeDoze;
 
-    private final @ModifiableField int mUserModifiedFields; // Bitwise representation
-
     private ZenDeviceEffects(boolean grayscale, boolean suppressAmbientDisplay,
             boolean dimWallpaper, boolean nightMode, boolean disableAutoBrightness,
             boolean disableTapToWake, boolean disableTiltToWake, boolean disableTouch,
-            boolean minimizeRadioUsage, boolean maximizeDoze,
-            @ModifiableField int userModifiedFields) {
+            boolean minimizeRadioUsage, boolean maximizeDoze) {
         mGrayscale = grayscale;
         mSuppressAmbientDisplay = suppressAmbientDisplay;
         mDimWallpaper = dimWallpaper;
@@ -136,7 +122,6 @@
         mDisableTouch = disableTouch;
         mMinimizeRadioUsage = minimizeRadioUsage;
         mMaximizeDoze = maximizeDoze;
-        mUserModifiedFields = userModifiedFields;
     }
 
     @Override
@@ -153,15 +138,14 @@
                 && this.mDisableTiltToWake == that.mDisableTiltToWake
                 && this.mDisableTouch == that.mDisableTouch
                 && this.mMinimizeRadioUsage == that.mMinimizeRadioUsage
-                && this.mMaximizeDoze == that.mMaximizeDoze
-                && this.mUserModifiedFields == that.mUserModifiedFields;
+                && this.mMaximizeDoze == that.mMaximizeDoze;
     }
 
     @Override
     public int hashCode() {
         return Objects.hash(mGrayscale, mSuppressAmbientDisplay, mDimWallpaper, mNightMode,
                 mDisableAutoBrightness, mDisableTapToWake, mDisableTiltToWake, mDisableTouch,
-                mMinimizeRadioUsage, mMaximizeDoze, mUserModifiedFields);
+                mMinimizeRadioUsage, mMaximizeDoze);
     }
 
     @Override
@@ -177,11 +161,11 @@
         if (mDisableTouch) effects.add("disableTouch");
         if (mMinimizeRadioUsage) effects.add("minimizeRadioUsage");
         if (mMaximizeDoze) effects.add("maximizeDoze");
-        return "[" + String.join(", ", effects) + "]"
-                + " userModifiedFields: " + modifiedFieldsToString(mUserModifiedFields);
+        return "[" + String.join(", ", effects) + "]";
     }
 
-    private String modifiedFieldsToString(int bitmask) {
+    /** @hide */
+    public static String fieldsToString(@ModifiableField int bitmask) {
         ArrayList<String> modified = new ArrayList<>();
         if ((bitmask & FIELD_GRAYSCALE) != 0) {
             modified.add("FIELD_GRAYSCALE");
@@ -312,7 +296,7 @@
             return new ZenDeviceEffects(in.readBoolean(),
                     in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(),
                     in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(),
-                    in.readBoolean(), in.readInt());
+                    in.readBoolean());
         }
 
         @Override
@@ -321,16 +305,6 @@
         }
     };
 
-    /**
-     * Gets the bitmask representing which fields are user modified. Bits are set using
-     * {@link ModifiableField}.
-     * @hide
-     */
-    @TestApi
-    public @ModifiableField int getUserModifiedFields() {
-        return mUserModifiedFields;
-    }
-
     @Override
     public int describeContents() {
         return 0;
@@ -348,7 +322,6 @@
         dest.writeBoolean(mDisableTouch);
         dest.writeBoolean(mMinimizeRadioUsage);
         dest.writeBoolean(mMaximizeDoze);
-        dest.writeInt(mUserModifiedFields);
     }
 
     /** Builder class for {@link ZenDeviceEffects} objects. */
@@ -365,7 +338,6 @@
         private boolean mDisableTouch;
         private boolean mMinimizeRadioUsage;
         private boolean mMaximizeDoze;
-        private @ModifiableField int mUserModifiedFields;
 
         /**
          * Instantiates a new {@link ZenPolicy.Builder} with all effects set to default (disabled).
@@ -388,7 +360,6 @@
             mDisableTouch = zenDeviceEffects.shouldDisableTouch();
             mMinimizeRadioUsage = zenDeviceEffects.shouldMinimizeRadioUsage();
             mMaximizeDoze = zenDeviceEffects.shouldMaximizeDoze();
-            mUserModifiedFields = zenDeviceEffects.mUserModifiedFields;
         }
 
         /**
@@ -510,24 +481,13 @@
             return this;
         }
 
-        /**
-         * Sets the bitmask representing which fields are user modified. See the FIELD_ constants.
-         * @hide
-         */
-        @TestApi
-        @NonNull
-        public Builder setUserModifiedFields(@ModifiableField int userModifiedFields) {
-            mUserModifiedFields = userModifiedFields;
-            return this;
-        }
-
         /** Builds a {@link ZenDeviceEffects} object based on the builder's state. */
         @NonNull
         public ZenDeviceEffects build() {
             return new ZenDeviceEffects(mGrayscale,
                     mSuppressAmbientDisplay, mDimWallpaper, mNightMode, mDisableAutoBrightness,
                     mDisableTapToWake, mDisableTiltToWake, mDisableTouch, mMinimizeRadioUsage,
-                    mMaximizeDoze, mUserModifiedFields);
+                    mMaximizeDoze);
         }
     }
 }
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index d4d602d..c479877 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -206,7 +206,7 @@
     private static final String ALLOW_ATT_CONV = "convos";
     private static final String ALLOW_ATT_CONV_FROM = "convosFrom";
     private static final String ALLOW_ATT_CHANNELS = "priorityChannels";
-    private static final String USER_MODIFIED_FIELDS = "policyUserModifiedFields";
+    private static final String POLICY_USER_MODIFIED_FIELDS = "policyUserModifiedFields";
     private static final String DISALLOW_TAG = "disallow";
     private static final String DISALLOW_ATT_VISUAL_EFFECTS = "visualEffects";
     private static final String STATE_TAG = "state";
@@ -806,6 +806,9 @@
             rt.triggerDescription = parser.getAttributeValue(null, RULE_ATT_TRIGGER_DESC);
             rt.type = safeInt(parser, RULE_ATT_TYPE, AutomaticZenRule.TYPE_UNKNOWN);
             rt.userModifiedFields = safeInt(parser, RULE_ATT_USER_MODIFIED_FIELDS, 0);
+            rt.zenPolicyUserModifiedFields = safeInt(parser, POLICY_USER_MODIFIED_FIELDS, 0);
+            rt.zenDeviceEffectsUserModifiedFields = safeInt(parser,
+                    DEVICE_EFFECT_USER_MODIFIED_FIELDS, 0);
             Long deletionInstant = tryParseLong(
                     parser.getAttributeValue(null, RULE_ATT_DELETION_INSTANT), null);
             if (deletionInstant != null) {
@@ -858,6 +861,9 @@
             }
             out.attributeInt(null, RULE_ATT_TYPE, rule.type);
             out.attributeInt(null, RULE_ATT_USER_MODIFIED_FIELDS, rule.userModifiedFields);
+            out.attributeInt(null, POLICY_USER_MODIFIED_FIELDS, rule.zenPolicyUserModifiedFields);
+            out.attributeInt(null, DEVICE_EFFECT_USER_MODIFIED_FIELDS,
+                    rule.zenDeviceEffectsUserModifiedFields);
             if (rule.deletionInstant != null) {
                 out.attributeLong(null, RULE_ATT_DELETION_INSTANT,
                         rule.deletionInstant.toEpochMilli());
@@ -924,7 +930,6 @@
                 builder.allowPriorityChannels(channels == ZenPolicy.STATE_ALLOW);
                 policySet = true;
             }
-            builder.setUserModifiedFields(safeInt(parser, USER_MODIFIED_FIELDS, 0));
         }
 
         if (calls != ZenPolicy.PEOPLE_TYPE_UNSET) {
@@ -1037,7 +1042,6 @@
 
         if (Flags.modesApi()) {
             writeZenPolicyState(ALLOW_ATT_CHANNELS, policy.getPriorityChannels(), out);
-            out.attributeInt(null, USER_MODIFIED_FIELDS, policy.getUserModifiedFields());
         }
     }
 
@@ -1083,7 +1087,6 @@
                 .setShouldMinimizeRadioUsage(
                         safeBoolean(parser, DEVICE_EFFECT_MINIMIZE_RADIO_USAGE, false))
                 .setShouldMaximizeDoze(safeBoolean(parser, DEVICE_EFFECT_MAXIMIZE_DOZE, false))
-                .setUserModifiedFields(safeInt(parser, DEVICE_EFFECT_USER_MODIFIED_FIELDS, 0))
                 .build();
 
         return deviceEffects.hasEffects() ? deviceEffects : null;
@@ -1108,8 +1111,6 @@
         writeBooleanIfTrue(out, DEVICE_EFFECT_MINIMIZE_RADIO_USAGE,
                 deviceEffects.shouldMinimizeRadioUsage());
         writeBooleanIfTrue(out, DEVICE_EFFECT_MAXIMIZE_DOZE, deviceEffects.shouldMaximizeDoze());
-        out.attributeInt(null, DEVICE_EFFECT_USER_MODIFIED_FIELDS,
-                deviceEffects.getUserModifiedFields());
     }
 
     private static void writeBooleanIfTrue(TypedXmlSerializer out, String att, boolean value)
@@ -2041,7 +2042,9 @@
         public String triggerDescription;
         public String iconResName;
         public boolean allowManualInvocation;
-        public int userModifiedFields;
+        @AutomaticZenRule.ModifiableField public int userModifiedFields;
+        @ZenPolicy.ModifiableField public int zenPolicyUserModifiedFields;
+        @ZenDeviceEffects.ModifiableField public int zenDeviceEffectsUserModifiedFields;
         @Nullable public Instant deletionInstant; // Only set on deleted rules.
 
         public ZenRule() { }
@@ -2076,6 +2079,8 @@
                 triggerDescription = source.readString();
                 type = source.readInt();
                 userModifiedFields = source.readInt();
+                zenPolicyUserModifiedFields = source.readInt();
+                zenDeviceEffectsUserModifiedFields = source.readInt();
                 if (source.readInt() == 1) {
                     deletionInstant = Instant.ofEpochMilli(source.readLong());
                 }
@@ -2083,15 +2088,21 @@
         }
 
         /**
-         * @see AutomaticZenRule#canUpdate()
+         * Whether this ZenRule can be updated by an app. In general, rules that have been
+         * customized by the user cannot be further updated by an app, with some exceptions:
+         * <ul>
+         *     <li>Non user-configurable fields, like type, icon, configurationActivity, etc.
+         *     <li>Name, if the name was not specifically modified by the user (to support language
+         *          switches).
+         * </ul>
          */
         @FlaggedApi(Flags.FLAG_MODES_API)
         public boolean canBeUpdatedByApp() {
             // The rule is considered updateable if its bitmask has no user modifications, and
             // the bitmasks of the policy and device effects have no modification.
             return userModifiedFields == 0
-                    && (zenPolicy == null || zenPolicy.getUserModifiedFields() == 0)
-                    && (zenDeviceEffects == null || zenDeviceEffects.getUserModifiedFields() == 0);
+                    && zenPolicyUserModifiedFields == 0
+                    && zenDeviceEffectsUserModifiedFields == 0;
         }
 
         @Override
@@ -2139,6 +2150,8 @@
                 dest.writeString(triggerDescription);
                 dest.writeInt(type);
                 dest.writeInt(userModifiedFields);
+                dest.writeInt(zenPolicyUserModifiedFields);
+                dest.writeInt(zenDeviceEffectsUserModifiedFields);
                 if (deletionInstant != null) {
                     dest.writeInt(1);
                     dest.writeLong(deletionInstant.toEpochMilli());
@@ -2173,8 +2186,20 @@
                         .append(",allowManualInvocation=").append(allowManualInvocation)
                         .append(",iconResName=").append(iconResName)
                         .append(",triggerDescription=").append(triggerDescription)
-                        .append(",type=").append(type)
-                        .append(",userModifiedFields=").append(userModifiedFields);
+                        .append(",type=").append(type);
+                if (userModifiedFields != 0) {
+                    sb.append(",userModifiedFields=")
+                            .append(AutomaticZenRule.fieldsToString(userModifiedFields));
+                }
+                if (zenPolicyUserModifiedFields != 0) {
+                    sb.append(",zenPolicyUserModifiedFields=")
+                            .append(ZenPolicy.fieldsToString(zenPolicyUserModifiedFields));
+                }
+                if (zenDeviceEffectsUserModifiedFields != 0) {
+                    sb.append(",zenDeviceEffectsUserModifiedFields=")
+                            .append(ZenDeviceEffects.fieldsToString(
+                                    zenDeviceEffectsUserModifiedFields));
+                }
                 if (deletionInstant != null) {
                     sb.append(",deletionInstant=").append(deletionInstant);
                 }
@@ -2238,6 +2263,9 @@
                         && Objects.equals(other.triggerDescription, triggerDescription)
                         && other.type == type
                         && other.userModifiedFields == userModifiedFields
+                        && other.zenPolicyUserModifiedFields == zenPolicyUserModifiedFields
+                        && other.zenDeviceEffectsUserModifiedFields
+                            == zenDeviceEffectsUserModifiedFields
                         && Objects.equals(other.deletionInstant, deletionInstant);
             }
 
@@ -2250,7 +2278,8 @@
                 return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
                         component, configurationActivity, pkg, id, enabler, zenPolicy,
                         zenDeviceEffects, modified, allowManualInvocation, iconResName,
-                        triggerDescription, type, userModifiedFields, deletionInstant);
+                        triggerDescription, type, userModifiedFields, zenPolicyUserModifiedFields,
+                        zenDeviceEffectsUserModifiedFields, deletionInstant);
             }
             return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
                     component, configurationActivity, pkg, id, enabler, zenPolicy, modified);
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index 2cda7c8..fb491d0 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -45,8 +45,8 @@
  */
 public final class ZenPolicy implements Parcelable {
 
-    /** Used to track which rule variables have been modified by the user.
-     * Should be checked against the bitmask {@link #getUserModifiedFields()}.
+    /**
+     * Enum for the user-modifiable fields in this object.
      * @hide
      */
     @IntDef(flag = true, prefix = { "FIELD_" }, value = {
@@ -76,7 +76,6 @@
      * the same time.
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_MESSAGES = 1 << 0;
     /**
@@ -84,7 +83,6 @@
      * the same time.
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_CALLS = 1 << 1;
     /**
@@ -92,13 +90,11 @@
      * set at the same time.
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_CONVERSATIONS = 1 << 2;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_ALLOW_CHANNELS = 1 << 3;
     /**
@@ -109,73 +105,61 @@
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_PRIORITY_CATEGORY_EVENTS = 1 << 5;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS = 1 << 6;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_PRIORITY_CATEGORY_ALARMS = 1 << 7;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_PRIORITY_CATEGORY_MEDIA = 1 << 8;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_PRIORITY_CATEGORY_SYSTEM = 1 << 9;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT = 1 << 10;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_VISUAL_EFFECT_LIGHTS = 1 << 11;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_VISUAL_EFFECT_PEEK = 1 << 12;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_VISUAL_EFFECT_STATUS_BAR = 1 << 13;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_VISUAL_EFFECT_BADGE = 1 << 14;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_VISUAL_EFFECT_AMBIENT = 1 << 15;
     /**
      * @hide
      */
-    @TestApi
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int FIELD_VISUAL_EFFECT_NOTIFICATION_LIST = 1 << 16;
 
@@ -186,7 +170,6 @@
     private @ConversationSenders int mConversationSenders = CONVERSATION_SENDERS_UNSET;
     @FlaggedApi(Flags.FLAG_MODES_API)
     private @ChannelType int mAllowChannels = CHANNEL_POLICY_UNSET;
-    private final @ModifiableField int mUserModifiedFields; // Bitwise representation
 
     /** @hide */
     @IntDef(prefix = { "PRIORITY_CATEGORY_" }, value = {
@@ -388,22 +371,19 @@
     public ZenPolicy() {
         mPriorityCategories = new ArrayList<>(Collections.nCopies(NUM_PRIORITY_CATEGORIES, 0));
         mVisualEffects = new ArrayList<>(Collections.nCopies(NUM_VISUAL_EFFECTS, 0));
-        mUserModifiedFields = 0;
     }
 
     /** @hide */
     @FlaggedApi(Flags.FLAG_MODES_API)
     public ZenPolicy(List<Integer> priorityCategories, List<Integer> visualEffects,
                      @PeopleType int priorityMessages, @PeopleType int priorityCalls,
-                     @ConversationSenders int conversationSenders, @ChannelType int allowChannels,
-                     @ModifiableField int userModifiedFields) {
+                     @ConversationSenders int conversationSenders, @ChannelType int allowChannels) {
         mPriorityCategories = priorityCategories;
         mVisualEffects = visualEffects;
         mPriorityMessages = priorityMessages;
         mPriorityCalls = priorityCalls;
         mConversationSenders = conversationSenders;
         mAllowChannels = allowChannels;
-        mUserModifiedFields = userModifiedFields;
     }
 
     /**
@@ -633,8 +613,6 @@
      * is not set, it is (@link STATE_UNSET} and will not change the current set policy.
      */
     public static final class Builder {
-        private @ModifiableField int mUserModifiedFields;
-
         private ZenPolicy mZenPolicy;
 
         public Builder() {
@@ -649,9 +627,6 @@
         public Builder(@Nullable ZenPolicy policy) {
             if (policy != null) {
                 mZenPolicy = policy.copy();
-                if (Flags.modesApi()) {
-                    mUserModifiedFields = policy.mUserModifiedFields;
-                }
             } else {
                 mZenPolicy = new ZenPolicy();
             }
@@ -662,11 +637,10 @@
          */
         public @NonNull ZenPolicy build() {
             if (Flags.modesApi()) {
-                return new ZenPolicy(new ArrayList<Integer>(mZenPolicy.mPriorityCategories),
-                        new ArrayList<Integer>(mZenPolicy.mVisualEffects),
+                return new ZenPolicy(new ArrayList<>(mZenPolicy.mPriorityCategories),
+                        new ArrayList<>(mZenPolicy.mVisualEffects),
                         mZenPolicy.mPriorityMessages, mZenPolicy.mPriorityCalls,
-                        mZenPolicy.mConversationSenders, mZenPolicy.mAllowChannels,
-                        mUserModifiedFields);
+                        mZenPolicy.mConversationSenders, mZenPolicy.mAllowChannels);
             } else {
                 return mZenPolicy.copy();
             }
@@ -1025,28 +999,6 @@
             mZenPolicy.mAllowChannels = allow ? CHANNEL_POLICY_PRIORITY : CHANNEL_POLICY_NONE;
             return this;
         }
-
-        /**
-         * Sets the user modified fields bitmask.
-         * @hide
-         */
-        @TestApi
-        @FlaggedApi(Flags.FLAG_MODES_API)
-        public @NonNull Builder setUserModifiedFields(@ModifiableField int userModifiedFields) {
-            mUserModifiedFields = userModifiedFields;
-            return this;
-        }
-    }
-
-    /**
-     Gets the bitmask representing which fields are user modified. Bits are set using
-     * {@link ModifiableField}.
-     * @hide
-     */
-    @TestApi
-    @FlaggedApi(Flags.FLAG_MODES_API)
-    public @ModifiableField int getUserModifiedFields() {
-        return mUserModifiedFields;
     }
 
     @Override
@@ -1063,7 +1015,6 @@
         dest.writeInt(mConversationSenders);
         if (Flags.modesApi()) {
             dest.writeInt(mAllowChannels);
-            dest.writeInt(mUserModifiedFields);
         }
     }
 
@@ -1079,7 +1030,7 @@
                                 trimList(source.readArrayList(Integer.class.getClassLoader(),
                                         Integer.class), NUM_VISUAL_EFFECTS),
                                 source.readInt(), source.readInt(), source.readInt(),
-                                source.readInt(), source.readInt()
+                                source.readInt()
                         );
                     } else {
                         policy = new ZenPolicy();
@@ -1114,14 +1065,12 @@
                         conversationTypeToString(mConversationSenders));
         if (Flags.modesApi()) {
             sb.append(", allowChannels=").append(channelTypeToString(mAllowChannels));
-            sb.append(", userModifiedFields=")
-                    .append(modifiedFieldsToString(mUserModifiedFields));
         }
         return sb.append('}').toString();
     }
 
-    @FlaggedApi(Flags.FLAG_MODES_API)
-    private String modifiedFieldsToString(@ModifiableField int bitmask) {
+    /** @hide */
+    public static String fieldsToString(@ModifiableField int bitmask) {
         ArrayList<String> modified = new ArrayList<>();
         if ((bitmask & FIELD_MESSAGES) != 0) {
             modified.add("FIELD_MESSAGES");
@@ -1332,8 +1281,7 @@
                 && other.mPriorityMessages == mPriorityMessages
                 && other.mConversationSenders == mConversationSenders;
         if (Flags.modesApi()) {
-            return eq && other.mAllowChannels == mAllowChannels
-                    && other.mUserModifiedFields == mUserModifiedFields;
+            return eq && other.mAllowChannels == mAllowChannels;
         }
         return eq;
     }
@@ -1342,7 +1290,7 @@
     public int hashCode() {
         if (Flags.modesApi()) {
             return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls,
-                    mPriorityMessages, mConversationSenders, mAllowChannels, mUserModifiedFields);
+                    mPriorityMessages, mConversationSenders, mAllowChannels);
         }
         return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls, mPriorityMessages,
                 mConversationSenders);
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index bb16ad2..2b96ee6 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -69,11 +69,10 @@
 }
 
 flag {
-    name: "predictive_back_system_animations"
+    name: "predictive_back_system_anims"
     namespace: "systemui"
     description: "Predictive back for system animations"
-    bug: "319421778"
-    is_fixed_read_only: true
+    bug: "320510464"
 }
 
 flag {
diff --git a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
index 9d85b65..1925588 100644
--- a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
+++ b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
@@ -16,8 +16,6 @@
 
 package android.app;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.fail;
 
@@ -28,8 +26,6 @@
 import android.os.Parcel;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
-import android.service.notification.ZenDeviceEffects;
-import android.service.notification.ZenPolicy;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -230,66 +226,4 @@
 
         assertThrows(IllegalArgumentException.class, () -> builder.setType(100));
     }
-
-    @Test
-    @EnableFlags(Flags.FLAG_MODES_API)
-    public void testCanUpdate_nullPolicyAndDeviceEffects() {
-        AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("name",
-                Uri.parse("uri://short"));
-
-        AutomaticZenRule rule = builder.setUserModifiedFields(0)
-                .setZenPolicy(null)
-                .setDeviceEffects(null)
-                .build();
-
-        assertThat(rule.canUpdate()).isTrue();
-
-        rule = builder.setUserModifiedFields(1).build();
-        assertThat(rule.canUpdate()).isFalse();
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_MODES_API)
-    public void testCanUpdate_policyModified() {
-        ZenPolicy.Builder policyBuilder = new ZenPolicy.Builder().setUserModifiedFields(0);
-        ZenPolicy policy = policyBuilder.build();
-
-        AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("name",
-                Uri.parse("uri://short"));
-        AutomaticZenRule rule = builder.setUserModifiedFields(0)
-                .setZenPolicy(policy)
-                .setDeviceEffects(null).build();
-
-        // Newly created ZenPolicy is not user modified.
-        assertThat(policy.getUserModifiedFields()).isEqualTo(0);
-        assertThat(rule.canUpdate()).isTrue();
-
-        policy = policyBuilder.setUserModifiedFields(1).build();
-        assertThat(policy.getUserModifiedFields()).isEqualTo(1);
-        rule = builder.setZenPolicy(policy).build();
-        assertThat(rule.canUpdate()).isFalse();
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_MODES_API)
-    public void testCanUpdate_deviceEffectsModified() {
-        ZenDeviceEffects.Builder deviceEffectsBuilder =
-                new ZenDeviceEffects.Builder().setUserModifiedFields(0);
-        ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build();
-
-        AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("name",
-                Uri.parse("uri://short"));
-        AutomaticZenRule rule = builder.setUserModifiedFields(0)
-                .setZenPolicy(null)
-                .setDeviceEffects(deviceEffects).build();
-
-        // Newly created ZenDeviceEffects is not user modified.
-        assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(0);
-        assertThat(rule.canUpdate()).isTrue();
-
-        deviceEffects = deviceEffectsBuilder.setUserModifiedFields(1).build();
-        assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(1);
-        rule = builder.setDeviceEffects(deviceEffects).build();
-        assertThat(rule.canUpdate()).isFalse();
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index bb433db..e7f6f0d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -17,7 +17,7 @@
 package com.android.wm.shell.back;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
-import static com.android.window.flags.Flags.predictiveBackSystemAnimations;
+import static com.android.window.flags.Flags.predictiveBackSystemAnims;
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
@@ -244,7 +244,7 @@
     private void setupAnimationDeveloperSettingsObserver(
             @NonNull ContentResolver contentResolver,
             @NonNull @ShellBackgroundThread final Handler backgroundHandler) {
-        if (predictiveBackSystemAnimations()) {
+        if (predictiveBackSystemAnims()) {
             ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation aconfig flag is enabled, therefore "
                     + "developer settings flag is ignored and no content observer registered");
             return;
@@ -267,7 +267,7 @@
      */
     @ShellBackgroundThread
     private void updateEnableAnimationFromFlags() {
-        boolean isEnabled = predictiveBackSystemAnimations() || isDeveloperSettingEnabled();
+        boolean isEnabled = predictiveBackSystemAnims() || isDeveloperSettingEnabled();
         mEnableAnimations.set(isEnabled);
         ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled);
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 071afaf..f7f0673 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -117,6 +117,12 @@
             }
         }
 
+        if (cachedDevice.isHearingAidDevice()) {
+            return new Pair<>(getBluetoothDrawable(context,
+                    com.android.internal.R.drawable.ic_bt_hearing_aid),
+                    context.getString(R.string.bluetooth_talkback_hearing_aids));
+        }
+
         List<LocalBluetoothProfile> profiles = cachedDevice.getProfiles();
         int resId = 0;
         for (LocalBluetoothProfile profile : profiles) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 032a838..560bc46 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -412,6 +412,7 @@
 
     public void setHearingAidInfo(HearingAidInfo hearingAidInfo) {
         mHearingAidInfo = hearingAidInfo;
+        dispatchAttributesChanged();
     }
 
     public HearingAidInfo getHearingAidInfo() {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 7409eea..f7ec80b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -16,6 +16,7 @@
 package com.android.settingslib.bluetooth;
 
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -87,6 +88,14 @@
     }
 
     @Test
+    public void getBtClassDrawableWithDescription_typeHearingAid_returnHearingAidDrawable() {
+        when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
+        BluetoothUtils.getBtClassDrawableWithDescription(mContext, mCachedBluetoothDevice);
+
+        verify(mContext).getDrawable(com.android.internal.R.drawable.ic_bt_hearing_aid);
+    }
+
+    @Test
     public void getBtRainbowDrawableWithDescription_normalHeadset_returnAdaptiveIcon() {
         when(mBluetoothDevice.getMetadata(
                 BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn("false".getBytes());
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index c23a49c..b464498 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -343,3 +343,10 @@
    description: "Relocate Smartspace to bottom of the Lock Screen"
    bug: "316212788"
 }
+
+flag {
+   name: "pin_input_field_styled_focus_state"
+   namespace: "systemui"
+   description: "Enables styled focus states on pin input field if keyboard is connected"
+   bug: "316106516"
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index e893169..c4bcb53 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -35,6 +35,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.classifier.FalsingCollectorFake;
 import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository;
 import com.android.systemui.res.R;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
@@ -107,7 +108,7 @@
                 mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
                 mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
                 mEmergencyButtonController, mFalsingCollector, featureFlags,
-                mSelectedUserInteractor) {
+                mSelectedUserInteractor, new FakeKeyboardRepository()) {
             @Override
             public void onResume(int reason) {
                 super.onResume(reason);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 78b854e..c2efc05 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.DevicePostureController
 import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED
@@ -141,7 +142,8 @@
             postureController,
             featureFlags,
             mSelectedUserInteractor,
-            uiEventLogger
+            uiEventLogger,
+            FakeKeyboardRepository()
         )
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index f775175..0959f1b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.keyguard
 
-import android.telephony.PinResult
 import android.telephony.TelephonyManager
 import android.testing.TestableLooper
 import android.view.LayoutInflater
@@ -28,9 +27,11 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
 import com.android.systemui.res.R
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -39,7 +40,6 @@
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
@@ -75,8 +75,7 @@
         `when`(messageAreaControllerFactory.create(Mockito.any(KeyguardMessageArea::class.java)))
             .thenReturn(keyguardMessageAreaController)
         `when`(telephonyManager.createForSubscriptionId(anyInt())).thenReturn(telephonyManager)
-        `when`(telephonyManager.supplyIccLockPin(anyString()))
-            .thenReturn(mock(PinResult::class.java))
+        `when`(telephonyManager.supplyIccLockPin(anyString())).thenReturn(mock())
         simPinView =
             LayoutInflater.from(context).inflate(R.layout.keyguard_sim_pin_view, null)
                 as KeyguardSimPinView
@@ -97,7 +96,8 @@
                 falsingCollector,
                 emergencyButtonController,
                 fakeFeatureFlags,
-                mSelectedUserInteractor
+                mSelectedUserInteractor,
+                FakeKeyboardRepository()
             )
         underTest.init()
         underTest.onViewAttached()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index 45a60199..1281e44 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
 import com.android.systemui.res.R
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.any
@@ -91,6 +92,7 @@
                 emergencyButtonController,
                 fakeFeatureFlags,
                 mSelectedUserInteractor,
+                FakeKeyboardRepository()
             )
         underTest.init()
     }
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml
new file mode 100644
index 0000000..84b89ca
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_focused="true">
+        <shape android:shape="rectangle">
+            <corners android:radius="16dp" />
+            <!--By default this outline will not show hence 0 width.
+             width is set programmatically when needed and is gated by the flag:
+             com.android.systemui.Flags.pinInputFieldStyledFocusState-->
+            <stroke android:width="0dp"
+                android:color="@color/bouncer_password_focus_color" />
+        </shape>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
index 66c54f2..0b35559 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
@@ -23,7 +23,7 @@
         android:layout_marginTop="@dimen/keyguard_lock_padding"
         android:importantForAccessibility="no"
         android:ellipsize="marquee"
-        android:focusable="true"
+        android:focusable="false"
         android:gravity="center"
         android:singleLine="true" />
 </merge>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 0628c3e..ddad1e3 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -25,6 +25,12 @@
     <!-- Maximum width of the sliding KeyguardSecurityContainer -->
     <dimen name="keyguard_security_width">420dp</dimen>
 
+    <!-- Width for the keyguard pin input field -->
+    <dimen name="keyguard_pin_field_width">292dp</dimen>
+
+    <!-- Width for the keyguard pin input field -->
+    <dimen name="keyguard_pin_field_height">48dp</dimen>
+
     <!-- Height of the sliding KeyguardSecurityContainer
          (includes 2x keyguard_security_view_top_margin) -->
     <dimen name="keyguard_security_height">420dp</dimen>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 565ed10..f51e109 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -62,10 +62,10 @@
     <string name="keyguard_plugged_in_charging_slowly"><xliff:g id="percentage">%s</xliff:g> • Charging slowly</string>
 
     <!-- When the lock screen is showing and the phone plugged in, and the defend mode is triggered, say that charging is temporarily limited.  -->
-    <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging optimized to protect battery</string>
+    <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging on hold to protect battery</string>
 
     <!-- When the lock screen is showing and the phone plugged in with incompatible charger. -->
-    <string name="keyguard_plugged_in_incompatible_charger"><xliff:g id="percentage">%s</xliff:g> • Issue with charging accessory</string>
+    <string name="keyguard_plugged_in_incompatible_charger"><xliff:g id="percentage">%s</xliff:g> • Check charging accessory</string>
 
     <!-- SIM messages --><skip />
     <!-- When the user inserts a sim card from an unsupported network, it becomes network locked -->
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 2cca951..4789a22 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -76,6 +76,7 @@
     </style>
     <style name="Widget.TextView.Password" parent="@android:style/Widget.TextView">
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+        <item name="android:background">@drawable/bouncer_password_text_view_focused_background</item>
         <item name="android:gravity">center</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index bcc3c83..61a323d4 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -93,6 +93,8 @@
     <color name="qs_user_switcher_selected_avatar_icon_color">#202124</color>
     <!-- Color of background circle of user avatars in quick settings user switcher -->
     <color name="qs_user_switcher_avatar_background">#3C4043</color>
+    <!-- Color of border for keyguard password input when focused -->
+    <color name="bouncer_password_focus_color">@*android:color/system_secondary_dark</color>
 
     <!-- Accessibility floating menu -->
     <color name="accessibility_floating_menu_background">#B3000000</color> <!-- 70% -->
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 8be1cc7..3839dd9 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -56,6 +56,8 @@
     <color name="kg_user_switcher_restricted_avatar_icon_color">@color/GM2_grey_600</color>
     <!-- Color of background circle of user avatars in keyguard user switcher -->
     <color name="user_avatar_color_bg">?android:attr/colorBackgroundFloating</color>
+    <!-- Color of border for keyguard password input when focused -->
+    <color name="bouncer_password_focus_color">@*android:color/system_secondary_light</color>
 
     <!-- Icon color for user avatars in user switcher quick settings -->
     <color name="qs_user_switcher_avatar_icon_color">#3C4043</color>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 66f965a..efd8f7f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -37,6 +37,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyboard.data.repository.KeyboardRepository;
 import com.android.systemui.log.BouncerLogger;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.DevicePostureController;
@@ -210,6 +211,7 @@
         private final FeatureFlags mFeatureFlags;
         private final SelectedUserInteractor mSelectedUserInteractor;
         private final UiEventLogger mUiEventLogger;
+        private final KeyboardRepository mKeyboardRepository;
 
         @Inject
         public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -223,7 +225,8 @@
                 DevicePostureController devicePostureController,
                 KeyguardViewController keyguardViewController,
                 FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor,
-                UiEventLogger uiEventLogger) {
+                UiEventLogger uiEventLogger,
+                KeyboardRepository keyboardRepository) {
             mKeyguardUpdateMonitor = keyguardUpdateMonitor;
             mLockPatternUtils = lockPatternUtils;
             mLatencyTracker = latencyTracker;
@@ -240,6 +243,7 @@
             mFeatureFlags = featureFlags;
             mSelectedUserInteractor = selectedUserInteractor;
             mUiEventLogger = uiEventLogger;
+            mKeyboardRepository = keyboardRepository;
         }
 
         /** Create a new {@link KeyguardInputViewController}. */
@@ -268,19 +272,22 @@
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mLiftToActivateListener, emergencyButtonController, mFalsingCollector,
                         mDevicePostureController, mFeatureFlags, mSelectedUserInteractor,
-                        mUiEventLogger);
+                        mUiEventLogger, mKeyboardRepository
+                );
             } else if (keyguardInputView instanceof KeyguardSimPinView) {
                 return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
-                        emergencyButtonController, mFeatureFlags, mSelectedUserInteractor);
+                        emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
+                        mKeyboardRepository);
             } else if (keyguardInputView instanceof KeyguardSimPukView) {
                 return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
                         mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
-                        emergencyButtonController, mFeatureFlags, mSelectedUserInteractor);
+                        emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
+                        mKeyboardRepository);
             }
 
             throw new RuntimeException("Unable to find controller for " + keyguardInputView);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 36fe75f..fcff0db 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -25,6 +25,7 @@
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
+import static com.android.systemui.Flags.pinInputFieldStyledFocusState;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -168,7 +169,9 @@
 
         // Set selected property on so the view can send accessibility events.
         mPasswordEntry.setSelected(true);
-        mPasswordEntry.setDefaultFocusHighlightEnabled(false);
+        if (!pinInputFieldStyledFocusState()) {
+            mPasswordEntry.setDefaultFocusHighlightEnabled(false);
+        }
 
         mOkButton = findViewById(R.id.key_enter);
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index 376933d..60dd568 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -16,17 +16,25 @@
 
 package com.android.keyguard;
 
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+import static com.android.systemui.Flags.pinInputFieldStyledFocusState;
+
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.StateListDrawable;
+import android.util.TypedValue;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnKeyListener;
 import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
 
 import com.android.internal.util.LatencyTracker;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyboard.data.repository.KeyboardRepository;
 import com.android.systemui.res.R;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
@@ -35,6 +43,7 @@
 
     private final LiftToActivateListener mLiftToActivateListener;
     private final FalsingCollector mFalsingCollector;
+    private final KeyboardRepository mKeyboardRepository;
     protected PasswordTextView mPasswordEntry;
 
     private final OnKeyListener mOnKeyListener = (v, keyCode, event) -> {
@@ -65,12 +74,14 @@
             EmergencyButtonController emergencyButtonController,
             FalsingCollector falsingCollector,
             FeatureFlags featureFlags,
-            SelectedUserInteractor selectedUserInteractor) {
+            SelectedUserInteractor selectedUserInteractor,
+            KeyboardRepository keyboardRepository) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, falsingCollector,
                 emergencyButtonController, featureFlags, selectedUserInteractor);
         mLiftToActivateListener = liftToActivateListener;
         mFalsingCollector = falsingCollector;
+        mKeyboardRepository = keyboardRepository;
         mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
     }
 
@@ -120,6 +131,35 @@
             });
             okButton.setOnHoverListener(mLiftToActivateListener);
         }
+        if (pinInputFieldStyledFocusState()) {
+            collectFlow(mPasswordEntry, mKeyboardRepository.isAnyKeyboardConnected(),
+                    this::setKeyboardBasedFocusOutline);
+
+            /**
+             * new UI Specs for PIN Input field have new dimensions go/pin-focus-states.
+             * However we want these changes behind a flag, and resource files cannot be flagged
+             * hence the dimension change in code. When the flags are removed these dimensions
+             * should be set in resources permanently and the code below removed.
+             */
+            ViewGroup.LayoutParams layoutParams = mPasswordEntry.getLayoutParams();
+            layoutParams.width = (int) getResources().getDimension(
+                    R.dimen.keyguard_pin_field_width);
+            layoutParams.height = (int) getResources().getDimension(
+                    R.dimen.keyguard_pin_field_height);
+        }
+    }
+
+    private void setKeyboardBasedFocusOutline(boolean isAnyKeyboardConnected) {
+        StateListDrawable background = (StateListDrawable) mPasswordEntry.getBackground();
+        GradientDrawable stateDrawable = (GradientDrawable) background.getStateDrawable(0);
+        int color = getResources().getColor(R.color.bouncer_password_focus_color);
+        if (!isAnyKeyboardConnected) {
+            stateDrawable.setStroke(0, color);
+        } else {
+            int strokeWidthInDP = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3,
+                    getResources().getDisplayMetrics());
+            stateDrawable.setStroke(strokeWidthInDP, color);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 2aab1f1..b958f55 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -28,6 +28,7 @@
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyboard.data.repository.KeyboardRepository;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -58,12 +59,13 @@
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
             EmergencyButtonController emergencyButtonController,
             FalsingCollector falsingCollector,
-            DevicePostureController postureController,
-            FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor,
-            UiEventLogger uiEventLogger) {
+            DevicePostureController postureController, FeatureFlags featureFlags,
+            SelectedUserInteractor selectedUserInteractor, UiEventLogger uiEventLogger,
+            KeyboardRepository keyboardRepository) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
-                emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor);
+                emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
+                keyboardRepository);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mPostureController = postureController;
         mLockPatternUtils = lockPatternUtils;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 5729119..1cdcbd0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -44,6 +44,7 @@
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyboard.data.repository.KeyboardRepository;
 import com.android.systemui.res.R;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
@@ -93,10 +94,11 @@
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
             TelephonyManager telephonyManager, FalsingCollector falsingCollector,
             EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
-            SelectedUserInteractor selectedUserInteractor) {
+            SelectedUserInteractor selectedUserInteractor, KeyboardRepository keyboardRepository) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
-                emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor);
+                emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
+                keyboardRepository);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mTelephonyManager = telephonyManager;
         mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 05fb5fa..f019d61 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -39,6 +39,7 @@
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyboard.data.repository.KeyboardRepository;
 import com.android.systemui.res.R;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
@@ -90,10 +91,11 @@
             LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
             TelephonyManager telephonyManager, FalsingCollector falsingCollector,
             EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
-            SelectedUserInteractor selectedUserInteractor) {
+            SelectedUserInteractor selectedUserInteractor, KeyboardRepository keyboardRepository) {
         super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
                 messageAreaControllerFactory, latencyTracker, liftToActivateListener,
-                emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor);
+                emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
+                keyboardRepository);
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mTelephonyManager = telephonyManager;
         mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/LeftRightArrowPressedListener.kt b/packages/SystemUI/src/com/android/systemui/qs/LeftRightArrowPressedListener.kt
new file mode 100644
index 0000000..ca790e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/LeftRightArrowPressedListener.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.view.KeyEvent
+import android.view.View
+import androidx.core.util.Consumer
+
+/**
+ * Listens for left and right arrow keys pressed while focus is on the view.
+ *
+ * Key press is treated as correct when its full lifecycle happened on the view: first
+ * [KeyEvent.ACTION_DOWN] was performed, view didn't lose focus in the meantime and then
+ * [KeyEvent.ACTION_UP] was performed with the same [KeyEvent.getKeyCode]
+ */
+class LeftRightArrowPressedListener private constructor() :
+    View.OnKeyListener, View.OnFocusChangeListener {
+
+    private var lastKeyCode: Int? = 0
+    private var listener: Consumer<Int>? = null
+
+    fun setArrowKeyPressedListener(arrowPressedListener: Consumer<Int>) {
+        listener = arrowPressedListener
+    }
+
+    override fun onKey(view: View, keyCode: Int, keyEvent: KeyEvent): Boolean {
+        if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
+            // only scroll on ACTION_UP as we don't handle longpressing for now. Still we need
+            // to intercept even ACTION_DOWN otherwise keyboard focus will be moved before we
+            // have a chance to intercept ACTION_UP.
+            if (keyEvent.action == KeyEvent.ACTION_UP && keyCode == lastKeyCode) {
+                listener?.accept(keyCode)
+                lastKeyCode = null
+            } else if (keyEvent.repeatCount == 0) {
+                // we only read key events that are NOT coming from long pressing because that also
+                // causes reading ACTION_DOWN event (with repeated count > 0) when moving focus with
+                // arrow from another sibling view
+                lastKeyCode = keyCode
+            }
+            return true
+        }
+        return false
+    }
+
+    override fun onFocusChange(view: View, hasFocus: Boolean) {
+        // resetting lastKeyCode so we get fresh cleared state on focus
+        if (hasFocus) {
+            lastKeyCode = null
+        }
+    }
+
+    companion object {
+        @JvmStatic
+        fun createAndRegisterListenerForView(view: View): LeftRightArrowPressedListener {
+            val listener = LeftRightArrowPressedListener()
+            view.setOnKeyListener(listener)
+            view.onFocusChangeListener = listener
+            return listener
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
index 4770d52..1c9f5fd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
@@ -1,5 +1,8 @@
 package com.android.systemui.qs;
 
+import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.LEFT;
+import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.RIGHT;
+
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
@@ -9,10 +12,12 @@
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ImageView;
 
+import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 
 import com.android.settingslib.Utils;
@@ -43,6 +48,7 @@
 
     private int mPosition = -1;
     private boolean mAnimating;
+    private PageScrollActionListener mPageScrollActionListener;
 
     private final Animatable2.AnimationCallback mAnimationCallback =
             new Animatable2.AnimationCallback() {
@@ -77,6 +83,14 @@
         mPageIndicatorWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_width);
         mPageIndicatorHeight = res.getDimensionPixelSize(R.dimen.qs_page_indicator_height);
         mPageDotWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_dot_width);
+        LeftRightArrowPressedListener arrowListener =
+                LeftRightArrowPressedListener.createAndRegisterListenerForView(this);
+        arrowListener.setArrowKeyPressedListener(keyCode -> {
+            if (mPageScrollActionListener != null) {
+                int swipeDirection = keyCode == KeyEvent.KEYCODE_DPAD_LEFT ? LEFT : RIGHT;
+                mPageScrollActionListener.onScrollActionTriggered(swipeDirection);
+            }
+        });
     }
 
     public void setNumPages(int numPages) {
@@ -280,4 +294,19 @@
             getChildAt(i).layout(left, 0, mPageIndicatorWidth + left, mPageIndicatorHeight);
         }
     }
+
+    void setPageScrollActionListener(PageScrollActionListener listener) {
+        mPageScrollActionListener = listener;
+    }
+
+    interface PageScrollActionListener {
+
+        @IntDef({LEFT, RIGHT})
+        @interface Direction { }
+
+        int LEFT = 0;
+        int RIGHT = 1;
+
+        void onScrollActionTriggered(@Direction int swipeDirection);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 052c0da..43f3a22 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -1,6 +1,8 @@
 package com.android.systemui.qs;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE;
+import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.LEFT;
+import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.RIGHT;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -12,7 +14,6 @@
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.util.AttributeSet;
-import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -30,6 +31,7 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.PageIndicator.PageScrollActionListener.Direction;
 import com.android.systemui.qs.QSPanel.QSTileLayout;
 import com.android.systemui.qs.QSPanelControllerBase.TileRecord;
 import com.android.systemui.qs.logging.QSLogger;
@@ -310,26 +312,18 @@
         mPageIndicator = indicator;
         mPageIndicator.setNumPages(mPages.size());
         mPageIndicator.setLocation(mPageIndicatorPosition);
-        mPageIndicator.setOnKeyListener((view, keyCode, keyEvent) -> {
-            if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
-                // only scroll on ACTION_UP as we don't handle longpressing for now. Still we need
-                // to intercept even ACTION_DOWN otherwise keyboard focus will be moved before we
-                // have a chance to intercept ACTION_UP.
-                if (keyEvent.getAction() == KeyEvent.ACTION_UP && mScroller.isFinished()) {
-                    scrollByX(getDeltaXForKeyboardScrolling(keyCode),
-                            SINGLE_PAGE_SCROLL_DURATION_MILLIS);
-                }
-                return true;
+        mPageIndicator.setPageScrollActionListener(swipeDirection -> {
+            if (mScroller.isFinished()) {
+                scrollByX(getDeltaXForPageScrolling(swipeDirection),
+                        SINGLE_PAGE_SCROLL_DURATION_MILLIS);
             }
-            return false;
         });
     }
 
-    private int getDeltaXForKeyboardScrolling(int keyCode) {
-        if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && getCurrentItem() != 0) {
+    private int getDeltaXForPageScrolling(@Direction int swipeDirection) {
+        if (swipeDirection == LEFT && getCurrentItem() != 0) {
             return -getWidth();
-        } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
-                && getCurrentItem() != mPages.size() - 1) {
+        } else if (swipeDirection == RIGHT && getCurrentItem() != mPages.size() - 1) {
             return getWidth();
         }
         return 0;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt
new file mode 100644
index 0000000..60eb3ae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.testing.AndroidTestingRunner
+import android.view.KeyEvent
+import android.view.KeyEvent.KEYCODE_DPAD_LEFT
+import android.view.View
+import androidx.core.util.Consumer
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LeftRightArrowPressedListenerTest : SysuiTestCase() {
+
+    private lateinit var underTest: LeftRightArrowPressedListener
+    private val callback =
+        object : Consumer<Int> {
+            var lastValue: Int? = null
+
+            override fun accept(keyCode: Int?) {
+                lastValue = keyCode
+            }
+        }
+
+    private val view = View(context)
+
+    @Before
+    fun setUp() {
+        underTest = LeftRightArrowPressedListener.createAndRegisterListenerForView(view)
+        underTest.setArrowKeyPressedListener(callback)
+    }
+
+    @Test
+    fun shouldTriggerCallback_whenArrowUpReceived_afterArrowDownReceived() {
+        underTest.sendKey(KeyEvent.ACTION_DOWN, KEYCODE_DPAD_LEFT)
+
+        underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT)
+
+        assertThat(callback.lastValue).isEqualTo(KEYCODE_DPAD_LEFT)
+    }
+
+    @Test
+    fun shouldNotTriggerCallback_whenKeyUpReceived_ifKeyDownNotReceived() {
+        underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT)
+
+        assertThat(callback.lastValue).isNull()
+    }
+
+    @Test
+    fun shouldNotTriggerCallback_whenKeyUpReceived_ifKeyDownWasRepeated() {
+        underTest.sendKeyWithRepeat(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT, repeat = 2)
+        underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT)
+
+        assertThat(callback.lastValue).isNull()
+    }
+
+    @Test
+    fun shouldNotTriggerCallback_whenKeyUpReceived_ifKeyDownReceivedBeforeLosingFocus() {
+        underTest.sendKey(KeyEvent.ACTION_DOWN, KEYCODE_DPAD_LEFT)
+        underTest.onFocusChange(view, hasFocus = false)
+        underTest.onFocusChange(view, hasFocus = true)
+
+        underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT)
+
+        assertThat(callback.lastValue).isNull()
+    }
+
+    private fun LeftRightArrowPressedListener.sendKey(action: Int, keyCode: Int) {
+        onKey(view, keyCode, KeyEvent(action, keyCode))
+    }
+
+    private fun LeftRightArrowPressedListener.sendKeyWithRepeat(
+        action: Int,
+        keyCode: Int,
+        repeat: Int
+    ) {
+        val keyEvent =
+            KeyEvent(
+                /* downTime= */ 0L,
+                /* eventTime= */ 0L,
+                /* action= */ action,
+                /* code= */ KEYCODE_DPAD_LEFT,
+                /* repeat= */ repeat
+            )
+        onKey(view, keyCode, keyEvent)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt
index db9e548..8ef3f57 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt
@@ -2,11 +2,13 @@
 
 import android.content.Context
 import android.testing.AndroidTestingRunner
-import android.view.KeyEvent
 import android.view.View
 import android.widget.Scroller
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.PageIndicator.PageScrollActionListener
+import com.android.systemui.qs.PageIndicator.PageScrollActionListener.LEFT
+import com.android.systemui.qs.PageIndicator.PageScrollActionListener.RIGHT
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -22,7 +24,7 @@
 class PagedTileLayoutTest : SysuiTestCase() {
 
     @Mock private lateinit var pageIndicator: PageIndicator
-    @Captor private lateinit var captor: ArgumentCaptor<View.OnKeyListener>
+    @Captor private lateinit var captor: ArgumentCaptor<PageScrollActionListener>
 
     private lateinit var pageTileLayout: TestPagedTileLayout
     private lateinit var scroller: Scroller
@@ -32,7 +34,7 @@
         MockitoAnnotations.initMocks(this)
         pageTileLayout = TestPagedTileLayout(mContext)
         pageTileLayout.setPageIndicator(pageIndicator)
-        verify(pageIndicator).setOnKeyListener(captor.capture())
+        verify(pageIndicator).setPageScrollActionListener(captor.capture())
         setViewWidth(pageTileLayout, width = PAGE_WIDTH)
         scroller = pageTileLayout.mScroller
     }
@@ -43,28 +45,27 @@
     }
 
     @Test
-    fun scrollsRight_afterRightArrowPressed_whenFocusOnPagerIndicator() {
+    fun scrollsRight_afterRightScrollActionTriggered() {
         pageTileLayout.currentPageIndex = 0
 
-        sendUpEvent(KeyEvent.KEYCODE_DPAD_RIGHT)
+        sendScrollActionEvent(RIGHT)
 
         assertThat(scroller.isFinished).isFalse() // aka we're scrolling
         assertThat(scroller.finalX).isEqualTo(scroller.currX + PAGE_WIDTH)
     }
 
     @Test
-    fun scrollsLeft_afterLeftArrowPressed_whenFocusOnPagerIndicator() {
+    fun scrollsLeft_afterLeftScrollActionTriggered() {
         pageTileLayout.currentPageIndex = 1 // we won't scroll left if we're on the first page
 
-        sendUpEvent(KeyEvent.KEYCODE_DPAD_LEFT)
+        sendScrollActionEvent(LEFT)
 
         assertThat(scroller.isFinished).isFalse() // aka we're scrolling
         assertThat(scroller.finalX).isEqualTo(scroller.currX - PAGE_WIDTH)
     }
 
-    private fun sendUpEvent(keyCode: Int) {
-        val event = KeyEvent(KeyEvent.ACTION_UP, keyCode)
-        captor.value.onKey(pageIndicator, keyCode, event)
+    private fun sendScrollActionEvent(@PageScrollActionListener.Direction direction: Int) {
+        captor.value.onScrollActionTriggered(direction)
     }
 
     /**
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index a856f42..f3b74ea 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1735,6 +1735,7 @@
 
         processResponseLockedForPcc(response, response.getClientState(), requestFlags);
         mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis();
+        mFillResponseEventLogger.logAndEndEvent();
     }
 
 
@@ -1847,6 +1848,33 @@
             return;
         }
         synchronized (mLock) {
+            // TODO(b/319913595): refactor logging for fill response for primary and secondary
+            //  providers
+            // Start a new FillResponse logger for the success case.
+            mFillResponseEventLogger.startLogForNewResponse();
+            mFillResponseEventLogger.maybeSetRequestId(fillResponse.getRequestId());
+            mFillResponseEventLogger.maybeSetAppPackageUid(uid);
+            mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SUCCESS);
+            mFillResponseEventLogger.startResponseProcessingTime();
+            // Time passed since session was created
+            final long fillRequestReceivedRelativeTimestamp =
+                    SystemClock.elapsedRealtime() - mLatencyBaseTime;
+            mPresentationStatsEventLogger.maybeSetFillResponseReceivedTimestampMs(
+                    (int) (fillRequestReceivedRelativeTimestamp));
+            mFillResponseEventLogger.maybeSetLatencyFillResponseReceivedMillis(
+                    (int) (fillRequestReceivedRelativeTimestamp));
+            if (mDestroyed) {
+                Slog.w(TAG, "Call to Session#onSecondaryFillResponse() rejected - session: "
+                        + id + " destroyed");
+                mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED);
+                mFillResponseEventLogger.logAndEndEvent();
+                return;
+            }
+
+            List<Dataset> datasetList = fillResponse.getDatasets();
+            int datasetCount = (datasetList == null) ? 0 : datasetList.size();
+            mFillResponseEventLogger.maybeSetTotalDatasetsProvided(datasetCount);
+            mFillResponseEventLogger.maybeSetAvailableCount(datasetCount);
             if (mSecondaryResponses == null) {
                 mSecondaryResponses = new SparseArray<>(2);
             }
@@ -1859,6 +1887,8 @@
             if (currentView != null) {
                 currentView.maybeCallOnFillReady(flags);
             }
+            mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis();
+            mFillResponseEventLogger.logAndEndEvent();
         }
     }
 
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 6236e2b..46668de 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -1370,7 +1370,7 @@
     public boolean isVirtualDevice(int deviceId) {
         VirtualDeviceManagerInternal vdm = LocalServices.getService(
                 VirtualDeviceManagerInternal.class);
-        return vdm == null || vdm.isInputDeviceOwnedByVirtualDevice(deviceId);
+        return vdm != null && vdm.isInputDeviceOwnedByVirtualDevice(deviceId);
     }
 
     private static int[] getScriptCodes(@Nullable Locale locale) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c9f45e5..2ae040a6 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5931,8 +5931,7 @@
                         newVisualEffects, policy.priorityConversationSenders);
 
                 if (shouldApplyAsImplicitRule) {
-                    mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy,
-                            origin);
+                    mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy);
                 } else {
                     ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion,
                             policy);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index c7f5703..93ffd97 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -32,6 +32,7 @@
 import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
 
 import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
+import static com.android.internal.util.Preconditions.checkArgument;
 
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
@@ -427,6 +428,7 @@
 
     public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule,
             @ConfigChangeOrigin int origin, String reason, int callingUid) {
+        requirePublicOrigin("addAutomaticZenRule", origin);
         if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) {
             PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner());
             if (component == null) {
@@ -525,6 +527,7 @@
 
     public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule,
             @ConfigChangeOrigin int origin, String reason, int callingUid) {
+        requirePublicOrigin("updateAutomaticZenRule", origin);
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return false;
@@ -602,7 +605,11 @@
                     rule = newImplicitZenRule(callingPkg);
                     newConfig.automaticRules.put(rule.id, rule);
                 }
-                rule.zenMode = zenMode;
+                // If the user has changed the rule's *zenMode*, then don't let app overwrite it.
+                // We allow the update if the user has only changed other aspects of the rule.
+                if ((rule.userModifiedFields & AutomaticZenRule.FIELD_INTERRUPTION_FILTER) == 0) {
+                    rule.zenMode = zenMode;
+                }
                 rule.snoozing = false;
                 rule.condition = new Condition(rule.conditionId,
                         mContext.getString(R.string.zen_mode_implicit_activated),
@@ -625,7 +632,7 @@
      * {@link Global#ZEN_MODE_IMPORTANT_INTERRUPTIONS}.
      */
     void applyGlobalPolicyAsImplicitZenRule(String callingPkg, int callingUid,
-            NotificationManager.Policy policy, @ConfigChangeOrigin int origin) {
+            NotificationManager.Policy policy) {
         if (!android.app.Flags.modesApi()) {
             Log.wtf(TAG, "applyGlobalPolicyAsImplicitZenRule called with flag off!");
             return;
@@ -641,10 +648,17 @@
                 rule.zenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
                 newConfig.automaticRules.put(rule.id, rule);
             }
-            // TODO: b/308673679 - Keep user customization of this rule!
-            rule.zenPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy);
-            setConfigLocked(newConfig, /* triggeringComponent= */ null, origin,
-                    "applyGlobalPolicyAsImplicitZenRule", callingUid);
+            // If the user has changed the rule's *ZenPolicy*, then don't let app overwrite it.
+            // We allow the update if the user has only changed other aspects of the rule.
+            if (rule.zenPolicyUserModifiedFields == 0) {
+                updatePolicy(
+                        rule,
+                        ZenAdapters.notificationPolicyToZenPolicy(policy),
+                        /* updateBitmask= */ false);
+
+                setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP,
+                        "applyGlobalPolicyAsImplicitZenRule", callingUid);
+            }
         }
     }
 
@@ -726,6 +740,7 @@
 
     boolean removeAutomaticZenRule(String id, @ConfigChangeOrigin int origin, String reason,
             int callingUid) {
+        requirePublicOrigin("removeAutomaticZenRule", origin);
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return false;
@@ -758,6 +773,7 @@
 
     boolean removeAutomaticZenRules(String packageName, @ConfigChangeOrigin int origin,
             String reason, int callingUid) {
+        requirePublicOrigin("removeAutomaticZenRules", origin);
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return false;
@@ -806,6 +822,7 @@
 
     void setAutomaticZenRuleState(String id, Condition condition, @ConfigChangeOrigin int origin,
             int callingUid) {
+        requirePublicOrigin("setAutomaticZenRuleState", origin);
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return;
@@ -819,6 +836,7 @@
 
     void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition,
             @ConfigChangeOrigin int origin, int callingUid) {
+        requirePublicOrigin("setAutomaticZenRuleState", origin);
         ZenModeConfig newConfig;
         synchronized (mConfigLock) {
             if (mConfig == null) return;
@@ -988,7 +1006,7 @@
         return null;
     }
 
-    void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule,
+    private void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule,
                          @ConfigChangeOrigin int origin, boolean isNew) {
         if (Flags.modesApi()) {
             // These values can always be edited by the app, so we apply changes immediately.
@@ -1053,11 +1071,9 @@
             rule.zenMode = newZenMode;
 
             // Updates the bitmask and values for all policy fields, based on the origin.
-            rule.zenPolicy = updatePolicy(rule.zenPolicy, automaticZenRule.getZenPolicy(),
-                    updateBitmask);
+            updatePolicy(rule, automaticZenRule.getZenPolicy(), updateBitmask);
             // Updates the bitmask and values for all device effect fields, based on the origin.
-            rule.zenDeviceEffects = updateZenDeviceEffects(
-                    rule.zenDeviceEffects, automaticZenRule.getDeviceEffects(),
+            updateZenDeviceEffects(rule, automaticZenRule.getDeviceEffects(),
                     origin == UPDATE_ORIGIN_APP, updateBitmask);
         } else {
             if (rule.enabled != automaticZenRule.isEnabled()) {
@@ -1069,13 +1085,6 @@
             rule.enabled = automaticZenRule.isEnabled();
             rule.modified = automaticZenRule.isModified();
             rule.zenPolicy = automaticZenRule.getZenPolicy();
-            if (Flags.modesApi()) {
-                rule.zenDeviceEffects = updateZenDeviceEffects(
-                        rule.zenDeviceEffects,
-                        automaticZenRule.getDeviceEffects(),
-                        origin == UPDATE_ORIGIN_APP,
-                        origin == UPDATE_ORIGIN_USER);
-            }
             rule.zenMode = NotificationManager.zenModeFromInterruptionFilter(
                     automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF);
             rule.configurationActivity = automaticZenRule.getConfigurationActivity();
@@ -1099,28 +1108,28 @@
     }
 
     /**
-     * Modifies {@link ZenPolicy} that is being stored as part of a new or updated ZenRule.
-     * Returns a policy based on {@code oldPolicy}, but with fields updated to match
-     * {@code newPolicy} where they differ, and updating the internal user-modified bitmask to
-     * track these changes, if applicable based on {@code origin}.
+     * Modifies the {@link ZenPolicy} associated to a new or updated ZenRule.
+     *
+     * <p>The new policy is {@code newPolicy}, while the user-modified bitmask is updated to reflect
+     * the changes being applied (if applicable, i.e. if the update is from the user).
      */
-    @Nullable
-    private ZenPolicy updatePolicy(@Nullable ZenPolicy oldPolicy, @Nullable ZenPolicy newPolicy,
-                                   boolean updateBitmask) {
-        // If the update is to make the policy null, we don't need to update the bitmask,
-        // because it won't be stored anywhere anyway.
+    private void updatePolicy(ZenRule zenRule, @Nullable ZenPolicy newPolicy,
+            boolean updateBitmask) {
         if (newPolicy == null) {
-            return null;
+            // TODO: b/319242206 - Treat as newPolicy == default policy and continue below.
+            zenRule.zenPolicy = null;
+            return;
         }
 
         // If oldPolicy is null, we compare against the default policy when determining which
         // fields in the bitmask should be marked as updated.
-        if (oldPolicy == null) {
-            oldPolicy = mDefaultConfig.toZenPolicy();
-        }
+        ZenPolicy oldPolicy =
+                zenRule.zenPolicy != null ? zenRule.zenPolicy : mDefaultConfig.toZenPolicy();
 
-        int userModifiedFields = oldPolicy.getUserModifiedFields();
+        zenRule.zenPolicy = newPolicy;
+
         if (updateBitmask) {
+            int userModifiedFields = zenRule.zenPolicyUserModifiedFields;
             if (oldPolicy.getPriorityMessageSenders() != newPolicy.getPriorityMessageSenders()) {
                 userModifiedFields |= ZenPolicy.FIELD_MESSAGES;
             }
@@ -1178,66 +1187,47 @@
                     != newPolicy.getVisualEffectNotificationList()) {
                 userModifiedFields |= ZenPolicy.FIELD_VISUAL_EFFECT_NOTIFICATION_LIST;
             }
+            zenRule.zenPolicyUserModifiedFields = userModifiedFields;
         }
-
-        // After all bitmask changes have been made, sets the bitmask.
-        return new ZenPolicy.Builder(newPolicy).setUserModifiedFields(userModifiedFields).build();
     }
 
     /**
-     * Modifies {@link ZenDeviceEffects} that are being stored as part of a new or updated ZenRule.
-     * Returns a {@link ZenDeviceEffects} based on {@code oldEffects}, but with fields updated to
-     * match {@code newEffects} where they differ, and updating the internal user-modified bitmask
-     * to track these changes, if applicable based on {@code origin}.
-     * <ul>
-     *     <li> Apps cannot turn on hidden effects (those tagged as {@code @hide}) since they are
-     *     intended for platform-specific rules (e.g. wearables). If it's a new rule, we blank them
-     *     out; if it's an update, we preserve the previous values.
-     * </ul>
+     * Modifies the {@link ZenDeviceEffects} associated to a new or updated ZenRule.
+     *
+     * <p>The new value is {@code newEffects}, while the user-modified bitmask is updated to reflect
+     * the changes being applied (if applicable, i.e. if the update is from the user).
+     *
+     * <p>Apps cannot turn on hidden effects (those tagged as {@code @hide}), so those fields are
+     * treated especially: for a new rule, they are blanked out; for an updated rule, previous
+     * values are preserved.
      */
-    @Nullable
-    private static ZenDeviceEffects updateZenDeviceEffects(@Nullable ZenDeviceEffects oldEffects,
-                                                           @Nullable ZenDeviceEffects newEffects,
-                                                           boolean isFromApp,
-                                                           boolean updateBitmask) {
+    private static void updateZenDeviceEffects(ZenRule zenRule,
+            @Nullable ZenDeviceEffects newEffects, boolean isFromApp, boolean updateBitmask) {
         if (newEffects == null) {
-            return null;
+            zenRule.zenDeviceEffects = null;
+            return;
         }
 
-        // Since newEffects is not null, we want to adopt all the new provided device effects.
-        ZenDeviceEffects.Builder builder = new ZenDeviceEffects.Builder(newEffects);
+        ZenDeviceEffects oldEffects = zenRule.zenDeviceEffects != null
+                ? zenRule.zenDeviceEffects
+                : new ZenDeviceEffects.Builder().build();
 
         if (isFromApp) {
-            if (oldEffects != null) {
-                // We can do this because we know we don't need to update the bitmask FROM_APP.
-                return builder
-                        .setShouldDisableAutoBrightness(oldEffects.shouldDisableAutoBrightness())
-                        .setShouldDisableTapToWake(oldEffects.shouldDisableTapToWake())
-                        .setShouldDisableTiltToWake(oldEffects.shouldDisableTiltToWake())
-                        .setShouldDisableTouch(oldEffects.shouldDisableTouch())
-                        .setShouldMinimizeRadioUsage(oldEffects.shouldMinimizeRadioUsage())
-                        .setShouldMaximizeDoze(oldEffects.shouldMaximizeDoze())
-                        .build();
-            } else {
-                return builder
-                        .setShouldDisableAutoBrightness(false)
-                        .setShouldDisableTapToWake(false)
-                        .setShouldDisableTiltToWake(false)
-                        .setShouldDisableTouch(false)
-                        .setShouldMinimizeRadioUsage(false)
-                        .setShouldMaximizeDoze(false)
-                        .build();
-            }
+            // Don't allow apps to toggle hidden effects.
+            newEffects = new ZenDeviceEffects.Builder(newEffects)
+                    .setShouldDisableAutoBrightness(oldEffects.shouldDisableAutoBrightness())
+                    .setShouldDisableTapToWake(oldEffects.shouldDisableTapToWake())
+                    .setShouldDisableTiltToWake(oldEffects.shouldDisableTiltToWake())
+                    .setShouldDisableTouch(oldEffects.shouldDisableTouch())
+                    .setShouldMinimizeRadioUsage(oldEffects.shouldMinimizeRadioUsage())
+                    .setShouldMaximizeDoze(oldEffects.shouldMaximizeDoze())
+                    .build();
         }
 
-        // If oldEffects is null, we compare against the default device effects object when
-        // determining which fields in the bitmask should be marked as updated.
-        if (oldEffects == null) {
-            oldEffects = new ZenDeviceEffects.Builder().build();
-        }
+        zenRule.zenDeviceEffects = newEffects;
 
-        int userModifiedFields = oldEffects.getUserModifiedFields();
         if (updateBitmask) {
+            int userModifiedFields = zenRule.zenDeviceEffectsUserModifiedFields;
             if (oldEffects.shouldDisplayGrayscale() != newEffects.shouldDisplayGrayscale()) {
                 userModifiedFields |= ZenDeviceEffects.FIELD_GRAYSCALE;
             }
@@ -1270,11 +1260,8 @@
             if (oldEffects.shouldMaximizeDoze() != newEffects.shouldMaximizeDoze()) {
                 userModifiedFields |= ZenDeviceEffects.FIELD_MAXIMIZE_DOZE;
             }
+            zenRule.zenDeviceEffectsUserModifiedFields = userModifiedFields;
         }
-
-        // Since newEffects is not null, we want to adopt all the new provided device effects.
-        // Set the usermodifiedFields value separately, to reflect the updated bitmask.
-        return builder.setUserModifiedFields(userModifiedFields).build();
     }
 
     private AutomaticZenRule zenRuleToAutomaticZenRule(ZenRule rule) {
@@ -1293,7 +1280,6 @@
                     .setOwner(rule.component)
                     .setConfigurationActivity(rule.configurationActivity)
                     .setTriggerDescription(rule.triggerDescription)
-                    .setUserModifiedFields(rule.userModifiedFields)
                     .build();
         } else {
             azr = new AutomaticZenRule(rule.name, rule.component,
@@ -2369,6 +2355,19 @@
             return null;
         }
     }
+
+    /** Checks that the {@code origin} supplied to a ZenModeHelper "API" method makes sense. */
+    private static void requirePublicOrigin(String method, @ConfigChangeOrigin int origin) {
+        if (!Flags.modesApi()) {
+            return;
+        }
+        checkArgument(origin == UPDATE_ORIGIN_APP || origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+                        || origin == UPDATE_ORIGIN_USER,
+                "Expected one of UPDATE_ORIGIN_APP, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, or "
+                        + "UPDATE_ORIGIN_USER for %s, but received '%s'.",
+                method, origin);
+    }
+
     private final class Metrics extends Callback {
         private static final String COUNTER_MODE_PREFIX = "dnd_mode_";
         private static final String COUNTER_TYPE_PREFIX = "dnd_type_";
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c1b7489..7b0a69b 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -46,6 +46,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityManagerNative;
+import android.app.ActivityOptions;
 import android.app.BroadcastOptions;
 import android.app.IActivityManager;
 import android.app.IStopUserCallback;
@@ -587,7 +588,10 @@
         public void onFinished(int id, Bundle extras) {
             mHandler.post(() -> {
                 try {
-                    mContext.startIntentSender(mTarget, null, 0, 0, 0);
+                    ActivityOptions activityOptions =
+                            ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode(
+                                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+                    mContext.startIntentSender(mTarget, null, 0, 0, 0, activityOptions.toBundle());
                 } catch (IntentSender.SendIntentException e) {
                     Slog.e(LOG_TAG, "Failed to start the target in the callback", e);
                 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
index ff91d34..92016df 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
@@ -20,11 +20,10 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.Mode.INVALID_MODE_ID;
 
-
 import static com.android.server.display.mode.DisplayModeDirector.SYNCHRONIZED_REFRESH_RATE_TOLERANCE;
 import static com.android.server.display.mode.Vote.PRIORITY_LIMIT_MODE;
-import static com.android.server.display.mode.Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE;
 import static com.android.server.display.mode.Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE;
+import static com.android.server.display.mode.Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE;
 import static com.android.server.display.mode.VotesStorage.GLOBAL_ID;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -43,6 +42,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.provider.DeviceConfigInterface;
+import android.test.mock.MockContentResolver;
 import android.view.Display;
 import android.view.DisplayInfo;
 
@@ -51,21 +51,26 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.display.feature.DisplayManagerFlags;
 import com.android.server.sensors.SensorManagerInternal;
 
+import junitparams.JUnitParamsRunner;
+
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import junitparams.JUnitParamsRunner;
-
-
 @SmallTest
 @RunWith(JUnitParamsRunner.class)
 public class DisplayObserverTest {
+    @Rule
+    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
     private static final int EXTERNAL_DISPLAY = 1;
     private static final int MAX_WIDTH = 1920;
     private static final int MAX_HEIGHT = 1080;
@@ -120,6 +125,8 @@
         mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         mResources = mock(Resources.class);
         when(mContext.getResources()).thenReturn(mResources);
+        MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
+        when(mContext.getContentResolver()).thenReturn(resolver);
         when(mResources.getInteger(R.integer.config_externalDisplayPeakRefreshRate))
                 .thenReturn(0);
         when(mResources.getInteger(R.integer.config_externalDisplayPeakWidth))
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index c1f35cc..723ac15 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -13792,8 +13792,7 @@
         NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
         mBinderService.setNotificationPolicy("package", policy, false);
 
-        verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy),
-                eq(ZenModeConfig.UPDATE_ORIGIN_APP));
+        verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy));
     }
 
     @Test
@@ -13859,7 +13858,7 @@
             verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
         } else {
             verify(zenModeHelper).applyGlobalPolicyAsImplicitZenRule(anyString(), anyInt(),
-                    eq(policy), anyInt());
+                    eq(policy));
         }
     }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
index 3d8ec2e..f604f1e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
@@ -52,7 +52,6 @@
                 .setShouldMaximizeDoze(true)
                 .setShouldUseNightMode(false)
                 .setShouldSuppressAmbientDisplay(false).setShouldSuppressAmbientDisplay(true)
-                .setUserModifiedFields(8)
                 .build();
 
         assertThat(deviceEffects.shouldDimWallpaper()).isTrue();
@@ -65,7 +64,6 @@
         assertThat(deviceEffects.shouldMinimizeRadioUsage()).isFalse();
         assertThat(deviceEffects.shouldUseNightMode()).isFalse();
         assertThat(deviceEffects.shouldSuppressAmbientDisplay()).isTrue();
-        assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(8);
     }
 
     @Test
@@ -97,7 +95,6 @@
                 .setShouldMinimizeRadioUsage(true)
                 .setShouldUseNightMode(true)
                 .setShouldSuppressAmbientDisplay(true)
-                .setUserModifiedFields(6)
                 .build();
 
         Parcel parcel = Parcel.obtain();
@@ -116,7 +113,6 @@
         assertThat(copy.shouldUseNightMode()).isTrue();
         assertThat(copy.shouldSuppressAmbientDisplay()).isTrue();
         assertThat(copy.shouldDisplayGrayscale()).isFalse();
-        assertThat(copy.getUserModifiedFields()).isEqualTo(6);
     }
 
     @Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
index db92719..e523e79f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -285,7 +285,6 @@
         assertEquals(expected.getPriorityCallSenders(), actual.getPriorityCallSenders());
         assertEquals(expected.getPriorityMessageSenders(), actual.getPriorityMessageSenders());
         assertEquals(expected.getPriorityChannels(), actual.getPriorityChannels());
-        assertEquals(expected.getUserModifiedFields(), actual.getUserModifiedFields());
     }
 
     @Test
@@ -342,45 +341,32 @@
         ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
         rule.zenPolicy = null;
         rule.zenDeviceEffects = null;
-
         assertThat(rule.canBeUpdatedByApp()).isTrue();
 
         rule.userModifiedFields = 1;
+
         assertThat(rule.canBeUpdatedByApp()).isFalse();
     }
 
     @Test
     public void testCanBeUpdatedByApp_policyModified() throws Exception {
-        ZenPolicy.Builder policyBuilder = new ZenPolicy.Builder().setUserModifiedFields(0);
-        ZenPolicy policy = policyBuilder.build();
-
         ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
-        rule.zenPolicy = policy;
-
-        assertThat(rule.userModifiedFields).isEqualTo(0);
+        rule.zenPolicy = new ZenPolicy();
         assertThat(rule.canBeUpdatedByApp()).isTrue();
 
-        policy = policyBuilder.setUserModifiedFields(1).build();
-        assertThat(policy.getUserModifiedFields()).isEqualTo(1);
-        rule.zenPolicy = policy;
+        rule.zenPolicyUserModifiedFields = 1;
+
         assertThat(rule.canBeUpdatedByApp()).isFalse();
     }
 
     @Test
     public void testCanBeUpdatedByApp_deviceEffectsModified() throws Exception {
-        ZenDeviceEffects.Builder deviceEffectsBuilder =
-                new ZenDeviceEffects.Builder().setUserModifiedFields(0);
-        ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build();
-
         ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
-        rule.zenDeviceEffects = deviceEffects;
-
-        assertThat(rule.userModifiedFields).isEqualTo(0);
+        rule.zenDeviceEffects = new ZenDeviceEffects.Builder().build();
         assertThat(rule.canBeUpdatedByApp()).isTrue();
 
-        deviceEffects = deviceEffectsBuilder.setUserModifiedFields(1).build();
-        assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(1);
-        rule.zenDeviceEffects = deviceEffects;
+        rule.zenDeviceEffectsUserModifiedFields = 1;
+
         assertThat(rule.canBeUpdatedByApp()).isFalse();
     }
 
@@ -406,6 +392,8 @@
         rule.allowManualInvocation = ALLOW_MANUAL;
         rule.type = TYPE;
         rule.userModifiedFields = 16;
+        rule.zenPolicyUserModifiedFields = 5;
+        rule.zenDeviceEffectsUserModifiedFields = 2;
         rule.iconResName = ICON_RES_NAME;
         rule.triggerDescription = TRIGGER_DESC;
         rule.deletionInstant = Instant.ofEpochMilli(1701790147000L);
@@ -432,6 +420,9 @@
         assertEquals(rule.iconResName, parceled.iconResName);
         assertEquals(rule.type, parceled.type);
         assertEquals(rule.userModifiedFields, parceled.userModifiedFields);
+        assertEquals(rule.zenPolicyUserModifiedFields, parceled.zenPolicyUserModifiedFields);
+        assertEquals(rule.zenDeviceEffectsUserModifiedFields,
+                parceled.zenDeviceEffectsUserModifiedFields);
         assertEquals(rule.triggerDescription, parceled.triggerDescription);
         assertEquals(rule.zenPolicy, parceled.zenPolicy);
         assertEquals(rule.deletionInstant, parceled.deletionInstant);
@@ -511,6 +502,8 @@
         rule.allowManualInvocation = ALLOW_MANUAL;
         rule.type = TYPE;
         rule.userModifiedFields = 4;
+        rule.zenPolicyUserModifiedFields = 5;
+        rule.zenDeviceEffectsUserModifiedFields = 2;
         rule.iconResName = ICON_RES_NAME;
         rule.triggerDescription = TRIGGER_DESC;
         rule.deletionInstant = Instant.ofEpochMilli(1701790147000L);
@@ -541,6 +534,9 @@
         assertEquals(rule.allowManualInvocation, fromXml.allowManualInvocation);
         assertEquals(rule.type, fromXml.type);
         assertEquals(rule.userModifiedFields, fromXml.userModifiedFields);
+        assertEquals(rule.zenPolicyUserModifiedFields, fromXml.zenPolicyUserModifiedFields);
+        assertEquals(rule.zenDeviceEffectsUserModifiedFields,
+                fromXml.zenDeviceEffectsUserModifiedFields);
         assertEquals(rule.triggerDescription, fromXml.triggerDescription);
         assertEquals(rule.iconResName, fromXml.iconResName);
         assertEquals(rule.deletionInstant, fromXml.deletionInstant);
@@ -697,7 +693,6 @@
                 .allowPriorityChannels(false)
                 .hideAllVisualEffects()
                 .showVisualEffect(ZenPolicy.VISUAL_EFFECT_AMBIENT, true)
-                .setUserModifiedFields(4)
                 .build();
 
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -732,7 +727,6 @@
         assertEquals(policy.getVisualEffectAmbient(), fromXml.getVisualEffectAmbient());
         assertEquals(policy.getVisualEffectNotificationList(),
                 fromXml.getVisualEffectNotificationList());
-        assertEquals(policy.getUserModifiedFields(), fromXml.getUserModifiedFields());
     }
 
     private ZenModeConfig getMutedRingerConfig() {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
index 9d7cf53..2e64645 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
@@ -73,13 +73,15 @@
                     : Set.of("version", "manualRule", "automaticRules");
 
     // Differences for flagged fields are only generated if the flag is enabled.
-    // "Metadata" fields (userModifiedFields, deletionInstant) are not compared.
+    // "Metadata" fields (userModifiedFields & co, deletionInstant) are not compared.
     private static final Set<String> ZEN_RULE_EXEMPT_FIELDS =
             android.app.Flags.modesApi()
-                    ? Set.of("userModifiedFields", "deletionInstant")
+                    ? Set.of("userModifiedFields", "zenPolicyUserModifiedFields",
+                            "zenDeviceEffectsUserModifiedFields", "deletionInstant")
                     : Set.of(RuleDiff.FIELD_TYPE, RuleDiff.FIELD_TRIGGER_DESCRIPTION,
                             RuleDiff.FIELD_ICON_RES, RuleDiff.FIELD_ALLOW_MANUAL,
                             RuleDiff.FIELD_ZEN_DEVICE_EFFECTS, "userModifiedFields",
+                            "zenPolicyUserModifiedFields", "zenDeviceEffectsUserModifiedFields",
                             "deletionInstant");
 
     // allowPriorityChannels is flagged by android.app.modes_api
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 78fe41f..edc876a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -46,6 +46,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
 import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
 import static android.provider.Settings.Global.ZEN_MODE_OFF;
 import static android.service.notification.Condition.SOURCE_SCHEDULE;
 import static android.service.notification.Condition.SOURCE_USER_ACTION;
@@ -67,6 +68,7 @@
 import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW;
 import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE;
 
+import static com.google.common.collect.Iterables.getOnlyElement;
 import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.Assert.assertEquals;
@@ -295,6 +297,8 @@
         when(appInfoSpy.loadLabel(any())).thenReturn(CUSTOM_APP_LABEL);
         when(mPackageManager.getApplicationInfo(eq(CUSTOM_PKG_NAME), anyInt()))
                 .thenReturn(appInfoSpy);
+        when(mPackageManager.getApplicationInfo(eq(mContext.getPackageName()), anyInt()))
+                .thenReturn(appInfoSpy);
         mZenModeHelper.mPm = mPackageManager;
 
         mZenModeEventLogger.reset();
@@ -2236,12 +2240,7 @@
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
-        // savedRule.getDeviceEffects() is equal to zde, except for the userModifiedFields.
-        // So we clear before comparing.
-        ZenDeviceEffects savedEffects = new ZenDeviceEffects.Builder(savedRule.getDeviceEffects())
-                .setUserModifiedFields(0).build();
-
-        assertThat(savedEffects).isEqualTo(zde);
+        assertThat(savedRule.getDeviceEffects()).isEqualTo(zde);
     }
 
     @Test
@@ -2331,12 +2330,7 @@
 
         AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
-        // savedRule.getDeviceEffects() is equal to updateFromUser, except for the
-        // userModifiedFields, so we clear before comparing.
-        ZenDeviceEffects savedEffects = new ZenDeviceEffects.Builder(savedRule.getDeviceEffects())
-                .setUserModifiedFields(0).build();
-
-        assertThat(savedEffects).isEqualTo(updateFromUser);
+        assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromUser);
     }
 
     @Test
@@ -3411,7 +3405,6 @@
 
         rule.allowManualInvocation = ALLOW_MANUAL;
         rule.type = TYPE;
-        rule.userModifiedFields = AutomaticZenRule.FIELD_NAME;
         rule.iconResName = ICON_RES_NAME;
         rule.triggerDescription = TRIGGER_DESC;
 
@@ -3426,7 +3419,6 @@
         assertEquals(POLICY, actual.getZenPolicy());
         assertEquals(CONFIG_ACTIVITY, actual.getConfigurationActivity());
         assertEquals(TYPE, actual.getType());
-        assertEquals(AutomaticZenRule.FIELD_NAME, actual.getUserModifiedFields());
         assertEquals(ALLOW_MANUAL, actual.isManualInvocationAllowed());
         assertEquals(CREATION_TIME, actual.getCreationTime());
         assertEquals(OWNER.getPackageName(), actual.getPackageName());
@@ -3453,29 +3445,31 @@
                 .setManualInvocationAllowed(ALLOW_MANUAL)
                 .build();
 
-        ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
+        String ruleId = mZenModeHelper.addAutomaticZenRule(OWNER.getPackageName(), azr,
+                UPDATE_ORIGIN_APP, "add", CUSTOM_PKG_UID);
 
-        mZenModeHelper.populateZenRule(OWNER.getPackageName(), azr, rule, UPDATE_ORIGIN_APP, true);
+        ZenModeConfig.ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
 
-        assertEquals(NAME, rule.name);
-        assertEquals(OWNER, rule.component);
-        assertEquals(CONDITION_ID, rule.conditionId);
-        assertEquals(INTERRUPTION_FILTER_ZR, rule.zenMode);
-        assertEquals(ENABLED, rule.enabled);
-        assertEquals(POLICY, rule.zenPolicy);
-        assertEquals(CONFIG_ACTIVITY, rule.configurationActivity);
-        assertEquals(TYPE, rule.type);
-        assertEquals(ALLOW_MANUAL, rule.allowManualInvocation);
-        assertEquals(OWNER.getPackageName(), rule.getPkg());
-        assertEquals(ICON_RES_NAME, rule.iconResName);
+        assertThat(storedRule).isNotNull();
+        assertEquals(NAME, storedRule.name);
+        assertEquals(OWNER, storedRule.component);
+        assertEquals(CONDITION_ID, storedRule.conditionId);
+        assertEquals(INTERRUPTION_FILTER_ZR, storedRule.zenMode);
+        assertEquals(ENABLED, storedRule.enabled);
+        assertEquals(POLICY, storedRule.zenPolicy);
+        assertEquals(CONFIG_ACTIVITY, storedRule.configurationActivity);
+        assertEquals(TYPE, storedRule.type);
+        assertEquals(ALLOW_MANUAL, storedRule.allowManualInvocation);
+        assertEquals(OWNER.getPackageName(), storedRule.getPkg());
+        assertEquals(ICON_RES_NAME, storedRule.iconResName);
         // Because the origin of the update is the app, we don't expect the bitmask to change.
-        assertEquals(0, rule.userModifiedFields);
-        assertEquals(TRIGGER_DESC, rule.triggerDescription);
+        assertEquals(0, storedRule.userModifiedFields);
+        assertEquals(TRIGGER_DESC, storedRule.triggerDescription);
     }
 
     @Test
     @EnableFlags(Flags.FLAG_MODES_API)
-    public void automaticZenRuleToZenRule_updatesNameUnlessUserModified() {
+    public void updateAutomaticZenRule_fromApp_updatesNameUnlessUserModified() {
         // Add a starting rule with the name OriginalName.
         AutomaticZenRule azrBase = new AutomaticZenRule.Builder("OriginalName", CONDITION_ID)
                 .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
@@ -3492,7 +3486,6 @@
                 Process.SYSTEM_UID);
         rule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(rule.getName()).isEqualTo("NewName");
-        assertThat(rule.canUpdate()).isTrue();
 
         // The user modifies some other field in the rule, which makes the rule as a whole not
         // app modifiable.
@@ -3501,10 +3494,6 @@
                 .build();
         mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_USER, "reason",
                 Process.SYSTEM_UID);
-        rule = mZenModeHelper.getAutomaticZenRule(ruleId);
-        assertThat(rule.getUserModifiedFields())
-                .isEqualTo(AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
-        assertThat(rule.canUpdate()).isFalse();
 
         // ...but the app can still modify the name, because the name itself hasn't been modified
         // by the user.
@@ -3524,8 +3513,6 @@
                 Process.SYSTEM_UID);
         rule = mZenModeHelper.getAutomaticZenRule(ruleId);
         assertThat(rule.getName()).isEqualTo("UserProvidedName");
-        assertThat(rule.getUserModifiedFields()).isEqualTo(AutomaticZenRule.FIELD_NAME
-                | AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
 
         // The app is no longer able to modify the name.
         azrUpdate = new AutomaticZenRule.Builder(rule)
@@ -3539,7 +3526,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_MODES_API)
-    public void automaticZenRuleToZenRule_updatesBitmaskAndValueForUserOrigin() {
+    public void updateAutomaticZenRule_fromUser_updatesBitmaskAndValue() {
         // Adds a starting rule with empty zen policies and device effects
         AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
                 .setZenPolicy(new ZenPolicy.Builder().build())
@@ -3571,84 +3558,21 @@
 
         // UPDATE_ORIGIN_USER should change the bitmask and change the values.
         assertThat(rule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
-        assertThat(rule.getUserModifiedFields())
-                .isEqualTo(AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
-        assertThat(rule.getZenPolicy().getUserModifiedFields())
-                .isEqualTo(ZenPolicy.FIELD_ALLOW_CHANNELS);
         assertThat(rule.getZenPolicy().getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
-        assertThat(rule.getDeviceEffects().getUserModifiedFields())
-                .isEqualTo(ZenDeviceEffects.FIELD_GRAYSCALE);
         assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
+
+        ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(storedRule.userModifiedFields)
+                .isEqualTo(AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
+        assertThat(storedRule.zenPolicyUserModifiedFields)
+                .isEqualTo(ZenPolicy.FIELD_ALLOW_CHANNELS);
+        assertThat(storedRule.zenDeviceEffectsUserModifiedFields)
+                .isEqualTo(ZenDeviceEffects.FIELD_GRAYSCALE);
     }
 
     @Test
     @EnableFlags(Flags.FLAG_MODES_API)
-    public void automaticZenRuleToZenRule_doesNotUpdateValuesForInitUserOrigin() {
-        // Adds a starting rule with empty zen policies and device effects
-        AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
-                .setInterruptionFilter(INTERRUPTION_FILTER_ALL) // Already the default, no change
-                .setZenPolicy(new ZenPolicy.Builder()
-                        .allowReminders(false)
-                        .build())
-                .setDeviceEffects(new ZenDeviceEffects.Builder()
-                        .setShouldDisplayGrayscale(false)
-                        .build())
-                .build();
-        // Adds the rule using the user, to set user-modified bits.
-        String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                azrBase, UPDATE_ORIGIN_USER, "reason", Process.SYSTEM_UID);
-        AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
-        assertThat(rule.canUpdate()).isFalse();
-        assertThat(rule.getUserModifiedFields()).isEqualTo(AutomaticZenRule.FIELD_NAME);
-
-        ZenPolicy policy = new ZenPolicy.Builder(rule.getZenPolicy())
-                .allowReminders(true)
-                .build();
-        ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder(rule.getDeviceEffects())
-                .setShouldDisplayGrayscale(true)
-                .build();
-        AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule)
-                .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
-                .setZenPolicy(policy)
-                .setDeviceEffects(deviceEffects)
-                .build();
-
-        // Attempts to update the rule with the AZR from origin init user.
-        mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_INIT_USER, "reason",
-                Process.SYSTEM_UID);
-        AutomaticZenRule unchangedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
-
-        // UPDATE_ORIGIN_INIT_USER does not change the bitmask or values if rule is user modified.
-        // TODO: b/318506692 - Remove once we check that INIT origins can't call add/updateAZR.
-        assertThat(unchangedRule.getUserModifiedFields()).isEqualTo(rule.getUserModifiedFields());
-        assertThat(unchangedRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALL);
-        assertThat(unchangedRule.getZenPolicy().getUserModifiedFields()).isEqualTo(
-                rule.getZenPolicy().getUserModifiedFields());
-        assertThat(unchangedRule.getZenPolicy().getPriorityCategoryReminders()).isEqualTo(
-                ZenPolicy.STATE_DISALLOW);
-        assertThat(unchangedRule.getDeviceEffects().getUserModifiedFields()).isEqualTo(
-                rule.getDeviceEffects().getUserModifiedFields());
-        assertThat(unchangedRule.getDeviceEffects().shouldDisplayGrayscale()).isFalse();
-
-        // Creates a new rule with the AZR from origin init user.
-        String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                azrUpdate, UPDATE_ORIGIN_INIT_USER, "reason", Process.SYSTEM_UID);
-        AutomaticZenRule newRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
-
-        // UPDATE_ORIGIN_INIT_USER does change the values if the rule is new,
-        // but does not update the bitmask.
-        assertThat(newRule.getUserModifiedFields()).isEqualTo(0);
-        assertThat(newRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
-        assertThat(newRule.getZenPolicy().getUserModifiedFields()).isEqualTo(0);
-        assertThat(newRule.getZenPolicy().getPriorityCategoryReminders())
-                .isEqualTo(ZenPolicy.STATE_ALLOW);
-        assertThat(newRule.getDeviceEffects().getUserModifiedFields()).isEqualTo(0);
-        assertThat(newRule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
-    }
-
-    @Test
-    @EnableFlags(Flags.FLAG_MODES_API)
-    public void automaticZenRuleToZenRule_updatesValuesForSystemUiOrigin() {
+    public void updateAutomaticZenRule_fromSystemUi_updatesValues() {
         // Adds a starting rule with empty zen policies and device effects
         AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
                 .setInterruptionFilter(INTERRUPTION_FILTER_ALL)
@@ -3684,17 +3608,19 @@
         rule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
         // UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI should change the value but NOT update the bitmask.
-        assertThat(rule.getUserModifiedFields()).isEqualTo(0);
-        assertThat(rule.getZenPolicy().getUserModifiedFields()).isEqualTo(0);
         assertThat(rule.getZenPolicy().getPriorityCategoryReminders())
                 .isEqualTo(ZenPolicy.STATE_ALLOW);
-        assertThat(rule.getDeviceEffects().getUserModifiedFields()).isEqualTo(0);
         assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
+
+        ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(storedRule.userModifiedFields).isEqualTo(0);
+        assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(0);
+        assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(0);
     }
 
     @Test
     @EnableFlags(Flags.FLAG_MODES_API)
-    public void automaticZenRuleToZenRule_updatesValuesIfRuleNotUserModified() {
+    public void updateAutomaticZenRule_fromApp_updatesValuesIfRuleNotUserModified() {
         // Adds a starting rule with empty zen policies and device effects
         AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
                 .setInterruptionFilter(INTERRUPTION_FILTER_ALL)
@@ -3709,7 +3635,6 @@
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
                 azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
         AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
-        assertThat(rule.canUpdate()).isTrue();
 
         ZenPolicy policy = new ZenPolicy.Builder()
                 .allowReminders(true)
@@ -3717,57 +3642,59 @@
         ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder()
                 .setShouldDisplayGrayscale(true)
                 .build();
-        AutomaticZenRule azrUpdate =  new AutomaticZenRule.Builder(rule)
+        AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule)
                 .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
                 .setZenPolicy(policy)
                 .setDeviceEffects(deviceEffects)
                 .build();
 
-        // Since the rule is not already user modified, UPDATE_ORIGIN_UNKNOWN can modify the rule.
+        // Since the rule is not already user modified, UPDATE_ORIGIN_APP can modify the rule.
         // The bitmask is not modified.
-        mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_UNKNOWN, "reason",
+        mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_APP, "reason",
                 Process.SYSTEM_UID);
-        AutomaticZenRule unchangedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
-        assertThat(unchangedRule.getUserModifiedFields()).isEqualTo(rule.getUserModifiedFields());
-        assertThat(unchangedRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
-        assertThat(unchangedRule.getZenPolicy().getUserModifiedFields()).isEqualTo(
-                rule.getZenPolicy().getUserModifiedFields());
-        assertThat(unchangedRule.getZenPolicy().getPriorityCategoryReminders())
+        ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(storedRule.userModifiedFields).isEqualTo(0);
+
+        assertThat(storedRule.zenMode).isEqualTo(ZEN_MODE_ALARMS);
+        assertThat(storedRule.zenPolicy.getPriorityCategoryReminders())
                 .isEqualTo(ZenPolicy.STATE_ALLOW);
-        assertThat(unchangedRule.getDeviceEffects().getUserModifiedFields()).isEqualTo(
-                rule.getDeviceEffects().getUserModifiedFields());
-        assertThat(unchangedRule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
+        assertThat(storedRule.zenDeviceEffects.shouldDisplayGrayscale()).isTrue();
+        assertThat(storedRule.userModifiedFields).isEqualTo(0);
+        assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(0);
+        assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(0);
 
         // Creates another rule, this time from user. This will have user modified bits set.
         String ruleIdUser = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
                 azrBase, UPDATE_ORIGIN_USER, "reason", Process.SYSTEM_UID);
-        AutomaticZenRule ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser);
-        assertThat(ruleUser.canUpdate()).isFalse();
+        storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleIdUser);
+        int ruleModifiedFields = storedRule.userModifiedFields;
+        int rulePolicyModifiedFields = storedRule.zenPolicyUserModifiedFields;
+        int ruleDeviceEffectsModifiedFields = storedRule.zenDeviceEffectsUserModifiedFields;
 
-        // Zen rule update coming from unknown origin. This cannot fully update the rule, because
+        // Zen rule update coming from the app again. This cannot fully update the rule, because
         // the rule is already considered user modified.
-        mZenModeHelper.updateAutomaticZenRule(ruleIdUser, azrUpdate, UPDATE_ORIGIN_UNKNOWN,
+        mZenModeHelper.updateAutomaticZenRule(ruleIdUser, azrUpdate, UPDATE_ORIGIN_APP,
                 "reason", Process.SYSTEM_UID);
-        ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser);
+        AutomaticZenRule ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser);
 
-        // UPDATE_ORIGIN_UNKNOWN can only change the value if the rule is not already user modified,
+        // The app can only change the value if the rule is not already user modified,
         // so the rule is not changed, and neither is the bitmask.
         assertThat(ruleUser.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALL);
-        // Interruption Filter All is the default value, so it's not included as a modified field.
-        assertThat(ruleUser.getUserModifiedFields() | AutomaticZenRule.FIELD_NAME).isGreaterThan(0);
-        assertThat(ruleUser.getZenPolicy().getUserModifiedFields()
-                | ZenPolicy.FIELD_PRIORITY_CATEGORY_REMINDERS).isGreaterThan(0);
         assertThat(ruleUser.getZenPolicy().getPriorityCategoryReminders())
                 .isEqualTo(ZenPolicy.STATE_DISALLOW);
-        assertThat(ruleUser.getDeviceEffects().getUserModifiedFields()
-                | ZenDeviceEffects.FIELD_GRAYSCALE).isGreaterThan(0);
         assertThat(ruleUser.getDeviceEffects().shouldDisplayGrayscale()).isFalse();
+
+        storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleIdUser);
+        assertThat(storedRule.userModifiedFields).isEqualTo(ruleModifiedFields);
+        assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(rulePolicyModifiedFields);
+        assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(
+                ruleDeviceEffectsModifiedFields);
     }
 
     @Test
     @EnableFlags(Flags.FLAG_MODES_API)
-    public void automaticZenRuleToZenRule_updatesValuesIfRuleNew() {
+    public void addAutomaticZenRule_updatesValues() {
         // Adds a starting rule with empty zen policies and device effects
         AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
                 .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
@@ -3778,21 +3705,22 @@
                         .setShouldDisplayGrayscale(true)
                         .build())
                 .build();
-        // Adds the rule using origin unknown, to show that a new rule is always allowed.
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
-                azrBase, UPDATE_ORIGIN_UNKNOWN, "reason", Process.SYSTEM_UID);
+                azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
         AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
         // The values are modified but the bitmask is not.
-        assertThat(rule.canUpdate()).isTrue();
         assertThat(rule.getZenPolicy().getPriorityCategoryReminders())
                 .isEqualTo(ZenPolicy.STATE_ALLOW);
         assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
+
+        ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(storedRule.canBeUpdatedByApp()).isTrue();
     }
 
     @Test
     @EnableFlags(Flags.FLAG_MODES_API)
-    public void automaticZenRuleToZenRule_nullDeviceEffectsUpdate() {
+    public void updateAutomaticZenRule_nullDeviceEffectsUpdate() {
         // Adds a starting rule with empty zen policies and device effects
         AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
                 .setDeviceEffects(new ZenDeviceEffects.Builder().build())
@@ -3807,9 +3735,9 @@
                 .setDeviceEffects(null)
                 .build();
 
-        // Zen rule update coming from unknown origin, but since the rule isn't already
+        // Zen rule update coming from app, but since the rule isn't already
         // user modified, it can be updated.
-        mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_UNKNOWN, "reason",
+        mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_APP, "reason",
                 Process.SYSTEM_UID);
         rule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
@@ -3819,7 +3747,7 @@
 
     @Test
     @EnableFlags(Flags.FLAG_MODES_API)
-    public void automaticZenRuleToZenRule_nullPolicyUpdate() {
+    public void updateAutomaticZenRule_nullPolicyUpdate() {
         // Adds a starting rule with empty zen policies and device effects
         AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
                 .setZenPolicy(new ZenPolicy.Builder().build())
@@ -3828,16 +3756,15 @@
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
                 azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
         AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
-        assertThat(rule.canUpdate()).isTrue();
 
         AutomaticZenRule azr = new AutomaticZenRule.Builder(azrBase)
                 // Set zen policy to null
                 .setZenPolicy(null)
                 .build();
 
-        // Zen rule update coming from unknown origin, but since the rule isn't already
+        // Zen rule update coming from app, but since the rule isn't already
         // user modified, it can be updated.
-        mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_UNKNOWN, "reason",
+        mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_APP, "reason",
                 Process.SYSTEM_UID);
         rule = mZenModeHelper.getAutomaticZenRule(ruleId);
 
@@ -3859,7 +3786,6 @@
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
                 azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
         AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
-        assertThat(rule.canUpdate()).isTrue();
 
         // Create a fully populated ZenPolicy.
         ZenPolicy policy = new ZenPolicy.Builder()
@@ -3894,8 +3820,10 @@
         // New ZenPolicy differs from the default config
         assertThat(rule.getZenPolicy()).isNotNull();
         assertThat(rule.getZenPolicy().getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
-        assertThat(rule.canUpdate()).isFalse();
-        assertThat(rule.getZenPolicy().getUserModifiedFields()).isEqualTo(
+
+        ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(storedRule.canBeUpdatedByApp()).isFalse();
+        assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(
                 ZenPolicy.FIELD_ALLOW_CHANNELS
                 | ZenPolicy.FIELD_PRIORITY_CATEGORY_REMINDERS
                 | ZenPolicy.FIELD_PRIORITY_CATEGORY_EVENTS
@@ -3918,7 +3846,6 @@
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
                 azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
         AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
-        assertThat(rule.canUpdate()).isTrue();
 
         ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder()
                 .setShouldDisplayGrayscale(true)
@@ -3935,8 +3862,10 @@
         // New ZenDeviceEffects is used; all fields considered set, since previously were null.
         assertThat(rule.getDeviceEffects()).isNotNull();
         assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
-        assertThat(rule.canUpdate()).isFalse();
-        assertThat(rule.getDeviceEffects().getUserModifiedFields()).isEqualTo(
+
+        ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(storedRule.canBeUpdatedByApp()).isFalse();
+        assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(
                 ZenDeviceEffects.FIELD_GRAYSCALE);
     }
 
@@ -4339,7 +4268,6 @@
         String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
                 UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
         assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
-        assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).canUpdate()).isTrue();
 
         // User customizes it.
         AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
@@ -4371,9 +4299,11 @@
         assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
         assertThat(finalRule.getZenPolicy().getPriorityCategoryRepeatCallers()).isEqualTo(
                 ZenPolicy.STATE_ALLOW);
-        assertThat(finalRule.getUserModifiedFields()).isEqualTo(
+
+        ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+        assertThat(storedRule.userModifiedFields).isEqualTo(
                 AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
-        assertThat(finalRule.getZenPolicy().getUserModifiedFields()).isEqualTo(
+        assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(
                 ZenPolicy.FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS);
 
         // Also, we discarded the "deleted rule" since we already used it for restoration.
@@ -4652,7 +4582,7 @@
                 ZEN_MODE_IMPORTANT_INTERRUPTIONS);
 
         assertThat(mZenModeHelper.mConfig.automaticRules.values())
-                .comparingElementsUsing(IGNORE_TIMESTAMPS)
+                .comparingElementsUsing(IGNORE_METADATA)
                 .containsExactly(
                         expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                                 null, true));
@@ -4672,12 +4602,75 @@
                 ZEN_MODE_ALARMS);
 
         assertThat(mZenModeHelper.mConfig.automaticRules.values())
-                .comparingElementsUsing(IGNORE_TIMESTAMPS)
+                .comparingElementsUsing(IGNORE_METADATA)
                 .containsExactly(
                         expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_ALARMS, null, true));
     }
 
     @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    public void applyGlobalZenModeAsImplicitZenRule_ruleCustomized_doesNotUpdateRule() {
+        mZenModeHelper.mConfig.automaticRules.clear();
+        String pkg = mContext.getPackageName();
+
+        // From app, call "setInterruptionFilter" and create and implicit rule.
+        mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+                ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+        String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
+        assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
+                .isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+        // From user, update that rule's interruption filter.
+        AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+        AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
+                .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
+                Process.SYSTEM_UID);
+
+        // From app, call "setInterruptionFilter" again.
+        mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+                ZEN_MODE_NO_INTERRUPTIONS);
+
+        // The app's update was ignored, and the user's update is still current, and the current
+        // mode is the one they chose.
+        assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
+                .isEqualTo(ZEN_MODE_ALARMS);
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    public void applyGlobalZenModeAsImplicitZenRule_ruleCustomizedButNotFilter_updatesRule() {
+        mZenModeHelper.mConfig.automaticRules.clear();
+        String pkg = mContext.getPackageName();
+
+        // From app, call "setInterruptionFilter" and create and implicit rule.
+        mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+                ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+        String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
+        assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
+                .isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+        // From user, update something in that rule, but not the interruption filter.
+        AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+        AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
+                .setName("Renamed")
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
+                Process.SYSTEM_UID);
+
+        // From app, call "setInterruptionFilter" again.
+        mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+                ZEN_MODE_NO_INTERRUPTIONS);
+
+        // The app's update was accepted, and the current mode is the one that they wanted.
+        assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
+                .isEqualTo(ZEN_MODE_NO_INTERRUPTIONS);
+        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_NO_INTERRUPTIONS);
+    }
+
+    @Test
     public void applyGlobalZenModeAsImplicitZenRule_modeOff_deactivatesImplicitRule() {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
         mZenModeHelper.mConfig.automaticRules.clear();
@@ -4747,8 +4740,7 @@
         Policy policy = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
                 PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
                 Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
-        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, policy,
-                UPDATE_ORIGIN_APP);
+        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, policy);
 
         ZenPolicy expectedZenPolicy = new ZenPolicy.Builder()
                 .disallowAllSounds()
@@ -4758,7 +4750,7 @@
                 .allowPriorityChannels(true)
                 .build();
         assertThat(mZenModeHelper.mConfig.automaticRules.values())
-                .comparingElementsUsing(IGNORE_TIMESTAMPS)
+                .comparingElementsUsing(IGNORE_METADATA)
                 .containsExactly(
                         expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                                 expectedZenPolicy, /* conditionActive= */ null));
@@ -4773,14 +4765,13 @@
                 PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
                 Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
         mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
-                original, UPDATE_ORIGIN_APP);
+                original);
 
         // Change priorityCallSenders: contacts -> starred.
         Policy updated = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
                 PRIORITY_SENDERS_STARRED, PRIORITY_SENDERS_STARRED,
                 Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
-        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, updated,
-                UPDATE_ORIGIN_APP);
+        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, updated);
 
         ZenPolicy expectedZenPolicy = new ZenPolicy.Builder()
                 .disallowAllSounds()
@@ -4790,20 +4781,87 @@
                 .allowPriorityChannels(true)
                 .build();
         assertThat(mZenModeHelper.mConfig.automaticRules.values())
-                .comparingElementsUsing(IGNORE_TIMESTAMPS)
+                .comparingElementsUsing(IGNORE_METADATA)
                 .containsExactly(
                         expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                                 expectedZenPolicy, /* conditionActive= */ null));
     }
 
     @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    public void applyGlobalPolicyAsImplicitZenRule_ruleCustomized_doesNotUpdateRule() {
+        mZenModeHelper.mConfig.automaticRules.clear();
+        String pkg = mContext.getPackageName();
+
+        // From app, call "setNotificationPolicy" and create and implicit rule.
+        Policy originalPolicy = new Policy(PRIORITY_CATEGORY_MEDIA, 0, 0);
+        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, originalPolicy);
+        String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
+
+        // From user, update that rule's policy.
+        AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+        ZenPolicy userUpdateZenPolicy = new ZenPolicy.Builder().disallowAllSounds()
+                .allowAlarms(true).build();
+        AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
+                .setZenPolicy(userUpdateZenPolicy)
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
+                Process.SYSTEM_UID);
+
+        // From app, call "setNotificationPolicy" again.
+        Policy appUpdatePolicy = new Policy(PRIORITY_CATEGORY_SYSTEM, 0, 0);
+        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, appUpdatePolicy);
+
+        // The app's update was ignored, and the user's update is still current.
+        assertThat(mZenModeHelper.mConfig.automaticRules.values())
+                .comparingElementsUsing(IGNORE_METADATA)
+                .containsExactly(
+                        expectedImplicitRule(pkg, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                                userUpdateZenPolicy,
+                                /* conditionActive= */ null));
+    }
+
+    @Test
+    @EnableFlags(android.app.Flags.FLAG_MODES_API)
+    public void applyGlobalPolicyAsImplicitZenRule_ruleCustomizedButNotZenPolicy_updatesRule() {
+        mZenModeHelper.mConfig.automaticRules.clear();
+        String pkg = mContext.getPackageName();
+
+        // From app, call "setNotificationPolicy" and create and implicit rule.
+        Policy originalPolicy = new Policy(PRIORITY_CATEGORY_MEDIA, 0, 0);
+        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, originalPolicy);
+        String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
+
+        // From user, update something in that rule, but not the ZenPolicy.
+        AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+        AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
+                .setName("Rule renamed, not touching policy")
+                .build();
+        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
+                Process.SYSTEM_UID);
+
+        // From app, call "setNotificationPolicy" again.
+        Policy appUpdatePolicy = new Policy(PRIORITY_CATEGORY_SYSTEM, 0, 0);
+        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, appUpdatePolicy);
+
+        // The app's update was applied.
+        ZenPolicy appsSecondZenPolicy = new ZenPolicy.Builder()
+                .disallowAllSounds()
+                .allowSystem(true)
+                .allowPriorityChannels(true)
+                .build();
+        assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenPolicy)
+                .isEqualTo(appsSecondZenPolicy);
+    }
+
+    @Test
     public void applyGlobalPolicyAsImplicitZenRule_flagOff_ignored() {
         mSetFlagsRule.disableFlags(android.app.Flags.FLAG_MODES_API);
         mZenModeHelper.mConfig.automaticRules.clear();
 
         withoutWtfCrash(
                 () -> mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME,
-                        CUSTOM_PKG_UID, new Policy(0, 0, 0), UPDATE_ORIGIN_APP));
+                        CUSTOM_PKG_UID, new Policy(0, 0, 0)));
 
         assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty();
     }
@@ -4816,7 +4874,7 @@
                 Policy.getAllSuppressedVisualEffects(), STATE_FALSE,
                 CONVERSATION_SENDERS_IMPORTANT);
         mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
-                writtenPolicy, UPDATE_ORIGIN_APP);
+                writtenPolicy);
 
         Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
                 CUSTOM_PKG_NAME);
@@ -4856,7 +4914,7 @@
         assertThat(readPolicy.allowConversations()).isFalse();
     }
 
-    private static final Correspondence<ZenRule, ZenRule> IGNORE_TIMESTAMPS =
+    private static final Correspondence<ZenRule, ZenRule> IGNORE_METADATA =
             Correspondence.transforming(zr -> {
                 Parcel p = Parcel.obtain();
                 try {
@@ -4864,12 +4922,15 @@
                     p.setDataPosition(0);
                     ZenRule copy = new ZenRule(p);
                     copy.creationTime = 0;
+                    copy.userModifiedFields = 0;
+                    copy.zenPolicyUserModifiedFields = 0;
+                    copy.zenDeviceEffectsUserModifiedFields = 0;
                     return copy;
                 } finally {
                     p.recycle();
                 }
             },
-            "Ignoring timestamps");
+            "Ignoring timestamp and userModifiedFields");
 
     private ZenRule expectedImplicitRule(String ownerPkg, int zenMode, ZenPolicy policy,
             @Nullable Boolean conditionActive) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
index 7941eb4..4ed55df 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
@@ -645,38 +645,12 @@
     }
 
     @Test
-    public void testFromParcel() {
-        ZenPolicy.Builder builder = new ZenPolicy.Builder();
-        builder.setUserModifiedFields(10);
-
-        ZenPolicy policy = builder.build();
-        assertThat(policy.getUserModifiedFields()).isEqualTo(10);
-
-        Parcel parcel = Parcel.obtain();
-        policy.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-
-        ZenPolicy fromParcel = ZenPolicy.CREATOR.createFromParcel(parcel);
-        assertThat(fromParcel.getUserModifiedFields()).isEqualTo(10);
-    }
-
-    @Test
-    public void testPolicy_userModifiedFields() {
-        ZenPolicy.Builder builder = new ZenPolicy.Builder();
-        builder.setUserModifiedFields(10);
-        assertThat(builder.build().getUserModifiedFields()).isEqualTo(10);
-
-        builder.setUserModifiedFields(0);
-        assertThat(builder.build().getUserModifiedFields()).isEqualTo(0);
-    }
-
-    @Test
     public void testPolicyBuilder_constructFromPolicy() {
         ZenPolicy.Builder builder = new ZenPolicy.Builder();
         ZenPolicy policy = builder.allowRepeatCallers(true).allowAlarms(false)
                 .showLights(true).showBadges(false)
                 .allowPriorityChannels(true)
-                .setUserModifiedFields(20).build();
+                .build();
 
         ZenPolicy newPolicy = new ZenPolicy.Builder(policy).build();
 
@@ -689,7 +663,6 @@
         assertThat(newPolicy.getVisualEffectPeek()).isEqualTo(ZenPolicy.STATE_UNSET);
 
         assertThat(newPolicy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
-        assertThat(newPolicy.getUserModifiedFields()).isEqualTo(20);
     }
 
     @Test
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
index e3988cd..c44d943 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
@@ -68,7 +68,7 @@
                         // This triggers a recalcuation of splitatributes.
                         ActivityEmbeddingController
                                 .getInstance(ActivityEmbeddingSecondaryActivity.this)
-                                .invalidateTopVisibleActivityStacks();
+                                .invalidateVisibleActivityStacks();
                     }
         });
         findViewById(R.id.secondary_enter_pip_button).setOnClickListener(