summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Matías Hernández <matiashe@google.com> 2024-03-21 10:55:10 +0100
committer Matías Hernández <matiashe@google.com> 2024-03-26 11:55:37 +0100
commita5504bae1e344b5c670a9f60d6c21f355501e48c (patch)
treeb7d7d9c699f3cb23ab58e35874ccfa3aae746365
parent97c7930d070bddf59c9cfa5d780a836ef7096b36 (diff)
Populate new fields in time- and calendar-schedule modes
* Upgrade existing (and default strarting) zen rules with such conditions to have the expected type and trigger description on configuration load. * Update trigger descriptions for them on locale change. (With a small fix to TestableResources to support getString(id, formatArgs...)). Bug: 317370174 Bug: 320997361 Test: atest ZenModeHelperTest SystemZenRulesTest Change-Id: I35b11bfc3b1d0d9cf620f258c91ec18af95659de
-rw-r--r--core/java/android/service/notification/SystemZenRules.java262
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java10
-rw-r--r--core/res/res/values/strings.xml7
-rw-r--r--core/res/res/values/symbols.xml4
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java2
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java54
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java3
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/SystemZenRulesTest.java198
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java74
-rw-r--r--tests/testables/src/android/testing/TestableResources.java11
10 files changed, 573 insertions, 52 deletions
diff --git a/core/java/android/service/notification/SystemZenRules.java b/core/java/android/service/notification/SystemZenRules.java
new file mode 100644
index 000000000000..302efb364864
--- /dev/null
+++ b/core/java/android/service/notification/SystemZenRules.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.notification;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AutomaticZenRule;
+import android.app.Flags;
+import android.content.Context;
+import android.service.notification.ZenModeConfig.EventInfo;
+import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.service.notification.ZenModeConfig.ZenRule;
+import android.text.format.DateFormat;
+import android.util.Log;
+
+import com.android.internal.R;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * Helper methods for schedule-type (system-owned) rules.
+ * @hide
+ */
+public final class SystemZenRules {
+
+ private static final String TAG = "SystemZenRules";
+
+ public static final String PACKAGE_ANDROID = "android";
+
+ /** Updates existing system-owned rules to use the new Modes fields (type, etc). */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static void maybeUpgradeRules(Context context, ZenModeConfig config) {
+ for (ZenRule rule : config.automaticRules.values()) {
+ if (isSystemOwnedRule(rule) && rule.type == AutomaticZenRule.TYPE_UNKNOWN) {
+ upgradeSystemProviderRule(context, rule);
+ }
+ }
+ }
+
+ /**
+ * Returns whether the rule corresponds to a system ConditionProviderService (i.e. it is owned
+ * by the "android" package).
+ */
+ public static boolean isSystemOwnedRule(ZenRule rule) {
+ return PACKAGE_ANDROID.equals(rule.pkg);
+ }
+
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ private static void upgradeSystemProviderRule(Context context, ZenRule rule) {
+ ScheduleInfo scheduleInfo = ZenModeConfig.tryParseScheduleConditionId(rule.conditionId);
+ if (scheduleInfo != null) {
+ rule.type = AutomaticZenRule.TYPE_SCHEDULE_TIME;
+ rule.triggerDescription = getTriggerDescriptionForScheduleTime(context, scheduleInfo);
+ return;
+ }
+ EventInfo eventInfo = ZenModeConfig.tryParseEventConditionId(rule.conditionId);
+ if (eventInfo != null) {
+ rule.type = AutomaticZenRule.TYPE_SCHEDULE_CALENDAR;
+ rule.triggerDescription = getTriggerDescriptionForScheduleEvent(context, eventInfo);
+ return;
+ }
+ Log.wtf(TAG, "Couldn't determine type of system-owned ZenRule " + rule);
+ }
+
+ /**
+ * Updates the {@link ZenRule#triggerDescription} of the system-owned rule based on the schedule
+ * or event condition encoded in its {@link ZenRule#conditionId}.
+ *
+ * @return {@code true} if the trigger description was updated.
+ */
+ public static boolean updateTriggerDescription(Context context, ZenRule rule) {
+ ScheduleInfo scheduleInfo = ZenModeConfig.tryParseScheduleConditionId(rule.conditionId);
+ if (scheduleInfo != null) {
+ return updateTriggerDescription(rule,
+ getTriggerDescriptionForScheduleTime(context, scheduleInfo));
+ }
+ EventInfo eventInfo = ZenModeConfig.tryParseEventConditionId(rule.conditionId);
+ if (eventInfo != null) {
+ return updateTriggerDescription(rule,
+ getTriggerDescriptionForScheduleEvent(context, eventInfo));
+ }
+ Log.wtf(TAG, "Couldn't determine type of system-owned ZenRule " + rule);
+ return false;
+ }
+
+ private static boolean updateTriggerDescription(ZenRule rule, String triggerDescription) {
+ if (!Objects.equals(rule.triggerDescription, triggerDescription)) {
+ rule.triggerDescription = triggerDescription;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns a suitable trigger description for a time-schedule rule (e.g. "Mon-Fri, 8:00-10:00"),
+ * using the Context's current locale.
+ */
+ @Nullable
+ public static String getTriggerDescriptionForScheduleTime(Context context,
+ @NonNull ScheduleInfo schedule) {
+ final StringBuilder sb = new StringBuilder();
+ String daysSummary = getShortDaysSummary(context, schedule);
+ if (daysSummary == null) {
+ // no use outputting times without dates
+ return null;
+ }
+ sb.append(daysSummary);
+ sb.append(context.getString(R.string.zen_mode_trigger_summary_divider_text));
+ sb.append(context.getString(
+ R.string.zen_mode_trigger_summary_range_symbol_combination,
+ timeString(context, schedule.startHour, schedule.startMinute),
+ timeString(context, schedule.endHour, schedule.endMinute)));
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns an ordered summarized list of the days on which this schedule applies, with
+ * adjacent days grouped together ("Sun-Wed" instead of "Sun,Mon,Tue,Wed").
+ */
+ @Nullable
+ private static String getShortDaysSummary(Context context, @NonNull ScheduleInfo schedule) {
+ // Compute a list of days with contiguous days grouped together, for example: "Sun-Thu" or
+ // "Sun-Mon,Wed,Fri"
+ final int[] days = schedule.days;
+ if (days != null && days.length > 0) {
+ final StringBuilder sb = new StringBuilder();
+ final Calendar cStart = Calendar.getInstance(getLocale(context));
+ final Calendar cEnd = Calendar.getInstance(getLocale(context));
+ int[] daysOfWeek = getDaysOfWeekForLocale(cStart);
+ // the i for loop goes through days in order as determined by locale. as we walk through
+ // the days of the week, keep track of "start" and "last seen" as indicators for
+ // what's contiguous, and initialize them to something not near actual indices
+ int startDay = Integer.MIN_VALUE;
+ int lastSeenDay = Integer.MIN_VALUE;
+ for (int i = 0; i < daysOfWeek.length; i++) {
+ final int day = daysOfWeek[i];
+
+ // by default, output if this day is *not* included in the schedule, and thus
+ // ends a previously existing block. if this day is included in the schedule
+ // after all (as will be determined in the inner for loop), then output will be set
+ // to false.
+ boolean output = (i == lastSeenDay + 1);
+ for (int j = 0; j < days.length; j++) {
+ if (day == days[j]) {
+ // match for this day in the schedule (indicated by counter i)
+ if (i == lastSeenDay + 1) {
+ // contiguous to the block we're walking through right now, record it
+ // (specifically, i, the day index) and move on to the next day
+ lastSeenDay = i;
+ output = false;
+ } else {
+ // it's a match, but not 1 past the last match, we are starting a new
+ // block
+ startDay = i;
+ lastSeenDay = i;
+ }
+
+ // if there is a match on the last day, also make sure to output at the end
+ // of this loop, and mark the day as the last day we'll have seen in the
+ // scheduled days.
+ if (i == daysOfWeek.length - 1) {
+ output = true;
+ }
+ break;
+ }
+ }
+
+ // output in either of 2 cases: this day is not a match, so has ended any previous
+ // block, or this day *is* a match but is the last day of the week, so we need to
+ // summarize
+ if (output) {
+ // either describe just the single day if startDay == lastSeenDay, or
+ // output "startDay - lastSeenDay" as a group
+ if (sb.length() > 0) {
+ sb.append(
+ context.getString(R.string.zen_mode_trigger_summary_divider_text));
+ }
+
+ SimpleDateFormat dayFormat = new SimpleDateFormat("EEE", getLocale(context));
+ if (startDay == lastSeenDay) {
+ // last group was only one day
+ cStart.set(Calendar.DAY_OF_WEEK, daysOfWeek[startDay]);
+ sb.append(dayFormat.format(cStart.getTime()));
+ } else {
+ // last group was a contiguous group of days, so group them together
+ cStart.set(Calendar.DAY_OF_WEEK, daysOfWeek[startDay]);
+ cEnd.set(Calendar.DAY_OF_WEEK, daysOfWeek[lastSeenDay]);
+ sb.append(context.getString(
+ R.string.zen_mode_trigger_summary_range_symbol_combination,
+ dayFormat.format(cStart.getTime()),
+ dayFormat.format(cEnd.getTime())));
+ }
+ }
+ }
+
+ if (sb.length() > 0) {
+ return sb.toString();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Convenience method for representing the specified time in string format.
+ */
+ private static String timeString(Context context, int hour, int minute) {
+ final Calendar c = Calendar.getInstance(getLocale(context));
+ c.set(Calendar.HOUR_OF_DAY, hour);
+ c.set(Calendar.MINUTE, minute);
+ return DateFormat.getTimeFormat(context).format(c.getTime());
+ }
+
+ private static int[] getDaysOfWeekForLocale(Calendar c) {
+ int[] daysOfWeek = new int[7];
+ int currentDay = c.getFirstDayOfWeek();
+ for (int i = 0; i < daysOfWeek.length; i++) {
+ if (currentDay > 7) currentDay = 1;
+ daysOfWeek[i] = currentDay;
+ currentDay++;
+ }
+ return daysOfWeek;
+ }
+
+ private static Locale getLocale(Context context) {
+ return context.getResources().getConfiguration().getLocales().get(0);
+ }
+
+ /**
+ * Returns a suitable trigger description for a calendar-schedule rule (either the name of the
+ * calendar, or a message indicating all calendars are included).
+ */
+ public static String getTriggerDescriptionForScheduleEvent(Context context,
+ @NonNull EventInfo event) {
+ if (event.calName != null) {
+ return event.calName;
+ } else {
+ return context.getResources().getString(
+ R.string.zen_mode_trigger_event_calendar_any);
+ }
+ }
+
+ private SystemZenRules() {}
+}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 1d6dd1ebd54a..610a317db6d2 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -1773,7 +1773,12 @@ public class ZenModeConfig implements Parcelable {
return true;
}
+ /**
+ * Returns the {@link ScheduleInfo} encoded in the condition id, or {@code null} if it could not
+ * be decoded.
+ */
@UnsupportedAppUsage
+ @Nullable
public static ScheduleInfo tryParseScheduleConditionId(Uri conditionId) {
final boolean isSchedule = conditionId != null
&& Condition.SCHEME.equals(conditionId.getScheme())
@@ -1882,6 +1887,11 @@ public class ZenModeConfig implements Parcelable {
return tryParseEventConditionId(conditionId) != null;
}
+ /**
+ * Returns the {@link EventInfo} encoded in the condition id, or {@code null} if it could not be
+ * decoded.
+ */
+ @Nullable
public static EventInfo tryParseEventConditionId(Uri conditionId) {
final boolean isEvent = conditionId != null
&& Condition.SCHEME.equals(conditionId.getScheme())
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index c7e08e2b5b92..75349199895e 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5301,6 +5301,13 @@
<!-- Zen mode - Condition summary when a rule is deactivated due to a call to setInterruptionFilter(). [CHAR_LIMIT=NONE] -->
<string name="zen_mode_implicit_deactivated">Off</string>
+ <!-- [CHAR LIMIT=40] General divider text when concatenating multiple items in a text summary, used for the trigger description of a Zen mode -->
+ <string name="zen_mode_trigger_summary_divider_text">,\u0020</string>
+ <!-- [CHAR LIMIT=40] General template for a symbolic start - end range in a text summary, used for the trigger description of a Zen mode -->
+ <string name="zen_mode_trigger_summary_range_symbol_combination"><xliff:g id="start" example="Sun">%1$s</xliff:g> - <xliff:g id="end" example="Thu">%2$s</xliff:g></string>
+ <!-- [CHAR LIMIT=40] Event-based rule calendar option value for any calendar, used for the trigger description of a Zen mode -->
+ <string name="zen_mode_trigger_event_calendar_any">Any calendar</string>
+
<!-- Indication that the current volume and other effects (vibration) are being suppressed by a third party, such as a notification listener. [CHAR LIMIT=30] -->
<string name="muted_by"><xliff:g id="third_party">%1$s</xliff:g> is muting some sounds</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c603fa78b08e..abc9954daaa2 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2609,6 +2609,10 @@
<java-symbol type="string" name="zen_mode_implicit_trigger_description" />
<java-symbol type="string" name="zen_mode_implicit_activated" />
<java-symbol type="string" name="zen_mode_implicit_deactivated" />
+ <java-symbol type="string" name="zen_mode_trigger_summary_divider_text" />
+ <java-symbol type="string" name="zen_mode_trigger_summary_range_symbol_combination" />
+ <java-symbol type="string" name="zen_mode_trigger_event_calendar_any" />
+
<java-symbol type="string" name="display_rotation_camera_compat_toast_after_rotation" />
<java-symbol type="string" name="display_rotation_camera_compat_toast_in_multi_window" />
<java-symbol type="array" name="config_system_condition_providers" />
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index e80c79a8cffb..9dc131371a0e 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1885,7 +1885,7 @@ public class NotificationManagerService extends SystemService {
if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
// update system notification channels
SystemNotificationChannels.createAll(context);
- mZenModeHelper.updateDefaultZenRules(Binder.getCallingUid());
+ mZenModeHelper.updateZenRulesOnLocaleChange();
mPreferencesHelper.onLocaleChanged(context, ActivityManager.getCurrentUser());
}
}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 289faf45a253..20b7fd46beb5 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -86,6 +86,7 @@ import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.ConditionProviderService;
import android.service.notification.DeviceEffectsApplier;
+import android.service.notification.SystemZenRules;
import android.service.notification.ZenAdapters;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
@@ -214,7 +215,7 @@ public class ZenModeHelper {
mNotificationManager = context.getSystemService(NotificationManager.class);
mDefaultConfig = readDefaultConfig(mContext.getResources());
- updateDefaultAutomaticRuleNames();
+ updateDefaultConfigAutomaticRules();
if (Flags.modesApi()) {
updateDefaultAutomaticRulePolicies();
}
@@ -1020,27 +1021,41 @@ public class ZenModeHelper {
}
}
- protected void updateDefaultZenRules(int callingUid) {
- updateDefaultAutomaticRuleNames();
+ void updateZenRulesOnLocaleChange() {
+ updateDefaultConfigAutomaticRules();
synchronized (mConfigLock) {
+ if (mConfig == null) {
+ return;
+ }
+ ZenModeConfig config = mConfig.copy();
+ boolean updated = false;
for (ZenRule defaultRule : mDefaultConfig.automaticRules.values()) {
- ZenRule currRule = mConfig.automaticRules.get(defaultRule.id);
- // if default rule wasn't user-modified nor enabled, use localized name
+ ZenRule currRule = config.automaticRules.get(defaultRule.id);
+ // if default rule wasn't user-modified use localized name
// instead of previous system name
- if (currRule != null && !currRule.modified && !currRule.enabled
+ if (currRule != null
+ && !currRule.modified
+ && (currRule.zenPolicyUserModifiedFields & AutomaticZenRule.FIELD_NAME) == 0
&& !defaultRule.name.equals(currRule.name)) {
- if (canManageAutomaticZenRule(currRule)) {
- if (DEBUG) {
- Slog.d(TAG, "Locale change - updating default zen rule name "
- + "from " + currRule.name + " to " + defaultRule.name);
- }
- // update default rule (if locale changed, name of rule will change)
- currRule.name = defaultRule.name;
- updateAutomaticZenRule(defaultRule.id, zenRuleToAutomaticZenRule(currRule),
- UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "locale changed", callingUid);
+ if (DEBUG) {
+ Slog.d(TAG, "Locale change - updating default zen rule name "
+ + "from " + currRule.name + " to " + defaultRule.name);
+ }
+ currRule.name = defaultRule.name;
+ updated = true;
+ }
+ }
+ if (Flags.modesApi() && Flags.modesUi()) {
+ for (ZenRule rule : config.automaticRules.values()) {
+ if (SystemZenRules.isSystemOwnedRule(rule)) {
+ updated |= SystemZenRules.updateTriggerDescription(mContext, rule);
}
}
}
+ if (updated) {
+ setConfigLocked(config, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI,
+ "updateZenRulesOnLocaleChange", Process.SYSTEM_UID);
+ }
}
}
@@ -1631,6 +1646,10 @@ public class ZenModeHelper {
reason += ", reset to default rules";
}
+ if (Flags.modesApi() && Flags.modesUi()) {
+ SystemZenRules.maybeUpgradeRules(mContext, config);
+ }
+
// Resolve user id for settings.
userId = userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM : userId;
if (config.version < ZenModeConfig.XML_VERSION_ZEN_UPGRADE) {
@@ -2054,7 +2073,7 @@ public class ZenModeHelper {
}
}
- private void updateDefaultAutomaticRuleNames() {
+ private void updateDefaultConfigAutomaticRules() {
for (ZenRule rule : mDefaultConfig.automaticRules.values()) {
if (ZenModeConfig.EVENTS_DEFAULT_RULE_ID.equals(rule.id)) {
rule.name = mContext.getResources()
@@ -2063,6 +2082,9 @@ public class ZenModeHelper {
rule.name = mContext.getResources()
.getString(R.string.zen_mode_default_every_night_name);
}
+ if (Flags.modesApi() && Flags.modesUi()) {
+ SystemZenRules.updateTriggerDescription(mContext, rule);
+ }
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 99ab40569b70..9b35c80ce867 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -5693,8 +5693,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.mLocaleChangeReceiver.onReceive(mContext,
new Intent(Intent.ACTION_LOCALE_CHANGED));
- verify(mZenModeHelper, times(1)).updateDefaultZenRules(
- anyInt());
+ verify(mZenModeHelper).updateZenRulesOnLocaleChange();
}
private void simulateNotificationTimeoutBroadcast(String notificationKey) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SystemZenRulesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SystemZenRulesTest.java
new file mode 100644
index 000000000000..e78246110e99
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SystemZenRulesTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2024 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 android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleTime;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AutomaticZenRule;
+import android.content.res.Configuration;
+import android.os.LocaleList;
+import android.service.notification.SystemZenRules;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.EventInfo;
+import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.service.notification.ZenModeConfig.ZenRule;
+
+import com.android.internal.R;
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Calendar;
+import java.util.Locale;
+
+public class SystemZenRulesTest extends UiServiceTestCase {
+
+ private static final ScheduleInfo SCHEDULE_INFO;
+ private static final EventInfo EVENT_INFO;
+
+ static {
+ SCHEDULE_INFO = new ScheduleInfo();
+ SCHEDULE_INFO.days = new int[] { Calendar.WEDNESDAY };
+ SCHEDULE_INFO.startHour = 8;
+ SCHEDULE_INFO.endHour = 9;
+ EVENT_INFO = new EventInfo();
+ EVENT_INFO.calendarId = 1L;
+ EVENT_INFO.calName = "myCalendar";
+ }
+
+ @Before
+ public void setUp() {
+ Configuration config = new Configuration();
+ config.setLocales(new LocaleList(Locale.US));
+ mContext.getOrCreateTestableResources().overrideConfiguration(config);
+ mContext.getOrCreateTestableResources().addOverride(
+ R.string.zen_mode_trigger_summary_range_symbol_combination, "%1$s-%2$s");
+ mContext.getOrCreateTestableResources().addOverride(
+ R.string.zen_mode_trigger_summary_divider_text, ",");
+ }
+
+ @Test
+ public void maybeUpgradeRules_oldSystemRules_upgraded() {
+ ZenModeConfig config = new ZenModeConfig();
+ ZenRule timeRule = new ZenRule();
+ timeRule.pkg = SystemZenRules.PACKAGE_ANDROID;
+ timeRule.conditionId = ZenModeConfig.toScheduleConditionId(SCHEDULE_INFO);
+ config.automaticRules.put("time", timeRule);
+ ZenRule calendarRule = new ZenRule();
+ calendarRule.pkg = SystemZenRules.PACKAGE_ANDROID;
+ calendarRule.conditionId = ZenModeConfig.toEventConditionId(EVENT_INFO);
+ config.automaticRules.put("calendar", calendarRule);
+
+ SystemZenRules.maybeUpgradeRules(mContext, config);
+
+ assertThat(timeRule.type).isEqualTo(AutomaticZenRule.TYPE_SCHEDULE_TIME);
+ assertThat(timeRule.triggerDescription).isNotEmpty();
+ assertThat(calendarRule.type).isEqualTo(AutomaticZenRule.TYPE_SCHEDULE_CALENDAR);
+ assertThat(timeRule.triggerDescription).isNotEmpty();
+ }
+
+ @Test
+ public void maybeUpgradeRules_newSystemRules_untouched() {
+ ZenModeConfig config = new ZenModeConfig();
+ ZenRule timeRule = new ZenRule();
+ timeRule.pkg = SystemZenRules.PACKAGE_ANDROID;
+ timeRule.type = AutomaticZenRule.TYPE_SCHEDULE_TIME;
+ timeRule.conditionId = ZenModeConfig.toScheduleConditionId(SCHEDULE_INFO);
+ config.automaticRules.put("time", timeRule);
+ ZenRule original = timeRule.copy();
+
+ SystemZenRules.maybeUpgradeRules(mContext, config);
+
+ assertThat(timeRule).isEqualTo(original);
+ }
+
+ @Test
+ public void maybeUpgradeRules_appOwnedRules_untouched() {
+ ZenModeConfig config = new ZenModeConfig();
+ ZenRule timeRule = new ZenRule();
+ timeRule.pkg = "some_other_package";
+ timeRule.type = AutomaticZenRule.TYPE_SCHEDULE_TIME;
+ timeRule.conditionId = ZenModeConfig.toScheduleConditionId(SCHEDULE_INFO);
+ config.automaticRules.put("time", timeRule);
+ ZenRule original = timeRule.copy();
+
+ SystemZenRules.maybeUpgradeRules(mContext, config);
+
+ assertThat(timeRule).isEqualTo(original);
+ }
+
+ @Test
+ public void getTriggerDescriptionForScheduleTime_noOrSingleDays() {
+ // Test various cases for grouping and not-grouping of days.
+ ScheduleInfo scheduleInfo = new ScheduleInfo();
+ scheduleInfo.startHour = 10;
+ scheduleInfo.endHour = 16;
+
+ // No days
+ scheduleInfo.days = new int[]{};
+ assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo)).isNull();
+
+ // A single day at the beginning of the week
+ scheduleInfo.days = new int[]{Calendar.SUNDAY};
+ assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo))
+ .isEqualTo("Sun,10:00 AM-4:00 PM");
+
+ // A single day in the middle of the week
+ scheduleInfo.days = new int[]{Calendar.THURSDAY};
+ assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo))
+ .isEqualTo("Thu,10:00 AM-4:00 PM");
+
+ // A single day at the end of the week
+ scheduleInfo.days = new int[]{Calendar.SATURDAY};
+ assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo))
+ .isEqualTo("Sat,10:00 AM-4:00 PM");
+ }
+
+ @Test
+ public void getTriggerDescriptionForScheduleTime_oneGroup() {
+ ScheduleInfo scheduleInfo = new ScheduleInfo();
+ scheduleInfo.startHour = 10;
+ scheduleInfo.endHour = 16;
+
+ // The whole week
+ scheduleInfo.days = new int[] {Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY,
+ Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY};
+ assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo))
+ .isEqualTo("Sun-Sat,10:00 AM-4:00 PM");
+
+ // Various cases of one big group
+ // Sunday through Thursday
+ scheduleInfo.days = new int[] {Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY,
+ Calendar.WEDNESDAY, Calendar.THURSDAY};
+ assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo))
+ .isEqualTo("Sun-Thu,10:00 AM-4:00 PM");
+
+ // Wednesday through Saturday
+ scheduleInfo.days = new int[] {Calendar.WEDNESDAY, Calendar.THURSDAY,
+ Calendar.FRIDAY, Calendar.SATURDAY};
+ assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo))
+ .isEqualTo("Wed-Sat,10:00 AM-4:00 PM");
+
+ // Monday through Friday
+ scheduleInfo.days = new int[] {Calendar.MONDAY, Calendar.TUESDAY,
+ Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY};
+ assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo))
+ .isEqualTo("Mon-Fri,10:00 AM-4:00 PM");
+ }
+
+ @Test
+ public void getTriggerDescriptionForScheduleTime_mixedDays() {
+ ScheduleInfo scheduleInfo = new ScheduleInfo();
+ scheduleInfo.startHour = 10;
+ scheduleInfo.endHour = 16;
+
+ // cases combining groups and single days scattered around
+ scheduleInfo.days = new int[] {Calendar.SUNDAY, Calendar.TUESDAY,
+ Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.SATURDAY};
+ assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo))
+ .isEqualTo("Sun,Tue-Thu,Sat,10:00 AM-4:00 PM");
+
+ scheduleInfo.days = new int[] {Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY,
+ Calendar.WEDNESDAY, Calendar.FRIDAY, Calendar.SATURDAY};
+ assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo))
+ .isEqualTo("Sun-Wed,Fri-Sat,10:00 AM-4:00 PM");
+
+ scheduleInfo.days = new int[] {Calendar.MONDAY, Calendar.WEDNESDAY,
+ Calendar.FRIDAY, Calendar.SATURDAY};
+ assertThat(getTriggerDescriptionForScheduleTime(mContext, scheduleInfo))
+ .isEqualTo("Mon,Wed,Fri-Sat,10:00 AM-4:00 PM");
+ }
+}
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 c4d246052580..ac7c2f4bd07a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -136,6 +136,7 @@ import android.provider.Settings;
import android.provider.Settings.Global;
import android.service.notification.Condition;
import android.service.notification.DeviceEffectsApplier;
+import android.service.notification.SystemZenRules;
import android.service.notification.ZenAdapters;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
@@ -193,6 +194,7 @@ import java.time.Instant;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
+import java.util.Calendar;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
@@ -1087,6 +1089,11 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.mConfig.manualRule.enabled = true;
ZenModeConfig expected = mZenModeHelper.mConfig.copy();
+ if (Flags.modesUi()) {
+ // Reading the configuration will upgrade it, so for equality comparison also upgrade
+ // the expected value.
+ SystemZenRules.maybeUpgradeRules(mContext, expected);
+ }
ByteArrayOutputStream baos = writeXmlAndPurge(null);
TypedXmlPullParser parser = getParserForByteStream(baos);
@@ -1404,6 +1411,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.readXml(parser, true, 11);
ZenModeConfig actual = mZenModeHelper.mConfigs.get(10);
+ if (Flags.modesUi()) {
+ // Reading the configuration will upgrade it, so for equality comparison also upgrade
+ // the expected value.
+ SystemZenRules.maybeUpgradeRules(mContext, config10);
+ SystemZenRules.maybeUpgradeRules(mContext, config11);
+ }
+
assertEquals(
"Config mismatch: current vs expected: "
+ new ZenModeDiff.ConfigDiff(actual, config10), config10, actual);
@@ -1483,6 +1497,11 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.mConfig.automaticRules = automaticRules;
ZenModeConfig expected = mZenModeHelper.mConfig.copy();
+ if (Flags.modesUi()) {
+ // Reading the configuration will upgrade it, so for equality comparison also upgrade
+ // the expected value.
+ SystemZenRules.maybeUpgradeRules(mContext, expected);
+ }
ByteArrayOutputStream baos = writeXmlAndPurge(null);
TypedXmlPullParser parser = Xml.newFastPullParser();
@@ -1494,7 +1513,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.ZenRule original = expected.automaticRules.get(ruleId);
ZenModeConfig.ZenRule current = mZenModeHelper.mConfig.automaticRules.get(ruleId);
- assertEquals("Automatic rules mismatch", original, current);
+ assertEquals("Automatic rules mismatch: current vs expected: "
+ + new ZenModeDiff.RuleDiff(original, current), original, current);
}
@Test
@@ -1524,6 +1544,11 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.mConfig.automaticRules = automaticRules;
ZenModeConfig expected = mZenModeHelper.mConfig.copy();
+ if (Flags.modesUi()) {
+ // Reading the configuration will upgrade it, so for equality comparison also upgrade
+ // the expected value.
+ SystemZenRules.maybeUpgradeRules(mContext, expected);
+ }
ByteArrayOutputStream baos = writeXmlAndPurgeForUser(null, UserHandle.USER_SYSTEM);
TypedXmlPullParser parser = getParserForByteStream(baos);
@@ -1532,7 +1557,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.ZenRule original = expected.automaticRules.get(ruleId);
ZenModeConfig.ZenRule current = mZenModeHelper.mConfig.automaticRules.get(ruleId);
- assertEquals("Automatic rules mismatch", original, current);
+ assertEquals("Automatic rules mismatch: current vs expected: "
+ + new ZenModeDiff.RuleDiff(original, current), original, current);
}
@Test
@@ -2112,7 +2138,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig config = new ZenModeConfig();
config.automaticRules = new ArrayMap<>();
mZenModeHelper.mConfig = config;
- mZenModeHelper.updateDefaultZenRules(Process.SYSTEM_UID); // shouldn't throw null pointer
+ mZenModeHelper.updateZenRulesOnLocaleChange(); // shouldn't throw null pointer
mZenModeHelper.pullRules(events); // shouldn't throw null pointer
}
@@ -2137,33 +2163,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
autoRules.put(SCHEDULE_DEFAULT_RULE_ID, updatedDefaultRule);
mZenModeHelper.mConfig.automaticRules = autoRules;
- mZenModeHelper.updateDefaultZenRules(Process.SYSTEM_UID);
- assertEquals(updatedDefaultRule,
- mZenModeHelper.mConfig.automaticRules.get(SCHEDULE_DEFAULT_RULE_ID));
- }
-
- @Test
- public void testDoNotUpdateEnabledDefaultAutoRule() {
- // mDefaultConfig is set to default config in setup by getDefaultConfigParser
- when(mContext.checkCallingPermission(anyString()))
- .thenReturn(PERMISSION_GRANTED);
-
- // shouldn't update the rule that's enabled
- ZenModeConfig.ZenRule updatedDefaultRule = new ZenModeConfig.ZenRule();
- updatedDefaultRule.enabled = true;
- updatedDefaultRule.modified = false;
- updatedDefaultRule.creationTime = 0;
- updatedDefaultRule.id = SCHEDULE_DEFAULT_RULE_ID;
- updatedDefaultRule.name = "Schedule Default Rule";
- updatedDefaultRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- updatedDefaultRule.conditionId = ZenModeConfig.toScheduleConditionId(new ScheduleInfo());
- updatedDefaultRule.component = new ComponentName("android", "ScheduleConditionProvider");
-
- ArrayMap<String, ZenModeConfig.ZenRule> autoRules = new ArrayMap<>();
- autoRules.put(SCHEDULE_DEFAULT_RULE_ID, updatedDefaultRule);
- mZenModeHelper.mConfig.automaticRules = autoRules;
-
- mZenModeHelper.updateDefaultZenRules(Process.SYSTEM_UID);
+ mZenModeHelper.updateZenRulesOnLocaleChange();
assertEquals(updatedDefaultRule,
mZenModeHelper.mConfig.automaticRules.get(SCHEDULE_DEFAULT_RULE_ID));
}
@@ -2177,27 +2177,35 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// will update rule that is not enabled and modified
ZenModeConfig.ZenRule customDefaultRule = new ZenModeConfig.ZenRule();
+ customDefaultRule.pkg = SystemZenRules.PACKAGE_ANDROID;
customDefaultRule.enabled = false;
customDefaultRule.modified = false;
customDefaultRule.creationTime = 0;
customDefaultRule.id = SCHEDULE_DEFAULT_RULE_ID;
customDefaultRule.name = "Schedule Default Rule";
customDefaultRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- customDefaultRule.conditionId = ZenModeConfig.toScheduleConditionId(new ScheduleInfo());
+ ScheduleInfo scheduleInfo = new ScheduleInfo();
+ scheduleInfo.days = new int[] { Calendar.SUNDAY };
+ scheduleInfo.startHour = 18;
+ scheduleInfo.endHour = 19;
+ customDefaultRule.conditionId = ZenModeConfig.toScheduleConditionId(scheduleInfo);
customDefaultRule.component = new ComponentName("android", "ScheduleConditionProvider");
ArrayMap<String, ZenModeConfig.ZenRule> autoRules = new ArrayMap<>();
autoRules.put(SCHEDULE_DEFAULT_RULE_ID, customDefaultRule);
mZenModeHelper.mConfig.automaticRules = autoRules;
- mZenModeHelper.updateDefaultZenRules(Process.SYSTEM_UID);
+ mZenModeHelper.updateZenRulesOnLocaleChange();
ZenModeConfig.ZenRule ruleAfterUpdating =
mZenModeHelper.mConfig.automaticRules.get(SCHEDULE_DEFAULT_RULE_ID);
assertEquals(customDefaultRule.enabled, ruleAfterUpdating.enabled);
assertEquals(customDefaultRule.modified, ruleAfterUpdating.modified);
assertEquals(customDefaultRule.id, ruleAfterUpdating.id);
assertEquals(customDefaultRule.conditionId, ruleAfterUpdating.conditionId);
- assertFalse(Objects.equals(defaultRuleName, ruleAfterUpdating.name)); // update name
+ assertNotEquals(defaultRuleName, ruleAfterUpdating.name); // update name
+ if (Flags.modesUi()) {
+ assertThat(ruleAfterUpdating.triggerDescription).isNotEmpty(); // update trigger desc
+ }
}
@Test
diff --git a/tests/testables/src/android/testing/TestableResources.java b/tests/testables/src/android/testing/TestableResources.java
index 0ec106e329f6..384a21e7c91a 100644
--- a/tests/testables/src/android/testing/TestableResources.java
+++ b/tests/testables/src/android/testing/TestableResources.java
@@ -26,6 +26,8 @@ import android.util.SparseArray;
import org.mockito.invocation.InvocationOnMock;
+import java.util.Arrays;
+
/**
* Provides a version of Resources that defaults to all existing resources, but can have ids
* changed to return specific values.
@@ -103,6 +105,15 @@ public class TestableResources {
if (index >= 0) {
Object value = mOverrides.valueAt(index);
if (value == null) throw new Resources.NotFoundException();
+ // Support for Resources.getString(resId, Object... formatArgs)
+ if (value instanceof String
+ && invocationOnMock.getMethod().getName().equals("getString")
+ && invocationOnMock.getArguments().length > 1) {
+ value = String.format(mResources.getConfiguration().getLocales().get(0),
+ (String) value,
+ Arrays.copyOfRange(invocationOnMock.getArguments(), 1,
+ invocationOnMock.getArguments().length));
+ }
return value;
}
} catch (Resources.NotFoundException e) {