• 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 
addAlarm(ContentResolver contentResolver, Alarm alarm)188     public static Alarm addAlarm(ContentResolver contentResolver, Alarm alarm) {
189         ContentValues values = createContentValues(alarm);
190         Uri uri = contentResolver.insert(CONTENT_URI, values);
191         alarm.id = getId(uri);
192         return alarm;
193     }
194 
updateAlarm(ContentResolver contentResolver, Alarm alarm)195     public static boolean updateAlarm(ContentResolver contentResolver, Alarm alarm) {
196         if (alarm.id == Alarm.INVALID_ID) return false;
197         ContentValues values = createContentValues(alarm);
198         long rowsUpdated = contentResolver.update(getUri(alarm.id), values, null, null);
199         return rowsUpdated == 1;
200     }
201 
deleteAlarm(ContentResolver contentResolver, long alarmId)202     public static boolean deleteAlarm(ContentResolver contentResolver, long alarmId) {
203         if (alarmId == INVALID_ID) return false;
204         int deletedRows = contentResolver.delete(getUri(alarmId), "", null);
205         return deletedRows == 1;
206     }
207 
208     public static final Parcelable.Creator<Alarm> CREATOR = new Parcelable.Creator<Alarm>() {
209         public Alarm createFromParcel(Parcel p) {
210             return new Alarm(p);
211         }
212 
213         public Alarm[] newArray(int size) {
214             return new Alarm[size];
215         }
216     };
217 
218     // Public fields
219     // TODO: Refactor instance names
220     public long id;
221     public boolean enabled;
222     public int hour;
223     public int minutes;
224     public DaysOfWeek daysOfWeek;
225     public boolean vibrate;
226     public String label;
227     public Uri alert;
228     public boolean deleteAfterUse;
229 
230     // Creates a default alarm at the current time.
Alarm()231     public Alarm() {
232         this(0, 0);
233     }
234 
Alarm(int hour, int minutes)235     public Alarm(int hour, int minutes) {
236         this.id = INVALID_ID;
237         this.hour = hour;
238         this.minutes = minutes;
239         this.vibrate = true;
240         this.daysOfWeek = new DaysOfWeek(0);
241         this.label = "";
242         this.alert = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
243         this.deleteAfterUse = false;
244     }
245 
Alarm(Cursor c)246     public Alarm(Cursor c) {
247         id = c.getLong(ID_INDEX);
248         enabled = c.getInt(ENABLED_INDEX) == 1;
249         hour = c.getInt(HOUR_INDEX);
250         minutes = c.getInt(MINUTES_INDEX);
251         daysOfWeek = new DaysOfWeek(c.getInt(DAYS_OF_WEEK_INDEX));
252         vibrate = c.getInt(VIBRATE_INDEX) == 1;
253         label = c.getString(LABEL_INDEX);
254         deleteAfterUse = c.getInt(DELETE_AFTER_USE_INDEX) == 1;
255 
256         if (c.isNull(RINGTONE_INDEX)) {
257             // Should we be saving this with the current ringtone or leave it null
258             // so it changes when user changes default ringtone?
259             alert = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
260         } else {
261             alert = Uri.parse(c.getString(RINGTONE_INDEX));
262         }
263     }
264 
Alarm(Parcel p)265     Alarm(Parcel p) {
266         id = p.readLong();
267         enabled = p.readInt() == 1;
268         hour = p.readInt();
269         minutes = p.readInt();
270         daysOfWeek = new DaysOfWeek(p.readInt());
271         vibrate = p.readInt() == 1;
272         label = p.readString();
273         alert = (Uri) p.readParcelable(null);
274         deleteAfterUse = p.readInt() == 1;
275     }
276 
getLabelOrDefault(Context context)277     public String getLabelOrDefault(Context context) {
278         if (label == null || label.length() == 0) {
279             return context.getString(R.string.default_label);
280         }
281         return label;
282     }
283 
writeToParcel(Parcel p, int flags)284     public void writeToParcel(Parcel p, int flags) {
285         p.writeLong(id);
286         p.writeInt(enabled ? 1 : 0);
287         p.writeInt(hour);
288         p.writeInt(minutes);
289         p.writeInt(daysOfWeek.getBitSet());
290         p.writeInt(vibrate ? 1 : 0);
291         p.writeString(label);
292         p.writeParcelable(alert, flags);
293         p.writeInt(deleteAfterUse ? 1 : 0);
294     }
295 
describeContents()296     public int describeContents() {
297         return 0;
298     }
299 
createInstanceAfter(Calendar time)300     public AlarmInstance createInstanceAfter(Calendar time) {
301         Calendar nextInstanceTime = Calendar.getInstance();
302         nextInstanceTime.set(Calendar.YEAR, time.get(Calendar.YEAR));
303         nextInstanceTime.set(Calendar.MONTH, time.get(Calendar.MONTH));
304         nextInstanceTime.set(Calendar.DAY_OF_MONTH, time.get(Calendar.DAY_OF_MONTH));
305         nextInstanceTime.set(Calendar.HOUR_OF_DAY, hour);
306         nextInstanceTime.set(Calendar.MINUTE, minutes);
307         nextInstanceTime.set(Calendar.SECOND, 0);
308         nextInstanceTime.set(Calendar.MILLISECOND, 0);
309 
310         // If we are still behind the passed in time, then add a day
311         if (nextInstanceTime.getTimeInMillis() <= time.getTimeInMillis()) {
312             nextInstanceTime.add(Calendar.DAY_OF_YEAR, 1);
313         }
314 
315         // The day of the week might be invalid, so find next valid one
316         int addDays = daysOfWeek.calculateDaysToNextAlarm(nextInstanceTime);
317         if (addDays > 0) {
318             nextInstanceTime.add(Calendar.DAY_OF_WEEK, addDays);
319         }
320 
321         AlarmInstance result = new AlarmInstance(nextInstanceTime, id);
322         result.mVibrate = vibrate;
323         result.mLabel = label;
324         result.mRingtone = alert;
325         return result;
326     }
327 
328     @Override
equals(Object o)329     public boolean equals(Object o) {
330         if (!(o instanceof Alarm)) return false;
331         final Alarm other = (Alarm) o;
332         return id == other.id;
333     }
334 
335     @Override
hashCode()336     public int hashCode() {
337         return Long.valueOf(id).hashCode();
338     }
339 
340     @Override
toString()341     public String toString() {
342         return "Alarm{" +
343                 "alert=" + alert +
344                 ", id=" + id +
345                 ", enabled=" + enabled +
346                 ", hour=" + hour +
347                 ", minutes=" + minutes +
348                 ", daysOfWeek=" + daysOfWeek +
349                 ", vibrate=" + vibrate +
350                 ", label='" + label + '\'' +
351                 ", deleteAfterUse=" + deleteAfterUse +
352                 '}';
353     }
354 }
355