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

Commit a3d51823 authored by Matías Hernández's avatar Matías Hernández Committed by Android (Google) Code Review
Browse files

Merge "Distinguish implicit modes in ZenMode" into main

parents 3ffa9b59 909be72f
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
@@ -187,6 +187,13 @@ public class ZenModeConfig implements Parcelable {
    @Retention(RetentionPolicy.SOURCE)
    public @interface ConfigOrigin {}

    /**
     * Prefix for the ids of implicit Zen rules. Implicit rules are those created automatically
     * on behalf of apps that call {@link NotificationManager#setNotificationPolicy} or
     * {@link NotificationManager#setInterruptionFilter}.
     */
    private static final String IMPLICIT_RULE_ID_PREFIX = "implicit_"; // + pkg_name

    public static final int SOURCE_ANYONE = Policy.PRIORITY_SENDERS_ANY;
    public static final int SOURCE_CONTACT = Policy.PRIORITY_SENDERS_CONTACTS;
    public static final int SOURCE_STAR = Policy.PRIORITY_SENDERS_STARRED;
@@ -2492,6 +2499,16 @@ public class ZenModeConfig implements Parcelable {

    // ==== End built-in system conditions ====

    /** Generate the rule id for the implicit rule for the specified package. */
    public static String implicitRuleId(String forPackage) {
        return IMPLICIT_RULE_ID_PREFIX + forPackage;
    }

    /** Returns whether the rule id corresponds to an implicit rule. */
    public static boolean isImplicitRuleId(@NonNull String ruleId) {
        return ruleId.startsWith(IMPLICIT_RULE_ID_PREFIX);
    }

    private static int[] tryParseHourAndMinute(String value) {
        if (TextUtils.isEmpty(value)) return null;
        final int i = value.indexOf('.');
+61 −19
Original line number Diff line number Diff line
@@ -117,6 +117,21 @@ public class ZenMode implements Parcelable {
            .thenComparing(ZenMode::getType, PRIORITIZED_TYPE_COMPARATOR)
            .thenComparing(ZenMode::getName);

    public enum Kind {
        /** A "normal" mode, created by apps or users via {@code addAutomaticZenRule()}. */
        NORMAL,

        /** The special, built-in "Do Not Disturb" mode. */
        MANUAL_DND,

        /**
         * An implicit mode, automatically created and managed by the system on behalf of apps that
         * call {@code setInterruptionFilter()} or {@code setNotificationPolicy()} (with some
         * exceptions).
         */
        IMPLICIT,
    }

    public enum Status {
        ENABLED,
        ENABLED_AND_ACTIVE,
@@ -126,8 +141,8 @@ public class ZenMode implements Parcelable {

    private final String mId;
    private final AutomaticZenRule mRule;
    private final Kind mKind;
    private final Status mStatus;
    private final boolean mIsManualDnd;

    /**
     * Initializes a {@link ZenMode}, mainly based on the information from the
@@ -137,9 +152,11 @@ public class ZenMode implements Parcelable {
     * active, or the reason it was disabled) are read from the {@link ZenModeConfig.ZenRule} --
     * see {@link #computeStatus}.
     */
    public ZenMode(String id, @NonNull AutomaticZenRule rule,
    ZenMode(String id, @NonNull AutomaticZenRule rule,
            @NonNull ZenModeConfig.ZenRule zenRuleExtraData) {
        this(id, rule, computeStatus(zenRuleExtraData), false);
        this(id, rule,
                ZenModeConfig.isImplicitRuleId(id) ? Kind.IMPLICIT : Kind.NORMAL,
                computeStatus(zenRuleExtraData));
    }

    private static Status computeStatus(@NonNull ZenModeConfig.ZenRule zenRuleExtraData) {
@@ -158,13 +175,16 @@ public class ZenMode implements Parcelable {
        }
    }

    public static ZenMode manualDndMode(AutomaticZenRule manualRule, boolean isActive) {
    static ZenMode manualDndMode(AutomaticZenRule manualRule, boolean isActive) {
        // Manual rule is owned by the system, so we set it here
        AutomaticZenRule manualRuleWithPkg = new AutomaticZenRule.Builder(manualRule)
                .setPackage(PACKAGE_ANDROID)
                .build();
        return new ZenMode(MANUAL_DND_MODE_ID, manualRuleWithPkg,
                isActive ? Status.ENABLED_AND_ACTIVE : Status.ENABLED, true);
        return new ZenMode(
                MANUAL_DND_MODE_ID,
                manualRuleWithPkg,
                Kind.MANUAL_DND,
                isActive ? Status.ENABLED_AND_ACTIVE : Status.ENABLED);
    }

    /**
@@ -183,19 +203,19 @@ public class ZenMode implements Parcelable {
                .setIconResId(iconResId)
                .setManualInvocationAllowed(true)
                .build();
        return new ZenMode(TEMP_NEW_MODE_ID, rule, Status.ENABLED, false);
        return new ZenMode(TEMP_NEW_MODE_ID, rule, Kind.NORMAL, Status.ENABLED);
    }

    private ZenMode(String id, @NonNull AutomaticZenRule rule, Status status, boolean isManualDnd) {
    private ZenMode(String id, @NonNull AutomaticZenRule rule, Kind kind, Status status) {
        mId = id;
        mRule = rule;
        mKind = kind;
        mStatus = status;
        mIsManualDnd = isManualDnd;
    }

    /** Creates a deep copy of this object. */
    public ZenMode copy() {
        return new ZenMode(mId, new AutomaticZenRule.Builder(mRule).build(), mStatus, mIsManualDnd);
        return new ZenMode(mId, new AutomaticZenRule.Builder(mRule).build(), mKind, mStatus);
    }

    @NonNull
@@ -264,10 +284,32 @@ public class ZenMode implements Parcelable {
        return mRule.getType() + ":" + mRule.getPackageName() + ":" + mRule.getIconResId();
    }

    /**
     * Returns the mode icon -- which can be either app-provided (via {@code addAutomaticZenRule}),
     * user-chosen (via the icon picker in Settings), the app's launcher icon for implicit rules
     * (in its monochrome variant, if available), or a default icon based on the mode type.
     */
    @NonNull
    public ListenableFuture<Drawable> getIcon(@NonNull Context context,
            @NonNull ZenIconLoader iconLoader) {
        if (mIsManualDnd) {
        if (mKind == Kind.MANUAL_DND) {
            return Futures.immediateFuture(requireNonNull(
                    context.getDrawable(R.drawable.ic_do_not_disturb_on_24dp)));
        }

        return iconLoader.getIcon(context, mRule);
    }

    /**
     * Returns an alternative mode icon. The difference with {@link #getIcon} is that it's the
     * basic DND icon not only for Manual DND, but also for <em>implicit rules</em>. As such, it's
     * suitable for places where showing the launcher icon of an app could be confusing, such as
     * the status bar or lockscreen.
     */
    @NonNull
    public ListenableFuture<Drawable> getLockscreenIcon(@NonNull Context context,
            @NonNull ZenIconLoader iconLoader) {
        if (mKind == Kind.MANUAL_DND || mKind == Kind.IMPLICIT) {
            return Futures.immediateFuture(requireNonNull(
                    context.getDrawable(R.drawable.ic_do_not_disturb_on_24dp)));
        }
@@ -373,7 +415,7 @@ public class ZenMode implements Parcelable {
    }

    public boolean isManualDnd() {
        return mIsManualDnd;
        return mKind == Kind.MANUAL_DND;
    }

    /**
@@ -404,18 +446,18 @@ public class ZenMode implements Parcelable {
        return obj instanceof ZenMode other
                && mId.equals(other.mId)
                && mRule.equals(other.mRule)
                && mStatus.equals(other.mStatus)
                && mIsManualDnd == other.mIsManualDnd;
                && mKind.equals(other.mKind)
                && mStatus.equals(other.mStatus);
    }

    @Override
    public int hashCode() {
        return Objects.hash(mId, mRule, mStatus, mIsManualDnd);
        return Objects.hash(mId, mRule, mKind, mStatus);
    }

    @Override
    public String toString() {
        return mId + " (" + mStatus + ") -> " + mRule;
        return mId + " (" + mKind + ", " + mStatus + ") -> " + mRule;
    }

    @Override
@@ -427,8 +469,8 @@ public class ZenMode implements Parcelable {
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeString(mId);
        dest.writeParcelable(mRule, 0);
        dest.writeString(mKind.name());
        dest.writeString(mStatus.name());
        dest.writeBoolean(mIsManualDnd);
    }

    public static final Creator<ZenMode> CREATOR = new Creator<ZenMode>() {
@@ -438,8 +480,8 @@ public class ZenMode implements Parcelable {
                    in.readString(),
                    checkNotNull(in.readParcelable(AutomaticZenRule.class.getClassLoader(),
                            AutomaticZenRule.class)),
                    Status.valueOf(in.readString()),
                    in.readBoolean());
                    Kind.valueOf(in.readString()),
                    Status.valueOf(in.readString()));
        }

        @Override
+90 −0
Original line number Diff line number Diff line
@@ -30,7 +30,14 @@ import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import android.app.AutomaticZenRule;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Parcel;
import android.service.notification.Condition;
@@ -40,9 +47,12 @@ import android.service.notification.ZenPolicy;

import com.android.internal.R;

import com.google.common.util.concurrent.ListenableFuture;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;

import java.util.ArrayList;
import java.util.List;
@@ -60,6 +70,13 @@ public class ZenModeTest {
                    .setZenPolicy(ZEN_POLICY)
                    .build();

    private static final String IMPLICIT_RULE_ID = ZenModeConfig.implicitRuleId("some.package");
    private static final AutomaticZenRule IMPLICIT_ZEN_RULE =
            new AutomaticZenRule.Builder("Implicit", Uri.parse("implicit/some.package"))
                    .setPackage("some.package")
                    .setType(TYPE_OTHER)
                    .build();

    @Test
    public void testBasicMethods() {
        ZenMode zenMode = new ZenMode("id", ZEN_RULE, zenConfigRuleFor(ZEN_RULE, true));
@@ -265,6 +282,79 @@ public class ZenModeTest {

        assertUnparceledIsEqualToOriginal("custom_manual",
                ZenMode.newCustomManual("New mode", R.drawable.ic_zen_mode_type_immersive));

        assertUnparceledIsEqualToOriginal("implicit",
                new ZenMode(IMPLICIT_RULE_ID, IMPLICIT_ZEN_RULE,
                        zenConfigRuleFor(IMPLICIT_ZEN_RULE, false)));
    }

    @Test
    public void getIcon_normalMode_loadsIconNormally() {
        ZenIconLoader iconLoader = mock(ZenIconLoader.class);
        ZenMode mode = new ZenMode("id", ZEN_RULE, zenConfigRuleFor(ZEN_RULE, false));

        ListenableFuture<Drawable> unused = mode.getIcon(RuntimeEnvironment.getApplication(),
                iconLoader);

        verify(iconLoader).getIcon(any(), eq(ZEN_RULE));
    }

    @Test
    public void getIcon_manualDnd_returnsFixedIcon() {
        ZenIconLoader iconLoader = mock(ZenIconLoader.class);

        ListenableFuture<Drawable> future = TestModeBuilder.MANUAL_DND_INACTIVE.getIcon(
                RuntimeEnvironment.getApplication(), iconLoader);

        assertThat(future.isDone()).isTrue();
        verify(iconLoader, never()).getIcon(any(), any());
    }

    @Test
    public void getIcon_implicitMode_loadsIconNormally() {
        ZenIconLoader iconLoader = mock(ZenIconLoader.class);
        ZenMode mode = new ZenMode(IMPLICIT_RULE_ID, IMPLICIT_ZEN_RULE,
                zenConfigRuleFor(IMPLICIT_ZEN_RULE, false));

        ListenableFuture<Drawable> unused = mode.getIcon(RuntimeEnvironment.getApplication(),
                iconLoader);

        verify(iconLoader).getIcon(any(), eq(IMPLICIT_ZEN_RULE));
    }

    @Test
    public void getLockscreenIcon_normalMode_loadsIconNormally() {
        ZenIconLoader iconLoader = mock(ZenIconLoader.class);
        ZenMode mode = new ZenMode("id", ZEN_RULE, zenConfigRuleFor(ZEN_RULE, false));

        ListenableFuture<Drawable> unused = mode.getLockscreenIcon(
                RuntimeEnvironment.getApplication(), iconLoader);

        verify(iconLoader).getIcon(any(), eq(ZEN_RULE));
    }

    @Test
    public void getLockscreenIcon_manualDnd_returnsFixedIcon() {
        ZenIconLoader iconLoader = mock(ZenIconLoader.class);

        ListenableFuture<Drawable> future = TestModeBuilder.MANUAL_DND_INACTIVE.getLockscreenIcon(
                RuntimeEnvironment.getApplication(), iconLoader);

        assertThat(future.isDone()).isTrue();
        verify(iconLoader, never()).getIcon(any(), any());
    }

    @Test
    public void getLockscreenIcon_implicitMode_returnsFixedIcon() {
        ZenIconLoader iconLoader = mock(ZenIconLoader.class);
        ZenMode mode = new ZenMode(IMPLICIT_RULE_ID, IMPLICIT_ZEN_RULE,
                zenConfigRuleFor(IMPLICIT_ZEN_RULE, false));

        ListenableFuture<Drawable> future = mode.getLockscreenIcon(
                RuntimeEnvironment.getApplication(), iconLoader);

        assertThat(future.isDone()).isTrue();
        verify(iconLoader, never()).getIcon(any(), any());
    }

    private static void assertUnparceledIsEqualToOriginal(String type, ZenMode original) {
+1 −1
Original line number Diff line number Diff line
@@ -5675,7 +5675,7 @@ public class NotificationManagerService extends SystemService {
            // a "normal" rule, it must provide a CP/ConfigActivity too.
            if (android.app.Flags.modesApi()) {
                boolean isImplicitRuleUpdateFromSystem = updateId != null
                        && ZenModeHelper.isImplicitRuleId(updateId)
                        && ZenModeConfig.isImplicitRuleId(updateId)
                        && isCallerSystemOrSystemUi();
                if (!isImplicitRuleUpdateFromSystem
                        && rule.getOwner() == null
+1 −10
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_APP;
import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_ACTIVATE;
import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE;
import static android.service.notification.ZenModeConfig.implicitRuleId;

import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.Preconditions.checkArgument;
@@ -155,8 +156,6 @@ public class ZenModeHelper {
    static final int RULE_LIMIT_PER_PACKAGE = 100;
    private static final Duration DELETED_RULE_KEPT_FOR = Duration.ofDays(30);

    private static final String IMPLICIT_RULE_ID_PREFIX = "implicit_"; // + pkg_name

    private static final int MAX_ICON_RESOURCE_NAME_LENGTH = 1000;

    /**
@@ -783,14 +782,6 @@ public class ZenModeHelper {
        return rule;
    }

    private static String implicitRuleId(String forPackage) {
        return IMPLICIT_RULE_ID_PREFIX + forPackage;
    }

    static boolean isImplicitRuleId(@NonNull String ruleId) {
        return ruleId.startsWith(IMPLICIT_RULE_ID_PREFIX);
    }

    boolean removeAutomaticZenRule(String id, @ConfigOrigin int origin, String reason,
            int callingUid) {
        checkManageRuleOrigin("removeAutomaticZenRule", origin);