1 /* 2 * Copyright 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.example.android.basicsyncadapter.provider; 18 19 import android.content.ContentProvider; 20 import android.content.ContentValues; 21 import android.content.Context; 22 import android.content.UriMatcher; 23 import android.database.Cursor; 24 import android.database.sqlite.SQLiteDatabase; 25 import android.database.sqlite.SQLiteOpenHelper; 26 import android.net.Uri; 27 28 import com.example.android.common.db.SelectionBuilder; 29 30 public class FeedProvider extends ContentProvider { 31 FeedDatabase mDatabaseHelper; 32 33 /** 34 * Content authority for this provider. 35 */ 36 private static final String AUTHORITY = FeedContract.CONTENT_AUTHORITY; 37 38 // The constants below represent individual URI routes, as IDs. Every URI pattern recognized by 39 // this ContentProvider is defined using sUriMatcher.addURI(), and associated with one of these 40 // IDs. 41 // 42 // When a incoming URI is run through sUriMatcher, it will be tested against the defined 43 // URI patterns, and the corresponding route ID will be returned. 44 /** 45 * URI ID for route: /entries 46 */ 47 public static final int ROUTE_ENTRIES = 1; 48 49 /** 50 * URI ID for route: /entries/{ID} 51 */ 52 public static final int ROUTE_ENTRIES_ID = 2; 53 54 /** 55 * UriMatcher, used to decode incoming URIs. 56 */ 57 private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 58 static { sUriMatcher.addURI(AUTHORITY, "entries", ROUTE_ENTRIES)59 sUriMatcher.addURI(AUTHORITY, "entries", ROUTE_ENTRIES); sUriMatcher.addURI(AUTHORITY, "entries/*", ROUTE_ENTRIES_ID)60 sUriMatcher.addURI(AUTHORITY, "entries/*", ROUTE_ENTRIES_ID); 61 } 62 63 @Override onCreate()64 public boolean onCreate() { 65 mDatabaseHelper = new FeedDatabase(getContext()); 66 return true; 67 } 68 69 /** 70 * Determine the mime type for entries returned by a given URI. 71 */ 72 @Override getType(Uri uri)73 public String getType(Uri uri) { 74 final int match = sUriMatcher.match(uri); 75 switch (match) { 76 case ROUTE_ENTRIES: 77 return FeedContract.Entry.CONTENT_TYPE; 78 case ROUTE_ENTRIES_ID: 79 return FeedContract.Entry.CONTENT_ITEM_TYPE; 80 default: 81 throw new UnsupportedOperationException("Unknown uri: " + uri); 82 } 83 } 84 85 /** 86 * Perform a database query by URI. 87 * 88 * <p>Currently supports returning all entries (/entries) and individual entries by ID 89 * (/entries/{ID}). 90 */ 91 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)92 public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, 93 String sortOrder) { 94 SQLiteDatabase db = mDatabaseHelper.getReadableDatabase(); 95 SelectionBuilder builder = new SelectionBuilder(); 96 int uriMatch = sUriMatcher.match(uri); 97 switch (uriMatch) { 98 case ROUTE_ENTRIES_ID: 99 // Return a single entry, by ID. 100 String id = uri.getLastPathSegment(); 101 builder.where(FeedContract.Entry._ID + "=?", id); 102 case ROUTE_ENTRIES: 103 // Return all known entries. 104 builder.table(FeedContract.Entry.TABLE_NAME) 105 .where(selection, selectionArgs); 106 Cursor c = builder.query(db, projection, sortOrder); 107 // Note: Notification URI must be manually set here for loaders to correctly 108 // register ContentObservers. 109 Context ctx = getContext(); 110 assert ctx != null; 111 c.setNotificationUri(ctx.getContentResolver(), uri); 112 return c; 113 default: 114 throw new UnsupportedOperationException("Unknown uri: " + uri); 115 } 116 } 117 118 /** 119 * Insert a new entry into the database. 120 */ 121 @Override insert(Uri uri, ContentValues values)122 public Uri insert(Uri uri, ContentValues values) { 123 final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase(); 124 assert db != null; 125 final int match = sUriMatcher.match(uri); 126 Uri result; 127 switch (match) { 128 case ROUTE_ENTRIES: 129 long id = db.insertOrThrow(FeedContract.Entry.TABLE_NAME, null, values); 130 result = Uri.parse(FeedContract.Entry.CONTENT_URI + "/" + id); 131 break; 132 case ROUTE_ENTRIES_ID: 133 throw new UnsupportedOperationException("Insert not supported on URI: " + uri); 134 default: 135 throw new UnsupportedOperationException("Unknown uri: " + uri); 136 } 137 // Send broadcast to registered ContentObservers, to refresh UI. 138 Context ctx = getContext(); 139 assert ctx != null; 140 ctx.getContentResolver().notifyChange(uri, null, false); 141 return result; 142 } 143 144 /** 145 * Delete an entry by database by URI. 146 */ 147 @Override delete(Uri uri, String selection, String[] selectionArgs)148 public int delete(Uri uri, String selection, String[] selectionArgs) { 149 SelectionBuilder builder = new SelectionBuilder(); 150 final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase(); 151 final int match = sUriMatcher.match(uri); 152 int count; 153 switch (match) { 154 case ROUTE_ENTRIES: 155 count = builder.table(FeedContract.Entry.TABLE_NAME) 156 .where(selection, selectionArgs) 157 .delete(db); 158 break; 159 case ROUTE_ENTRIES_ID: 160 String id = uri.getLastPathSegment(); 161 count = builder.table(FeedContract.Entry.TABLE_NAME) 162 .where(FeedContract.Entry._ID + "=?", id) 163 .where(selection, selectionArgs) 164 .delete(db); 165 break; 166 default: 167 throw new UnsupportedOperationException("Unknown uri: " + uri); 168 } 169 // Send broadcast to registered ContentObservers, to refresh UI. 170 Context ctx = getContext(); 171 assert ctx != null; 172 ctx.getContentResolver().notifyChange(uri, null, false); 173 return count; 174 } 175 176 /** 177 * Update an etry in the database by URI. 178 */ 179 @Override update(Uri uri, ContentValues values, String selection, String[] selectionArgs)180 public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 181 SelectionBuilder builder = new SelectionBuilder(); 182 final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase(); 183 final int match = sUriMatcher.match(uri); 184 int count; 185 switch (match) { 186 case ROUTE_ENTRIES: 187 count = builder.table(FeedContract.Entry.TABLE_NAME) 188 .where(selection, selectionArgs) 189 .update(db, values); 190 break; 191 case ROUTE_ENTRIES_ID: 192 String id = uri.getLastPathSegment(); 193 count = builder.table(FeedContract.Entry.TABLE_NAME) 194 .where(FeedContract.Entry._ID + "=?", id) 195 .where(selection, selectionArgs) 196 .update(db, values); 197 break; 198 default: 199 throw new UnsupportedOperationException("Unknown uri: " + uri); 200 } 201 Context ctx = getContext(); 202 assert ctx != null; 203 ctx.getContentResolver().notifyChange(uri, null, false); 204 return count; 205 } 206 207 /** 208 * SQLite backend for @{link FeedProvider}. 209 * 210 * Provides access to an disk-backed, SQLite datastore which is utilized by FeedProvider. This 211 * database should never be accessed by other parts of the application directly. 212 */ 213 static class FeedDatabase extends SQLiteOpenHelper { 214 /** Schema version. */ 215 public static final int DATABASE_VERSION = 1; 216 /** Filename for SQLite file. */ 217 public static final String DATABASE_NAME = "feed.db"; 218 219 private static final String TYPE_TEXT = " TEXT"; 220 private static final String TYPE_INTEGER = " INTEGER"; 221 private static final String COMMA_SEP = ","; 222 /** SQL statement to create "entry" table. */ 223 private static final String SQL_CREATE_ENTRIES = 224 "CREATE TABLE " + FeedContract.Entry.TABLE_NAME + " (" + 225 FeedContract.Entry._ID + " INTEGER PRIMARY KEY," + 226 FeedContract.Entry.COLUMN_NAME_ENTRY_ID + TYPE_TEXT + COMMA_SEP + 227 FeedContract.Entry.COLUMN_NAME_TITLE + TYPE_TEXT + COMMA_SEP + 228 FeedContract.Entry.COLUMN_NAME_LINK + TYPE_TEXT + COMMA_SEP + 229 FeedContract.Entry.COLUMN_NAME_PUBLISHED + TYPE_INTEGER + ")"; 230 231 /** SQL statement to drop "entry" table. */ 232 private static final String SQL_DELETE_ENTRIES = 233 "DROP TABLE IF EXISTS " + FeedContract.Entry.TABLE_NAME; 234 FeedDatabase(Context context)235 public FeedDatabase(Context context) { 236 super(context, DATABASE_NAME, null, DATABASE_VERSION); 237 } 238 239 @Override onCreate(SQLiteDatabase db)240 public void onCreate(SQLiteDatabase db) { 241 db.execSQL(SQL_CREATE_ENTRIES); 242 } 243 244 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)245 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 246 // This database is only a cache for online data, so its upgrade policy is 247 // to simply to discard the data and start over 248 db.execSQL(SQL_DELETE_ENTRIES); 249 onCreate(db); 250 } 251 } 252 } 253