AlarmInstance.java revision 9db602092a4c7dc532c83ca0ebcfe9faaeefbc88
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
17package com.android.deskclock.provider;
18
19import android.content.ContentResolver;
20import android.content.ContentUris;
21import android.content.ContentValues;
22import android.content.Context;
23import android.content.Intent;
24import android.database.Cursor;
25import android.media.RingtoneManager;
26import android.net.Uri;
27import android.preference.PreferenceManager;
28
29import com.android.deskclock.Log;
30import com.android.deskclock.R;
31import com.android.deskclock.SettingsActivity;
32
33import java.util.Calendar;
34import java.util.LinkedList;
35import java.util.List;
36
37public 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                Log.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