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.Intent;
     24 import android.database.Cursor;
     25 import android.media.RingtoneManager;
     26 import android.net.Uri;
     27 import android.preference.PreferenceManager;
     28 
     29 import com.android.deskclock.LogUtils;
     30 import com.android.deskclock.R;
     31 import com.android.deskclock.SettingsActivity;
     32 
     33 import java.util.Calendar;
     34 import java.util.LinkedList;
     35 import java.util.List;
     36 
     37 public final class AlarmInstance implements ClockContract.InstancesColumns {
     38     /**
     39      * Offset from alarm time to show low priority notification
     40      */
     41     public static final int LOW_NOTIFICATION_HOUR_OFFSET = -2;
     42 
     43     /**
     44      * Offset from alarm time to show high priority notification
     45      */
     46     public static final int HIGH_NOTIFICATION_MINUTE_OFFSET = -30;
     47 
     48     /**
     49      * Offset from alarm time to stop showing missed notification.
     50      */
     51     private static final int MISSED_TIME_TO_LIVE_HOUR_OFFSET = 12;
     52 
     53     /**
     54      * Default timeout for alarms in minutes.
     55      */
     56     private static final String DEFAULT_ALARM_TIMEOUT_SETTING = "10";
     57 
     58     /**
     59      * AlarmInstances start with an invalid id when it hasn't been saved to the database.
     60      */
     61     public static final long INVALID_ID = -1;
     62 
     63     private static final String[] QUERY_COLUMNS = {
     64             _ID,
     65             YEAR,
     66             MONTH,
     67             DAY,
     68             HOUR,
     69             MINUTES,
     70             LABEL,
     71             VIBRATE,
     72             RINGTONE,
     73             ALARM_ID,
     74             ALARM_STATE
     75     };
     76 
     77     /**
     78      * These save calls to cursor.getColumnIndexOrThrow()
     79      * THEY MUST BE KEPT IN SYNC WITH ABOVE QUERY COLUMNS
     80      */
     81     private static final int ID_INDEX = 0;
     82     private static final int YEAR_INDEX = 1;
     83     private static final int MONTH_INDEX = 2;
     84     private static final int DAY_INDEX = 3;
     85     private static final int HOUR_INDEX = 4;
     86     private static final int MINUTES_INDEX = 5;
     87     private static final int LABEL_INDEX = 6;
     88     private static final int VIBRATE_INDEX = 7;
     89     private static final int RINGTONE_INDEX = 8;
     90     private static final int ALARM_ID_INDEX = 9;
     91     private static final int ALARM_STATE_INDEX = 10;
     92 
     93     private static final int COLUMN_COUNT = ALARM_STATE_INDEX + 1;
     94     private Calendar mTimeout;
     95 
     96     public static ContentValues createContentValues(AlarmInstance instance) {
     97         ContentValues values = new ContentValues(COLUMN_COUNT);
     98         if (instance.mId != INVALID_ID) {
     99             values.put(_ID, instance.mId);
    100         }
    101 
    102         values.put(YEAR, instance.mYear);
    103         values.put(MONTH, instance.mMonth);
    104         values.put(DAY, instance.mDay);
    105         values.put(HOUR, instance.mHour);
    106         values.put(MINUTES, instance.mMinute);
    107         values.put(LABEL, instance.mLabel);
    108         values.put(VIBRATE, instance.mVibrate ? 1 : 0);
    109         if (instance.mRingtone == null) {
    110             // We want to put null in the database, so we'll be able
    111             // to pick up on changes to the default alarm
    112             values.putNull(RINGTONE);
    113         } else {
    114             values.put(RINGTONE, instance.mRingtone.toString());
    115         }
    116         values.put(ALARM_ID, instance.mAlarmId);
    117         values.put(ALARM_STATE, instance.mAlarmState);
    118         return values;
    119     }
    120 
    121     public static Intent createIntent(String action, long instanceId) {
    122         return new Intent(action).setData(getUri(instanceId));
    123     }
    124 
    125     public static Intent createIntent(Context context, Class<?> cls, long instanceId) {
    126         return new Intent(context, cls).setData(getUri(instanceId));
    127     }
    128 
    129     public static long getId(Uri contentUri) {
    130         return ContentUris.parseId(contentUri);
    131     }
    132 
    133     public static Uri getUri(long instanceId) {
    134         return ContentUris.withAppendedId(CONTENT_URI, instanceId);
    135     }
    136 
    137     /**
    138      * Get alarm instance from instanceId.
    139      *
    140      * @param contentResolver to perform the query on.
    141      * @param instanceId for the desired instance.
    142      * @return instance if found, null otherwise
    143      */
    144     public static AlarmInstance getInstance(ContentResolver contentResolver, long instanceId) {
    145         Cursor cursor = contentResolver.query(getUri(instanceId), QUERY_COLUMNS, null, null, null);
    146         AlarmInstance result = null;
    147         if (cursor == null) {
    148             return result;
    149         }
    150 
    151         try {
    152             if (cursor.moveToFirst()) {
    153                 result = new AlarmInstance(cursor);
    154             }
    155         } finally {
    156             cursor.close();
    157         }
    158 
    159         return result;
    160     }
    161 
    162     /**
    163      * Get an alarm instances by alarmId.
    164      *
    165      * @param contentResolver to perform the query on.
    166      * @param alarmId of instances desired.
    167      * @return list of alarms instances that are owned by alarmId.
    168      */
    169     public static List<AlarmInstance> getInstancesByAlarmId(ContentResolver contentResolver,
    170             long alarmId) {
    171         return getInstances(contentResolver, ALARM_ID + "=" + alarmId);
    172     }
    173 
    174     /**
    175      * Get a list of instances given selection.
    176      *
    177      * @param contentResolver to perform the query on.
    178      * @param selection A filter declaring which rows to return, formatted as an
    179      *         SQL WHERE clause (excluding the WHERE itself). Passing null will
    180      *         return all rows for the given URI.
    181      * @param selectionArgs You may include ?s in selection, which will be
    182      *         replaced by the values from selectionArgs, in the order that they
    183      *         appear in the selection. The values will be bound as Strings.
    184      * @return list of alarms matching where clause or empty list if none found.
    185      */
    186     public static List<AlarmInstance> getInstances(ContentResolver contentResolver,
    187             String selection, String ... selectionArgs) {
    188         Cursor cursor  = contentResolver.query(CONTENT_URI, QUERY_COLUMNS,
    189                 selection, selectionArgs, null);
    190         List<AlarmInstance> result = new LinkedList<AlarmInstance>();
    191         if (cursor == null) {
    192             return result;
    193         }
    194 
    195         try {
    196             if (cursor.moveToFirst()) {
    197                 do {
    198                     result.add(new AlarmInstance(cursor));
    199                 } while (cursor.moveToNext());
    200             }
    201         } finally {
    202             cursor.close();
    203         }
    204 
    205         return result;
    206     }
    207 
    208     public static AlarmInstance addInstance(ContentResolver contentResolver,
    209             AlarmInstance instance) {
    210         // Make sure we are not adding a duplicate instances. This is not a
    211         // fix and should never happen. This is only a safe guard against bad code, and you
    212         // should fix the root issue if you see the error message.
    213         String dupSelector = AlarmInstance.ALARM_ID + " = " + instance.mAlarmId;
    214         for (AlarmInstance otherInstances : getInstances(contentResolver, dupSelector)) {
    215             if (otherInstances.getAlarmTime().equals(instance.getAlarmTime())) {
    216                 LogUtils.i("Detected duplicate instance in DB. Updating " + otherInstances + " to "
    217                         + instance);
    218                 // Copy over the new instance values and update the db
    219                 instance.mId = otherInstances.mId;
    220                 updateInstance(contentResolver, instance);
    221                 return instance;
    222             }
    223         }
    224 
    225         ContentValues values = createContentValues(instance);
    226         Uri uri = contentResolver.insert(CONTENT_URI, values);
    227         instance.mId = getId(uri);
    228         return instance;
    229     }
    230 
    231     public static boolean updateInstance(ContentResolver contentResolver, AlarmInstance instance) {
    232         if (instance.mId == INVALID_ID) return false;
    233         ContentValues values = createContentValues(instance);
    234         long rowsUpdated = contentResolver.update(getUri(instance.mId), values, null, null);
    235         return rowsUpdated == 1;
    236     }
    237 
    238     public static boolean deleteInstance(ContentResolver contentResolver, long instanceId) {
    239         if (instanceId == INVALID_ID) return false;
    240         int deletedRows = contentResolver.delete(getUri(instanceId), "", null);
    241         return deletedRows == 1;
    242     }
    243 
    244     // Public fields
    245     public long mId;
    246     public int mYear;
    247     public int mMonth;
    248     public int mDay;
    249     public int mHour;
    250     public int mMinute;
    251     public String mLabel;
    252     public boolean mVibrate;
    253     public Uri mRingtone;
    254     public Long mAlarmId;
    255     public int mAlarmState;
    256 
    257     public AlarmInstance(Calendar calendar, Long alarmId) {
    258         this(calendar);
    259         mAlarmId = alarmId;
    260     }
    261 
    262     public AlarmInstance(Calendar calendar) {
    263         mId = INVALID_ID;
    264         setAlarmTime(calendar);
    265         mLabel = "";
    266         mVibrate = false;
    267         mRingtone = null;
    268         mAlarmState = SILENT_STATE;
    269     }
    270 
    271     public AlarmInstance(Cursor c) {
    272         mId = c.getLong(ID_INDEX);
    273         mYear = c.getInt(YEAR_INDEX);
    274         mMonth = c.getInt(MONTH_INDEX);
    275         mDay = c.getInt(DAY_INDEX);
    276         mHour = c.getInt(HOUR_INDEX);
    277         mMinute = c.getInt(MINUTES_INDEX);
    278         mLabel = c.getString(LABEL_INDEX);
    279         mVibrate = c.getInt(VIBRATE_INDEX) == 1;
    280         if (c.isNull(RINGTONE_INDEX)) {
    281             // Should we be saving this with the current ringtone or leave it null
    282             // so it changes when user changes default ringtone?
    283             mRingtone = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
    284         } else {
    285             mRingtone = Uri.parse(c.getString(RINGTONE_INDEX));
    286         }
    287 
    288         if (!c.isNull(ALARM_ID_INDEX)) {
    289             mAlarmId = c.getLong(ALARM_ID_INDEX);
    290         }
    291         mAlarmState = c.getInt(ALARM_STATE_INDEX);
    292     }
    293 
    294     public String getLabelOrDefault(Context context) {
    295         return mLabel.isEmpty() ? context.getString(R.string.default_label) : mLabel;
    296     }
    297 
    298     public void setAlarmTime(Calendar calendar) {
    299         mYear = calendar.get(Calendar.YEAR);
    300         mMonth = calendar.get(Calendar.MONTH);
    301         mDay = calendar.get(Calendar.DAY_OF_MONTH);
    302         mHour = calendar.get(Calendar.HOUR_OF_DAY);
    303         mMinute = calendar.get(Calendar.MINUTE);
    304     }
    305 
    306     /**
    307      * Return the time when a alarm should fire.
    308      *
    309      * @return the time
    310      */
    311     public Calendar getAlarmTime() {
    312         Calendar calendar = Calendar.getInstance();
    313         calendar.set(Calendar.YEAR, mYear);
    314         calendar.set(Calendar.MONTH, mMonth);
    315         calendar.set(Calendar.DAY_OF_MONTH, mDay);
    316         calendar.set(Calendar.HOUR_OF_DAY, mHour);
    317         calendar.set(Calendar.MINUTE, mMinute);
    318         calendar.set(Calendar.SECOND, 0);
    319         calendar.set(Calendar.MILLISECOND, 0);
    320         return calendar;
    321     }
    322 
    323     /**
    324      * Return the time when a low priority notification should be shown.
    325      *
    326      * @return the time
    327      */
    328     public Calendar getLowNotificationTime() {
    329         Calendar calendar = getAlarmTime();
    330         calendar.add(Calendar.HOUR_OF_DAY, LOW_NOTIFICATION_HOUR_OFFSET);
    331         return calendar;
    332     }
    333 
    334     /**
    335      * Return the time when a high priority notification should be shown.
    336      *
    337      * @return the time
    338      */
    339     public Calendar getHighNotificationTime() {
    340         Calendar calendar = getAlarmTime();
    341         calendar.add(Calendar.MINUTE, HIGH_NOTIFICATION_MINUTE_OFFSET);
    342         return calendar;
    343     }
    344 
    345     /**
    346      * Return the time when a missed notification should be removed.
    347      *
    348      * @return the time
    349      */
    350     public Calendar getMissedTimeToLive() {
    351         Calendar calendar = getAlarmTime();
    352         calendar.add(Calendar.HOUR, MISSED_TIME_TO_LIVE_HOUR_OFFSET);
    353         return calendar;
    354     }
    355 
    356     /**
    357      * Return the time when the alarm should stop firing and be marked as missed.
    358      *
    359      * @param context to figure out the timeout setting
    360      * @return the time when alarm should be silence, or null if never
    361      */
    362     public Calendar getTimeout(Context context) {
    363         String timeoutSetting = PreferenceManager.getDefaultSharedPreferences(context)
    364                 .getString(SettingsActivity.KEY_AUTO_SILENCE, DEFAULT_ALARM_TIMEOUT_SETTING);
    365         int timeoutMinutes = Integer.parseInt(timeoutSetting);
    366 
    367         // Alarm silence has been set to "None"
    368         if (timeoutMinutes < 0) {
    369             return null;
    370         }
    371 
    372         Calendar calendar = getAlarmTime();
    373         calendar.add(Calendar.MINUTE, timeoutMinutes);
    374         return calendar;
    375     }
    376 
    377     @Override
    378     public boolean equals(Object o) {
    379         if (!(o instanceof AlarmInstance)) return false;
    380         final AlarmInstance other = (AlarmInstance) o;
    381         return mId == other.mId;
    382     }
    383 
    384     @Override
    385     public int hashCode() {
    386         return Long.valueOf(mId).hashCode();
    387     }
    388 
    389     @Override
    390     public String toString() {
    391         return "AlarmInstance{" +
    392                 "mId=" + mId +
    393                 ", mYear=" + mYear +
    394                 ", mMonth=" + mMonth +
    395                 ", mDay=" + mDay +
    396                 ", mHour=" + mHour +
    397                 ", mMinute=" + mMinute +
    398                 ", mLabel=" + mLabel +
    399                 ", mVibrate=" + mVibrate +
    400                 ", mRingtone=" + mRingtone +
    401                 ", mAlarmId=" + mAlarmId +
    402                 ", mAlarmState=" + mAlarmState +
    403                 '}';
    404     }
    405 }
    406