Merge "Add ZenDeviceEffects and its Builder" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 55933d43..4333cd4 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5316,21 +5316,23 @@
     method public android.net.Uri getConditionId();
     method @Nullable public android.content.ComponentName getConfigurationActivity();
     method public long getCreationTime();
+    method @FlaggedApi("android.app.modes_api") @Nullable public android.service.notification.ZenDeviceEffects getDeviceEffects();
     method @FlaggedApi("android.app.modes_api") @DrawableRes public int getIconResId();
     method public int getInterruptionFilter();
     method public String getName();
     method public android.content.ComponentName getOwner();
     method @FlaggedApi("android.app.modes_api") @Nullable public String getTriggerDescription();
     method @FlaggedApi("android.app.modes_api") public int getType();
-    method public android.service.notification.ZenPolicy getZenPolicy();
+    method @Nullable public android.service.notification.ZenPolicy getZenPolicy();
     method public boolean isEnabled();
     method @FlaggedApi("android.app.modes_api") public boolean isManualInvocationAllowed();
     method public void setConditionId(android.net.Uri);
     method public void setConfigurationActivity(@Nullable android.content.ComponentName);
+    method @FlaggedApi("android.app.modes_api") public void setDeviceEffects(@Nullable android.service.notification.ZenDeviceEffects);
     method public void setEnabled(boolean);
     method public void setInterruptionFilter(int);
     method public void setName(String);
-    method public void setZenPolicy(android.service.notification.ZenPolicy);
+    method public void setZenPolicy(@Nullable android.service.notification.ZenPolicy);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.AutomaticZenRule> CREATOR;
     field @FlaggedApi("android.app.modes_api") public static final int TYPE_BEDTIME = 3; // 0x3
@@ -5350,6 +5352,7 @@
     method @NonNull public android.app.AutomaticZenRule build();
     method @NonNull public android.app.AutomaticZenRule.Builder setConditionId(@NonNull android.net.Uri);
     method @NonNull public android.app.AutomaticZenRule.Builder setConfigurationActivity(@Nullable android.content.ComponentName);
+    method @NonNull public android.app.AutomaticZenRule.Builder setDeviceEffects(@Nullable android.service.notification.ZenDeviceEffects);
     method @NonNull public android.app.AutomaticZenRule.Builder setEnabled(boolean);
     method @NonNull public android.app.AutomaticZenRule.Builder setIconResId(@DrawableRes int);
     method @NonNull public android.app.AutomaticZenRule.Builder setInterruptionFilter(int);
@@ -40717,6 +40720,26 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.StatusBarNotification> CREATOR;
   }
 
