1 /*
2  * Copyright (C) 2013 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.deskclock.provider;
18 
19 import android.content.Context;
20 
21 import com.android.deskclock.R;
22 
23 import java.text.DateFormatSymbols;
24 import java.util.Calendar;
25 import java.util.HashSet;
26 
27 /*
28  * Days of week code as a single int.
29  * 0x00: no day
30  * 0x01: Monday
31  * 0x02: Tuesday
32  * 0x04: Wednesday
33  * 0x08: Thursday
34  * 0x10: Friday
35  * 0x20: Saturday
36  * 0x40: Sunday
37  */
38 public final class DaysOfWeek {
39     // Number if days in the week.
40     public static final int DAYS_IN_A_WEEK = 7;
41 
42     // Value when all days are set
43     public static final int ALL_DAYS_SET = 0x7f;
44 
45     // Value when no days are set
46     public static final int NO_DAYS_SET = 0;
47 
48     /**
49      * Need to have monday start at index 0 to be backwards compatible. This converts
50      * Calendar.DAY_OF_WEEK constants to our internal bit structure.
51      */
convertDayToBitIndex(int day)52     static int convertDayToBitIndex(int day) {
53         return (day + 5) % DAYS_IN_A_WEEK;
54     }
55 
56     /**
57      * Need to have monday start at index 0 to be backwards compatible. This converts
58      * our bit structure to Calendar.DAY_OF_WEEK constant value.
59      */
convertBitIndexToDay(int bitIndex)60     static int convertBitIndexToDay(int bitIndex) {
61         return (bitIndex + 1) % DAYS_IN_A_WEEK + 1;
62     }
63 
64     // Bitmask of all repeating days
65     private int mBitSet;
66 
DaysOfWeek(int bitSet)67     public DaysOfWeek(int bitSet) {
68         mBitSet = bitSet;
69     }
70 
toString(Context context, int firstDay)71     public String toString(Context context, int firstDay) {
72         return toString(context, firstDay, false /* forAccessibility */);
73     }
74 
toAccessibilityString(Context context, int firstDay)75     public String toAccessibilityString(Context context, int firstDay) {
76         return toString(context, firstDay, true /* forAccessibility */);
77     }
78 
toString(Context context, int firstDay, boolean forAccessibility)79     private String toString(Context context, int firstDay, boolean forAccessibility) {
80         StringBuilder ret = new StringBuilder();
81 
82         // no days
83         if (mBitSet == NO_DAYS_SET) {
84             return "";
85         }
86 
87         // every day
88         if (mBitSet == ALL_DAYS_SET) {
89             return context.getText(R.string.every_day).toString();
90         }
91 
92         // count selected days
93         int dayCount = 0;
94         int bitSet = mBitSet;
95         while (bitSet > 0) {
96             if ((bitSet & 1) == 1) dayCount++;
97             bitSet >>= 1;
98         }
99 
100         // short or long form?
101         DateFormatSymbols dfs = new DateFormatSymbols();
102         String[] dayList = (forAccessibility || dayCount <= 1) ?
103                 dfs.getWeekdays() :
104                 dfs.getShortWeekdays();
105 
106         // In this system, Mon = 0, Sun = 6, etc.
107         // startDay is stored corresponding to Calendar.DAY_OF_WEEK where Sun = 0, Mon = 2, etc.
108         final int startDay = convertDayToBitIndex(firstDay);
109 
110         // selected days, starting from user-selected start day of week
111         // iterate starting from user-selected start of day
112         for (int bitIndex = startDay; bitIndex < DAYS_IN_A_WEEK + startDay; ++bitIndex) {
113             if ((mBitSet & (1 << (bitIndex % DAYS_IN_A_WEEK))) != 0) {
114                 ret.append(dayList[convertBitIndexToDay(bitIndex)]);
115                 dayCount -= 1;
116                 if (dayCount > 0) ret.append(context.getText(R.string.day_concat));
117             }
118         }
119         return ret.toString();
120     }
121 
122     /**
123      * Enables or disable certain days of the week.
124      *
125      * @param daysOfWeek Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, etc.
126      */
setDaysOfWeek(boolean value, int ... daysOfWeek)127     public void setDaysOfWeek(boolean value, int ... daysOfWeek) {
128         for (int day : daysOfWeek) {
129             setBit(convertDayToBitIndex(day), value);
130         }
131     }
132 
isBitEnabled(int bitIndex)133     private boolean isBitEnabled(int bitIndex) {
134         return ((mBitSet & (1 << bitIndex)) > 0);
135     }
136 
setBit(int bitIndex, boolean set)137     private void setBit(int bitIndex, boolean set) {
138         if (set) {
139             mBitSet |= (1 << bitIndex);
140         } else {
141             mBitSet &= ~(1 << bitIndex);
142         }
143     }
144 
setBitSet(int bitSet)145     public void setBitSet(int bitSet) {
146         mBitSet = bitSet;
147     }
148 
getBitSet()149     public int getBitSet() {
150         return mBitSet;
151     }
152 
153     /**
154      * Returns set of Calendar.MONDAY, Calendar.TUESDAY, etc based on the current mBitSet value
155      */
getSetDays()156     public HashSet<Integer> getSetDays() {
157         final HashSet<Integer> result = new HashSet<Integer>();
158         for (int bitIndex = 0; bitIndex < DAYS_IN_A_WEEK; bitIndex++) {
159             if (isBitEnabled(bitIndex)) {
160                 result.add(convertBitIndexToDay(bitIndex));
161             }
162         }
163         return result;
164     }
165 
isRepeating()166     public boolean isRepeating() {
167         return mBitSet != NO_DAYS_SET;
168     }
169 
170     /**
171      * Returns number of days backwards from today to previous alarm.
172      * ex:
173      * Daily alarm, current = Tuesday -> 1
174      * Weekly alarm on Wednesday, current = Tuesday -> 6
175      * One time alarm -> -1
176      *
177      * @param current must be set to today
178      */
calculateDaysToPreviousAlarm(Calendar current)179     public int calculateDaysToPreviousAlarm(Calendar current) {
180         if (!isRepeating()) {
181             return -1;
182         }
183 
184         // We only use this on preemptively dismissed alarms, and alarms can only fire once a day,
185         // so there is no chance that the previous fire time is on the same day. Start dayCount on
186         // previous day.
187         int dayCount = -1;
188         int currentDayIndex = convertDayToBitIndex(current.get(Calendar.DAY_OF_WEEK));
189         for (; dayCount >= -DAYS_IN_A_WEEK; dayCount--) {
190             int previousAlarmBitIndex = (currentDayIndex + dayCount);
191             if (previousAlarmBitIndex < 0) {
192                 // Ex. previousAlarmBitIndex = -1 means the day before index 0 = index 6
193                 previousAlarmBitIndex = previousAlarmBitIndex + DAYS_IN_A_WEEK;
194             }
195             if (isBitEnabled(previousAlarmBitIndex)) {
196                 break;
197             }
198         }
199         // return a positive value
200         return dayCount * -1;
201     }
202 
203     /**
204      * Returns number of days from today until next alarm.
205      *
206      * @param current must be set to today or the day after the currentTime
207      */
calculateDaysToNextAlarm(Calendar current)208     public int calculateDaysToNextAlarm(Calendar current) {
209         if (!isRepeating()) {
210             return -1;
211         }
212 
213         int dayCount = 0;
214         int currentDayIndex = convertDayToBitIndex(current.get(Calendar.DAY_OF_WEEK));
215         for (; dayCount < DAYS_IN_A_WEEK; dayCount++) {
216             int nextAlarmBitIndex = (currentDayIndex + dayCount) % DAYS_IN_A_WEEK;
217             if (isBitEnabled(nextAlarmBitIndex)) {
218                 break;
219             }
220         }
221         return dayCount;
222     }
223 
clearAllDays()224     public void clearAllDays() {
225         mBitSet = NO_DAYS_SET;
226     }
227 
228     @Override
toString()229     public String toString() {
230         return "DaysOfWeek{" +
231                 "mBitSet=" + mBitSet +
232                 '}';
233     }
234 }
235