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 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 
    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 
    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.
    231     public Alarm() {
    232         this(0, 0);
    233     }
    234 
    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 
    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 
    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 
    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 
    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 
    296     public int describeContents() {
    297         return 0;
    298     }
    299 
    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
    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
    336     public int hashCode() {
    337         return Long.valueOf(id).hashCode();
    338     }
    339 
    340     @Override
    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