• 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.annotation.TargetApi;
20 import android.content.ContentProvider;
21 import android.content.ContentResolver;
22 import android.content.ContentUris;
23 import android.content.ContentValues;
24 import android.content.Context;
25 import android.content.UriMatcher;
26 import android.database.Cursor;
27 import android.database.sqlite.SQLiteDatabase;
28 import android.database.sqlite.SQLiteQueryBuilder;
29 import android.net.Uri;
30 import android.os.Build;
31 import android.support.annotation.NonNull;
32 import android.text.TextUtils;
33 import android.util.ArrayMap;
34 
35 import com.android.deskclock.LogUtils;
36 import com.android.deskclock.Utils;
37 
38 import java.util.Map;
39 
40 import static com.android.deskclock.provider.ClockContract.AlarmsColumns;
41 import static com.android.deskclock.provider.ClockContract.InstancesColumns;
42 import static com.android.deskclock.provider.ClockDatabaseHelper.ALARMS_TABLE_NAME;
43 import static com.android.deskclock.provider.ClockDatabaseHelper.INSTANCES_TABLE_NAME;
44 
45 public class ClockProvider extends ContentProvider {
46 
47     private ClockDatabaseHelper mOpenHelper;
48 
49     private static final int ALARMS = 1;
50     private static final int ALARMS_ID = 2;
51     private static final int INSTANCES = 3;
52     private static final int INSTANCES_ID = 4;
53     private static final int ALARMS_WITH_INSTANCES = 5;
54 
55     /**
56      * Projection map used by query for snoozed alarms.
57      */
58     private static final Map<String, String> sAlarmsWithInstancesProjection = new ArrayMap<>();
59     static {
60         sAlarmsWithInstancesProjection.put(ALARMS_TABLE_NAME + "." + AlarmsColumns._ID,
61                 ALARMS_TABLE_NAME + "." + AlarmsColumns._ID);
62         sAlarmsWithInstancesProjection.put(ALARMS_TABLE_NAME + "." + AlarmsColumns.HOUR,
63                 ALARMS_TABLE_NAME + "." + AlarmsColumns.HOUR);
64         sAlarmsWithInstancesProjection.put(ALARMS_TABLE_NAME + "." + AlarmsColumns.MINUTES,
65                 ALARMS_TABLE_NAME + "." + AlarmsColumns.MINUTES);
66         sAlarmsWithInstancesProjection.put(ALARMS_TABLE_NAME + "." + AlarmsColumns.DAYS_OF_WEEK,
67                 ALARMS_TABLE_NAME + "." + AlarmsColumns.DAYS_OF_WEEK);
68         sAlarmsWithInstancesProjection.put(ALARMS_TABLE_NAME + "." + AlarmsColumns.ENABLED,
69                 ALARMS_TABLE_NAME + "." + AlarmsColumns.ENABLED);
70         sAlarmsWithInstancesProjection.put(ALARMS_TABLE_NAME + "." + AlarmsColumns.VIBRATE,
71                 ALARMS_TABLE_NAME + "." + AlarmsColumns.VIBRATE);
72         sAlarmsWithInstancesProjection.put(ALARMS_TABLE_NAME + "." + AlarmsColumns.LABEL,
73                 ALARMS_TABLE_NAME + "." + AlarmsColumns.LABEL);
74         sAlarmsWithInstancesProjection.put(ALARMS_TABLE_NAME + "." + AlarmsColumns.RINGTONE,
75                 ALARMS_TABLE_NAME + "." + AlarmsColumns.RINGTONE);
76         sAlarmsWithInstancesProjection.put(ALARMS_TABLE_NAME + "." + AlarmsColumns.DELETE_AFTER_USE,
77                 ALARMS_TABLE_NAME + "." + AlarmsColumns.DELETE_AFTER_USE);
78         sAlarmsWithInstancesProjection.put(INSTANCES_TABLE_NAME + "."
79                 + InstancesColumns.ALARM_STATE,
80                 INSTANCES_TABLE_NAME + "." + InstancesColumns.ALARM_STATE);
81         sAlarmsWithInstancesProjection.put(INSTANCES_TABLE_NAME + "." + InstancesColumns._ID,
82                 INSTANCES_TABLE_NAME + "." + InstancesColumns._ID);
83         sAlarmsWithInstancesProjection.put(INSTANCES_TABLE_NAME + "." + InstancesColumns.YEAR,
84                 INSTANCES_TABLE_NAME + "." + InstancesColumns.YEAR);
85         sAlarmsWithInstancesProjection.put(INSTANCES_TABLE_NAME + "." + InstancesColumns.MONTH,
86                 INSTANCES_TABLE_NAME + "." + InstancesColumns.MONTH);
87         sAlarmsWithInstancesProjection.put(INSTANCES_TABLE_NAME + "." + InstancesColumns.DAY,
88                 INSTANCES_TABLE_NAME + "." + InstancesColumns.DAY);
89         sAlarmsWithInstancesProjection.put(INSTANCES_TABLE_NAME + "." + InstancesColumns.HOUR,
90                 INSTANCES_TABLE_NAME + "." + InstancesColumns.HOUR);
91         sAlarmsWithInstancesProjection.put(INSTANCES_TABLE_NAME + "." + InstancesColumns.MINUTES,
92                 INSTANCES_TABLE_NAME + "." + InstancesColumns.MINUTES);
93         sAlarmsWithInstancesProjection.put(INSTANCES_TABLE_NAME + "." + InstancesColumns.LABEL,
94                 INSTANCES_TABLE_NAME + "." + InstancesColumns.LABEL);
95         sAlarmsWithInstancesProjection.put(INSTANCES_TABLE_NAME + "." + InstancesColumns.VIBRATE,
96                 INSTANCES_TABLE_NAME + "." + InstancesColumns.VIBRATE);
97     }
98 
99     private static final String ALARM_JOIN_INSTANCE_TABLE_STATEMENT =
100             ALARMS_TABLE_NAME + " LEFT JOIN " + INSTANCES_TABLE_NAME + " ON (" +
101             ALARMS_TABLE_NAME + "." + AlarmsColumns._ID + " = " + InstancesColumns.ALARM_ID + ")";
102 
103     private static final String ALARM_JOIN_INSTANCE_WHERE_STATEMENT =
104             INSTANCES_TABLE_NAME + "." + InstancesColumns._ID + " IS NULL OR " +
105             INSTANCES_TABLE_NAME + "." + InstancesColumns._ID + " = (" +
106                     "SELECT " + InstancesColumns._ID +
107                     " FROM " + INSTANCES_TABLE_NAME +
108                     " WHERE " + InstancesColumns.ALARM_ID +
109                     " = " + ALARMS_TABLE_NAME + "." + AlarmsColumns._ID +
110                     " ORDER BY " + InstancesColumns.ALARM_STATE + ", " +
111                     InstancesColumns.YEAR + ", " + InstancesColumns.MONTH + ", " +
112                     InstancesColumns.DAY + " LIMIT 1)";
113 
114     private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
115     static {
sURIMatcher.addURI(ClockContract.AUTHORITY, "alarms", ALARMS)116         sURIMatcher.addURI(ClockContract.AUTHORITY, "alarms", ALARMS);
sURIMatcher.addURI(ClockContract.AUTHORITY, "alarms/#", ALARMS_ID)117         sURIMatcher.addURI(ClockContract.AUTHORITY, "alarms/#", ALARMS_ID);
sURIMatcher.addURI(ClockContract.AUTHORITY, "instances", INSTANCES)118         sURIMatcher.addURI(ClockContract.AUTHORITY, "instances", INSTANCES);
sURIMatcher.addURI(ClockContract.AUTHORITY, "instances/#", INSTANCES_ID)119         sURIMatcher.addURI(ClockContract.AUTHORITY, "instances/#", INSTANCES_ID);
sURIMatcher.addURI(ClockContract.AUTHORITY, "alarms_with_instances", ALARMS_WITH_INSTANCES)120         sURIMatcher.addURI(ClockContract.AUTHORITY, "alarms_with_instances", ALARMS_WITH_INSTANCES);
121     }
122 
ClockProvider()123     public ClockProvider() {
124     }
125 
126     @Override
127     @TargetApi(Build.VERSION_CODES.N)
onCreate()128     public boolean onCreate() {
129         final Context context = getContext();
130         final Context storageContext;
131         if (Utils.isNOrLater()) {
132             // All N devices have split storage areas, but we may need to
133             // migrate existing database into the new device encrypted
134             // storage area, which is where our data lives from now on.
135             storageContext = context.createDeviceProtectedStorageContext();
136             if (!storageContext.moveDatabaseFrom(context, ClockDatabaseHelper.DATABASE_NAME)) {
137                 LogUtils.wtf("Failed to migrate database: %s", ClockDatabaseHelper.DATABASE_NAME);
138             }
139         } else {
140             storageContext = context;
141         }
142 
143         mOpenHelper = new ClockDatabaseHelper(storageContext);
144         return true;
145     }
146 
147     @Override
query(@onNull Uri uri, String[] projectionIn, String selection, String[] selectionArgs, String sort)148     public Cursor query(@NonNull Uri uri, String[] projectionIn, String selection,
149             String[] selectionArgs, String sort) {
150         SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
151         SQLiteDatabase db = mOpenHelper.getReadableDatabase();
152 
153         // Generate the body of the query
154         int match = sURIMatcher.match(uri);
155         switch (match) {
156             case ALARMS:
157                 qb.setTables(ALARMS_TABLE_NAME);
158                 break;
159             case ALARMS_ID:
160                 qb.setTables(ALARMS_TABLE_NAME);
161                 qb.appendWhere(AlarmsColumns._ID + "=");
162                 qb.appendWhere(uri.getLastPathSegment());
163                 break;
164             case INSTANCES:
165                 qb.setTables(INSTANCES_TABLE_NAME);
166                 break;
167             case INSTANCES_ID:
168                 qb.setTables(INSTANCES_TABLE_NAME);
169                 qb.appendWhere(InstancesColumns._ID + "=");
170                 qb.appendWhere(uri.getLastPathSegment());
171                 break;
172             case ALARMS_WITH_INSTANCES:
173                 qb.setTables(ALARM_JOIN_INSTANCE_TABLE_STATEMENT);
174                 qb.appendWhere(ALARM_JOIN_INSTANCE_WHERE_STATEMENT);
175                 qb.setProjectionMap(sAlarmsWithInstancesProjection);
176                 break;
177             default:
178                 throw new IllegalArgumentException("Unknown URI " + uri);
179         }
180 
181         Cursor ret = qb.query(db, projectionIn, selection, selectionArgs, null, null, sort);
182 
183         if (ret == null) {
184             LogUtils.e("Alarms.query: failed");
185         } else {
186             ret.setNotificationUri(getContext().getContentResolver(), uri);
187         }
188 
189         return ret;
190     }
191 
192     @Override
getType(@onNull Uri uri)193     public String getType(@NonNull Uri uri) {
194         int match = sURIMatcher.match(uri);
195         switch (match) {
196             case ALARMS:
197                 return "vnd.android.cursor.dir/alarms";
198             case ALARMS_ID:
199                 return "vnd.android.cursor.item/alarms";
200             case INSTANCES:
201                 return "vnd.android.cursor.dir/instances";
202             case INSTANCES_ID:
203                 return "vnd.android.cursor.item/instances";
204             default:
205                 throw new IllegalArgumentException("Unknown URI");
206         }
207     }
208 
209     @Override
update(@onNull Uri uri, ContentValues values, String where, String[] whereArgs)210     public int update(@NonNull Uri uri, ContentValues values, String where, String[] whereArgs) {
211         int count;
212         String alarmId;
213         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
214         switch (sURIMatcher.match(uri)) {
215             case ALARMS_ID:
216                 alarmId = uri.getLastPathSegment();
217                 count = db.update(ALARMS_TABLE_NAME, values,
218                         AlarmsColumns._ID + "=" + alarmId,
219                         null);
220                 break;
221             case INSTANCES_ID:
222                 alarmId = uri.getLastPathSegment();
223                 count = db.update(INSTANCES_TABLE_NAME, values,
224                         InstancesColumns._ID + "=" + alarmId,
225                         null);
226                 break;
227             default: {
228                 throw new UnsupportedOperationException("Cannot update URI: " + uri);
229             }
230         }
231         LogUtils.v("*** notifyChange() id: " + alarmId + " url " + uri);
232         notifyChange(getContext().getContentResolver(), uri);
233         return count;
234     }
235 
236     @Override
insert(@onNull Uri uri, ContentValues initialValues)237     public Uri insert(@NonNull Uri uri, ContentValues initialValues) {
238         long rowId;
239         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
240         switch (sURIMatcher.match(uri)) {
241             case ALARMS:
242                 rowId = mOpenHelper.fixAlarmInsert(initialValues);
243                 break;
244             case INSTANCES:
245                 rowId = db.insert(INSTANCES_TABLE_NAME, null, initialValues);
246                 break;
247             default:
248                 throw new IllegalArgumentException("Cannot insert from URI: " + uri);
249         }
250 
251         Uri uriResult = ContentUris.withAppendedId(uri, rowId);
252         notifyChange(getContext().getContentResolver(), uriResult);
253         return uriResult;
254     }
255 
256     @Override
delete(@onNull Uri uri, String where, String[] whereArgs)257     public int delete(@NonNull Uri uri, String where, String[] whereArgs) {
258         int count;
259         String primaryKey;
260         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
261         switch (sURIMatcher.match(uri)) {
262             case ALARMS:
263                 count = db.delete(ALARMS_TABLE_NAME, where, whereArgs);
264                 break;
265             case ALARMS_ID:
266                 primaryKey = uri.getLastPathSegment();
267                 if (TextUtils.isEmpty(where)) {
268                     where = AlarmsColumns._ID + "=" + primaryKey;
269                 } else {
270                     where = AlarmsColumns._ID + "=" + primaryKey + " AND (" + where + ")";
271                 }
272                 count = db.delete(ALARMS_TABLE_NAME, where, whereArgs);
273                 break;
274             case INSTANCES:
275                 count = db.delete(INSTANCES_TABLE_NAME, where, whereArgs);
276                 break;
277             case INSTANCES_ID:
278                 primaryKey = uri.getLastPathSegment();
279                 if (TextUtils.isEmpty(where)) {
280                     where = InstancesColumns._ID + "=" + primaryKey;
281                 } else {
282                     where = InstancesColumns._ID + "=" + primaryKey + " AND (" + where + ")";
283                 }
284                 count = db.delete(INSTANCES_TABLE_NAME, where, whereArgs);
285                 break;
286             default:
287                 throw new IllegalArgumentException("Cannot delete from URI: " + uri);
288         }
289 
290         notifyChange(getContext().getContentResolver(), uri);
291         return count;
292     }
293 
294     /**
295      * Notify affected URIs of changes.
296      */
notifyChange(ContentResolver resolver, Uri uri)297     private void notifyChange(ContentResolver resolver, Uri uri) {
298         resolver.notifyChange(uri, null);
299 
300         final int match = sURIMatcher.match(uri);
301         // Also notify the joined table of changes to instances or alarms.
302         if (match == ALARMS || match == INSTANCES || match == ALARMS_ID || match == INSTANCES_ID) {
303             resolver.notifyChange(AlarmsColumns.ALARMS_WITH_INSTANCES_URI, null);
304         }
305     }
306 }
307