1/*
2 * Copyright (C) 2009 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;
18
19import android.content.Context;
20import android.database.Cursor;
21import android.media.RingtoneManager;
22import android.net.Uri;
23import android.os.Parcel;
24import android.os.Parcelable;
25import android.provider.BaseColumns;
26
27import java.text.DateFormatSymbols;
28import java.util.Calendar;
29import java.util.HashMap;
30import java.util.HashSet;
31
32public final class Alarm implements Parcelable {
33
34    //////////////////////////////
35    // Parcelable apis
36    //////////////////////////////
37    public static final Parcelable.Creator<Alarm> CREATOR
38            = new Parcelable.Creator<Alarm>() {
39                public Alarm createFromParcel(Parcel p) {
40                    return new Alarm(p);
41                }
42
43                public Alarm[] newArray(int size) {
44                    return new Alarm[size];
45                }
46            };
47
48    public int describeContents() {
49        return 0;
50    }
51
52    public void writeToParcel(Parcel p, int flags) {
53        p.writeInt(id);
54        p.writeInt(enabled ? 1 : 0);
55        p.writeInt(hour);
56        p.writeInt(minutes);
57        p.writeInt(daysOfWeek.getCoded());
58        p.writeLong(time);
59        p.writeInt(vibrate ? 1 : 0);
60        p.writeString(label);
61        p.writeParcelable(alert, flags);
62        p.writeInt(silent ? 1 : 0);
63    }
64    //////////////////////////////
65    // end Parcelable apis
66    //////////////////////////////
67
68    //////////////////////////////
69    // Column definitions
70    //////////////////////////////
71    public static class Columns implements BaseColumns {
72        /**
73         * The content:// style URL for this table
74         */
75        public static final Uri CONTENT_URI =
76                Uri.parse("content://com.android.deskclock/alarm");
77
78        /**
79         * Hour in 24-hour localtime 0 - 23.
80         * <P>Type: INTEGER</P>
81         */
82        public static final String HOUR = "hour";
83
84        /**
85         * Minutes in localtime 0 - 59
86         * <P>Type: INTEGER</P>
87         */
88        public static final String MINUTES = "minutes";
89
90        /**
91         * Days of week coded as integer
92         * <P>Type: INTEGER</P>
93         */
94        public static final String DAYS_OF_WEEK = "daysofweek";
95
96        /**
97         * Alarm time in UTC milliseconds from the epoch.
98         * <P>Type: INTEGER</P>
99         */
100        public static final String ALARM_TIME = "alarmtime";
101
102        /**
103         * True if alarm is active
104         * <P>Type: BOOLEAN</P>
105         */
106        public static final String ENABLED = "enabled";
107
108        /**
109         * True if alarm should vibrate
110         * <P>Type: BOOLEAN</P>
111         */
112        public static final String VIBRATE = "vibrate";
113
114        /**
115         * Message to show when alarm triggers
116         * Note: not currently used
117         * <P>Type: STRING</P>
118         */
119        public static final String MESSAGE = "message";
120
121        /**
122         * Audio alert to play when alarm triggers
123         * <P>Type: STRING</P>
124         */
125        public static final String ALERT = "alert";
126
127        /**
128         * The default sort order for this table
129         */
130        public static final String DEFAULT_SORT_ORDER =
131                HOUR + ", " + MINUTES + " ASC" + ", " + _ID + " DESC";
132
133        // Used when filtering enabled alarms.
134        public static final String WHERE_ENABLED = ENABLED + "=1";
135
136        static final String[] ALARM_QUERY_COLUMNS = {
137            _ID, HOUR, MINUTES, DAYS_OF_WEEK, ALARM_TIME,
138            ENABLED, VIBRATE, MESSAGE, ALERT };
139
140        /**
141         * These save calls to cursor.getColumnIndexOrThrow()
142         * THEY MUST BE KEPT IN SYNC WITH ABOVE QUERY COLUMNS
143         */
144        public static final int ALARM_ID_INDEX = 0;
145        public static final int ALARM_HOUR_INDEX = 1;
146        public static final int ALARM_MINUTES_INDEX = 2;
147        public static final int ALARM_DAYS_OF_WEEK_INDEX = 3;
148        public static final int ALARM_TIME_INDEX = 4;
149        public static final int ALARM_ENABLED_INDEX = 5;
150        public static final int ALARM_VIBRATE_INDEX = 6;
151        public static final int ALARM_MESSAGE_INDEX = 7;
152        public static final int ALARM_ALERT_INDEX = 8;
153    }
154    //////////////////////////////
155    // End column definitions
156    //////////////////////////////
157
158    // Public fields
159    public int        id;
160    public boolean    enabled;
161    public int        hour;
162    public int        minutes;
163    public DaysOfWeek daysOfWeek;
164    public long       time;
165    public boolean    vibrate;
166    public String     label;
167    public Uri        alert;
168    public boolean    silent;
169
170    @Override
171    public String toString() {
172        return "Alarm{" +
173                "alert=" + alert +
174                ", id=" + id +
175                ", enabled=" + enabled +
176                ", hour=" + hour +
177                ", minutes=" + minutes +
178                ", daysOfWeek=" + daysOfWeek +
179                ", time=" + time +
180                ", vibrate=" + vibrate +
181                ", label='" + label + '\'' +
182                ", silent=" + silent +
183                '}';
184    }
185
186    public Alarm(Cursor c) {
187        id = c.getInt(Columns.ALARM_ID_INDEX);
188        enabled = c.getInt(Columns.ALARM_ENABLED_INDEX) == 1;
189        hour = c.getInt(Columns.ALARM_HOUR_INDEX);
190        minutes = c.getInt(Columns.ALARM_MINUTES_INDEX);
191        daysOfWeek = new DaysOfWeek(c.getInt(Columns.ALARM_DAYS_OF_WEEK_INDEX));
192        time = c.getLong(Columns.ALARM_TIME_INDEX);
193        vibrate = c.getInt(Columns.ALARM_VIBRATE_INDEX) == 1;
194        label = c.getString(Columns.ALARM_MESSAGE_INDEX);
195        String alertString = c.getString(Columns.ALARM_ALERT_INDEX);
196        if (Alarms.ALARM_ALERT_SILENT.equals(alertString)) {
197            if (Log.LOGV) {
198                Log.v("Alarm is marked as silent");
199            }
200            silent = true;
201        } else {
202            if (alertString != null && alertString.length() != 0) {
203                alert = Uri.parse(alertString);
204            }
205
206            // If the database alert is null or it failed to parse, use the
207            // default alert.
208            if (alert == null) {
209                alert = RingtoneManager.getDefaultUri(
210                        RingtoneManager.TYPE_ALARM);
211            }
212        }
213    }
214
215    public Alarm(Parcel p) {
216        id = p.readInt();
217        enabled = p.readInt() == 1;
218        hour = p.readInt();
219        minutes = p.readInt();
220        daysOfWeek = new DaysOfWeek(p.readInt());
221        time = p.readLong();
222        vibrate = p.readInt() == 1;
223        label = p.readString();
224        alert = (Uri) p.readParcelable(null);
225        silent = p.readInt() == 1;
226    }
227
228    // Creates a default alarm at the current time.
229    public Alarm() {
230        id = -1;
231        hour = 0;
232        minutes = 0;
233        vibrate = true;
234        daysOfWeek = new DaysOfWeek(0);
235        label = "";
236        alert = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
237    }
238
239    public String getLabelOrDefault(Context context) {
240        if (label == null || label.length() == 0) {
241            return context.getString(R.string.default_label);
242        }
243        return label;
244    }
245
246    @Override
247    public int hashCode() {
248        return id;
249    }
250
251    @Override
252    public boolean equals(Object o) {
253        if (!(o instanceof Alarm)) return false;
254        final Alarm other = (Alarm) o;
255        return id == other.id;
256    }
257
258
259    /*
260     * Days of week code as a single int.
261     * 0x00: no day
262     * 0x01: Monday
263     * 0x02: Tuesday
264     * 0x04: Wednesday
265     * 0x08: Thursday
266     * 0x10: Friday
267     * 0x20: Saturday
268     * 0x40: Sunday
269     */
270    static final class DaysOfWeek {
271
272        private static int[] DAY_MAP = new int[] {
273            Calendar.MONDAY,
274            Calendar.TUESDAY,
275            Calendar.WEDNESDAY,
276            Calendar.THURSDAY,
277            Calendar.FRIDAY,
278            Calendar.SATURDAY,
279            Calendar.SUNDAY,
280        };
281
282
283        private static HashMap<Integer, Integer> DAY_TO_BIT_MASK = new HashMap<Integer, Integer>();
284        static {
285            for (int i = 0; i < DAY_MAP.length; i++) {
286                DAY_TO_BIT_MASK.put(DAY_MAP[i], i);
287            }
288        }
289
290        // Bitmask of all repeating days
291        private int mDays;
292
293        DaysOfWeek(int days) {
294            mDays = days;
295        }
296
297        public String toString(Context context, boolean showNever) {
298            return toString(context, showNever, false);
299        }
300
301        public String toAccessibilityString(Context context) {
302            return toString(context, false, true);
303        }
304
305        private String toString(Context context, boolean showNever, boolean forAccessibility) {
306            StringBuilder ret = new StringBuilder();
307
308            // no days
309            if (mDays == 0) {
310                return showNever ?
311                        context.getText(R.string.never).toString() : "";
312            }
313
314            // every day
315            if (mDays == 0x7f) {
316                return context.getText(R.string.every_day).toString();
317            }
318
319            // count selected days
320            int dayCount = 0, days = mDays;
321            while (days > 0) {
322                if ((days & 1) == 1) dayCount++;
323                days >>= 1;
324            }
325
326            // short or long form?
327            DateFormatSymbols dfs = new DateFormatSymbols();
328            String[] dayList = (forAccessibility || dayCount <= 1) ?
329                            dfs.getWeekdays() :
330                            dfs.getShortWeekdays();
331
332            // selected days
333            for (int i = 0; i < 7; i++) {
334                if ((mDays & (1 << i)) != 0) {
335                    ret.append(dayList[DAY_MAP[i]]);
336                    dayCount -= 1;
337                    if (dayCount > 0) ret.append(
338                            context.getText(R.string.day_concat));
339                }
340            }
341            return ret.toString();
342        }
343
344        private boolean isSet(int day) {
345            return ((mDays & (1 << day)) > 0);
346        }
347
348        /**
349         * Sets the repeat day for the alarm.
350         *
351         * @param dayOfWeek One of: Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY, etc.
352         * @param set Whether to set or unset.
353         */
354        public void setDayOfWeek(int dayOfWeek, boolean set) {
355            final int bitIndex = DAY_TO_BIT_MASK.get(dayOfWeek);
356            set(bitIndex, set);
357        }
358
359        public void set(int day, boolean set) {
360            if (set) {
361                mDays |= (1 << day);
362            } else {
363                mDays &= ~(1 << day);
364            }
365        }
366
367        public void set(DaysOfWeek dow) {
368            mDays = dow.mDays;
369        }
370
371        public int getCoded() {
372            return mDays;
373        }
374
375        public HashSet<Integer> getSetDays() {
376            final HashSet<Integer> set = new HashSet<Integer>();
377            for (int i = 0; i < 7; i++) {
378                if (isSet(i)) {
379                    set.add(DAY_MAP[i]);
380                }
381            }
382            return set;
383        }
384
385        // Returns days of week encoded in an array of booleans.
386        public boolean[] getBooleanArray() {
387            boolean[] ret = new boolean[7];
388            for (int i = 0; i < 7; i++) {
389                ret[i] = isSet(i);
390            }
391            return ret;
392        }
393
394        public boolean isRepeatSet() {
395            return mDays != 0;
396        }
397
398        /**
399         * returns number of days from today until next alarm
400         * @param c must be set to today
401         */
402        public int getNextAlarm(Calendar c) {
403            if (mDays == 0) {
404                return -1;
405            }
406
407            int today = (c.get(Calendar.DAY_OF_WEEK) + 5) % 7;
408
409            int day = 0;
410            int dayCount = 0;
411            for (; dayCount < 7; dayCount++) {
412                day = (today + dayCount) % 7;
413                if (isSet(day)) {
414                    break;
415                }
416            }
417            return dayCount;
418        }
419
420        @Override
421        public String toString() {
422            return "DaysOfWeek{" +
423                    "mDays=" + mDays +
424                    '}';
425        }
426    }
427}
428