+  @FlaggedApi("android.app.modes_api") public final class ZenDeviceEffects implements android.os.Parcelable {
+    method public int describeContents();
+    method public boolean shouldDimWallpaper();
+    method public boolean shouldDisplayGrayscale();
+    method public boolean shouldSuppressAmbientDisplay();
+    method public boolean shouldUseNightMode();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.ZenDeviceEffects> CREATOR;
+  }
+
+  @FlaggedApi("android.app.modes_api") public static final class ZenDeviceEffects.Builder {
+    ctor public ZenDeviceEffects.Builder();
+    ctor public ZenDeviceEffects.Builder(@NonNull android.service.notification.ZenDeviceEffects);
+    method @NonNull public android.service.notification.ZenDeviceEffects build();
+    method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldDimWallpaper(boolean);
+    method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldDisplayGrayscale(boolean);
+    method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldSuppressAmbientDisplay(boolean);
+    method @NonNull public android.service.notification.ZenDeviceEffects.Builder setShouldUseNightMode(boolean);
+  }
+
   public final class ZenPolicy implements android.os.Parcelable {
     method public int describeContents();
     method public int getPriorityCallSenders();
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index 919e084..a7b29aa 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -22,12 +22,12 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.NotificationManager.InterruptionFilter;
-import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.service.notification.Condition;
+import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenPolicy;
 import android.view.WindowInsetsController;
 
@@ -111,6 +111,7 @@
     private ComponentName configurationActivity;
     private long creationTime;
     private ZenPolicy mZenPolicy;
+    private ZenDeviceEffects mDeviceEffects;
     private boolean mModified = false;
     private String mPkg;
     private int mType = TYPE_UNKNOWN;
@@ -190,6 +191,7 @@
     /**
      * @hide
      */
+    // TODO: b/310620812 - Remove when the flag is inlined (all system callers should use Builder).
     public AutomaticZenRule(String name, ComponentName owner, ComponentName configurationActivity,
             Uri conditionId, ZenPolicy policy, int interruptionFilter, boolean enabled,
             long creationTime) {
@@ -209,10 +211,11 @@
         configurationActivity = getTrimmedComponentName(
                 source.readParcelable(null, android.content.ComponentName.class));
         creationTime = source.readLong();
-        mZenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class);
+        mZenPolicy = source.readParcelable(null, ZenPolicy.class);
         mModified = source.readInt() == ENABLED;
         mPkg = source.readString();
         if (Flags.modesApi()) {
+            mDeviceEffects = source.readParcelable(null, ZenDeviceEffects.class);
             mAllowManualInvocation = source.readBoolean();
             mIconResId = source.readInt();
             mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH);
@@ -274,10 +277,18 @@
     /**
      * Gets the zen policy.
      */
+    @Nullable
     public ZenPolicy getZenPolicy() {
         return mZenPolicy == null ? null : this.mZenPolicy.copy();
     }
 
+    /** Gets the {@link ZenDeviceEffects} of this rule. */
+    @Nullable
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public ZenDeviceEffects getDeviceEffects() {
+        return mDeviceEffects;
+    }
+
     /**
      * Returns the time this rule was created, represented as milliseconds since the epoch.
      */
@@ -325,11 +336,21 @@
     /**
      * Sets the zen policy.
      */
-    public void setZenPolicy(ZenPolicy zenPolicy) {
+    public void setZenPolicy(@Nullable ZenPolicy zenPolicy) {
         this.mZenPolicy = (zenPolicy == null ? null : zenPolicy.copy());
     }
 
     /**
+     * Sets the {@link ZenDeviceEffects} associated to this rule. Device effects specify changes to
+     * the device behavior that should apply while the rule is active, but are not directly related
+     * to suppressing notifications (for example: disabling always-on display).
+     */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public void setDeviceEffects(@Nullable ZenDeviceEffects deviceEffects) {
+        mDeviceEffects = deviceEffects;
+    }
+
+    /**
      * Sets the configuration activity - an activity that handles
      * {@link NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} that shows the user more information
      * about this rule and/or allows them to configure it. This is required to be non-null for rules
@@ -451,6 +472,7 @@
         dest.writeInt(mModified ? ENABLED : DISABLED);
         dest.writeString(mPkg);
         if (Flags.modesApi()) {
+            dest.writeParcelable(mDeviceEffects, 0);
             dest.writeBoolean(mAllowManualInvocation);
             dest.writeInt(mIconResId);
             dest.writeString(mTriggerDescription);
@@ -472,7 +494,8 @@
                 .append(",mZenPolicy=").append(mZenPolicy);
 
         if (Flags.modesApi()) {
-            sb.append(",allowManualInvocation=").append(mAllowManualInvocation)
+            sb.append(",deviceEffects=").append(mDeviceEffects)
+                    .append(",allowManualInvocation=").append(mAllowManualInvocation)
                     .append(",iconResId=").append(mIconResId)
                     .append(",triggerDescription=").append(mTriggerDescription)
                     .append(",type=").append(mType);
@@ -498,6 +521,7 @@
                 && other.creationTime == creationTime;
         if (Flags.modesApi()) {
             return finalEquals
+                    && Objects.equals(other.mDeviceEffects, mDeviceEffects)
                     && other.mAllowManualInvocation == mAllowManualInvocation
                     && other.mIconResId == mIconResId
                     && Objects.equals(other.mTriggerDescription, mTriggerDescription)
@@ -510,8 +534,8 @@
     public int hashCode() {
         if (Flags.modesApi()) {
             return Objects.hash(enabled, name, interruptionFilter, conditionId, owner,
-                    configurationActivity, mZenPolicy, mModified, creationTime, mPkg,
-                    mAllowManualInvocation, mIconResId, mTriggerDescription, mType);
+                    configurationActivity, mZenPolicy, mDeviceEffects, mModified, creationTime,
+                    mPkg, mAllowManualInvocation, mIconResId, mTriggerDescription, mType);
         }
         return Objects.hash(enabled, name, interruptionFilter, conditionId, owner,
                 configurationActivity, mZenPolicy, mModified, creationTime, mPkg);
@@ -573,6 +597,7 @@
         private boolean mEnabled;
         private ComponentName mConfigurationActivity = null;
         private ZenPolicy mPolicy = null;
+        private ZenDeviceEffects mDeviceEffects = null;
         private int mType;
         private String mDescription;
         private int mIconResId;
@@ -588,6 +613,7 @@
             mEnabled = rule.isEnabled();
             mConfigurationActivity = rule.getConfigurationActivity();
             mPolicy = rule.getZenPolicy();
+            mDeviceEffects = rule.getDeviceEffects();
             mType = rule.getType();
             mDescription = rule.getTriggerDescription();
             mIconResId = rule.getIconResId();
@@ -639,6 +665,17 @@
         }
 
         /**
+         * Sets the {@link ZenDeviceEffects} associated to this rule. Device effects specify changes
+         * to the device behavior that should apply while the rule is active, but are not directly
+         * related to suppressing notifications (for example: disabling always-on display).
+         */
+        @NonNull
+        public Builder setDeviceEffects(@Nullable ZenDeviceEffects deviceEffects) {
+            mDeviceEffects = deviceEffects;
+            return this;
+        }
+
+        /**
          * Sets the type of the rule
          */
         public @NonNull Builder setType(@Type int type) {
@@ -687,6 +724,7 @@
         public @NonNull AutomaticZenRule build() {
             AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConfigurationActivity,
                     mConditionId, mPolicy, mInterruptionFilter, mEnabled);
+            rule.mDeviceEffects = mDeviceEffects;
             rule.creationTime = mCreationTime;
             rule.mType = mType;
             rule.mTriggerDescription = mDescription;
diff --git a/core/java/android/service/notification/ZenDeviceEffects.java b/core/java/android/service/notification/ZenDeviceEffects.java
new file mode 100644
index 0000000..5b096c6
--- /dev/null
+++ b/core/java/android/service/notification/ZenDeviceEffects.java
@@ -0,0 +1,360 @@
+/*
+ * 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 android.service.notification;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.app.Flags;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+/**
+ * Represents the set of device effects (affecting display and device behavior in general) that
+ * are applied whenever an {@link android.app.AutomaticZenRule} is active.
+ */
+@FlaggedApi(Flags.FLAG_MODES_API)
+public final class ZenDeviceEffects implements Parcelable {
+
+    private final boolean mGrayscale;
+    private final boolean mSuppressAmbientDisplay;
+    private final boolean mDimWallpaper;
+    private final boolean mNightMode;
+
+    private final boolean mDisableAutoBrightness;
+    private final boolean mDisableTapToWake;
+    private final boolean mDisableTiltToWake;
+    private final boolean mDisableTouch;
+    private final boolean mMinimizeRadioUsage;
+    private final boolean mMaximizeDoze;
+
+    private ZenDeviceEffects(boolean grayscale, boolean suppressAmbientDisplay,
+            boolean dimWallpaper, boolean nightMode, boolean disableAutoBrightness,
+            boolean disableTapToWake, boolean disableTiltToWake, boolean disableTouch,
+            boolean minimizeRadioUsage, boolean maximizeDoze) {
+        mGrayscale = grayscale;
+        mSuppressAmbientDisplay = suppressAmbientDisplay;
+        mDimWallpaper = dimWallpaper;
+        mNightMode = nightMode;
+        mDisableAutoBrightness = disableAutoBrightness;
+        mDisableTapToWake = disableTapToWake;
+        mDisableTiltToWake = disableTiltToWake;
+        mDisableTouch = disableTouch;
+        mMinimizeRadioUsage = minimizeRadioUsage;
+        mMaximizeDoze = maximizeDoze;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof final ZenDeviceEffects that)) return false;
+        if (obj == this) return true;
+
+        return this.mGrayscale == that.mGrayscale
+                && this.mSuppressAmbientDisplay == that.mSuppressAmbientDisplay
+                && this.mDimWallpaper == that.mDimWallpaper
+                && this.mNightMode == that.mNightMode
+                && this.mDisableAutoBrightness == that.mDisableAutoBrightness
+                && this.mDisableTapToWake == that.mDisableTapToWake
+                && this.mDisableTiltToWake == that.mDisableTiltToWake
+                && this.mDisableTouch == that.mDisableTouch
+                && this.mMinimizeRadioUsage == that.mMinimizeRadioUsage
+                && this.mMaximizeDoze == that.mMaximizeDoze;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mGrayscale, mSuppressAmbientDisplay, mDimWallpaper, mNightMode,
+                mDisableAutoBrightness, mDisableTapToWake, mDisableTiltToWake, mDisableTouch,
+                mMinimizeRadioUsage, mMaximizeDoze);
+    }
+
+    @Override
+    public String toString() {
+        ArrayList<String> effects = new ArrayList<>(10);
+        if (mGrayscale) effects.add("grayscale");
+        if (mSuppressAmbientDisplay) effects.add("suppressAmbientDisplay");
+        if (mDimWallpaper) effects.add("dimWallpaper");
+        if (mNightMode) effects.add("nightMode");
+        if (mDisableAutoBrightness) effects.add("disableAutoBrightness");
+        if (mDisableTapToWake) effects.add("disableTapToWake");
+        if (mDisableTiltToWake) effects.add("disableTiltToWake");
+        if (mDisableTouch) effects.add("disableTouch");
+        if (mMinimizeRadioUsage) effects.add("minimizeRadioUsage");
+        if (mMaximizeDoze) effects.add("maximizeDoze");
+        return "[" + String.join(", ", effects) + "]";
+    }
+
+    /**
+     * Whether the level of color saturation of the display should be set to minimum, effectively
+     * switching it to grayscale, while the rule is active.
+     */
+    public boolean shouldDisplayGrayscale() {
+        return mGrayscale;
+    }
+
+    /**
+     * Whether the ambient (always-on) display feature should be disabled while the rule is active.
+     * This will have no effect if the device doesn't support always-on display or if it's not
+     * generally enabled.
+     */
+    public boolean shouldSuppressAmbientDisplay() {
+        return mSuppressAmbientDisplay;
+    }
+
+    /** Whether the wallpaper should be dimmed while the rule is active. */
+    public boolean shouldDimWallpaper() {
+        return mDimWallpaper;
+    }
+
+    /** Whether night mode (aka dark theme) should be applied while the rule is active. */
+    public boolean shouldUseNightMode() {
+        return mNightMode;
+    }
+
+    /**
+     * Whether the display's automatic brightness adjustment should be disabled while the rule is
+     * active.
+     * @hide
+     */
+    public boolean shouldDisableAutoBrightness() {
+        return mDisableAutoBrightness;
+    }
+
+    /**
+     * Whether "tap to wake" should be disabled while the rule is active.
+     * @hide
+     */
+    public boolean shouldDisableTapToWake() {
+        return mDisableTapToWake;
+    }
+
+    /**
+     * Whether "tilt to wake" should be disabled while the rule is active.
+     * @hide
+     */
+    public boolean shouldDisableTiltToWake() {
+        return mDisableTiltToWake;
+    }
+
+    /**
+     * Whether touch interactions should be disabled while the rule is active.
+     * @hide
+     */
+    public boolean shouldDisableTouch() {
+        return mDisableTouch;
+    }
+
+    /**
+     * Whether radio (wi-fi, LTE, etc) traffic, and its attendant battery consumption, should be
+     * minimized while the rule is active.
+     * @hide
+     */
+    public boolean shouldMinimizeRadioUsage() {
+        return mMinimizeRadioUsage;
+    }
+
+    /**
+     * Whether Doze should be enhanced (e.g. with more aggresive activation, or less frequent
+     * maintenance windows) while the rule is active.
+     * @hide
+     */
+    public boolean shouldMaximizeDoze() {
+        return mMaximizeDoze;
+    }
+
+    /** {@link Parcelable.Creator} that instantiates {@link ZenDeviceEffects} objects. */
+    @NonNull
+    public static final Creator<ZenDeviceEffects> CREATOR = new Creator<ZenDeviceEffects>() {
+        @Override
+        public ZenDeviceEffects createFromParcel(Parcel in) {
+            return new ZenDeviceEffects(in.readBoolean(), in.readBoolean(), in.readBoolean(),
+                    in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(),
+                    in.readBoolean(), in.readBoolean(), in.readBoolean());
+        }
+
+        @Override
+        public ZenDeviceEffects[] newArray(int size) {
+            return new ZenDeviceEffects[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeBoolean(mGrayscale);
+        dest.writeBoolean(mSuppressAmbientDisplay);
+        dest.writeBoolean(mDimWallpaper);
+        dest.writeBoolean(mNightMode);
+        dest.writeBoolean(mDisableAutoBrightness);
+        dest.writeBoolean(mDisableTapToWake);
+        dest.writeBoolean(mDisableTiltToWake);
+        dest.writeBoolean(mDisableTouch);
+        dest.writeBoolean(mMinimizeRadioUsage);
+        dest.writeBoolean(mMaximizeDoze);
+    }
+
+    /** Builder class for {@link ZenDeviceEffects} objects. */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public static final class Builder {
+
+        private boolean mGrayscale;
+        private boolean mSuppressAmbientDisplay;
+        private boolean mDimWallpaper;
+        private boolean mNightMode;
+        private boolean mDisableAutoBrightness;
+        private boolean mDisableTapToWake;
+        private boolean mDisableTiltToWake;
+        private boolean mDisableTouch;
+        private boolean mMinimizeRadioUsage;
+        private boolean mMaximizeDoze;
+
+        /**
+         * Instantiates a new {@link ZenPolicy.Builder} with all effects set to default (disabled).
+         */
+        public Builder() {
+        }
+
+        /**
+         * Instantiates a new {@link ZenPolicy.Builder} with all effects set to their corresponding
+         * values in the supplied {@link ZenDeviceEffects}.
+         */
+        public Builder(@NonNull ZenDeviceEffects zenDeviceEffects) {
+            mGrayscale = zenDeviceEffects.shouldDisplayGrayscale();
+            mSuppressAmbientDisplay = zenDeviceEffects.shouldSuppressAmbientDisplay();
+            mDimWallpaper = zenDeviceEffects.shouldDimWallpaper();
+            mNightMode = zenDeviceEffects.shouldUseNightMode();
+            mDisableAutoBrightness = zenDeviceEffects.shouldDisableAutoBrightness();
+            mDisableTapToWake = zenDeviceEffects.shouldDisableTapToWake();
+            mDisableTiltToWake = zenDeviceEffects.shouldDisableTiltToWake();
+            mDisableTouch = zenDeviceEffects.shouldDisableTouch();
+            mMinimizeRadioUsage = zenDeviceEffects.shouldMinimizeRadioUsage();
+            mMaximizeDoze = zenDeviceEffects.shouldMaximizeDoze();
+        }
+
+        /**
+         * Sets whether the level of color saturation of the display should be set to minimum,
+         * effectively switching it to grayscale, while the rule is active.
+         */
+        @NonNull
+        public Builder setShouldDisplayGrayscale(boolean grayscale) {
+            mGrayscale = grayscale;
+            return this;
+        }
+
+        /**
+         * Sets whether the ambient (always-on) display feature should be disabled while the rule
+         * is active. This will have no effect if the device doesn't support always-on display or if
+         * it's not generally enabled.
+         */
+        @NonNull
+        public Builder setShouldSuppressAmbientDisplay(boolean suppressAmbientDisplay) {
+            mSuppressAmbientDisplay = suppressAmbientDisplay;
+            return this;
+        }
+
+        /** Sets whether the wallpaper should be dimmed while the rule is active. */
+        @NonNull
+        public Builder setShouldDimWallpaper(boolean dimWallpaper) {
+            mDimWallpaper = dimWallpaper;
+            return this;
+        }
+
+        /** Sets whether night mode (aka dark theme) should be applied while the rule is active. */
+        @NonNull
+        public Builder setShouldUseNightMode(boolean nightMode) {
+            mNightMode = nightMode;
+            return this;
+        }
+
+        /**
+         * Sets whether the display's automatic brightness adjustment should be disabled while the
+         * rule is active.
+         * @hide
+         */
+        @NonNull
+        public Builder setShouldDisableAutoBrightness(boolean disableAutoBrightness) {
+            mDisableAutoBrightness = disableAutoBrightness;
+            return this;
+        }
+
+        /**
+         * Sets whether "tap to wake" should be disabled while the rule is active.
+         * @hide
+         */
+        @NonNull
+        public Builder setShouldDisableTapToWake(boolean disableTapToWake) {
+            mDisableTapToWake = disableTapToWake;
+            return this;
+        }
+
+        /**
+         * Sets whether "tilt to wake" should be disabled while the rule is active.
+         * @hide
+         */
+        @NonNull
+        public Builder setShouldDisableTiltToWake(boolean disableTiltToWake) {
+            mDisableTiltToWake = disableTiltToWake;
+            return this;
+        }
+
+        /**
+         * Sets whether touch interactions should be disabled while the rule is active.
+         * @hide
+         */
+        @NonNull
+        public Builder setShouldDisableTouch(boolean disableTouch) {
+            mDisableTouch = disableTouch;
+            return this;
+        }
+
+        /**
+         * Sets whether radio (wi-fi, LTE, etc) traffic, and its attendant battery consumption,
+         * should be minimized while the rule is active.
+         * @hide
+         */
+        @NonNull
+        public Builder setShouldMinimizeRadioUsage(boolean minimizeRadioUsage) {
+            mMinimizeRadioUsage = minimizeRadioUsage;
+            return this;
+        }
+
+        /**
+         * Sets whether Doze should be enhanced (e.g. with more aggresive activation, or less
+         * frequent maintenance windows) while the rule is active.
+         * @hide
+         */
+        @NonNull
+        public Builder setShouldMaximizeDoze(boolean maximizeDoze) {
+            mMaximizeDoze = maximizeDoze;
+            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);
+        }
+    }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
new file mode 100644
index 0000000..8dcf89b
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.service.notification.ZenDeviceEffects;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ZenDeviceEffectsTest extends UiServiceTestCase {
+
+    @Test
+    public void builder() {
+        ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder()
+                .setShouldDimWallpaper(true)
+                .setShouldDisableTapToWake(true).setShouldDisableTapToWake(false)
+                .setShouldDisableTiltToWake(true)
+                .setShouldMaximizeDoze(true)
+                .setShouldUseNightMode(false)
+                .setShouldSuppressAmbientDisplay(false).setShouldSuppressAmbientDisplay(true)
+                .build();
+
+        assertThat(deviceEffects.shouldDimWallpaper()).isTrue();
+        assertThat(deviceEffects.shouldDisableAutoBrightness()).isFalse();
+        assertThat(deviceEffects.shouldDisableTapToWake()).isFalse();
+        assertThat(deviceEffects.shouldDisableTiltToWake()).isTrue();
+        assertThat(deviceEffects.shouldDisableTouch()).isFalse();
+        assertThat(deviceEffects.shouldDisplayGrayscale()).isFalse();
+        assertThat(deviceEffects.shouldMaximizeDoze()).isTrue();
+        assertThat(deviceEffects.shouldMinimizeRadioUsage()).isFalse();
+        assertThat(deviceEffects.shouldUseNightMode()).isFalse();
+        assertThat(deviceEffects.shouldSuppressAmbientDisplay()).isTrue();
+    }
+
+    @Test
+    public void builder_fromInstance() {
+        ZenDeviceEffects original = new ZenDeviceEffects.Builder()
+                .setShouldDimWallpaper(true)
+                .setShouldDisableTiltToWake(true)
+                .setShouldUseNightMode(true)
+                .setShouldSuppressAmbientDisplay(true)
+                .build();
+
+        ZenDeviceEffects modified = new ZenDeviceEffects.Builder(original)
+                .setShouldDisplayGrayscale(true)
+                .setShouldUseNightMode(false)
+                .build();
+
+        assertThat(modified.shouldDimWallpaper()).isTrue(); // from original
+        assertThat(modified.shouldDisableTiltToWake()).isTrue(); // from original
+        assertThat(modified.shouldDisplayGrayscale()).isTrue(); // updated
+        assertThat(modified.shouldUseNightMode()).isFalse(); // updated
+        assertThat(modified.shouldSuppressAmbientDisplay()).isTrue(); // from original
+    }
+
+    @Test
+    public void writeToParcel_parcelsAndUnparcels() {
+        ZenDeviceEffects source = new ZenDeviceEffects.Builder()
+                .setShouldDimWallpaper(true)
+                .setShouldDisableTouch(true)
+                .setShouldMinimizeRadioUsage(true)
+                .setShouldUseNightMode(true)
+                .setShouldSuppressAmbientDisplay(true)
+                .build();
+
+        Parcel parcel = Parcel.obtain();
+        ZenDeviceEffects copy;
+        try {
+            source.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            copy = ZenDeviceEffects.CREATOR.createFromParcel(parcel);
+        } finally {
+            parcel.recycle();
+        }
+
+        assertThat(copy.shouldDimWallpaper()).isTrue();
+        assertThat(copy.shouldDisableTouch()).isTrue();
+        assertThat(copy.shouldMinimizeRadioUsage()).isTrue();
+        assertThat(copy.shouldUseNightMode()).isTrue();
+        assertThat(copy.shouldSuppressAmbientDisplay()).isTrue();
+        assertThat(copy.shouldDisplayGrayscale()).isFalse();
+    }
+}