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

Commit d8234e17 authored by Yuri Lin's avatar Yuri Lin Committed by Automerger Merge Worker
Browse files

Merge "Separate diffing functionality into a separate class." into udc-dev am:...

Merge "Separate diffing functionality into a separate class." into udc-dev am: 68650e77 am: a1f9fd19

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/22350228



Change-Id: If2965fbfb2e22fdd1f938d8936db463d05547edd
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents 472e98de a1f9fd19
Loading
Loading
Loading
Loading
+0 −176
Original line number Original line Diff line number Diff line
@@ -45,7 +45,6 @@ import android.provider.Settings.Global;
import android.text.TextUtils;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.text.format.DateFormat;
import android.util.ArrayMap;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.PluralsMessageFormatter;
import android.util.PluralsMessageFormatter;
import android.util.Slog;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.util.proto.ProtoOutputStream;
@@ -59,7 +58,6 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserException;


import java.io.IOException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Calendar;
import java.util.Date;
import java.util.Date;
@@ -310,86 +308,6 @@ public class ZenModeConfig implements Parcelable {
        return buffer.toString();
        return buffer.toString();
    }
    }


    public Diff diff(ZenModeConfig to) {
        final Diff d = new Diff();
        if (to == null) {
            return d.addLine("config", "delete");
        }
        if (user != to.user) {
            d.addLine("user", user, to.user);
        }
        if (allowAlarms != to.allowAlarms) {
            d.addLine("allowAlarms", allowAlarms, to.allowAlarms);
        }
        if (allowMedia != to.allowMedia) {
            d.addLine("allowMedia", allowMedia, to.allowMedia);
        }
        if (allowSystem != to.allowSystem) {
            d.addLine("allowSystem", allowSystem, to.allowSystem);
        }
        if (allowCalls != to.allowCalls) {
            d.addLine("allowCalls", allowCalls, to.allowCalls);
        }
        if (allowReminders != to.allowReminders) {
            d.addLine("allowReminders", allowReminders, to.allowReminders);
        }
        if (allowEvents != to.allowEvents) {
            d.addLine("allowEvents", allowEvents, to.allowEvents);
        }
        if (allowRepeatCallers != to.allowRepeatCallers) {
            d.addLine("allowRepeatCallers", allowRepeatCallers, to.allowRepeatCallers);
        }
        if (allowMessages != to.allowMessages) {
            d.addLine("allowMessages", allowMessages, to.allowMessages);
        }
        if (allowCallsFrom != to.allowCallsFrom) {
            d.addLine("allowCallsFrom", allowCallsFrom, to.allowCallsFrom);
        }
        if (allowMessagesFrom != to.allowMessagesFrom) {
            d.addLine("allowMessagesFrom", allowMessagesFrom, to.allowMessagesFrom);
        }
        if (suppressedVisualEffects != to.suppressedVisualEffects) {
            d.addLine("suppressedVisualEffects", suppressedVisualEffects,
                    to.suppressedVisualEffects);
        }
        final ArraySet<String> allRules = new ArraySet<>();
        addKeys(allRules, automaticRules);
        addKeys(allRules, to.automaticRules);
        final int N = allRules.size();
        for (int i = 0; i < N; i++) {
            final String rule = allRules.valueAt(i);
            final ZenRule fromRule = automaticRules != null ? automaticRules.get(rule) : null;
            final ZenRule toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null;
            ZenRule.appendDiff(d, "automaticRule[" + rule + "]", fromRule, toRule);
        }
        ZenRule.appendDiff(d, "manualRule", manualRule, to.manualRule);

        if (areChannelsBypassingDnd != to.areChannelsBypassingDnd) {
            d.addLine("areChannelsBypassingDnd", areChannelsBypassingDnd,
                    to.areChannelsBypassingDnd);
        }
        return d;
    }

    public static Diff diff(ZenModeConfig from, ZenModeConfig to) {
        if (from == null) {
            final Diff d = new Diff();
            if (to != null) {
                d.addLine("config", "insert");
            }
            return d;
        }
        return from.diff(to);
    }

    private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) {
        if (map != null) {
            for (int i = 0; i < map.size(); i++) {
                set.add(map.keyAt(i));
            }
        }
    }

    public boolean isValid() {
    public boolean isValid() {
        if (!isValidManualRule(manualRule)) return false;
        if (!isValidManualRule(manualRule)) return false;
        final int N = automaticRules.size();
        final int N = automaticRules.size();
@@ -1922,66 +1840,6 @@ public class ZenModeConfig implements Parcelable {
            proto.end(token);
            proto.end(token);
        }
        }


        private static void appendDiff(Diff d, String item, ZenRule from, ZenRule to) {
            if (d == null) return;
            if (from == null) {
                if (to != null) {
                    d.addLine(item, "insert");
                }
                return;
            }
            from.appendDiff(d, item, to);
        }

        private void appendDiff(Diff d, String item, ZenRule to) {
            if (to == null) {
                d.addLine(item, "delete");
                return;
            }
            if (enabled != to.enabled) {
                d.addLine(item, "enabled", enabled, to.enabled);
            }
            if (snoozing != to.snoozing) {
                d.addLine(item, "snoozing", snoozing, to.snoozing);
            }
            if (!Objects.equals(name, to.name)) {
                d.addLine(item, "name", name, to.name);
            }
            if (zenMode != to.zenMode) {
                d.addLine(item, "zenMode", zenMode, to.zenMode);
            }
            if (!Objects.equals(conditionId, to.conditionId)) {
                d.addLine(item, "conditionId", conditionId, to.conditionId);
            }
            if (!Objects.equals(condition, to.condition)) {
                d.addLine(item, "condition", condition, to.condition);
            }
            if (!Objects.equals(component, to.component)) {
                d.addLine(item, "component", component, to.component);
            }
            if (!Objects.equals(configurationActivity, to.configurationActivity)) {
                d.addLine(item, "configActivity", configurationActivity, to.configurationActivity);
            }
            if (!Objects.equals(id, to.id)) {
                d.addLine(item, "id", id, to.id);
            }
            if (creationTime != to.creationTime) {
                d.addLine(item, "creationTime", creationTime, to.creationTime);
            }
            if (!Objects.equals(enabler, to.enabler)) {
                d.addLine(item, "enabler", enabler, to.enabler);
            }
            if (!Objects.equals(zenPolicy, to.zenPolicy)) {
                d.addLine(item, "zenPolicy", zenPolicy, to.zenPolicy);
            }
            if (modified != to.modified) {
                d.addLine(item, "modified", modified, to.modified);
            }
            if (!Objects.equals(pkg, to.pkg)) {
                d.addLine(item, "pkg", pkg, to.pkg);
            }
        }

        @Override
        @Override
        public boolean equals(@Nullable Object o) {
        public boolean equals(@Nullable Object o) {
            if (!(o instanceof ZenRule)) return false;
            if (!(o instanceof ZenRule)) return false;
@@ -2040,40 +1898,6 @@ public class ZenModeConfig implements Parcelable {
        };
        };
    }
    }


    public static class Diff {
        private final ArrayList<String> lines = new ArrayList<>();

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder("Diff[");
            final int N = lines.size();
            for (int i = 0; i < N; i++) {
                if (i > 0) {
                    sb.append(",\n");
                }
                sb.append(lines.get(i));
            }
            return sb.append(']').toString();
        }

        private Diff addLine(String item, String action) {
            lines.add(item + ":" + action);
            return this;
        }

        public Diff addLine(String item, String subitem, Object from, Object to) {
            return addLine(item + "." + subitem, from, to);
        }

        public Diff addLine(String item, Object from, Object to) {
            return addLine(item, from + "->" + to);
        }

        public boolean isEmpty() {
            return lines.isEmpty();
        }
    }

    /**
    /**
     * Determines whether dnd behavior should mute all ringer-controlled sounds
     * Determines whether dnd behavior should mute all ringer-controlled sounds
     * This includes notification, ringer and system sounds
     * This includes notification, ringer and system sounds
+542 −0

File added.

Preview size limit exceeded, changes collapsed.

+4 −3
Original line number Original line Diff line number Diff line
@@ -27,6 +27,7 @@ import android.service.notification.Condition;
import android.service.notification.IConditionProvider;
import android.service.notification.IConditionProvider;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationListenerService;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeDiff;
import android.util.Log;
import android.util.Log;
import android.util.Slog;
import android.util.Slog;


@@ -146,13 +147,13 @@ public class ZenLog {


    public static void traceConfig(String reason, ZenModeConfig oldConfig,
    public static void traceConfig(String reason, ZenModeConfig oldConfig,
            ZenModeConfig newConfig) {
            ZenModeConfig newConfig) {
        ZenModeConfig.Diff diff = ZenModeConfig.diff(oldConfig, newConfig);
        ZenModeDiff.ConfigDiff diff = new ZenModeDiff.ConfigDiff(oldConfig, newConfig);
        if (diff.isEmpty()) {
        if (diff == null || !diff.hasDiff()) {
            append(TYPE_CONFIG, reason + " no changes");
            append(TYPE_CONFIG, reason + " no changes");
        } else {
        } else {
            append(TYPE_CONFIG, reason
            append(TYPE_CONFIG, reason
                    + ",\n" + (newConfig != null ? newConfig.toString() : null)
                    + ",\n" + (newConfig != null ? newConfig.toString() : null)
                    + ",\n" + ZenModeConfig.diff(oldConfig, newConfig));
                    + ",\n" + diff);
        }
        }
    }
    }


+319 −0
Original line number Original line Diff line number Diff line
/*
 * 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 junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;

import android.content.ComponentName;
import android.net.Uri;
import android.provider.Settings;
import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeDiff;
import android.service.notification.ZenPolicy;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArrayMap;

import androidx.test.filters.SmallTest;

import com.android.server.UiServiceTestCase;

import org.junit.Test;
import org.junit.runner.RunWith;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class ZenModeDiffTest extends UiServiceTestCase {
    // version is not included in the diff; manual & automatic rules have special handling
    public static final Set<String> ZEN_MODE_CONFIG_EXEMPT_FIELDS =
            Set.of("version", "manualRule", "automaticRules");

    @Test
    public void testRuleDiff_addRemoveSame() {
        // Test add, remove, and both sides same
        ZenModeConfig.ZenRule r = makeRule();

        // Both sides same rule
        ZenModeDiff.RuleDiff dSame = new ZenModeDiff.RuleDiff(r, r);
        assertFalse(dSame.hasDiff());

        // from existent rule to null: expect deleted
        ZenModeDiff.RuleDiff deleted = new ZenModeDiff.RuleDiff(r, null);
        assertTrue(deleted.hasDiff());
        assertTrue(deleted.wasRemoved());

        // from null to new rule: expect added
        ZenModeDiff.RuleDiff added = new ZenModeDiff.RuleDiff(null, r);
        assertTrue(added.hasDiff());
        assertTrue(added.wasAdded());
    }

    @Test
    public void testRuleDiff_fieldDiffs() throws Exception {
        // Start these the same
        ZenModeConfig.ZenRule r1 = makeRule();
        ZenModeConfig.ZenRule r2 = makeRule();

        // maps mapping field name -> expected output value as we set diffs
        ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
        ArrayMap<String, Object> expectedTo = new ArrayMap<>();
        List<Field> fieldsForDiff = getFieldsForDiffCheck(
                ZenModeConfig.ZenRule.class, Set.of()); // actually no exempt fields for ZenRule
        generateFieldDiffs(r1, r2, fieldsForDiff, expectedFrom, expectedTo);

        ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2);
        assertTrue(d.hasDiff());

        // Now diff them and check that each of the fields has a diff
        for (Field f : fieldsForDiff) {
            String name = f.getName();
            assertNotNull("diff not found for field: " + name, d.getDiffForField(name));
            assertTrue(d.getDiffForField(name).hasDiff());
            assertTrue("unexpected field: " + name, expectedFrom.containsKey(name));
            assertTrue("unexpected field: " + name, expectedTo.containsKey(name));
            assertEquals(expectedFrom.get(name), d.getDiffForField(name).from());
            assertEquals(expectedTo.get(name), d.getDiffForField(name).to());
        }
    }

    @Test
    public void testConfigDiff_addRemoveSame() {
        // Default config, will test add, remove, and no change
        ZenModeConfig c = new ZenModeConfig();

        ZenModeDiff.ConfigDiff dSame = new ZenModeDiff.ConfigDiff(c, c);
        assertFalse(dSame.hasDiff());

        ZenModeDiff.ConfigDiff added = new ZenModeDiff.ConfigDiff(null, c);
        assertTrue(added.hasDiff());
        assertTrue(added.wasAdded());

        ZenModeDiff.ConfigDiff removed = new ZenModeDiff.ConfigDiff(c, null);
        assertTrue(removed.hasDiff());
        assertTrue(removed.wasRemoved());
    }

    @Test
    public void testConfigDiff_fieldDiffs() throws Exception {
        // these two start the same
        ZenModeConfig c1 = new ZenModeConfig();
        ZenModeConfig c2 = new ZenModeConfig();

        // maps mapping field name -> expected output value as we set diffs
        ArrayMap<String, Object> expectedFrom = new ArrayMap<>();
        ArrayMap<String, Object> expectedTo = new ArrayMap<>();
        List<Field> fieldsForDiff = getFieldsForDiffCheck(
                ZenModeConfig.class, ZEN_MODE_CONFIG_EXEMPT_FIELDS);
        generateFieldDiffs(c1, c2, fieldsForDiff, expectedFrom, expectedTo);

        ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
        assertTrue(d.hasDiff());

        // Now diff them and check that each of the fields has a diff
        for (Field f : fieldsForDiff) {
            String name = f.getName();
            assertNotNull("diff not found for field: " + name, d.getDiffForField(name));
            assertTrue(d.getDiffForField(name).hasDiff());
            assertTrue("unexpected field: " + name, expectedFrom.containsKey(name));
            assertTrue("unexpected field: " + name, expectedTo.containsKey(name));
            assertEquals(expectedFrom.get(name), d.getDiffForField(name).from());
            assertEquals(expectedTo.get(name), d.getDiffForField(name).to());
        }
    }

    @Test
    public void testConfigDiff_specialSenders() {
        // these two start the same
        ZenModeConfig c1 = new ZenModeConfig();
        ZenModeConfig c2 = new ZenModeConfig();

        // set c1 and c2 to have some different senders
        c1.allowMessagesFrom = ZenModeConfig.SOURCE_STAR;
        c2.allowMessagesFrom = ZenModeConfig.SOURCE_CONTACT;
        c1.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
        c2.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_NONE;

        ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2);
        assertTrue(d.hasDiff());

        // Diff in top-level fields
        assertTrue(d.getDiffForField("allowMessagesFrom").hasDiff());
        assertTrue(d.getDiffForField("allowConversationsFrom").hasDiff());

        // Bonus testing of stringification of people senders and conversation senders
        assertTrue(d.toString().contains("allowMessagesFrom:stars->contacts"));
        assertTrue(d.toString().contains("allowConversationsFrom:important->none"));
    }

    @Test
    public void testConfigDiff_hasRuleDiffs() {
        // two default configs
        ZenModeConfig c1 = new ZenModeConfig();
        ZenModeConfig c2 = new ZenModeConfig();

        // two initially-identical rules
        ZenModeConfig.ZenRule r1 = makeRule();
        ZenModeConfig.ZenRule r2 = makeRule();

        // one that will become a manual rule
        ZenModeConfig.ZenRule m = makeRule();

        // Add r1 to c1, but not r2 to c2 yet -- expect a rule to be deleted
        c1.automaticRules.put(r1.id, r1);
        ZenModeDiff.ConfigDiff deleteRule = new ZenModeDiff.ConfigDiff(c1, c2);
        assertTrue(deleteRule.hasDiff());
        assertNotNull(deleteRule.getAllAutomaticRuleDiffs());
        assertTrue(deleteRule.getAllAutomaticRuleDiffs().containsKey("ruleId"));
        assertTrue(deleteRule.getAllAutomaticRuleDiffs().get("ruleId").wasRemoved());

        // Change r2 a little, add r2 to c2 as an automatic rule and m as a manual rule
        r2.component = null;
        r2.pkg = "different";
        c2.manualRule = m;
        c2.automaticRules.put(r2.id, r2);

        // Expect diffs in both manual rule (added) and automatic rule (changed)
        ZenModeDiff.ConfigDiff changed = new ZenModeDiff.ConfigDiff(c1, c2);
        assertTrue(changed.hasDiff());
        assertTrue(changed.getManualRuleDiff().hasDiff());

        ArrayMap<String, ZenModeDiff.RuleDiff> automaticDiffs = changed.getAllAutomaticRuleDiffs();
        assertNotNull(automaticDiffs);
        assertTrue(automaticDiffs.containsKey("ruleId"));
        assertNotNull(automaticDiffs.get("ruleId").getDiffForField("component"));
        assertNull(automaticDiffs.get("ruleId").getDiffForField("component").to());
        assertNotNull(automaticDiffs.get("ruleId").getDiffForField("pkg"));
        assertEquals("different", automaticDiffs.get("ruleId").getDiffForField("pkg").to());
    }

    // Helper methods for working with configs, policies, rules
    // Just makes a zen rule with fields filled in
    private ZenModeConfig.ZenRule makeRule() {
        ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
        rule.configurationActivity = new ComponentName("a", "a");
        rule.component = new ComponentName("b", "b");
        rule.conditionId = new Uri.Builder().scheme("hello").build();
        rule.condition = new Condition(rule.conditionId, "", Condition.STATE_TRUE);
        rule.enabled = true;
        rule.creationTime = 123;
        rule.id = "ruleId";
        rule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
        rule.modified = false;
        rule.name = "name";
        rule.snoozing = true;
        rule.pkg = "a";
        return rule;
    }

    // Get the fields on which we would want to check a diff. The requirements are: not final or/
    // static (as these should/can never change), and not in a specific list that's exempted.
    private List<Field> getFieldsForDiffCheck(Class c, Set<String> exemptNames)
            throws SecurityException {
        Field[] fields = c.getDeclaredFields();
        ArrayList<Field> out = new ArrayList<>();

        for (Field field : fields) {
            // Check for exempt reasons
            int m = field.getModifiers();
            if (Modifier.isFinal(m)
                    || Modifier.isStatic(m)
                    || exemptNames.contains(field.getName())) {
                continue;
            }
            out.add(field);
        }
        return out;
    }

    // Generate a set of generic diffs for the specified two objects and the fields to generate
    // diffs for, and store the results in the provided expectation maps to be able to check the
    // output later.
    private void generateFieldDiffs(Object a, Object b, List<Field> fields,
            ArrayMap<String, Object> expectedA, ArrayMap<String, Object> expectedB)
            throws Exception {
        // different classes passed in means bad input
        assertEquals(a.getClass(), b.getClass());

        // Loop through fields for which we want to check diffs, set a diff and keep track of
        // what we set.
        for (Field f : fields) {
            f.setAccessible(true);
            // Just double-check also that the fields actually are for the class declared
            assertEquals(f.getDeclaringClass(), a.getClass());
            Class t = f.getType();
            // handle the full set of primitive types first
            if (boolean.class.equals(t)) {
                f.setBoolean(a, true);
                expectedA.put(f.getName(), true);
                f.setBoolean(b, false);
                expectedB.put(f.getName(), false);
            } else if (int.class.equals(t)) {
                // these are not actually valid going to be valid for arbitrary int enum fields, but
                // we just put something in there regardless.
                f.setInt(a, 2);
                expectedA.put(f.getName(), 2);
                f.setInt(b, 1);
                expectedB.put(f.getName(), 1);
            } else if (long.class.equals(t)) {
                f.setLong(a, 200L);
                expectedA.put(f.getName(), 200L);
                f.setLong(b, 100L);
                expectedB.put(f.getName(), 100L);
            } else if (t.isPrimitive()) {
                // This method doesn't yet handle other primitive types. If the relevant diff
                // classes gain new fields of these types, please add another clause here.
                fail("primitive type not handled by generateFieldDiffs: " + t.getName());
            } else if (String.class.equals(t)) {
                f.set(a, "string1");
                expectedA.put(f.getName(), "string1");
                f.set(b, "string2");
                expectedB.put(f.getName(), "string2");
            } else {
                // catch-all for other types: have the field be "added"
                f.set(a, null);
                expectedA.put(f.getName(), null);
                try {
                    f.set(b, t.getDeclaredConstructor().newInstance());
                    expectedB.put(f.getName(), t.getDeclaredConstructor().newInstance());
                } catch (Exception e) {
                    // No default constructor, or blithely attempting to construct something doesn't
                    // work for some reason. If the default value isn't null, then keep it.
                    if (f.get(b) != null) {
                        expectedB.put(f.getName(), f.get(b));
                    } else {
                        // If we can't even rely on that, fail. Have the test-writer special case
                        // something, as this is not able to be genericized.
                        fail("could not generically construct value for field: " + f.getName());
                    }
                }
            }
        }
    }
}
+10 −9

File changed.

Preview size limit exceeded, changes collapsed.