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