• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.ContentResolver;
20 import android.content.ContentUris;
21 import android.content.ContentValues;
22 import android.content.Context;
23 import android.content.CursorLoader;
24 import android.content.Intent;
25 import android.database.Cursor;
26 import android.media.RingtoneManager;
27 import android.net.Uri;
28 import android.os.Parcel;
29 import android.os.Parcelable;
30 
31 import com.android.deskclock.R;
32 
33 import java.util.Calendar;
34 import java.util.LinkedList;
35 import java.util.List;
36 
37 public final class Alarm implements Parcelable, ClockContract.AlarmsColumns {
38     /**
39      * Alarms start with an invalid id when it hasn't been saved to the database.
40      */
41     public static final long INVALID_ID = -1;
42 
43     /**
44      * The default sort order for this table
45      */
46     private static final String DEFAULT_SORT_ORDER =
47             HOUR + ", " +
48             MINUTES + " ASC" + ", " +
49             _ID + " DESC";
50 
51     private static final String[] QUERY_COLUMNS = {
52             _ID,
53             HOUR,
54             MINUTES,
55             DAYS_OF_WEEK,
56             ENABLED,
57             VIBRATE,
58             LABEL,
59             RINGTONE,
60             DELETE_AFTER_USE
61     };
62 
63     /**
64      * These save calls to cursor.getColumnIndexOrThrow()
65      * THEY MUST BE KEPT IN SYNC WITH ABOVE QUERY COLUMNS
66      */
67     private static final int ID_INDEX = 0;
68     private static final int HOUR_INDEX = 1;
69     private static final int MINUTES_INDEX = 2;
70     private static final int DAYS_OF_WEEK_INDEX = 3;
71     private static final int ENABLED_INDEX = 4;
72     private static final int VIBRATE_INDEX = 5;
73     private static final int LABEL_INDEX = 6;
74     private static final int RINGTONE_INDEX = 7;
75     private static final int DELETE_AFTER_USE_INDEX = 8;
76 
77     private static final int COLUMN_COUNT = DELETE_AFTER_USE_INDEX + 1;
78 
createContentValues(Alarm alarm)79     public static ContentValues createContentValues(Alarm alarm) {
80         ContentValues values = new ContentValues(COLUMN_COUNT);
81         if (alarm.id != INVALID_ID) {
82             values.put(ClockContract.AlarmsColumns._ID, alarm.id);
83         }
84 
85         values.put(ENABLED, alarm.enabled ? 1 : 0);
86         values.put(HOUR, alarm.hour);
87         values.put(MINUTES, alarm.minutes);
88         values.put(DAYS_OF_WEEK, alarm.daysOfWeek.getBitSet());
89         values.put(VIBRATE, alarm.vibrate ? 1 : 0);
90         values.put(LABEL, alarm.label);
91         values.put(DELETE_AFTER_USE, alarm.deleteAfterUse);
92         if (alarm.alert == null) {
93             // We want to put null, so default alarm changes
94             values.putNull(RINGTONE);
95         } else {
96             values.put(RINGTONE, alarm.alert.toString());
97         }
98 
99         return values;
100     }
101 
createIntent(String action, long alarmId)102     public static Intent createIntent(String action, long alarmId) {
103         return new Intent(action).setData(getUri(alarmId));
104     }
105 
createIntent(Context context, Class<?> cls, long alarmId)106     public static Intent createIntent(Context context, Class<?> cls, long alarmId) {
107         return new Intent(context, cls).setData(getUri(alarmId));
108     }
109 
getUri(long alarmId)110     public static Uri getUri(long alarmId) {
111         return ContentUris.withAppendedId(CONTENT_URI, alarmId);
112     }
113 
getId(Uri contentUri)114     public static long getId(Uri contentUri) {
115         return ContentUris.parseId(contentUri);
116     }
117 
118     /**
119      * Get alarm cursor loader for all alarms.
120      *
121      * @param context to query the database.
122      * @return cursor loader with all the alarms.
123      */
getAlarmsCursorLoader(Context context)124     public static CursorLoader getAlarmsCursorLoader(Context context) {
125         return new CursorLoader(context, ClockContract.AlarmsColumns.CONTENT_URI,
126                 QUERY_COLUMNS, null, null, DEFAULT_SORT_ORDER);
127     }
128 
129     /**
130      * Get alarm by id.
131      *
132      * @param contentResolver to perform the query on.
133      * @param alarmId for the desired alarm.
134      * @return alarm if found, null otherwise
135      */
getAlarm(ContentResolver contentResolver, long alarmId)136     public static Alarm getAlarm(ContentResolver contentResolver, long alarmId) {
137         Cursor cursor = contentResolver.query(getUri(alarmId), QUERY_COLUMNS, null, null, null);
138         Alarm result = null;
139         if (cursor == null) {
140             return result;
141         }
142 
143         try {
144             if (cursor.moveToFirst()) {
145                 result = new Alarm(cursor);
146             }
147         } finally {
148             cursor.close();
149         }
150 
151         return result;
152     }
153 
154     /**
155      * Get all alarms given conditions.
156      *
157      * @param contentResolver to perform the query on.
158      * @param selection A filter declaring which rows to return, formatted as an
159      *         SQL WHERE clause (excluding the WHERE itself). Passing null will
160      *         return all rows for the given URI.
161      * @param selectionArgs You may include ?s in selection, which will be
162      *         replaced by the values from selectionArgs, in the order that they
163      *         appear in the selection. The values will be bound as Strings.
164      * @return list of alarms matching where clause or empty list if none found.
165      */
getAlarms(ContentResolver contentResolver, String selection, String ... selectionArgs)166     public static List<Alarm> getAlarms(ContentResolver contentResolver,
167             String selection, String ... selectionArgs) {
168         Cursor cursor  = contentResolver.query(CONTENT_URI, QUERY_COLUMNS,
169                 selection, selectionArgs, null);
170         List<Alarm> result = new LinkedList<Alarm>();
171         if (cursor == null) {
172             return result;
173         }
174 
175         try {
176             if (cursor.moveToFirst()) {
177                 do {
178                     result.add(new Alarm(cursor));
179                 } while (cursor.moveToNext());
180             }
181         } finally {
182             cursor.close();
183         }
184 
185         return result;
186     }
187 
isTomorrow(Alarm alarm)188     public static boolean isTomorrow(Alarm alarm) {
189         final Calendar now = Calendar.getInstance();
190         final int alarmHour = alarm.hour;
191         final int currHour = now.get(Calendar.HOUR_OF_DAY);
192         return alarmHour < currHour ||
193                 (alarmHour == currHour && alarm.minutes <= now.get(Calendar.MINUTE));
194     }
195 
addAlarm(ContentResolver contentResolver, Alarm alarm)196     public static Alarm addAlarm(ContentResolver contentResolver, Alarm alarm) {
197         ContentValues values = createContentValues(alarm);
198         Uri uri = contentResolver.insert(CONTENT_URI, values);
199         alarm.id = getId(uri);
200         return alarm;
201     }
202 
updateAlarm(ContentResolver contentResolver, Alarm alarm)203     public static boolean updateAlarm(ContentResolver contentResolver, Alarm alarm) {
204         if (alarm.id == Alarm.INVALID_ID) return false;
205         ContentValues values = createContentValues(alarm);
206         long rowsUpdated = contentResolver.update(getUri(alarm.id), values, null, null);
207         return rowsUpdated == 1;
208     }
209 
deleteAlarm(ContentResolver contentResolver, long alarmId)210     public static boolean deleteAlarm(ContentResolver contentResolver, long alarmId) {
211         if (alarmId == INVALID_ID) return false;
212         int deletedRows = contentResolver.delete(getUri(alarmId), "", null);
213         return deletedRows == 1;
214     }
215 
216     public static final Parcelable.Creator<Alarm> CREATOR = new Parcelable.Creator<Alarm>() {
217         public Alarm createFromParcel(Parcel p) {
218             return new Alarm(p);
219         }
220 
221         public Alarm[] newArray(int size) {
222             return new Alarm[size];
223         }
224     };
225 
226     // Public fields
227     // TODO: Refactor instance names
228     public long id;
229     public boolean enabled;
230     public int hour;
231     public int minutes;
232     public DaysOfWeek daysOfWeek;
233     public boolean vibrate;
234     public String label;
235     public Uri alert;
236     public boolean deleteAfterUse;
237 
238     // Creates a default alarm at the current time.
Alarm()239     public Alarm() {
240         this(0, 0);
241     }
242 
Alarm(int hour, int minutes)243     public Alarm(int hour, int minutes) {
244         this.id = INVALID_ID;
245         this.hour = hour;
246         this.minutes = minutes;
247         this.vibrate = true;
248         this.daysOfWeek = new DaysOfWeek(0);
249         this.label = "";
250         this.alert = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
251         this.deleteAfterUse = false;
252     }
253 
Alarm(Cursor c)254     public Alarm(Cursor c) {
255         id = c.getLong(ID_INDEX);
256         enabled = c.getInt(ENABLED_INDEX) == 1;
257         hour = c.getInt(HOUR_INDEX);
258         minutes = c.getInt(MINUTES_INDEX);
259         daysOfWeek = new DaysOfWeek(c.getInt(DAYS_OF_WEEK_INDEX));
260         vibrate = c.getInt(VIBRATE_INDEX) == 1;
261         label = c.getString(LABEL_INDEX);
262         deleteAfterUse = c.getInt(DELETE_AFTER_USE_INDEX) == 1;
263 
264         if (c.isNull(RINGTONE_INDEX)) {
265             // Should we be saving this with the current ringtone or leave it null
266             // so it changes when user changes default ringtone?
267             alert = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
268         } else {
269             alert = Uri.parse(c.getString(RINGTONE_INDEX));
270         }
271     }
272 
Alarm(Parcel p)273     Alarm(Parcel p) {
274         id = p.readLong();
275         enabled = p.readInt() == 1;
276         hour = p.readInt();
277         minutes = p.readInt();
278         daysOfWeek = new DaysOfWeek(p.readInt());
279         vibrate = p.readInt() == 1;
280         label = p.readString();
281         alert = (Uri) p.readParcelable(null);
282         deleteAfterUse = p.readInt() == 1;
283     }
284 
getLabelOrDefault(Context context)285     public String getLabelOrDefault(Context context) {
286         if (label == null || label.length() == 0) {
287             return context.getString(R.string.default_label);
288         }
289         return label;
290     }
291 
writeToParcel(Parcel p, int flags)292     public void writeToParcel(Parcel p, int flags) {
293         p.writeLong(id);
294         p.writeInt(enabled ? 1 : 0);
295         p.writeInt(hour);
296         p.writeInt(minutes);
297         p.writeInt(daysOfWeek.getBitSet());
298         p.writeInt(vibrate ? 1 : 0);
299         p.writeString(label);
300         p.writeParcelable(alert, flags);
301         p.writeInt(deleteAfterUse ? 1 : 0);
302     }
303 
describeContents()304     public int describeContents() {
305         return 0;
306     }
307 
createInstanceAfter(Calendar time)308     public AlarmInstance createInstanceAfter(Calendar time) {
309         Calendar nextInstanceTime = getNextAlarmTime(time);
310         AlarmInstance result = new AlarmInstance(nextInstanceTime, id);
311         result.mVibrate = vibrate;
312         result.mLabel = label;
313         result.mRingtone = alert;
314         return result;
315     }
316 
317     /**
318      *
319      * @param currentTime
320      * @return Previous firing time, or null if this is a one-time alarm.
321      */
getPreviousAlarmTime(Calendar currentTime)322     public Calendar getPreviousAlarmTime(Calendar currentTime) {
323         Calendar previousInstanceTime = Calendar.getInstance();
324         previousInstanceTime.set(Calendar.YEAR, currentTime.get(Calendar.YEAR));
325         previousInstanceTime.set(Calendar.MONTH, currentTime.get(Calendar.MONTH));
326         previousInstanceTime.set(Calendar.DAY_OF_MONTH, currentTime.get(Calendar.DAY_OF_MONTH));
327         previousInstanceTime.set(Calendar.HOUR_OF_DAY, hour);
328         previousInstanceTime.set(Calendar.MINUTE, minutes);
329         previousInstanceTime.set(Calendar.SECOND, 0);
330         previousInstanceTime.set(Calendar.MILLISECOND, 0);
331 
332         int subtractDays = daysOfWeek.calculateDaysToPreviousAlarm(previousInstanceTime);
333         if (subtractDays > 0) {
334             previousInstanceTime.add(Calendar.DAY_OF_WEEK, -subtractDays);
335             return previousInstanceTime;
336         } else {
337             return null;
338         }
339     }
340 
getNextAlarmTime(Calendar currentTime)341     public Calendar getNextAlarmTime(Calendar currentTime) {
342         final Calendar nextInstanceTime = Calendar.getInstance();
343         nextInstanceTime.set(Calendar.YEAR, currentTime.get(Calendar.YEAR));
344         nextInstanceTime.set(Calendar.MONTH, currentTime.get(Calendar.MONTH));
345         nextInstanceTime.set(Calendar.DAY_OF_MONTH, currentTime.get(Calendar.DAY_OF_MONTH));
346         nextInstanceTime.set(Calendar.HOUR_OF_DAY, hour);
347         nextInstanceTime.set(Calendar.MINUTE, minutes);
348         nextInstanceTime.set(Calendar.SECOND, 0);
349         nextInstanceTime.set(Calendar.MILLISECOND, 0);
350 
351         // If we are still behind the passed in currentTime, then add a day
352         if (nextInstanceTime.getTimeInMillis() <= currentTime.getTimeInMillis()) {
353             nextInstanceTime.add(Calendar.DAY_OF_YEAR, 1);
354         }
355 
356         // The day of the week might be invalid, so find next valid one
357         int addDays = daysOfWeek.calculateDaysToNextAlarm(nextInstanceTime);
358         if (addDays > 0) {
359             nextInstanceTime.add(Calendar.DAY_OF_WEEK, addDays);
360         }
361         return nextInstanceTime;
362     }
363 
364     @Override
equals(Object o)365     public boolean equals(Object o) {
366         if (!(o instanceof Alarm)) return false;
367         final Alarm other = (Alarm) o;
368         return id == other.id;
369     }
370 
371     @Override
hashCode()372     public int hashCode() {
373         return Long.valueOf(id).hashCode();
374     }
375 
376     @Override
toString()377     public String toString() {
378         return "Alarm{" +
379                 "alert=" + alert +
380                 ", id=" + id +
381                 ", enabled=" + enabled +
382                 ", hour=" + hour +
383                 ", minutes=" + minutes +
384                 ", daysOfWeek=" + daysOfWeek +
385                 ", vibrate=" + vibrate +
386                 ", label='" + label + '\'' +
387                 ", deleteAfterUse=" + deleteAfterUse +
388                 '}';
389     }
390 }
391