mobile/android/base/db/TabsProvider.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

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

mercurial