Home | History | Annotate | Download | only in provider
      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 
     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 
    102     public static Intent createIntent(String action, long alarmId) {
    103         return new Intent(action).setData(getUri(alarmId));
    104     }
    105 
    106     public static Intent createIntent(Context context, Class<?> cls, long alarmId) {
    107         return new Intent(context, cls).setData(getUri(alarmId));
    108     }
    109 
    110     public static Uri getUri(long alarmId) {
    111         return ContentUris.withAppendedId(CONTENT_URI, alarmId);
    112     }
    113 
    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      */
    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      */
    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      */
    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 
    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 
    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 
    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 
    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.
    239     public Alarm() {
    240         this(0, 0);
    241     }
    242 
    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 
    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 
    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 
    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 
    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 
    304     public int describeContents() {
    305         return 0;
    306     }
    307 
    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      */
    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 
    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
    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
    372     public int hashCode() {
    373         return Long.valueOf(id).hashCode();
    374     }
    375 
    376     @Override
    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