Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f42149d2 authored by Matías Hernández's avatar Matías Hernández
Browse files

User toggling of a mode overrides automatic triggering

This reimplements "snoozing" (manually deactivating an active mode) and the opposite operation (manually activating an inactive mode) with the same semantics (the rule owner can only change a manually-applied state after cycling to and then away from that state). It also removes the attempt in ConditionProviders, which was incorrect (and didn't have enough information to make the decision there).

Also fix getAutomaticZenRuleState() which didn't consider snoozing and reported a rule as active in that case (oops).

Bug: 333527800
Test: unit test + cts + manual
Flag: android.app.modes_ui
Change-Id: Iebc9d5aad194fb0415960cccf4d3c2b8c4fc5e5a
parent 1fee29d6
Loading
Loading
Loading
Loading
+134 −11
Original line number Diff line number Diff line
@@ -25,7 +25,6 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
import static android.service.notification.Condition.STATE_TRUE;
import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
import static android.service.notification.ZenAdapters.peopleTypeToPrioritySenders;
import static android.service.notification.ZenAdapters.prioritySendersToPeopleType;
@@ -76,8 +75,9 @@ import android.util.PluralsMessageFormatter;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;

import androidx.annotation.VisibleForTesting;

import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -1074,7 +1074,7 @@ public class ZenModeConfig implements Parcelable {
                        rt.manualRule.type = AutomaticZenRule.TYPE_OTHER;
                        rt.manualRule.condition = new Condition(
                                rt.manualRule.conditionId != null ? rt.manualRule.conditionId
                                        : Uri.EMPTY, "", STATE_TRUE);
                                        : Uri.EMPTY, "", Condition.STATE_TRUE);
                    }
                }
                return rt;
@@ -2540,10 +2540,34 @@ public class ZenModeConfig implements Parcelable {
    }

    public static class ZenRule implements Parcelable {

        /** No manual override. Rule owner can decide its state. */
        public static final int OVERRIDE_NONE = 0;
        /**
         * User has manually activated a mode. This will temporarily overrule the rule owner's
         * decision to deactivate it (see {@link #reconsiderConditionOverride}).
         */
        public static final int OVERRIDE_ACTIVATE = 1;
        /**
         * User has manually deactivated an active mode, or setting ZEN_MODE_OFF (for the few apps
         * still allowed to do that) snoozed the mode. This will temporarily overrule the rule
         * owner's decision to activate it (see {@link #reconsiderConditionOverride}).
         */
        public static final int OVERRIDE_DEACTIVATE = 2;

        @IntDef(prefix = { "OVERRIDE" }, value = {
                OVERRIDE_NONE,
                OVERRIDE_ACTIVATE,
                OVERRIDE_DEACTIVATE
        })
        @Retention(RetentionPolicy.SOURCE)
        public @interface ConditionOverride {}

        @UnsupportedAppUsage
        public boolean enabled;
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
        public boolean snoozing;         // user manually disabled this instance
        @Deprecated
        public boolean snoozing; // user manually disabled this instance. Obsolete with MODES_UI
        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
        public String name;              // required for automatic
        @UnsupportedAppUsage
@@ -2579,6 +2603,15 @@ public class ZenModeConfig implements Parcelable {
        // ZenPolicy, so we store them here, only for the manual rule.
        @FlaggedApi(Flags.FLAG_MODES_UI)
        int legacySuppressedEffects;
        /**
         * Signals a user's action to (temporarily or permanently) activate or deactivate this
         * rule, overruling the condition set by the owner. This value is not stored to disk, as
         * it shouldn't survive reboots or be involved in B&R. It might be reset by certain
         * owner-provided state transitions as well.
         */
        @FlaggedApi(Flags.FLAG_MODES_UI)
        @ConditionOverride
        int conditionOverride = OVERRIDE_NONE;

        public ZenRule() { }

@@ -2620,6 +2653,7 @@ public class ZenModeConfig implements Parcelable {
                if (Flags.modesUi()) {
                    disabledOrigin = source.readInt();
                    legacySuppressedEffects = source.readInt();
                    conditionOverride = source.readInt();
                }
            }
        }
@@ -2698,6 +2732,7 @@ public class ZenModeConfig implements Parcelable {
                if (Flags.modesUi()) {
                    dest.writeInt(disabledOrigin);
                    dest.writeInt(legacySuppressedEffects);
                    dest.writeInt(conditionOverride);
                }
            }
        }
