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
17package com.android.deskclock.provider;
18
19import android.content.Context;
20
21import com.android.deskclock.R;
22
23import java.text.DateFormatSymbols;
24import java.util.Calendar;
25import 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 */
38public 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     */
52    private 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     */
60    private 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
67    public DaysOfWeek(int bitSet) {
68        mBitSet = bitSet;
69    }
70
71    public String toString(Context context, boolean showNever) {
72        return toString(context, showNever, false);
73    }
74
75    public String toAccessibilityString(Context context) {
76        return toString(context, false, true);
77    }
78
79    private String toString(Context context, boolean showNever, boolean forAccessibility) {
80        StringBuilder ret = new StringBuilder();
81
82        // no days
83        if (mBitSet == NO_DAYS_SET) {
84            return showNever ? context.getText(R.string.never).toString() : "";
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        // selected days
107        for (int bitIndex = 0; bitIndex < DAYS_IN_A_WEEK; bitIndex++) {
108            if ((mBitSet & (1 << bitIndex)) != 0) {
109                ret.append(dayList[convertBitIndexToDay(bitIndex)]);
110                dayCount -= 1;
111                if (dayCount > 0) ret.append(context.getText(R.string.day_concat));
112            }
113        }
114        return ret.toString();
115    }
116
117    /**
118     * Enables or disable certain days of the week.
119     *
120     * @param daysOfWeek Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, etc.
121     */
122    public void setDaysOfWeek(boolean value, int ... daysOfWeek) {
123        for (int day : daysOfWeek) {
124            setBit(convertDayToBitIndex(day), value);
125        }
126    }
127
128    private boolean isBitEnabled(int bitIndex) {
129        return ((mBitSet & (1 << bitIndex)) > 0);
130    }
131
132    private void setBit(int bitIndex, boolean set) {
133        if (set) {
134            mBitSet |= (1 << bitIndex);
135        } else {
136            mBitSet &= ~(1 << bitIndex);
137        }
138    }
139
140    public void setBitSet(int bitSet) {
141        mBitSet = bitSet;
142    }
143
144    public int getBitSet() {
145        return mBitSet;
146    }
147
148    public HashSet<Integer> getSetDays() {
149        final HashSet<Integer> result = new HashSet<Integer>();
150        for (int bitIndex = 0; bitIndex < DAYS_IN_A_WEEK; bitIndex++) {
151            if (isBitEnabled(bitIndex)) {
152                result.add(convertBitIndexToDay(bitIndex));
153            }
154        }
155        return result;
156    }
157
158    public boolean isRepeating() {
159        return mBitSet != NO_DAYS_SET;
160    }
161
162    /**
163     * Returns number of days from today until next alarm.
164     *
165     * @param current must be set to today
166     */
167    public int calculateDaysToNextAlarm(Calendar current) {
168        if (!isRepeating()) {
169            return -1;
170        }
171
172        int dayCount = 0;
173        int currentDayBit = convertDayToBitIndex(current.get(Calendar.DAY_OF_WEEK));
174        for (; dayCount < DAYS_IN_A_WEEK; dayCount++) {
175            int nextAlarmBit = (currentDayBit + dayCount) % DAYS_IN_A_WEEK;
176            if (isBitEnabled(nextAlarmBit)) {
177                break;
178            }
179        }
180        return dayCount;
181    }
182
183    public void clearAllDays() {
184        mBitSet = NO_DAYS_SET;
185    }
186
187    @Override
188    public String toString() {
189        return "DaysOfWeek{" +
190                "mBitSet=" + mBitSet +
191                '}';
192    }
193}
194