1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.notification.zen; 18 19 import android.content.Context; 20 import android.service.notification.ZenModeConfig.ScheduleInfo; 21 import android.text.format.DateFormat; 22 23 import com.android.internal.annotations.VisibleForTesting; 24 import com.android.settings.R; 25 26 import java.text.SimpleDateFormat; 27 import java.util.Calendar; 28 import java.util.Locale; 29 30 /** 31 * Helper class for shared functionality regarding descriptions of custom zen rule schedules. 32 */ 33 public class ZenRuleScheduleHelper { 34 // per-instance to ensure we're always using the current locale 35 private SimpleDateFormat mDayFormat; 36 37 // Default constructor, which will use the current locale. ZenRuleScheduleHelper()38 public ZenRuleScheduleHelper() { 39 mDayFormat = new SimpleDateFormat("EEE"); 40 } 41 42 // Constructor for tests to provide an explicit locale 43 @VisibleForTesting ZenRuleScheduleHelper(Locale locale)44 public ZenRuleScheduleHelper(Locale locale) { 45 mDayFormat = new SimpleDateFormat("EEE", locale); 46 } 47 48 /** 49 * Returns an ordered, comma-separated list of the days that a schedule applies, or null if no 50 * days. 51 */ getDaysDescription(Context context, ScheduleInfo schedule)52 public String getDaysDescription(Context context, ScheduleInfo schedule) { 53 // Compute an ordered, delimited list of day names based on the persisted user config. 54 final int[] days = schedule.days; 55 if (days != null && days.length > 0) { 56 final StringBuilder sb = new StringBuilder(); 57 final Calendar c = Calendar.getInstance(); 58 int[] daysOfWeek = ZenModeScheduleDaysSelection.getDaysOfWeekForLocale(c); 59 for (int i = 0; i < daysOfWeek.length; i++) { 60 final int day = daysOfWeek[i]; 61 for (int j = 0; j < days.length; j++) { 62 if (day == days[j]) { 63 c.set(Calendar.DAY_OF_WEEK, day); 64 if (sb.length() > 0) { 65 sb.append(context.getString(R.string.summary_divider_text)); 66 } 67 sb.append(mDayFormat.format(c.getTime())); 68 break; 69 } 70 } 71 } 72 73 if (sb.length() > 0) { 74 return sb.toString(); 75 } 76 } 77 return null; 78 } 79 80 /** 81 * Returns an ordered summarized list of the days on which this schedule applies, with 82 * adjacent days grouped together ("Sun-Wed" instead of "Sun,Mon,Tue,Wed"). 83 */ getShortDaysSummary(Context context, ScheduleInfo schedule)84 public String getShortDaysSummary(Context context, ScheduleInfo schedule) { 85 // Compute a list of days with contiguous days grouped together, for example: "Sun-Thu" or 86 // "Sun-Mon,Wed,Fri" 87 final int[] days = schedule.days; 88 if (days != null && days.length > 0) { 89 final StringBuilder sb = new StringBuilder(); 90 final Calendar cStart = Calendar.getInstance(); 91 final Calendar cEnd = Calendar.getInstance(); 92 int[] daysOfWeek = ZenModeScheduleDaysSelection.getDaysOfWeekForLocale(cStart); 93 // the i for loop goes through days in order as determined by locale. as we walk through 94 // the days of the week, keep track of "start" and "last seen" as indicators for 95 // what's contiguous, and initialize them to something not near actual indices 96 int startDay = Integer.MIN_VALUE; 97 int lastSeenDay = Integer.MIN_VALUE; 98 for (int i = 0; i < daysOfWeek.length; i++) { 99 final int day = daysOfWeek[i]; 100 101 // by default, output if this day is *not* included in the schedule, and thus 102 // ends a previously existing block. if this day is included in the schedule 103 // after all (as will be determined in the inner for loop), then output will be set 104 // to false. 105 boolean output = (i == lastSeenDay + 1); 106 for (int j = 0; j < days.length; j++) { 107 if (day == days[j]) { 108 // match for this day in the schedule (indicated by counter i) 109 if (i == lastSeenDay + 1) { 110 // contiguous to the block we're walking through right now, record it 111 // (specifically, i, the day index) and move on to the next day 112 lastSeenDay = i; 113 output = false; 114 } else { 115 // it's a match, but not 1 past the last match, we are starting a new 116 // block 117 startDay = i; 118 lastSeenDay = i; 119 } 120 121 // if there is a match on the last day, also make sure to output at the end 122 // of this loop, and mark the day as the last day we'll have seen in the 123 // scheduled days. 124 if (i == daysOfWeek.length - 1) { 125 output = true; 126 } 127 break; 128 } 129 } 130 131 // output in either of 2 cases: this day is not a match, so has ended any previous 132 // block, or this day *is* a match but is the last day of the week, so we need to 133 // summarize 134 if (output) { 135 // either describe just the single day if startDay == lastSeenDay, or 136 // output "startDay - lastSeenDay" as a group 137 if (sb.length() > 0) { 138 sb.append(context.getString(R.string.summary_divider_text)); 139 } 140 141 if (startDay == lastSeenDay) { 142 // last group was only one day 143 cStart.set(Calendar.DAY_OF_WEEK, daysOfWeek[startDay]); 144 sb.append(mDayFormat.format(cStart.getTime())); 145 } else { 146 // last group was a contiguous group of days, so group them together 147 cStart.set(Calendar.DAY_OF_WEEK, daysOfWeek[startDay]); 148 cEnd.set(Calendar.DAY_OF_WEEK, daysOfWeek[lastSeenDay]); 149 sb.append(context.getString(R.string.summary_range_symbol_combination, 150 mDayFormat.format(cStart.getTime()), 151 mDayFormat.format(cEnd.getTime()))); 152 } 153 } 154 } 155 156 if (sb.length() > 0) { 157 return sb.toString(); 158 } 159 } 160 return null; 161 } 162 163 /** 164 * Convenience method for representing the specified time in string format. 165 */ timeString(Context context, int hour, int minute)166 private String timeString(Context context, int hour, int minute) { 167 final Calendar c = Calendar.getInstance(); 168 c.set(Calendar.HOUR_OF_DAY, hour); 169 c.set(Calendar.MINUTE, minute); 170 return DateFormat.getTimeFormat(context).format(c.getTime()); 171 } 172 173 /** 174 * Combination description for a zen rule schedule including both day summary and time bounds. 175 */ getDaysAndTimeSummary(Context context, ScheduleInfo schedule)176 public String getDaysAndTimeSummary(Context context, ScheduleInfo schedule) { 177 final StringBuilder sb = new StringBuilder(); 178 String daysSummary = getShortDaysSummary(context, schedule); 179 if (daysSummary == null) { 180 // no use outputting times without dates 181 return null; 182 } 183 sb.append(daysSummary); 184 sb.append(context.getString(R.string.summary_divider_text)); 185 sb.append(context.getString(R.string.summary_range_symbol_combination, 186 timeString(context, schedule.startHour, schedule.startMinute), 187 timeString(context, schedule.endHour, schedule.endMinute))); 188 189 return sb.toString(); 190 } 191 } 192