@@ -2708,9 +2743,16 @@ public class ZenModeConfig implements Parcelable {
                    .append("id=").append(id)
                    .append(",state=").append(condition == null ? "STATE_FALSE"
                            : Condition.stateToString(condition.state))
                    .append(",enabled=").append(String.valueOf(enabled).toUpperCase())
                    .append(",snoozing=").append(snoozing)
                    .append(",name=").append(name)
                    .append(",enabled=").append(String.valueOf(enabled).toUpperCase());

            if (Flags.modesUi()) {
                sb.append(",conditionOverride=")
                        .append(conditionOverrideToString(conditionOverride));
            } else {
                sb.append(",snoozing=").append(snoozing);
            }

            sb.append(",name=").append(name)
                    .append(",zenMode=").append(Global.zenModeToString(zenMode))
                    .append(",conditionId=").append(conditionId)
                    .append(",pkg=").append(pkg)
@@ -2753,6 +2795,15 @@ public class ZenModeConfig implements Parcelable {
            return sb.append(']').toString();
        }

        private static String conditionOverrideToString(@ConditionOverride int value) {
            return switch(value) {
                case OVERRIDE_ACTIVATE -> "OVERRIDE_ACTIVATE";
                case OVERRIDE_DEACTIVATE -> "OVERRIDE_DEACTIVATE";
                case OVERRIDE_NONE -> "OVERRIDE_NONE";
                default -> "UNKNOWN";
            };
        }

        /** @hide */
        // TODO: add configuration activity
        public void dumpDebug(ProtoOutputStream proto, long fieldId) {
@@ -2763,7 +2814,11 @@ public class ZenModeConfig implements Parcelable {
            proto.write(ZenRuleProto.CREATION_TIME_MS, creationTime);
            proto.write(ZenRuleProto.ENABLED, enabled);
            proto.write(ZenRuleProto.ENABLER, enabler);
            if (Flags.modesApi() && Flags.modesUi()) {
                proto.write(ZenRuleProto.IS_SNOOZING, conditionOverride == OVERRIDE_DEACTIVATE);
            } else {
                proto.write(ZenRuleProto.IS_SNOOZING, snoozing);
            }
            proto.write(ZenRuleProto.ZEN_MODE, zenMode);
            if (conditionId != null) {
                proto.write(ZenRuleProto.CONDITION_ID, conditionId.toString());
@@ -2816,7 +2871,8 @@ public class ZenModeConfig implements Parcelable {
                if (Flags.modesUi()) {
                    finalEquals = finalEquals
                            && other.disabledOrigin == disabledOrigin
                            && other.legacySuppressedEffects == legacySuppressedEffects;
                            && other.legacySuppressedEffects == legacySuppressedEffects
                            && other.conditionOverride == conditionOverride;
                }
            }

@@ -2832,7 +2888,8 @@ public class ZenModeConfig implements Parcelable {
                            zenDeviceEffects, modified, allowManualInvocation, iconResName,
                            triggerDescription, type, userModifiedFields,
                            zenPolicyUserModifiedFields, zenDeviceEffectsUserModifiedFields,
                            deletionInstant, disabledOrigin, legacySuppressedEffects);
                            deletionInstant, disabledOrigin, legacySuppressedEffects,
                            conditionOverride);
                } else {
                    return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
                            component, configurationActivity, pkg, id, enabler, zenPolicy,
@@ -2858,9 +2915,75 @@ public class ZenModeConfig implements Parcelable {
            }
        }

        // TODO: b/333527800 - Rename to isActive()
        public boolean isAutomaticActive() {
            if (Flags.modesApi() && Flags.modesUi()) {
                if (!enabled || getPkg() == null) {
                    return false;
                } else if (conditionOverride == OVERRIDE_ACTIVATE) {
                    return true;
                } else if (conditionOverride == OVERRIDE_DEACTIVATE) {
                    return false;
                } else {
                    return isTrueOrUnknown();
                }
            } else {
                return enabled && !snoozing && getPkg() != null && isTrueOrUnknown();
            }
        }

        @VisibleForTesting(otherwise = VisibleForTesting.NONE)
        @ConditionOverride
        public int getConditionOverride() {
            if (Flags.modesApi() && Flags.modesUi()) {
                return conditionOverride;
            } else {
                return snoozing ? OVERRIDE_DEACTIVATE : OVERRIDE_NONE;
            }
        }

        public void setConditionOverride(@ConditionOverride int value) {
            if (Flags.modesApi() && Flags.modesUi()) {
                conditionOverride = value;
            } else {
                if (value == OVERRIDE_ACTIVATE) {
                    Slog.wtf(TAG, "Shouldn't set OVERRIDE_ACTIVATE if MODES_UI is off");
                } else if (value == OVERRIDE_DEACTIVATE) {
                    snoozing = true;
                } else if (value == OVERRIDE_NONE) {
                    snoozing = false;
                }
            }
        }

        public void resetConditionOverride() {
            setConditionOverride(OVERRIDE_NONE);
        }

        /**
         * Possibly remove the override, depending on the rule owner's intended state.
         *
         * <p>This allows rule owners to "take over" manually-provided state with their smartness,
         * but only once both agree.
         *
         * <p>For example, a manually activated rule wins over rule owner's opinion that it should
         * be off, until the owner says it should be on, at which point it will turn off (without
         * manual intervention) when the rule owner says it should be off. And symmetrically for
         * manual deactivation (which used to be called "snoozing").
         */
        public void reconsiderConditionOverride() {
            if (Flags.modesApi() && Flags.modesUi()) {
                if (conditionOverride == OVERRIDE_ACTIVATE && isTrueOrUnknown()) {
                    resetConditionOverride();
                } else if (conditionOverride == OVERRIDE_DEACTIVATE && !isTrueOrUnknown()) {
                    resetConditionOverride();
                }
            } else {
                if (snoozing && !isTrueOrUnknown()) {
                    snoozing = false;
                }
            }
        }

        public String getPkg() {
            return !TextUtils.isEmpty(pkg)
+11 −2
Original line number Diff line number Diff line
@@ -454,6 +454,8 @@ public class ZenModeDiff {
     */
    public static class RuleDiff extends BaseDiff {
        public static final String FIELD_ENABLED = "enabled";
        public static final String FIELD_CONDITION_OVERRIDE = "conditionOverride";
        @Deprecated
        public static final String FIELD_SNOOZING = "snoozing";
        public static final String FIELD_NAME = "name";
        public static final String FIELD_ZEN_MODE = "zenMode";
@@ -507,9 +509,16 @@ public class ZenModeDiff {
            if (from.enabled != to.enabled) {
                addField(FIELD_ENABLED, new FieldDiff<>(from.enabled, to.enabled));
            }
            if (Flags.modesApi() && Flags.modesUi()) {
                if (from.conditionOverride != to.conditionOverride) {
                    addField(FIELD_CONDITION_OVERRIDE,
                            new FieldDiff<>(from.conditionOverride, to.conditionOverride));
                }
            } else {
                if (from.snoozing != to.snoozing) {
                    addField(FIELD_SNOOZING, new FieldDiff<>(from.snoozing, to.snoozing));
                }
            }
            if (!Objects.equals(from.name, to.name)) {
                addField(FIELD_NAME, new FieldDiff<>(from.name, to.name));
            }
+0 −1
Original line number Diff line number Diff line
@@ -174,7 +174,6 @@ public class ZenModesBackend {
            mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_OFF, null, TAG,
                    /* fromUser= */ true);
        } else {
            // TODO: b/333527800 - This should (potentially) snooze the rule if it was active.
            mNotificationManager.setAutomaticZenRuleState(mode.getId(),
                    new Condition(mode.getRule().getConditionId(), "", Condition.STATE_FALSE,
                            Condition.SOURCE_USER_ACTION));
+0 −1
Original line number Diff line number Diff line
@@ -123,7 +123,6 @@ public class ZenModesBackendTest {
        zenRule.id = id;
        zenRule.pkg = "package";
        zenRule.enabled = azr.isEnabled();
        zenRule.snoozing = false;
        zenRule.conditionId = azr.getConditionId();
        zenRule.condition = new Condition(azr.getConditionId(), "",
                active ? Condition.STATE_TRUE : Condition.STATE_FALSE,
+1 −17
Original line number Diff line number Diff line
@@ -16,9 +16,6 @@

package com.android.server.notification;

import static android.service.notification.Condition.STATE_TRUE;
import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;

import android.app.INotificationManager;
import android.app.NotificationManager;
import android.content.ComponentName;
@@ -322,21 +319,8 @@ public class ConditionProviders extends ManagedServices {
                final Condition c = conditions[i];
                final ConditionRecord r = getRecordLocked(c.id, info.component, true /*create*/);
                r.info = info;
                if (android.app.Flags.modesUi()) {
                    // if user turned on the mode, ignore the update unless the app also wants the
                    // mode on. this will update the origin of the mode and let the owner turn it
                    // off when the context ends
                    if (r.condition != null && r.condition.source == ORIGIN_USER_IN_SYSTEMUI) {
                        if (r.condition.state == STATE_TRUE && c.state == STATE_TRUE) {
                            r.condition = c;
                        }
                    } else {
                r.condition = c;
            }
                } else {
                    r.condition = c;
                }
            }
        }
        final int N = conditions.length;
        for (int i = 0; i < N; i++) {
Loading