diff options
10 files changed, 928 insertions, 108 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index a5c2af76479b..bf75fcc6853a 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -371,6 +371,7 @@ package android.app { public class NotificationManager { method @FlaggedApi("android.app.modes_api") @NonNull public String addAutomaticZenRule(@NonNull android.app.AutomaticZenRule, boolean); method public void cleanUpCallersAfter(long); + method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy getDefaultZenPolicy(); method public android.content.ComponentName getEffectsSuppressor(); method public boolean isNotificationPolicyAccessGrantedForPackage(@NonNull String); method @FlaggedApi("android.app.modes_api") public boolean removeAutomaticZenRule(@NonNull String, boolean); @@ -3017,6 +3018,10 @@ package android.service.notification { method @Deprecated public boolean isBound(); } + public final class ZenPolicy implements android.os.Parcelable { + method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy overwrittenWith(@Nullable android.service.notification.ZenPolicy); + } + public static final class ZenPolicy.Builder { ctor public ZenPolicy.Builder(@Nullable android.service.notification.ZenPolicy); } diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index c3adbc30641f..578105f9f99e 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -38,6 +38,7 @@ import android.service.notification.IConditionProvider; import android.service.notification.INotificationListener; import android.service.notification.NotificationListenerFilter; import android.service.notification.StatusBarNotification; +import android.service.notification.ZenPolicy; import android.app.AutomaticZenRule; import android.service.notification.ZenModeConfig; @@ -213,6 +214,7 @@ interface INotificationManager boolean isNotificationPolicyAccessGrantedForPackage(String pkg); void setNotificationPolicyAccessGranted(String pkg, boolean granted); void setNotificationPolicyAccessGrantedForUser(String pkg, int userId, boolean granted); + ZenPolicy getDefaultZenPolicy(); AutomaticZenRule getAutomaticZenRule(String id); Map<String, AutomaticZenRule> getAutomaticZenRules(); // TODO: b/310620812 - Remove getZenRules() when MODES_API is inlined. diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 0b6e24cf5545..366b45badcfd 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -1750,6 +1750,20 @@ public class NotificationManager { @NonNull ComponentName listener, boolean granted) { setNotificationListenerAccessGranted(listener, granted, true); } + /** + * Gets the device-default notification policy as a ZenPolicy. + * @hide + */ + @TestApi + @FlaggedApi(Flags.FLAG_MODES_API) + public @NonNull ZenPolicy getDefaultZenPolicy() { + INotificationManager service = getService(); + try { + return service.getDefaultZenPolicy(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } /** * For apps targeting {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} and above, the diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index c479877fe98e..9895551a8672 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -185,7 +185,13 @@ public class ZenModeConfig implements Parcelable { SUPPRESSED_EFFECT_SCREEN_OFF | SUPPRESSED_EFFECT_FULL_SCREEN_INTENT | SUPPRESSED_EFFECT_LIGHTS | SUPPRESSED_EFFECT_PEEK | SUPPRESSED_EFFECT_AMBIENT; - public static final int XML_VERSION = 8; + // ZenModeConfig XML versions distinguishing key changes. + public static final int XML_VERSION_ZEN_UPGRADE = 8; + public static final int XML_VERSION_MODES_API = 11; + + // TODO: b/310620812 - Update XML_VERSION and update default_zen_config.xml accordingly when + // modes_api is inlined. + private static final int XML_VERSION = 10; public static final String ZEN_TAG = "zen"; private static final String ZEN_ATT_VERSION = "version"; private static final String ZEN_ATT_USER = "user"; @@ -586,6 +592,10 @@ public class ZenModeConfig implements Parcelable { } } + public static int getCurrentXmlVersion() { + return Flags.modesApi() ? XML_VERSION_MODES_API : XML_VERSION; + } + public static ZenModeConfig readXml(TypedXmlPullParser parser) throws XmlPullParserException, IOException { int type = parser.getEventType(); @@ -593,7 +603,7 @@ public class ZenModeConfig implements Parcelable { String tag = parser.getName(); if (!ZEN_TAG.equals(tag)) return null; final ZenModeConfig rt = new ZenModeConfig(); - rt.version = safeInt(parser, ZEN_ATT_VERSION, XML_VERSION); + rt.version = safeInt(parser, ZEN_ATT_VERSION, getCurrentXmlVersion()); rt.user = safeInt(parser, ZEN_ATT_USER, rt.user); boolean readSuppressedEffects = false; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { @@ -707,14 +717,16 @@ public class ZenModeConfig implements Parcelable { /** * Writes XML of current ZenModeConfig * @param out serializer - * @param version uses XML_VERSION if version is null + * @param version uses the current XML version if version is null * @throws IOException */ + public void writeXml(TypedXmlSerializer out, Integer version, boolean forBackup) throws IOException { + int xmlVersion = getCurrentXmlVersion(); out.startTag(null, ZEN_TAG); out.attribute(null, ZEN_ATT_VERSION, version == null - ? Integer.toString(XML_VERSION) : Integer.toString(version)); + ? Integer.toString(xmlVersion) : Integer.toString(version)); out.attributeInt(null, ZEN_ATT_USER, user); out.startTag(null, ALLOW_TAG); out.attributeBoolean(null, ALLOW_ATT_CALLS, allowCalls); diff --git a/core/java/android/service/notification/ZenPolicy.aidl b/core/java/android/service/notification/ZenPolicy.aidl new file mode 100644 index 000000000000..b56f5c6989bf --- /dev/null +++ b/core/java/android/service/notification/ZenPolicy.aidl @@ -0,0 +1,19 @@ +/* + * 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; + +parcelable ZenPolicy;
\ No newline at end of file diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java index fb491d010f54..d8318a6bee7c 100644 --- a/core/java/android/service/notification/ZenPolicy.java +++ b/core/java/android/service/notification/ZenPolicy.java @@ -1422,6 +1422,54 @@ public final class ZenPolicy implements Parcelable { } /** + * Overwrites any policy values in this ZenPolicy with set values from newPolicy and + * returns a copy of the resulting ZenPolicy. + * Unlike apply(), values set in newPolicy will always be kept over pre-existing + * fields. Any values in newPolicy that are not set keep their currently set values. + * + * @hide + */ + @TestApi + @FlaggedApi(Flags.FLAG_MODES_API) + public @NonNull ZenPolicy overwrittenWith(@Nullable ZenPolicy newPolicy) { + ZenPolicy result = this.copy(); + + if (newPolicy == null) { + return result; + } + + // set priority categories + for (int category = 0; category < mPriorityCategories.size(); category++) { + @State int newState = newPolicy.mPriorityCategories.get(category); + if (newState != STATE_UNSET) { + result.mPriorityCategories.set(category, newState); + + if (category == PRIORITY_CATEGORY_MESSAGES) { + result.mPriorityMessages = newPolicy.mPriorityMessages; + } else if (category == PRIORITY_CATEGORY_CALLS) { + result.mPriorityCalls = newPolicy.mPriorityCalls; + } else if (category == PRIORITY_CATEGORY_CONVERSATIONS) { + result.mConversationSenders = newPolicy.mConversationSenders; + } + } + } + + // set visual effects + for (int visualEffect = 0; visualEffect < mVisualEffects.size(); visualEffect++) { + if (newPolicy.mVisualEffects.get(visualEffect) != STATE_UNSET) { + result.mVisualEffects.set(visualEffect, newPolicy.mVisualEffects.get(visualEffect)); + } + } + + // set allowed channels + if (newPolicy.mAllowChannels != CHANNEL_POLICY_UNSET) { + result.mAllowChannels = newPolicy.mAllowChannels; + } + + return result; + } + + /** * @hide */ public void dumpDebug(ProtoOutputStream proto, long fieldId) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 2ae040a69583..4ba0217da8a7 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -281,6 +281,7 @@ import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeProto; +import android.service.notification.ZenPolicy; import android.telecom.TelecomManager; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; @@ -5944,6 +5945,20 @@ public class NotificationManagerService extends SystemService { } } + /** + * Gets the device-default zen policy as a ZenPolicy. + */ + @Override + public ZenPolicy getDefaultZenPolicy() { + enforceSystemOrSystemUI("INotificationManager.getDefaultZenPolicy"); + final long identity = Binder.clearCallingIdentity(); + try { + return mZenModeHelper.getDefaultZenPolicy(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + @Override public List<String> getEnabledNotificationListenerPackages() { checkCallerIsSystem(); diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 93ffd974bb80..8d4381c35ad5 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -603,6 +603,14 @@ public class ZenModeHelper { ZenRule rule = newConfig.automaticRules.get(implicitRuleId(callingPkg)); if (rule == null) { rule = newImplicitZenRule(callingPkg); + + // For new implicit rules, create a policy matching the current global + // (manual rule) settings, for consistency with the policy that + // would apply if changing the global interruption filter. We only do this + // for newly created rules, as existing rules have a pre-existing policy + // (whether initialized here or set via app or user). + rule.zenPolicy = mConfig.toZenPolicy(); + newConfig.automaticRules.put(rule.id, rule); } // If the user has changed the rule's *zenMode*, then don't let app overwrite it. @@ -614,6 +622,7 @@ public class ZenModeHelper { rule.condition = new Condition(rule.conditionId, mContext.getString(R.string.zen_mode_implicit_activated), Condition.STATE_TRUE); + setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP, "applyGlobalZenModeAsImplicitZenRule", callingUid); } @@ -642,8 +651,10 @@ public class ZenModeHelper { return; } ZenModeConfig newConfig = mConfig.copy(); + boolean isNew = false; ZenRule rule = newConfig.automaticRules.get(implicitRuleId(callingPkg)); if (rule == null) { + isNew = true; rule = newImplicitZenRule(callingPkg); rule.zenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; newConfig.automaticRules.put(rule.id, rule); @@ -651,10 +662,20 @@ public class ZenModeHelper { // 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) { + ZenPolicy newZenPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy); + if (isNew) { + // For new rules only, fill anything underspecified in the new policy with + // values from the global configuration, for consistency with the policy that + // would take effect if changing the global policy. + // Note that NotificationManager.Policy cannot have any unset priority + // categories, but *can* have unset visual effects, which is why we do this. + newZenPolicy = mConfig.toZenPolicy().overwrittenWith(newZenPolicy); + } updatePolicy( rule, - ZenAdapters.notificationPolicyToZenPolicy(policy), - /* updateBitmask= */ false); + newZenPolicy, + /* updateBitmask= */ false, + isNew); setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP, "applyGlobalPolicyAsImplicitZenRule", callingUid); @@ -684,6 +705,11 @@ public class ZenModeHelper { } ZenRule implicitRule = mConfig.automaticRules.get(implicitRuleId(callingPkg)); if (implicitRule != null && implicitRule.zenPolicy != null) { + // toNotificationPolicy takes defaults from mConfig, and technically, those are not + // the defaults that would apply if any fields were unset. However, all rules should + // have all fields set in their ZenPolicy objects upon rule creation, so in + // practice, this is only filling in the areChannelsBypassingDnd field, which is a + // state rather than a part of the policy. return mConfig.toNotificationPolicy(implicitRule.zenPolicy); } else { return getNotificationPolicy(); @@ -1071,7 +1097,8 @@ public class ZenModeHelper { rule.zenMode = newZenMode; // Updates the bitmask and values for all policy fields, based on the origin. - updatePolicy(rule, automaticZenRule.getZenPolicy(), updateBitmask); + updatePolicy(rule, automaticZenRule.getZenPolicy(), updateBitmask, isNew); + // Updates the bitmask and values for all device effect fields, based on the origin. updateZenDeviceEffects(rule, automaticZenRule.getDeviceEffects(), origin == UPDATE_ORIGIN_APP, updateBitmask); @@ -1110,14 +1137,19 @@ public class ZenModeHelper { /** * 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). + * <p>The update takes any set fields in {@code newPolicy} as new policy settings for the + * provided {@code ZenRule}, keeping any pre-existing settings from {@code zenRule.zenPolicy} + * for any unset policy fields in {@code newPolicy}. The user-modified bitmask is updated to + * reflect the changes being applied (if applicable, i.e. if the update is from the user). */ private void updatePolicy(ZenRule zenRule, @Nullable ZenPolicy newPolicy, - boolean updateBitmask) { + boolean updateBitmask, boolean isNew) { if (newPolicy == null) { - // TODO: b/319242206 - Treat as newPolicy == default policy and continue below. - zenRule.zenPolicy = null; + if (isNew) { + // Newly created rule with no provided policy; fill in with the default. + zenRule.zenPolicy = mDefaultConfig.toZenPolicy(); + } + // Otherwise, a null policy means no policy changes, so we can stop here. return; } @@ -1126,6 +1158,16 @@ public class ZenModeHelper { ZenPolicy oldPolicy = zenRule.zenPolicy != null ? zenRule.zenPolicy : mDefaultConfig.toZenPolicy(); + // If this is updating a rule rather than creating a new one, keep any fields from the + // old policy if they are unspecified in the new policy. For newly created rules, oldPolicy + // has been set to the default settings above, so any unspecified fields in a newly created + // policy are filled with default values. Then use the fully-specified version of the new + // policy for comparison below. + // + // Although we do not expect a policy update from the user to contain any unset fields, + // filling in fields here also guards against any unset fields counting as a "diff" when + // comparing fields for bitmask editing below. + newPolicy = oldPolicy.overwrittenWith(newPolicy); zenRule.zenPolicy = newPolicy; if (updateBitmask) { @@ -1451,11 +1493,27 @@ public class ZenModeHelper { } allRulesDisabled &= !automaticRule.enabled; + + // Upon upgrading to a version with modes_api enabled, keep all behaviors of + // rules with null ZenPolicies explicitly as a copy of the global policy. + if (Flags.modesApi() && config.version < ZenModeConfig.XML_VERSION_MODES_API) { + // Keep the manual ("global") policy that from config. + ZenPolicy manualRulePolicy = config.toZenPolicy(); + if (automaticRule.zenPolicy == null) { + automaticRule.zenPolicy = manualRulePolicy; + } else { + // newPolicy is a policy with all unset fields in the rule's zenPolicy + // set to their values from the values in config. Then convert that back + // to ZenPolicy to store with the automatic zen rule. + automaticRule.zenPolicy = + manualRulePolicy.overwrittenWith(automaticRule.zenPolicy); + } + } } } if (!hasDefaultRules && allRulesDisabled - && (forRestore || config.version < ZenModeConfig.XML_VERSION)) { + && (forRestore || config.version < ZenModeConfig.XML_VERSION_ZEN_UPGRADE)) { // reset zen automatic rules to default on restore or upgrade if: // - doesn't already have default rules and // - all previous automatic rules were disabled @@ -1472,7 +1530,7 @@ public class ZenModeHelper { // Resolve user id for settings. userId = userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM : userId; - if (config.version < ZenModeConfig.XML_VERSION) { + if (config.version < ZenModeConfig.XML_VERSION_ZEN_UPGRADE) { Settings.Secure.putIntForUser(mContext.getContentResolver(), Settings.Secure.SHOW_ZEN_UPGRADE_NOTIFICATION, 1, userId); } else { @@ -1599,6 +1657,14 @@ public class ZenModeHelper { return mConsolidatedPolicy.copy(); } + /** + * Returns a copy of the device default policy as a ZenPolicy object. + */ + @VisibleForTesting + protected ZenPolicy getDefaultZenPolicy() { + return mDefaultConfig.toZenPolicy(); + } + @GuardedBy("mConfigLock") private boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent, @ConfigChangeOrigin int origin, String reason, int callingUid) { @@ -1782,7 +1848,7 @@ public class ZenModeHelper { } @GuardedBy("mConfigLock") - private void applyCustomPolicy(ZenPolicy policy, ZenRule rule) { + private void applyCustomPolicy(ZenPolicy policy, ZenRule rule, boolean useManualConfig) { if (rule.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) { policy.apply(new ZenPolicy.Builder() .disallowAllSounds() @@ -1796,8 +1862,22 @@ public class ZenModeHelper { } else if (rule.zenPolicy != null) { policy.apply(rule.zenPolicy); } else { - // active rule with no specified policy inherits the default settings - policy.apply(mConfig.toZenPolicy()); + if (Flags.modesApi()) { + if (useManualConfig) { + // manual rule is configured using the settings stored directly in mConfig + policy.apply(mConfig.toZenPolicy()); + } else { + // under modes_api flag, an active automatic rule with no specified policy + // inherits the device default settings as stored in mDefaultConfig. While the + // rule's policy fields should be set upon creation, this is a fallback to + // catch any that may have fallen through the cracks. + Log.wtf(TAG, "active automatic rule found with no specified policy: " + rule); + policy.apply(mDefaultConfig.toZenPolicy()); + } + } else { + // active rule with no specified policy inherits the global config settings + policy.apply(mConfig.toZenPolicy()); + } } } @@ -1809,7 +1889,7 @@ public class ZenModeHelper { ZenPolicy policy = new ZenPolicy(); ZenDeviceEffects.Builder deviceEffectsBuilder = new ZenDeviceEffects.Builder(); if (mConfig.manualRule != null) { - applyCustomPolicy(policy, mConfig.manualRule); + applyCustomPolicy(policy, mConfig.manualRule, true); if (Flags.modesApi()) { deviceEffectsBuilder.add(mConfig.manualRule.zenDeviceEffects); } @@ -1821,7 +1901,7 @@ public class ZenModeHelper { // policy. This is relevant in case some other active rule has a more // restrictive INTERRUPTION_FILTER but a more lenient ZenPolicy! if (!Flags.modesApi() || automaticRule.zenMode != Global.ZEN_MODE_OFF) { - applyCustomPolicy(policy, automaticRule); + applyCustomPolicy(policy, automaticRule, false); } if (Flags.modesApi()) { deviceEffectsBuilder.add(automaticRule.zenDeviceEffects); @@ -1829,6 +1909,14 @@ public class ZenModeHelper { } } + // While mConfig.toNotificationPolicy fills in any unset fields from the provided + // config (which, in this case is the manual "global" config), under modes API changes, + // we should have no remaining unset fields: the manual policy gets every field from + // the global policy, and each automatic rule has all policy fields filled in on + // creation or update. + // However, the piece of information that comes from mConfig that we must keep is the + // areChannelsBypassingDnd bit, which is a state, rather than a policy, and even when + // all policy fields are set, this state comes to the new policy from this call. Policy newPolicy = mConfig.toNotificationPolicy(policy); if (!Objects.equals(mConsolidatedPolicy, newPolicy)) { mConsolidatedPolicy = newPolicy; 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 edc876aab388..5b73b0339e34 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -59,6 +59,7 @@ import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_UNKNOWN; import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER; import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS; +import static android.service.notification.ZenPolicy.PEOPLE_TYPE_NONE; import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED; import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.LOG_DND_STATE_EVENTS; @@ -123,6 +124,7 @@ import android.os.Parcel; import android.os.Process; import android.os.SimpleClock; import android.os.UserHandle; +import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; @@ -238,15 +240,19 @@ public class ZenModeHelperTest extends UiServiceTestCase { public TestWithLooperRule mLooperRule = new TestWithLooperRule(); ConditionProviders mConditionProviders; - @Mock NotificationManager mNotificationManager; - @Mock PackageManager mPackageManager; + @Mock + NotificationManager mNotificationManager; + @Mock + PackageManager mPackageManager; private Resources mResources; private TestableLooper mTestableLooper; private final TestClock mTestClock = new TestClock(); private ZenModeHelper mZenModeHelper; private ContentResolver mContentResolver; - @Mock DeviceEffectsApplier mDeviceEffectsApplier; - @Mock AppOpsManager mAppOps; + @Mock + DeviceEffectsApplier mDeviceEffectsApplier; + @Mock + AppOpsManager mAppOps; TestableFlagResolver mTestFlagResolver = new TestableFlagResolver(); ZenModeEventLoggerFake mZenModeEventLogger; @@ -290,7 +296,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { when(mPackageManager.getPackageUidAsUser(eq(CUSTOM_PKG_NAME), anyInt())) .thenReturn(CUSTOM_PKG_UID); when(mPackageManager.getPackagesForUid(anyInt())).thenReturn( - new String[] {pkg}); + new String[]{pkg}); ApplicationInfo appInfoSpy = spy(new ApplicationInfo()); appInfoSpy.icon = ICON_RES_ID; @@ -305,24 +311,26 @@ public class ZenModeHelperTest extends UiServiceTestCase { } private XmlResourceParser getDefaultConfigParser() throws IOException, XmlPullParserException { - String xml = "<zen version=\"8\" user=\"0\">\n" - + "<allow calls=\"false\" repeatCallers=\"false\" messages=\"true\" " - + "reminders=\"false\" events=\"false\" callsFrom=\"1\" messagesFrom=\"2\" " - + "visualScreenOff=\"true\" alarms=\"true\" " - + "media=\"true\" system=\"false\" conversations=\"true\"" - + " conversationsFrom=\"2\"/>\n" - + "<automatic ruleId=\"" + EVENTS_DEFAULT_RULE_ID - + "\" enabled=\"false\" snoozing=\"false\"" - + " name=\"Event\" zen=\"1\"" - + " component=\"android/com.android.server.notification.EventConditionProvider\"" - + " conditionId=\"condition://android/event?userId=-10000&calendar=&" + String xml = "<zen version=\"10\">\n" + + "<allow alarms=\"true\" media=\"true\" system=\"false\" calls=\"true\" " + + "callsFrom=\"2\" messages=\"true\"\n" + + "messagesFrom=\"2\" reminders=\"false\" events=\"false\" " + + "repeatCallers=\"true\" convos=\"true\"\n" + + "convosFrom=\"2\"/>\n" + + "<automatic ruleId=" + EVENTS_DEFAULT_RULE_ID + + " enabled=\"false\" snoozing=\"false\"" + + " name=\"Event\" zen=\"1\"\n" + + " component=\"android/com.android.server.notification.EventConditionProvider\"\n" + + " conditionId=\"condition://android/event?userId=-10000&calendar=&" + "reply=1\"/>\n" - + "<automatic ruleId=\"" + SCHEDULE_DEFAULT_RULE_ID + "\" enabled=\"false\"" - + " snoozing=\"false\" name=\"Sleeping\" zen=\"1\"" - + " component=\"android/com.android.server.notification.ScheduleConditionProvider\"" - + " conditionId=\"condition://android/schedule?days=1.2.3.4.5.6.7 &start=22.0" - + "&end=7.0&exitAtAlarm=true\"/>" - + "<disallow visualEffects=\"511\" />" + + "<automatic ruleId=" + SCHEDULE_DEFAULT_RULE_ID + " enabled=\"false\"" + + " snoozing=\"false\" name=\"Sleeping\"\n zen=\"1\"" + + " component=\"android/com.android.server.notification" + + ".ScheduleConditionProvider\"\n" + + " conditionId=\"condition://android/schedule?days=1.2.3.4.5.6.7&start=22.0" + + "&end=7.0&exitAtAlarm=true\"/>\n" + + "<disallow visualEffects=\"157\" />\n" + + "<state areChannelsBypassingDnd=\"false\" />\n" + "</zen>"; TypedXmlPullParser parser = Xml.newFastPullParser(); parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), null); @@ -408,7 +416,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testZenOff_NoMuteApplied() { mZenModeHelper.mZenMode = ZEN_MODE_OFF; - mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O}); + mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O}); mZenModeHelper.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS | PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0, 0); mZenModeHelper.applyRestrictions(); @@ -421,7 +429,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testZenOn_NotificationApplied() { mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; - mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O}); + mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O}); // The most permissive policy mZenModeHelper.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS | PRIORITY_CATEGORY_MEDIA | PRIORITY_CATEGORY_MESSAGES @@ -442,7 +450,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testZenOn_StarredCallers_CallTypesBlocked() { mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; - mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O}); + mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O}); // The most permissive policy mZenModeHelper.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS | PRIORITY_CATEGORY_MEDIA | PRIORITY_CATEGORY_MESSAGES @@ -462,7 +470,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testZenOn_AllCallers_CallTypesAllowed() { mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; - mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O}); + mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O}); // The most permissive policy mZenModeHelper.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS | PRIORITY_CATEGORY_MEDIA | PRIORITY_CATEGORY_MESSAGES @@ -481,7 +489,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testZenOn_AllowAlarmsMedia_NoAlarmMediaMuteApplied() { mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; - mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O}); + mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O}); mZenModeHelper.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS | PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0, 0); @@ -493,7 +501,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testZenOn_DisallowAlarmsMedia_AlarmMediaMuteApplied() { mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; - mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O}); + mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O}); mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0); mZenModeHelper.applyRestrictions(); verifyApplyRestrictions(true, true, AudioAttributes.USAGE_ALARM); @@ -506,7 +514,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testTotalSilence() { mZenModeHelper.mZenMode = Settings.Global.ZEN_MODE_NO_INTERRUPTIONS; - mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O}); + mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O}); mZenModeHelper.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS | PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0, 0); mZenModeHelper.applyRestrictions(); @@ -525,7 +533,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testAlarmsOnly_alarmMediaMuteNotApplied() { mZenModeHelper.mZenMode = Settings.Global.ZEN_MODE_ALARMS; - mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O}); + mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O}); mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0); mZenModeHelper.applyRestrictions(); @@ -545,7 +553,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testAlarmsOnly_callsMuteApplied() { mZenModeHelper.mZenMode = Settings.Global.ZEN_MODE_ALARMS; - mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O}); + mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O}); mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0); mZenModeHelper.applyRestrictions(); @@ -559,7 +567,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { public void testAlarmsOnly_allZenConfigToggledCannotBypass_alarmMuteNotApplied() { // Only audio attributes with SUPPRESIBLE_NEVER can bypass mZenModeHelper.mZenMode = Settings.Global.ZEN_MODE_ALARMS; - mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O}); + mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O}); mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0); mZenModeHelper.applyRestrictions(); @@ -571,7 +579,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Only audio attributes with SUPPRESIBLE_NEVER can bypass // with special case USAGE_ASSISTANCE_SONIFICATION mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; - mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O}); + mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O}); mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0); mZenModeHelper.applyRestrictions(); @@ -592,7 +600,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testApplyRestrictions_whitelist_priorityOnlyMode() { - mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O}); + mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O}); mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0); mZenModeHelper.applyRestrictions(); @@ -607,7 +615,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testApplyRestrictions_whitelist_alarmsOnlyMode() { - mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O}); + mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O}); mZenModeHelper.mZenMode = Global.ZEN_MODE_ALARMS; mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0); mZenModeHelper.applyRestrictions(); @@ -622,7 +630,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testApplyRestrictions_whitelist_totalSilenceMode() { - mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O}); + mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[]{PKG_O}); mZenModeHelper.mZenMode = Global.ZEN_MODE_NO_INTERRUPTIONS; mZenModeHelper.mConsolidatedPolicy = new Policy(0, 0, 0, 0, 0, 0); mZenModeHelper.applyRestrictions(); @@ -1007,7 +1015,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL); assertEquals("Config mismatch: current vs expected: " - + new ZenModeDiff.ConfigDiff(mZenModeHelper.mConfig, expected), expected, + + new ZenModeDiff.ConfigDiff(mZenModeHelper.mConfig, expected), expected, mZenModeHelper.mConfig); } @@ -1314,7 +1322,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.readXml(parser, true, UserHandle.USER_SYSTEM); assertEquals("Config mismatch: current vs original: " - + new ZenModeDiff.ConfigDiff(mZenModeHelper.mConfig, original), + + new ZenModeDiff.ConfigDiff(mZenModeHelper.mConfig, original), original, mZenModeHelper.mConfig); assertEquals(original.hashCode(), mZenModeHelper.mConfig.hashCode()); } @@ -1756,6 +1764,225 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void testReadXml_onModesApi_noUpgrade() throws Exception { + // When reading XML for something that is already on the modes API system, make sure no + // rules' policies get changed. + setupZenConfig(); + + // Shared for rules + ArrayMap<String, ZenModeConfig.ZenRule> enabledAutoRules = new ArrayMap<>(); + final ScheduleInfo weeknights = new ScheduleInfo(); + + // Custom rule with a custom policy + ZenModeConfig.ZenRule customRule = new ZenModeConfig.ZenRule(); + customRule.enabled = true; + customRule.name = "Custom Rule"; + customRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; + customRule.conditionId = ZenModeConfig.toScheduleConditionId(weeknights); + customRule.component = new ComponentName("android", "ScheduleConditionProvider"); + ZenPolicy policy = new ZenPolicy.Builder() + .allowCalls(PEOPLE_TYPE_CONTACTS) + .allowAlarms(true) + .allowRepeatCallers(false) + .build(); + // Fill in policy fields, since on modes api we do not expect any rules to have unset fields + customRule.zenPolicy = mZenModeHelper.getDefaultZenPolicy().overwrittenWith(policy); + enabledAutoRules.put("customRule", customRule); + mZenModeHelper.mConfig.automaticRules = enabledAutoRules; + + // set version to post-modes-API = 11 + ByteArrayOutputStream baos = writeXmlAndPurge(11); + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(baos.toByteArray())), null); + parser.nextTag(); + mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL); + + // basic check: global config maintained + setupZenConfigMaintained(); + + // Find our automatic rules. + ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules; + assertThat(rules).hasSize(1); + assertThat(rules).containsKey("customRule"); + ZenRule rule = rules.get("customRule"); + assertThat(rule.zenPolicy).isEqualTo(customRule.zenPolicy); + } + + @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void testReadXml_upgradeToModesApi_makesCustomPolicies() throws Exception { + // When reading in an XML file written from a pre-modes-API version, confirm that we create + // a custom policy matching the global config for any automatic rule with no specified + // policy. + setupZenConfig(); + + ArrayMap<String, ZenModeConfig.ZenRule> enabledAutoRule = new ArrayMap<>(); + ZenModeConfig.ZenRule customRule = new ZenModeConfig.ZenRule(); + final ScheduleInfo weeknights = new ScheduleInfo(); + customRule.enabled = true; + customRule.name = "Custom Rule"; + customRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; + customRule.conditionId = ZenModeConfig.toScheduleConditionId(weeknights); + customRule.component = new ComponentName("android", "ScheduleConditionProvider"); + enabledAutoRule.put("customRule", customRule); // no custom policy set + mZenModeHelper.mConfig.automaticRules = enabledAutoRule; + + // set version to pre-modes-API = 10 + ByteArrayOutputStream baos = writeXmlAndPurge(10); + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(baos.toByteArray())), null); + parser.nextTag(); + mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL); + + // basic check: global config maintained + setupZenConfigMaintained(); + + // Find our automatic rule and check that it has a policy set now + ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules; + assertThat(rules).hasSize(1); + assertThat(rules).containsKey("customRule"); + ZenRule rule = rules.get("customRule"); + assertThat(rule.zenPolicy).isNotNull(); + + // Check policy values as set up in setupZenConfig() to confirm they match + assertThat(rule.zenPolicy.getPriorityCategoryAlarms()).isEqualTo(STATE_DISALLOW); + assertThat(rule.zenPolicy.getPriorityCategoryMedia()).isEqualTo(STATE_DISALLOW); + assertThat(rule.zenPolicy.getPriorityCategorySystem()).isEqualTo(STATE_DISALLOW); + assertThat(rule.zenPolicy.getPriorityCategoryReminders()).isEqualTo(STATE_ALLOW); + assertThat(rule.zenPolicy.getPriorityCategoryCalls()).isEqualTo(STATE_ALLOW); + assertThat(rule.zenPolicy.getPriorityCallSenders()).isEqualTo(PEOPLE_TYPE_STARRED); + assertThat(rule.zenPolicy.getPriorityCategoryMessages()).isEqualTo(STATE_ALLOW); + assertThat(rule.zenPolicy.getPriorityCategoryConversations()).isEqualTo(STATE_ALLOW); + assertThat(rule.zenPolicy.getPriorityCategoryEvents()).isEqualTo(STATE_ALLOW); + assertThat(rule.zenPolicy.getPriorityCategoryRepeatCallers()).isEqualTo(STATE_ALLOW); + assertThat(rule.zenPolicy.getVisualEffectBadge()).isEqualTo(STATE_DISALLOW); + } + + @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void testReadXml_upgradeToModesApi_fillsInCustomPolicies() throws Exception { + // When reading in an XML file written from a pre-modes-API version, confirm that for an + // underspecified ZenPolicy, we fill in all of the gaps with things from the global config + // in order to maintain consistency of behavior. + setupZenConfig(); + + ArrayMap<String, ZenModeConfig.ZenRule> enabledAutoRule = new ArrayMap<>(); + ZenModeConfig.ZenRule customRule = new ZenModeConfig.ZenRule(); + final ScheduleInfo weeknights = new ScheduleInfo(); + customRule.enabled = true; + customRule.name = "Custom Rule"; + customRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; + customRule.conditionId = ZenModeConfig.toScheduleConditionId(weeknights); + customRule.component = new ComponentName("android", "ScheduleConditionProvider"); + customRule.zenPolicy = new ZenPolicy.Builder() + .allowAlarms(true) + .allowMedia(true) + .allowRepeatCallers(false) + .build(); + enabledAutoRule.put("customRule", customRule); + mZenModeHelper.mConfig.automaticRules = enabledAutoRule; + + // set version to pre-modes-API = 10 + ByteArrayOutputStream baos = writeXmlAndPurge(10); + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(baos.toByteArray())), null); + parser.nextTag(); + mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL); + + // basic check: global config maintained + setupZenConfigMaintained(); + + // Find our automatic rule and check that it has a policy set now + ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules; + assertThat(rules).hasSize(1); + assertThat(rules).containsKey("customRule"); + ZenRule rule = rules.get("customRule"); + assertThat(rule.zenPolicy).isNotNull(); + + // Check unset policy values match values in setupZenConfig(). + // Check that set policy values match the values set in the policy. + assertThat(rule.zenPolicy.getPriorityCategoryAlarms()).isEqualTo(STATE_ALLOW); + assertThat(rule.zenPolicy.getPriorityCategoryMedia()).isEqualTo(STATE_ALLOW); + assertThat(rule.zenPolicy.getPriorityCategoryRepeatCallers()).isEqualTo(STATE_DISALLOW); + + // Check that the rest is filled in from the default + assertThat(rule.zenPolicy.getPriorityCategorySystem()).isEqualTo(STATE_DISALLOW); + assertThat(rule.zenPolicy.getPriorityCategoryReminders()).isEqualTo(STATE_ALLOW); + assertThat(rule.zenPolicy.getPriorityCategoryCalls()).isEqualTo(STATE_ALLOW); + assertThat(rule.zenPolicy.getPriorityCallSenders()).isEqualTo(PEOPLE_TYPE_STARRED); + assertThat(rule.zenPolicy.getPriorityCategoryMessages()).isEqualTo(STATE_ALLOW); + assertThat(rule.zenPolicy.getPriorityCategoryConversations()).isEqualTo(STATE_ALLOW); + assertThat(rule.zenPolicy.getPriorityCategoryEvents()).isEqualTo(STATE_ALLOW); + assertThat(rule.zenPolicy.getVisualEffectBadge()).isEqualTo(STATE_DISALLOW); + } + + @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void testReadXml_upgradeToModesApi_existingDefaultRulesGetCustomPolicy() + throws Exception { + setupZenConfig(); + + // Default rules, if they exist and have no policies, should get a snapshot of the global + // policy, even if they are disabled upon upgrade. + ArrayMap<String, ZenModeConfig.ZenRule> automaticRules = new ArrayMap<>(); + ZenModeConfig.ZenRule defaultScheduleRule = new ZenModeConfig.ZenRule(); + final ScheduleInfo defaultScheduleRuleInfo = new ScheduleInfo(); + defaultScheduleRule.enabled = false; + defaultScheduleRule.name = "Default Schedule Rule"; + defaultScheduleRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; + defaultScheduleRule.conditionId = ZenModeConfig.toScheduleConditionId( + defaultScheduleRuleInfo); + defaultScheduleRule.id = ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID; + automaticRules.put(ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID, defaultScheduleRule); + + ZenModeConfig.ZenRule defaultEventRule = new ZenModeConfig.ZenRule(); + final ScheduleInfo defaultEventRuleInfo = new ScheduleInfo(); + defaultEventRule.enabled = false; + defaultEventRule.name = "Default Event Rule"; + defaultEventRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; + defaultEventRule.conditionId = ZenModeConfig.toScheduleConditionId( + defaultEventRuleInfo); + defaultEventRule.id = ZenModeConfig.EVENTS_DEFAULT_RULE_ID; + automaticRules.put(ZenModeConfig.EVENTS_DEFAULT_RULE_ID, defaultEventRule); + + mZenModeHelper.mConfig.automaticRules = automaticRules; + + // set previous version + ByteArrayOutputStream baos = writeXmlAndPurge(10); + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(baos.toByteArray())), null); + parser.nextTag(); + mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL); + + // check default rules + ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules; + assertThat(rules.size()).isGreaterThan(0); + for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) { + assertThat(rules).containsKey(defaultId); + ZenRule rule = rules.get(defaultId); + assertThat(rule.zenPolicy).isNotNull(); + + // Check policy values as set up in setupZenConfig() to confirm they match + assertThat(rule.zenPolicy.getPriorityCategoryAlarms()).isEqualTo(STATE_DISALLOW); + assertThat(rule.zenPolicy.getPriorityCategoryMedia()).isEqualTo(STATE_DISALLOW); + assertThat(rule.zenPolicy.getPriorityCategorySystem()).isEqualTo(STATE_DISALLOW); + assertThat(rule.zenPolicy.getPriorityCategoryReminders()).isEqualTo(STATE_ALLOW); + assertThat(rule.zenPolicy.getPriorityCategoryCalls()).isEqualTo(STATE_ALLOW); + assertThat(rule.zenPolicy.getPriorityCallSenders()).isEqualTo(PEOPLE_TYPE_STARRED); + assertThat(rule.zenPolicy.getPriorityCategoryMessages()).isEqualTo(STATE_ALLOW); + assertThat(rule.zenPolicy.getPriorityCategoryConversations()).isEqualTo(STATE_ALLOW); + assertThat(rule.zenPolicy.getPriorityCategoryEvents()).isEqualTo(STATE_ALLOW); + assertThat(rule.zenPolicy.getPriorityCategoryRepeatCallers()).isEqualTo(STATE_ALLOW); + assertThat(rule.zenPolicy.getVisualEffectBadge()).isEqualTo(STATE_DISALLOW); + } + } + + @Test public void testCountdownConditionSubscription() throws Exception { ZenModeConfig config = new ZenModeConfig(); mZenModeHelper.mConfig = config; @@ -2014,6 +2241,69 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void testAddAutomaticZenRule_modesApi_fillsInDefaultValues() { + // When a new automatic zen rule is added with only some fields filled in, ensure that + // all unset fields are filled in with device defaults. + + // Zen rule with null policy: should get entirely the default state + AutomaticZenRule zenRule1 = new AutomaticZenRule("name", + new ComponentName("android", "ScheduleConditionProvider"), + ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), + NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); + String id1 = mZenModeHelper.addAutomaticZenRule("android", zenRule1, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); + + // Zen rule with partially-filled policy: should get all of the filled fields set, and the + // rest filled with default state + AutomaticZenRule zenRule2 = new AutomaticZenRule("name", + null, + new ComponentName(mContext.getPackageName(), "ScheduleConditionProvider"), + ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), + new ZenPolicy.Builder() + .allowCalls(PEOPLE_TYPE_NONE) + .allowMessages(PEOPLE_TYPE_CONTACTS) + .showFullScreenIntent(true) + .build(), + NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); + String id2 = mZenModeHelper.addAutomaticZenRule("android", zenRule2, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); + + // rule 1 should exist + assertThat(id1).isNotNull(); + ZenModeConfig.ZenRule rule1InConfig = mZenModeHelper.mConfig.automaticRules.get(id1); + assertThat(rule1InConfig).isNotNull(); + assertThat(rule1InConfig.zenPolicy).isNotNull(); // we passed in null; it should now not be + + // all of rule 1 should be the device default's policy + assertThat(rule1InConfig.zenPolicy).isEqualTo(mZenModeHelper.getDefaultZenPolicy()); + + // rule 2 should exist + assertThat(id2).isNotNull(); + ZenModeConfig.ZenRule rule2InConfig = mZenModeHelper.mConfig.automaticRules.get(id2); + assertThat(rule2InConfig).isNotNull(); + + // rule 2: values set from the policy itself + assertThat(rule2InConfig.zenPolicy.getPriorityCallSenders()).isEqualTo(PEOPLE_TYPE_NONE); + assertThat(rule2InConfig.zenPolicy.getPriorityMessageSenders()) + .isEqualTo(PEOPLE_TYPE_CONTACTS); + assertThat(rule2InConfig.zenPolicy.getVisualEffectFullScreenIntent()) + .isEqualTo(ZenPolicy.STATE_ALLOW); + + // the rest of rule 2's settings should be the device defaults + assertThat(rule2InConfig.zenPolicy.getPriorityConversationSenders()) + .isEqualTo(CONVERSATION_SENDERS_IMPORTANT); + assertThat(rule2InConfig.zenPolicy.getPriorityCategorySystem()) + .isEqualTo(ZenPolicy.STATE_DISALLOW); + assertThat(rule2InConfig.zenPolicy.getPriorityCategoryAlarms()) + .isEqualTo(ZenPolicy.STATE_ALLOW); + assertThat(rule2InConfig.zenPolicy.getVisualEffectPeek()) + .isEqualTo(ZenPolicy.STATE_DISALLOW); + assertThat(rule2InConfig.zenPolicy.getVisualEffectNotificationList()) + .isEqualTo(ZenPolicy.STATE_ALLOW); + } + + @Test public void testSetAutomaticZenRuleState_nullPkg() { AutomaticZenRule zenRule = new AutomaticZenRule("name", null, @@ -2335,6 +2625,68 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test @EnableFlags(Flags.FLAG_MODES_API) + public void updateAutomaticZenRule_nullPolicy_doesNothing() { + // Test that when updateAutomaticZenRule is called with a null policy, nothing changes + // about the existing policy. + String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), + new AutomaticZenRule.Builder("Rule", CONDITION_ID) + .setOwner(OWNER) + .setZenPolicy(new ZenPolicy.Builder() + .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) // default is stars + .build()) + .build(), + UPDATE_ORIGIN_APP, "reasons", 0); + + mZenModeHelper.updateAutomaticZenRule(ruleId, + new AutomaticZenRule.Builder("Rule", CONDITION_ID) + // no zen policy + .build(), + UPDATE_ORIGIN_APP, "reasons", 0); + + AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); + assertThat(savedRule.getZenPolicy().getPriorityCategoryCalls()) + .isEqualTo(ZenPolicy.STATE_DISALLOW); + } + + @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void updateAutomaticZenRule_overwritesExistingPolicy() { + // Test that when updating an automatic zen rule with an existing policy, the newly set + // fields overwrite those from the previous policy, but unset fields in the new policy + // keep values from the previous one. + String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), + new AutomaticZenRule.Builder("Rule", CONDITION_ID) + .setOwner(OWNER) + .setZenPolicy(new ZenPolicy.Builder() + .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) // default is stars + .allowAlarms(false) + .allowReminders(true) + .build()) + .build(), + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0); + + mZenModeHelper.updateAutomaticZenRule(ruleId, + new AutomaticZenRule.Builder("Rule", CONDITION_ID) + .setZenPolicy(new ZenPolicy.Builder() + .allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS) + .build()) + .build(), + UPDATE_ORIGIN_APP, "reasons", 0); + + AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); + assertThat(savedRule.getZenPolicy().getPriorityCategoryCalls()) + .isEqualTo(ZenPolicy.STATE_ALLOW); // from update + assertThat(savedRule.getZenPolicy().getPriorityCallSenders()) + .isEqualTo(ZenPolicy.PEOPLE_TYPE_CONTACTS); // from update + assertThat(savedRule.getZenPolicy().getPriorityCategoryAlarms()) + .isEqualTo(ZenPolicy.STATE_DISALLOW); // from original + assertThat(savedRule.getZenPolicy().getPriorityCategoryReminders()) + .isEqualTo(ZenPolicy.STATE_ALLOW); // from original + } + + + @Test + @EnableFlags(Flags.FLAG_MODES_API) public void addAutomaticZenRule_withTypeBedtime_replacesDisabledSleeping() { ZenRule sleepingRule = createCustomAutomaticRule(ZEN_MODE_IMPORTANT_INTERRUPTIONS, ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID); @@ -2438,7 +2790,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { DISABLED(false, /* originForUserActionInSystemUi= */ UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI); private final boolean mEnabled; - @ConfigChangeOrigin private final int mOriginForUserActionInSystemUi; + @ConfigChangeOrigin + private final int mOriginForUserActionInSystemUi; ModesApiFlag(boolean enabled, @ConfigChangeOrigin int originForUserActionInSystemUi) { this.mEnabled = enabled; @@ -2484,7 +2837,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // - rules active = 1 // - user action = true (system-based turning zen mode on) // - package uid = system (as set above) - // - resulting DNDPolicyProto the same as the values in setupZenConfig() + // - resulting DNDPolicyProto the same as the values in setupZenConfig() (global policy) assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId(), mZenModeEventLogger.getEventId(0)); assertEquals(ZEN_MODE_OFF, mZenModeEventLogger.getPrevZenMode(0)); @@ -2578,7 +2931,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // - 1 rule (newly) active // - automatic (is not a user action) // - package UID is written to be the rule *owner* even though it "comes from system" - // - zen policy is the same as the set-up zen config + // - zen policy is the default as it's unspecified assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId(), mZenModeEventLogger.getEventId(0)); assertEquals(ZEN_MODE_OFF, mZenModeEventLogger.getPrevZenMode(0)); @@ -2587,10 +2940,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(1, mZenModeEventLogger.getNumRulesActive(0)); assertFalse(mZenModeEventLogger.getIsUserAction(0)); assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(0)); - checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(0)); + checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(0)); // When the automatic rule is disabled, this should turn off zen mode and also count as a - // user action. + // user action. We don't care what the consolidated policy is when DND turns off. assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_OFF.getId(), mZenModeEventLogger.getEventId(1)); assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getPrevZenMode(1)); @@ -2813,28 +3166,28 @@ public class ZenModeHelperTest extends UiServiceTestCase { // First: turn on rule 1 mZenModeHelper.setAutomaticZenRuleState(id, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Second: turn on rule 2 mZenModeHelper.setAutomaticZenRuleState(id2, new Condition(zenRule2.getConditionId(), "", STATE_TRUE), - UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Third: turn on rule 3 mZenModeHelper.setAutomaticZenRuleState(id3, new Condition(zenRule3.getConditionId(), "", STATE_TRUE), - UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Fourth: Turn *off* rule 2 mZenModeHelper.setAutomaticZenRuleState(id2, new Condition(zenRule2.getConditionId(), "", STATE_FALSE), - UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // This should result in a total of four events assertEquals(4, mZenModeEventLogger.numLoggedChanges()); // Event 1: rule 1 turns on. We expect this to turn on DND (zen mode) overall, so that's - // what the event should reflect. At this time, the policy is the same as initial setup. + // what the event should reflect. At this time, the policy is the default. assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId(), mZenModeEventLogger.getEventId(0)); assertEquals(ZEN_MODE_OFF, mZenModeEventLogger.getPrevZenMode(0)); @@ -2842,7 +3195,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(1, mZenModeEventLogger.getNumRulesActive(0)); assertFalse(mZenModeEventLogger.getIsUserAction(0)); assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(0)); - checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(0)); + checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(0)); // Event 2: rule 2 turns on. This should not change anything about the policy, so the only // change is that there are more rules active now. @@ -2851,7 +3204,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(2, mZenModeEventLogger.getNumRulesActive(1)); assertFalse(mZenModeEventLogger.getIsUserAction(1)); assertEquals(CUSTOM_PKG_UID, mZenModeEventLogger.getPackageUid(1)); - checkDndProtoMatchesSetupZenConfig(mZenModeEventLogger.getPolicyProto(1)); + checkDndProtoMatchesDefaultZenConfig(mZenModeEventLogger.getPolicyProto(1)); // Event 3: rule 3 turns on. This should trigger a policy change, and be classified as such, // but meanwhile also change the number of active rules. @@ -2904,12 +3257,16 @@ public class ZenModeHelperTest extends UiServiceTestCase { mTestFlagResolver.setFlagOverride(LOG_DND_STATE_EVENTS, true); setupZenConfig(); + // Explicitly set up all rules with the same policy as the manual rule so there will be + // no policy changes in this test case. + ZenPolicy manualRulePolicy = mZenModeHelper.mConfig.toZenPolicy(); + // Rule 1, owned by a package AutomaticZenRule zenRule = new AutomaticZenRule("name", null, new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"), ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), - null, + manualRulePolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, UPDATE_ORIGIN_APP, "test", Process.SYSTEM_UID); @@ -2919,7 +3276,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, new ComponentName("android", "ScheduleConditionProvider"), ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), - null, + manualRulePolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, modesApiFlag.mOriginForUserActionInSystemUi, "test", Process.SYSTEM_UID); @@ -3150,7 +3507,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - public void testUpdateConsolidatedPolicy_defaultRulesOnly() { + @DisableFlags(Flags.FLAG_MODES_API) + public void testUpdateConsolidatedPolicy_preModesApiDefaultRulesOnly_takesGlobalDefault() { setupZenConfig(); // When there's one automatic rule active and it doesn't specify a policy, test that the @@ -3183,12 +3541,39 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - public void testUpdateConsolidatedPolicy_customPolicyOnly() { + @EnableFlags(Flags.FLAG_MODES_API) + public void testUpdateConsolidatedPolicy_modesApiDefaultRulesOnly_takesDeviceDefault() { + setupZenConfig(); + + // When there's one automatic rule active and it doesn't specify a policy, test that the + // resulting consolidated policy is one that matches the default *device* settings. + AutomaticZenRule zenRule = new AutomaticZenRule("name", + null, + new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"), + ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), + null, // null policy + NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); + String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); + + // enable the rule + mZenModeHelper.setAutomaticZenRuleState(id, + new Condition(zenRule.getConditionId(), "", STATE_TRUE), + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); + + // inspect the consolidated policy, which should match the device default settings. + assertThat(ZenAdapters.notificationPolicyToZenPolicy(mZenModeHelper.mConsolidatedPolicy)) + .isEqualTo(mZenModeHelper.getDefaultZenPolicy()); + } + + @Test + @DisableFlags(Flags.FLAG_MODES_API) + public void testUpdateConsolidatedPolicy_preModesApiCustomPolicyOnly_fillInWithGlobal() { setupZenConfig(); // when there's only one automatic rule active and it has a custom policy, make sure that's - // what the consolidated policy reflects whether or not it's stricter than what the default - // would specify. + // what the consolidated policy reflects whether or not it's stricter than what the global + // config would specify. ZenPolicy customPolicy = new ZenPolicy.Builder() .allowAlarms(true) // more lenient than default .allowMedia(true) // more lenient than default @@ -3227,7 +3612,51 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - public void testUpdateConsolidatedPolicy_defaultAndCustomActive() { + @EnableFlags(Flags.FLAG_MODES_API) + public void testUpdateConsolidatedPolicy_modesApiCustomPolicyOnly_fillInWithDeviceDefault() { + setupZenConfig(); + + // when there's only one automatic rule active and it has a custom policy, make sure that's + // what the consolidated policy reflects whether or not it's stricter than what the default + // would specify. + ZenPolicy customPolicy = new ZenPolicy.Builder() + .allowSystem(true) // more lenient than default + .allowRepeatCallers(false) // more restrictive than default + .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) // more restrictive than default + .showFullScreenIntent(true) // more lenient + .showBadges(false) // more restrictive + .build(); + + AutomaticZenRule zenRule = new AutomaticZenRule("name", + null, + new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"), + ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), + customPolicy, + NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); + String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); + + // enable the rule; this will update the consolidated policy + mZenModeHelper.setAutomaticZenRuleState(id, + new Condition(zenRule.getConditionId(), "", STATE_TRUE), + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); + + // since this is the only active rule, the consolidated policy should match the custom + // policy for every field specified, and take default values for unspecified things + assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isTrue(); // default + assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isTrue(); // default + assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isTrue(); // custom + assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isFalse(); // default + assertThat(mZenModeHelper.mConsolidatedPolicy.allowCalls()).isFalse(); // custom + assertThat(mZenModeHelper.mConsolidatedPolicy.allowMessages()).isTrue(); // default + assertThat(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers()).isFalse(); // custom + assertThat(mZenModeHelper.mConsolidatedPolicy.showBadges()).isFalse(); // custom + assertThat(mZenModeHelper.mConsolidatedPolicy.showFullScreenIntents()).isTrue(); // custom + } + + @Test + @DisableFlags(Flags.FLAG_MODES_API) + public void testUpdateConsolidatedPolicy_preModesApiDefaultAndCustomActive_mergesWithGlobal() { setupZenConfig(); // when there are two rules active, one inheriting the default policy and one setting its @@ -3287,6 +3716,68 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void testUpdateConsolidatedPolicy_modesApiDefaultAndCustomActive_mergesWithDefault() { + setupZenConfig(); + + // when there are two rules active, one inheriting the default policy and one setting its + // own custom policy, they should be merged to form the most restrictive combination. + + // rule 1: no custom policy + AutomaticZenRule zenRule = new AutomaticZenRule("name", + null, + new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"), + ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), + null, + NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); + String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); + + // enable rule 1 + mZenModeHelper.setAutomaticZenRuleState(id, + new Condition(zenRule.getConditionId(), "", STATE_TRUE), + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); + + // custom policy for rule 2 + ZenPolicy customPolicy = new ZenPolicy.Builder() + .allowAlarms(false) // more restrictive than default + .allowSystem(true) // more lenient than default + .allowRepeatCallers(false) // more restrictive than default + .allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) // more restrictive than default + .showBadges(false) // more restrictive + .showPeeking(true) // more lenient + .build(); + + AutomaticZenRule zenRule2 = new AutomaticZenRule("name2", + null, + new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"), + ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), + customPolicy, + NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); + String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); + + // enable rule 2; this will update the consolidated policy + mZenModeHelper.setAutomaticZenRuleState(id2, + new Condition(zenRule2.getConditionId(), "", STATE_TRUE), + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); + + // now both rules should be on, and the consolidated policy should reflect the most + // restrictive option of each of the two + assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isFalse(); // custom stricter + assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isTrue(); // default + assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isFalse(); // default stricter + assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isFalse(); // default + assertThat(mZenModeHelper.mConsolidatedPolicy.allowCalls()).isFalse(); // custom stricter + assertThat(mZenModeHelper.mConsolidatedPolicy.allowMessages()).isTrue(); // default + assertThat(mZenModeHelper.mConsolidatedPolicy.allowConversations()).isTrue(); // default + assertThat(mZenModeHelper.mConsolidatedPolicy.allowRepeatCallers()) + .isFalse(); // custom stricter + assertThat(mZenModeHelper.mConsolidatedPolicy.showBadges()).isFalse(); // custom stricter + assertThat(mZenModeHelper.mConsolidatedPolicy.showPeeking()).isFalse(); // default stricter + } + + @Test public void testUpdateConsolidatedPolicy_allowChannels() { mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); setupZenConfig(); @@ -3350,7 +3841,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, new ComponentName(CUSTOM_PKG_NAME, "cls"), Uri.parse("priority"), - new ZenPolicy.Builder().allowMedia(true).build(), + new ZenPolicy.Builder() + .allowMedia(true) + .allowSystem(true) + .build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String rule1Id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRuleWithPriority, UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID); @@ -3372,10 +3866,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { UPDATE_ORIGIN_APP, CUSTOM_PKG_UID); // Consolidated Policy should be default + rule1. - assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isFalse(); // default + assertThat(mZenModeHelper.mConsolidatedPolicy.allowAlarms()).isTrue(); // default assertThat(mZenModeHelper.mConsolidatedPolicy.allowMedia()).isTrue(); // priority rule - assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isFalse(); // default - assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isTrue(); // default + assertThat(mZenModeHelper.mConsolidatedPolicy.allowSystem()).isTrue(); // priority rule + assertThat(mZenModeHelper.mConsolidatedPolicy.allowReminders()).isFalse(); // default assertThat(mZenModeHelper.mConsolidatedPolicy.allowCalls()).isTrue(); // default assertThat(mZenModeHelper.mConsolidatedPolicy.allowMessages()).isTrue(); // default assertThat(mZenModeHelper.mConsolidatedPolicy.allowConversations()).isTrue(); // default @@ -3386,7 +3880,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { public void zenRuleToAutomaticZenRule_allFields() { mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); when(mPackageManager.getPackagesForUid(anyInt())).thenReturn( - new String[] {OWNER.getPackageName()}); + new String[]{OWNER.getPackageName()}); ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); rule.configurationActivity = CONFIG_ACTIVITY; @@ -3430,7 +3924,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { public void automaticZenRuleToZenRule_allFields() { mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); when(mPackageManager.getPackagesForUid(anyInt())).thenReturn( - new String[] {OWNER.getPackageName()}); + new String[]{OWNER.getPackageName()}); AutomaticZenRule azr = new AutomaticZenRule.Builder(NAME, CONDITION_ID) .setEnabled(true) @@ -3456,7 +3950,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(CONDITION_ID, storedRule.conditionId); assertEquals(INTERRUPTION_FILTER_ZR, storedRule.zenMode); assertEquals(ENABLED, storedRule.enabled); - assertEquals(POLICY, storedRule.zenPolicy); + assertEquals(mZenModeHelper.getDefaultZenPolicy().overwrittenWith(POLICY), + storedRule.zenPolicy); assertEquals(CONFIG_ACTIVITY, storedRule.configurationActivity); assertEquals(TYPE, storedRule.type); assertEquals(ALLOW_MANUAL, storedRule.allowManualInvocation); @@ -3539,12 +4034,12 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Modifies the zen policy and device effects ZenPolicy policy = new ZenPolicy.Builder(rule.getZenPolicy()) - .allowPriorityChannels(true) + .allowPriorityChannels(false) .build(); ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder(rule.getDeviceEffects()) - .setShouldDisplayGrayscale(true) - .build(); + .setShouldDisplayGrayscale(true) + .build(); AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule) .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) .setZenPolicy(policy) @@ -3558,7 +4053,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { // UPDATE_ORIGIN_USER should change the bitmask and change the values. assertThat(rule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY); - assertThat(rule.getZenPolicy().getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW); + assertThat(rule.getZenPolicy().getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW); + assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue(); ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId); @@ -3748,9 +4244,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test @EnableFlags(Flags.FLAG_MODES_API) public void updateAutomaticZenRule_nullPolicyUpdate() { - // Adds a starting rule with empty zen policies and device effects + // Adds a starting rule with set zen policy and empty device effects AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID) - .setZenPolicy(new ZenPolicy.Builder().build()) + .setZenPolicy(POLICY) .build(); // Adds the rule using the app, to avoid having any user modified bits set. String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), @@ -3768,8 +4264,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { Process.SYSTEM_UID); rule = mZenModeHelper.getAutomaticZenRule(ruleId); - // When AZR's ZenPolicy is null, we expect the updated rule's policy to be null. - assertThat(rule.getZenPolicy()).isNull(); + // When AZR's ZenPolicy is null, we expect the updated rule's policy to be unchanged + // (equivalent to the provided policy, with additional fields filled in with defaults). + assertThat(rule.getZenPolicy()).isEqualTo( + mZenModeHelper.getDefaultZenPolicy().overwrittenWith(POLICY)); } @Test @@ -3825,13 +4323,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(storedRule.canBeUpdatedByApp()).isFalse(); assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo( ZenPolicy.FIELD_ALLOW_CHANNELS - | ZenPolicy.FIELD_PRIORITY_CATEGORY_REMINDERS - | ZenPolicy.FIELD_PRIORITY_CATEGORY_EVENTS - | ZenPolicy.FIELD_PRIORITY_CATEGORY_SYSTEM - | ZenPolicy.FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT - | ZenPolicy.FIELD_VISUAL_EFFECT_LIGHTS - | ZenPolicy.FIELD_VISUAL_EFFECT_PEEK - | ZenPolicy.FIELD_VISUAL_EFFECT_AMBIENT + | ZenPolicy.FIELD_PRIORITY_CATEGORY_REMINDERS + | ZenPolicy.FIELD_PRIORITY_CATEGORY_EVENTS + | ZenPolicy.FIELD_PRIORITY_CATEGORY_SYSTEM + | ZenPolicy.FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT + | ZenPolicy.FIELD_VISUAL_EFFECT_LIGHTS + | ZenPolicy.FIELD_VISUAL_EFFECT_PEEK + | ZenPolicy.FIELD_VISUAL_EFFECT_AMBIENT ); } @@ -3994,6 +4492,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { final int[] actualStatus = new int[2]; ZenModeHelper.Callback callback = new ZenModeHelper.Callback() { int i = 0; + @Override void onAutomaticRuleStatusChanged(int userId, String pkg, String id, int status) { if (Objects.equals(createdId, id)) { @@ -4034,6 +4533,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { final int[] actualStatus = new int[2]; ZenModeHelper.Callback callback = new ZenModeHelper.Callback() { int i = 0; + @Override void onAutomaticRuleStatusChanged(int userId, String pkg, String id, int status) { if (Objects.equals(createdId, id)) { @@ -4180,6 +4680,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .build()), eq(UPDATE_ORIGIN_APP)); } + @Test public void testDeviceEffects_noChangeToConsolidatedEffects_notApplied() { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); @@ -4585,7 +5086,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { .comparingElementsUsing(IGNORE_METADATA) .containsExactly( expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS, - null, true)); + mZenModeHelper.mConfig.toZenPolicy(), // copy of global config + true)); } @Test @@ -4604,7 +5106,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(mZenModeHelper.mConfig.automaticRules.values()) .comparingElementsUsing(IGNORE_METADATA) .containsExactly( - expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_ALARMS, null, true)); + expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_ALARMS, + mZenModeHelper.mConfig.toZenPolicy(), // copy of global config + true)); } @Test @@ -4798,6 +5302,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, originalPolicy); String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet()); + // Store this for checking later. + ZenPolicy originalEffectiveZenPolicy = new ZenPolicy.Builder( + mZenModeHelper.mConfig.toZenPolicy()).allowMedia(true).build(); + // From user, update that rule's policy. AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId); ZenPolicy userUpdateZenPolicy = new ZenPolicy.Builder().disallowAllSounds() @@ -4817,7 +5325,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { .comparingElementsUsing(IGNORE_METADATA) .containsExactly( expectedImplicitRule(pkg, ZEN_MODE_IMPORTANT_INTERRUPTIONS, - userUpdateZenPolicy, + // the final policy for the rule should contain the user's update + // overlaid on top of the original existing policy. + originalEffectiveZenPolicy.overwrittenWith(userUpdateZenPolicy), /* conditionActive= */ null)); } @@ -4832,6 +5342,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, originalPolicy); String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet()); + // Store this for checking later. + ZenPolicy originalEffectiveZenPolicy = new ZenPolicy.Builder( + mZenModeHelper.mConfig.toZenPolicy()).allowMedia(true).build(); + // From user, update something in that rule, but not the ZenPolicy. AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId); AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule) @@ -4851,7 +5365,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .allowPriorityChannels(true) .build(); assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenPolicy) - .isEqualTo(appsSecondZenPolicy); + .isEqualTo(originalEffectiveZenPolicy.overwrittenWith(appsSecondZenPolicy)); } @Test @@ -4883,13 +5397,17 @@ public class ZenModeHelperTest extends UiServiceTestCase { } @Test - public void getNotificationPolicyFromImplicitZenRule_ruleWithoutPolicy_returnsGlobalPolicy() { + public void getNotificationPolicyFromImplicitZenRule_ruleWithoutPolicy_copiesGlobalPolicy() { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + mZenModeHelper.mConfig.allowCalls = true; + mZenModeHelper.mConfig.allowConversations = false; + // Implicit rule will get the global policy at the time of rule creation. mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, ZEN_MODE_ALARMS); - mZenModeHelper.mConfig.allowCalls = true; - mZenModeHelper.mConfig.allowConversations = false; + + // If the policy then changes afterwards, we should keep the snapshotted version. + mZenModeHelper.mConfig.allowCalls = false; Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule( CUSTOM_PKG_NAME); @@ -4930,7 +5448,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { p.recycle(); } }, - "Ignoring timestamp and userModifiedFields"); + "Ignoring timestamp and userModifiedFields"); private ZenRule expectedImplicitRule(String ownerPkg, int zenMode, ZenPolicy policy, @Nullable Boolean conditionActive) { @@ -4940,7 +5458,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { if (conditionActive != null) { rule.condition = conditionActive ? new Condition(rule.conditionId, - mContext.getString(R.string.zen_mode_implicit_activated), STATE_TRUE) + mContext.getString(R.string.zen_mode_implicit_activated), STATE_TRUE) : new Condition(rule.conditionId, mContext.getString(R.string.zen_mode_implicit_deactivated), STATE_FALSE); @@ -5008,8 +5526,35 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertEquals(STATE_ALLOW, dndProto.getNotificationList().getNumber()); } + private void checkDndProtoMatchesDefaultZenConfig(DNDPolicyProto dndProto) { + if (!Flags.modesApi()) { + checkDndProtoMatchesSetupZenConfig(dndProto); + return; + } + + // When modes_api flag is on, the default zen config is the device defaults. + assertThat(dndProto.getAlarms().getNumber()).isEqualTo(STATE_ALLOW); + assertThat(dndProto.getMedia().getNumber()).isEqualTo(STATE_ALLOW); + assertThat(dndProto.getSystem().getNumber()).isEqualTo(STATE_DISALLOW); + assertThat(dndProto.getReminders().getNumber()).isEqualTo(STATE_DISALLOW); + assertThat(dndProto.getCalls().getNumber()).isEqualTo(STATE_ALLOW); + assertThat(dndProto.getAllowCallsFrom().getNumber()).isEqualTo(PEOPLE_STARRED); + assertThat(dndProto.getMessages().getNumber()).isEqualTo(STATE_ALLOW); + assertThat(dndProto.getAllowMessagesFrom().getNumber()).isEqualTo(PEOPLE_STARRED); + assertThat(dndProto.getEvents().getNumber()).isEqualTo(STATE_DISALLOW); + assertThat(dndProto.getRepeatCallers().getNumber()).isEqualTo(STATE_ALLOW); + assertThat(dndProto.getFullscreen().getNumber()).isEqualTo(STATE_DISALLOW); + assertThat(dndProto.getLights().getNumber()).isEqualTo(STATE_DISALLOW); + assertThat(dndProto.getPeek().getNumber()).isEqualTo(STATE_DISALLOW); + assertThat(dndProto.getStatusBar().getNumber()).isEqualTo(STATE_ALLOW); + assertThat(dndProto.getBadge().getNumber()).isEqualTo(STATE_ALLOW); + assertThat(dndProto.getAmbient().getNumber()).isEqualTo(STATE_DISALLOW); + assertThat(dndProto.getNotificationList().getNumber()).isEqualTo(STATE_ALLOW); + } + private static void withoutWtfCrash(Runnable test) { - Log.TerribleFailureHandler oldHandler = Log.setWtfHandler((tag, what, system) -> {}); + Log.TerribleFailureHandler oldHandler = Log.setWtfHandler((tag, what, system) -> { + }); try { test.run(); } finally { 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 4ed55df7775c..57e11328d5e1 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java @@ -269,6 +269,78 @@ public class ZenPolicyTest extends UiServiceTestCase { } @Test + public void testZenPolicyOverwrite_allUnsetPolicies() { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + + ZenPolicy.Builder builder = new ZenPolicy.Builder(); + ZenPolicy unset = builder.build(); + + builder.allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS); + builder.allowMedia(false); + builder.allowEvents(true); + builder.showFullScreenIntent(false); + builder.showInNotificationList(false); + ZenPolicy set = builder.build(); + + ZenPolicy overwritten = set.overwrittenWith(unset); + assertThat(overwritten).isEqualTo(set); + + // should actually work the other way too. + ZenPolicy overwrittenWithSet = unset.overwrittenWith(set); + assertThat(overwrittenWithSet).isEqualTo(set); + } + + @Test + public void testZenPolicyOverwrite_someOverlappingFields_takeNewPolicy() { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + + ZenPolicy p1 = new ZenPolicy.Builder() + .allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS) + .allowMessages(ZenPolicy.PEOPLE_TYPE_STARRED) + .allowMedia(false) + .showBadges(true) + .build(); + + ZenPolicy p2 = new ZenPolicy.Builder() + .allowRepeatCallers(false) + .allowConversations(ZenPolicy.CONVERSATION_SENDERS_IMPORTANT) + .allowMessages(ZenPolicy.PEOPLE_TYPE_NONE) + .showBadges(false) + .showPeeking(true) + .build(); + + // when p1 is overwritten with p2, all values from p2 win regardless of strictness, and + // remaining fields take values from p1. + ZenPolicy p1OverwrittenWithP2 = p1.overwrittenWith(p2); + assertThat(p1OverwrittenWithP2.getPriorityCallSenders()) + .isEqualTo(ZenPolicy.PEOPLE_TYPE_CONTACTS); // from p1 + assertThat(p1OverwrittenWithP2.getPriorityMessageSenders()) + .isEqualTo(ZenPolicy.PEOPLE_TYPE_NONE); // from p2 + assertThat(p1OverwrittenWithP2.getPriorityCategoryRepeatCallers()) + .isEqualTo(ZenPolicy.STATE_DISALLOW); // from p2 + assertThat(p1OverwrittenWithP2.getPriorityCategoryMedia()) + .isEqualTo(ZenPolicy.STATE_DISALLOW); // from p1 + assertThat(p1OverwrittenWithP2.getVisualEffectBadge()) + .isEqualTo(ZenPolicy.STATE_DISALLOW); // from p2 + assertThat(p1OverwrittenWithP2.getVisualEffectPeek()) + .isEqualTo(ZenPolicy.STATE_ALLOW); // from p2 + + ZenPolicy p2OverwrittenWithP1 = p2.overwrittenWith(p1); + assertThat(p2OverwrittenWithP1.getPriorityCallSenders()) + .isEqualTo(ZenPolicy.PEOPLE_TYPE_CONTACTS); // from p1 + assertThat(p2OverwrittenWithP1.getPriorityMessageSenders()) + .isEqualTo(ZenPolicy.PEOPLE_TYPE_STARRED); // from p1 + assertThat(p2OverwrittenWithP1.getPriorityCategoryRepeatCallers()) + .isEqualTo(ZenPolicy.STATE_DISALLOW); // from p2 + assertThat(p2OverwrittenWithP1.getPriorityCategoryMedia()) + .isEqualTo(ZenPolicy.STATE_DISALLOW); // from p1 + assertThat(p2OverwrittenWithP1.getVisualEffectBadge()) + .isEqualTo(ZenPolicy.STATE_ALLOW); // from p1 + assertThat(p2OverwrittenWithP1.getVisualEffectPeek()) + .isEqualTo(ZenPolicy.STATE_ALLOW); // from p2 + } + + @Test public void testZenPolicyMessagesInvalid() { ZenPolicy.Builder builder = new ZenPolicy.Builder(); |