mobile/android/base/db/TabsProvider.java

branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
equal deleted inserted replaced
-1:000000000000 0:39211f9e3955
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 package org.mozilla.gecko.db;
6
7 import java.util.Collections;
8 import java.util.HashMap;
9 import java.util.Map;
10
11 import org.mozilla.gecko.db.BrowserContract.Clients;
12 import org.mozilla.gecko.db.BrowserContract.Tabs;
13
14 import android.content.ContentUris;
15 import android.content.ContentValues;
16 import android.content.Context;
17 import android.content.UriMatcher;
18 import android.database.Cursor;
19 import android.database.sqlite.SQLiteDatabase;
20 import android.database.sqlite.SQLiteOpenHelper;
21 import android.database.sqlite.SQLiteQueryBuilder;
22 import android.net.Uri;
23 import android.text.TextUtils;
24
25 public class TabsProvider extends PerProfileDatabaseProvider<TabsProvider.TabsDatabaseHelper> {
26 static final String DATABASE_NAME = "tabs.db";
27
28 static final int DATABASE_VERSION = 2;
29
30 static final String TABLE_TABS = "tabs";
31 static final String TABLE_CLIENTS = "clients";
32
33 static final int TABS = 600;
34 static final int TABS_ID = 601;
35 static final int CLIENTS = 602;
36 static final int CLIENTS_ID = 603;
37
38 static final String DEFAULT_TABS_SORT_ORDER = Clients.LAST_MODIFIED + " DESC, " + Tabs.LAST_USED + " DESC";
39 static final String DEFAULT_CLIENTS_SORT_ORDER = Clients.LAST_MODIFIED + " DESC";
40
41 static final String INDEX_TABS_GUID = "tabs_guid_index";
42 static final String INDEX_TABS_POSITION = "tabs_position_index";
43 static final String INDEX_CLIENTS_GUID = "clients_guid_index";
44
45 static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
46
47 static final Map<String, String> TABS_PROJECTION_MAP;
48 static final Map<String, String> CLIENTS_PROJECTION_MAP;
49
50 static {
51 URI_MATCHER.addURI(BrowserContract.TABS_AUTHORITY, "tabs", TABS);
52 URI_MATCHER.addURI(BrowserContract.TABS_AUTHORITY, "tabs/#", TABS_ID);
53 URI_MATCHER.addURI(BrowserContract.TABS_AUTHORITY, "clients", CLIENTS);
54 URI_MATCHER.addURI(BrowserContract.TABS_AUTHORITY, "clients/#", CLIENTS_ID);
55
56 HashMap<String, String> map;
57
58 map = new HashMap<String, String>();
59 map.put(Tabs._ID, Tabs._ID);
60 map.put(Tabs.TITLE, Tabs.TITLE);
61 map.put(Tabs.URL, Tabs.URL);
62 map.put(Tabs.HISTORY, Tabs.HISTORY);
63 map.put(Tabs.FAVICON, Tabs.FAVICON);
64 map.put(Tabs.LAST_USED, Tabs.LAST_USED);
65 map.put(Tabs.POSITION, Tabs.POSITION);
66 map.put(Clients.GUID, Clients.GUID);
67 map.put(Clients.NAME, Clients.NAME);
68 map.put(Clients.LAST_MODIFIED, Clients.LAST_MODIFIED);
69 TABS_PROJECTION_MAP = Collections.unmodifiableMap(map);
70
71 map = new HashMap<String, String>();
72 map.put(Clients.GUID, Clients.GUID);
73 map.put(Clients.NAME, Clients.NAME);
74 map.put(Clients.LAST_MODIFIED, Clients.LAST_MODIFIED);
75 CLIENTS_PROJECTION_MAP = Collections.unmodifiableMap(map);
76 }
77
78 private static final String selectColumn(String table, String column) {
79 return table + "." + column + " = ?";
80 }
81
82 final class TabsDatabaseHelper extends SQLiteOpenHelper {
83 public TabsDatabaseHelper(Context context, String databasePath) {
84 super(context, databasePath, null, DATABASE_VERSION);
85 }
86
87 @Override
88 public void onCreate(SQLiteDatabase db) {
89 debug("Creating tabs.db: " + db.getPath());
90 debug("Creating " + TABLE_TABS + " table");
91
92 // Table for each tab on any client.
93 db.execSQL("CREATE TABLE " + TABLE_TABS + "(" +
94 Tabs._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
95 Tabs.CLIENT_GUID + " TEXT," +
96 Tabs.TITLE + " TEXT," +
97 Tabs.URL + " TEXT," +
98 Tabs.HISTORY + " TEXT," +
99 Tabs.FAVICON + " TEXT," +
100 Tabs.LAST_USED + " INTEGER," +
101 Tabs.POSITION + " INTEGER" +
102 ");");
103
104 // Indices on CLIENT_GUID and POSITION.
105 db.execSQL("CREATE INDEX " + INDEX_TABS_GUID +
106 " ON " + TABLE_TABS + "(" + Tabs.CLIENT_GUID + ")");
107 db.execSQL("CREATE INDEX " + INDEX_TABS_POSITION +
108 " ON " + TABLE_TABS + "(" + Tabs.POSITION + ")");
109
110 debug("Creating " + TABLE_CLIENTS + " table");
111
112 // Table for client's name-guid mapping.
113 db.execSQL("CREATE TABLE " + TABLE_CLIENTS + "(" +
114 Clients.GUID + " TEXT PRIMARY KEY," +
115 Clients.NAME + " TEXT," +
116 Clients.LAST_MODIFIED + " INTEGER" +
117 ");");
118
119 // Index on GUID.
120 db.execSQL("CREATE INDEX " + INDEX_CLIENTS_GUID +
121 " ON " + TABLE_CLIENTS + "(" + Clients.GUID + ")");
122
123 createLocalClient(db);
124 }
125
126 // Insert a client row for our local Fennec client.
127 private void createLocalClient(SQLiteDatabase db) {
128 debug("Inserting local Fennec client into " + TABLE_CLIENTS + " table");
129
130 ContentValues values = new ContentValues();
131 values.put(BrowserContract.Clients.LAST_MODIFIED, System.currentTimeMillis());
132 db.insertOrThrow(TABLE_CLIENTS, null, values);
133 }
134
135 @Override
136 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
137 debug("Upgrading tabs.db: " + db.getPath() + " from " +
138 oldVersion + " to " + newVersion);
139
140 // We have to do incremental upgrades until we reach the current
141 // database schema version.
142 for (int v = oldVersion + 1; v <= newVersion; v++) {
143 switch(v) {
144 case 2:
145 createLocalClient(db);
146 break;
147 }
148 }
149 }
150
151 @Override
152 public void onOpen(SQLiteDatabase db) {
153 debug("Opening tabs.db: " + db.getPath());
154 db.rawQuery("PRAGMA synchronous=OFF", null).close();
155
156 if (shouldUseTransactions()) {
157 db.enableWriteAheadLogging();
158 db.setLockingEnabled(false);
159 return;
160 }
161
162 // If we're not using transactions (in particular, prior to
163 // Honeycomb), then we can do some lesser optimizations.
164 db.rawQuery("PRAGMA journal_mode=PERSIST", null).close();
165 }
166 }
167
168 @Override
169 public String getType(Uri uri) {
170 final int match = URI_MATCHER.match(uri);
171
172 trace("Getting URI type: " + uri);
173
174 switch (match) {
175 case TABS:
176 trace("URI is TABS: " + uri);
177 return Tabs.CONTENT_TYPE;
178
179 case TABS_ID:
180 trace("URI is TABS_ID: " + uri);
181 return Tabs.CONTENT_ITEM_TYPE;
182
183 case CLIENTS:
184 trace("URI is CLIENTS: " + uri);
185 return Clients.CONTENT_TYPE;
186
187 case CLIENTS_ID:
188 trace("URI is CLIENTS_ID: " + uri);
189 return Clients.CONTENT_ITEM_TYPE;
190 }
191
192 debug("URI has unrecognized type: " + uri);
193
194 return null;
195 }
196
197 @SuppressWarnings("fallthrough")
198 public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
199 trace("Calling delete in transaction on URI: " + uri);
200
201 final int match = URI_MATCHER.match(uri);
202 int deleted = 0;
203
204 switch (match) {
205 case CLIENTS_ID:
206 trace("Delete on CLIENTS_ID: " + uri);
207 selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_CLIENTS, Clients.ROWID));
208 selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
209 new String[] { Long.toString(ContentUris.parseId(uri)) });
210 // fall through
211 case CLIENTS:
212 trace("Delete on CLIENTS: " + uri);
213 // Delete from both TABLE_TABS and TABLE_CLIENTS.
214 deleteValues(uri, selection, selectionArgs, TABLE_TABS);
215 deleted = deleteValues(uri, selection, selectionArgs, TABLE_CLIENTS);
216 break;
217
218 case TABS_ID:
219 trace("Delete on TABS_ID: " + uri);
220 selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_TABS, Tabs._ID));
221 selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
222 new String[] { Long.toString(ContentUris.parseId(uri)) });
223 // fall through
224 case TABS:
225 trace("Deleting on TABS: " + uri);
226 deleted = deleteValues(uri, selection, selectionArgs, TABLE_TABS);
227 break;
228
229 default:
230 throw new UnsupportedOperationException("Unknown delete URI " + uri);
231 }
232
233 debug("Deleted " + deleted + " rows for URI: " + uri);
234
235 return deleted;
236 }
237
238 public Uri insertInTransaction(Uri uri, ContentValues values) {
239 trace("Calling insert in transaction on URI: " + uri);
240
241 final SQLiteDatabase db = getWritableDatabase(uri);
242 int match = URI_MATCHER.match(uri);
243 long id = -1;
244
245 switch (match) {
246 case CLIENTS:
247 String guid = values.getAsString(Clients.GUID);
248 debug("Inserting client in database with GUID: " + guid);
249 id = db.insertOrThrow(TABLE_CLIENTS, Clients.GUID, values);
250 break;
251
252 case TABS:
253 String url = values.getAsString(Tabs.URL);
254 debug("Inserting tab in database with URL: " + url);
255 id = db.insertOrThrow(TABLE_TABS, Tabs.TITLE, values);
256 break;
257
258 default:
259 throw new UnsupportedOperationException("Unknown insert URI " + uri);
260 }
261
262 debug("Inserted ID in database: " + id);
263
264 if (id >= 0)
265 return ContentUris.withAppendedId(uri, id);
266
267 return null;
268 }
269
270 public int updateInTransaction(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
271 trace("Calling update in transaction on URI: " + uri);
272
273 int match = URI_MATCHER.match(uri);
274 int updated = 0;
275
276 switch (match) {
277 case CLIENTS_ID:
278 trace("Update on CLIENTS_ID: " + uri);
279 selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_CLIENTS, Clients.ROWID));
280 selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
281 new String[] { Long.toString(ContentUris.parseId(uri)) });
282 // fall through
283 case CLIENTS:
284 trace("Update on CLIENTS: " + uri);
285 updated = updateValues(uri, values, selection, selectionArgs, TABLE_CLIENTS);
286 break;
287
288 case TABS_ID:
289 trace("Update on TABS_ID: " + uri);
290 selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_TABS, Tabs._ID));
291 selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
292 new String[] { Long.toString(ContentUris.parseId(uri)) });
293 // fall through
294 case TABS:
295 trace("Update on TABS: " + uri);
296 updated = updateValues(uri, values, selection, selectionArgs, TABLE_TABS);
297 break;
298
299 default:
300 throw new UnsupportedOperationException("Unknown update URI " + uri);
301 }
302
303 debug("Updated " + updated + " rows for URI: " + uri);
304
305 return updated;
306 }
307
308 @Override
309 @SuppressWarnings("fallthrough")
310 public Cursor query(Uri uri, String[] projection, String selection,
311 String[] selectionArgs, String sortOrder) {
312 SQLiteDatabase db = getReadableDatabase(uri);
313 final int match = URI_MATCHER.match(uri);
314
315 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
316 String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT);
317
318 switch (match) {
319 case TABS_ID:
320 trace("Query is on TABS_ID: " + uri);
321 selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_TABS, Tabs._ID));
322 selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
323 new String[] { Long.toString(ContentUris.parseId(uri)) });
324 // fall through
325 case TABS:
326 trace("Query is on TABS: " + uri);
327 if (TextUtils.isEmpty(sortOrder)) {
328 sortOrder = DEFAULT_TABS_SORT_ORDER;
329 } else {
330 debug("Using sort order " + sortOrder + ".");
331 }
332
333 qb.setProjectionMap(TABS_PROJECTION_MAP);
334 qb.setTables(TABLE_TABS + " LEFT OUTER JOIN " + TABLE_CLIENTS + " ON (" + TABLE_TABS + "." + Tabs.CLIENT_GUID + " = " + TABLE_CLIENTS + "." + Clients.GUID + ")");
335 break;
336
337 case CLIENTS_ID:
338 trace("Query is on CLIENTS_ID: " + uri);
339 selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_CLIENTS, Clients.ROWID));
340 selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
341 new String[] { Long.toString(ContentUris.parseId(uri)) });
342 // fall through
343 case CLIENTS:
344 trace("Query is on CLIENTS: " + uri);
345 if (TextUtils.isEmpty(sortOrder)) {
346 sortOrder = DEFAULT_CLIENTS_SORT_ORDER;
347 } else {
348 debug("Using sort order " + sortOrder + ".");
349 }
350
351 qb.setProjectionMap(CLIENTS_PROJECTION_MAP);
352 qb.setTables(TABLE_CLIENTS);
353 break;
354
355 default:
356 throw new UnsupportedOperationException("Unknown query URI " + uri);
357 }
358
359 trace("Running built query.");
360 final Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder, limit);
361 cursor.setNotificationUri(getContext().getContentResolver(), BrowserContract.TABS_AUTHORITY_URI);
362
363 return cursor;
364 }
365
366 int updateValues(Uri uri, ContentValues values, String selection, String[] selectionArgs, String table) {
367 trace("Updating tabs on URI: " + uri);
368
369 final SQLiteDatabase db = getWritableDatabase(uri);
370 beginWrite(db);
371 return db.update(table, values, selection, selectionArgs);
372 }
373
374 int deleteValues(Uri uri, String selection, String[] selectionArgs, String table) {
375 debug("Deleting tabs for URI: " + uri);
376
377 final SQLiteDatabase db = getWritableDatabase(uri);
378 beginWrite(db);
379 return db.delete(table, selection, selectionArgs);
380 }
381
382 @Override
383 protected TabsDatabaseHelper createDatabaseHelper(Context context, String databasePath) {
384 return new TabsDatabaseHelper(context, databasePath);
385 }
386
387 @Override
388 protected String getDatabaseName() {
389 return DATABASE_NAME;
390 }
391 }

mercurial