diff options
| author | 2024-03-21 10:55:10 +0100 | |
|---|---|---|
| committer | 2024-03-26 11:55:37 +0100 | |
| commit | a5504bae1e344b5c670a9f60d6c21f355501e48c (patch) | |
| tree | b7d7d9c699f3cb23ab58e35874ccfa3aae746365 | |
| parent | 97c7930d070bddf59c9cfa5d780a836ef7096b36 (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
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) { |