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