1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/db/TabsProvider.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,391 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +package org.mozilla.gecko.db; 1.9 + 1.10 +import java.util.Collections; 1.11 +import java.util.HashMap; 1.12 +import java.util.Map; 1.13 + 1.14 +import org.mozilla.gecko.db.BrowserContract.Clients; 1.15 +import org.mozilla.gecko.db.BrowserContract.Tabs; 1.16 + 1.17 +import android.content.ContentUris; 1.18 +import android.content.ContentValues; 1.19 +import android.content.Context; 1.20 +import android.content.UriMatcher; 1.21 +import android.database.Cursor; 1.22 +import android.database.sqlite.SQLiteDatabase; 1.23 +import android.database.sqlite.SQLiteOpenHelper; 1.24 +import android.database.sqlite.SQLiteQueryBuilder; 1.25 +import android.net.Uri; 1.26 +import android.text.TextUtils; 1.27 + 1.28 +public class TabsProvider extends PerProfileDatabaseProvider<TabsProvider.TabsDatabaseHelper> { 1.29 + static final String DATABASE_NAME = "tabs.db"; 1.30 + 1.31 + static final int DATABASE_VERSION = 2; 1.32 + 1.33 + static final String TABLE_TABS = "tabs"; 1.34 + static final String TABLE_CLIENTS = "clients"; 1.35 + 1.36 + static final int TABS = 600; 1.37 + static final int TABS_ID = 601; 1.38 + static final int CLIENTS = 602; 1.39 + static final int CLIENTS_ID = 603; 1.40 + 1.41 + static final String DEFAULT_TABS_SORT_ORDER = Clients.LAST_MODIFIED + " DESC, " + Tabs.LAST_USED + " DESC"; 1.42 + static final String DEFAULT_CLIENTS_SORT_ORDER = Clients.LAST_MODIFIED + " DESC"; 1.43 + 1.44 + static final String INDEX_TABS_GUID = "tabs_guid_index"; 1.45 + static final String INDEX_TABS_POSITION = "tabs_position_index"; 1.46 + static final String INDEX_CLIENTS_GUID = "clients_guid_index"; 1.47 + 1.48 + static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); 1.49 + 1.50 + static final Map<String, String> TABS_PROJECTION_MAP; 1.51 + static final Map<String, String> CLIENTS_PROJECTION_MAP; 1.52 + 1.53 + static { 1.54 + URI_MATCHER.addURI(BrowserContract.TABS_AUTHORITY, "tabs", TABS); 1.55 + URI_MATCHER.addURI(BrowserContract.TABS_AUTHORITY, "tabs/#", TABS_ID); 1.56 + URI_MATCHER.addURI(BrowserContract.TABS_AUTHORITY, "clients", CLIENTS); 1.57 + URI_MATCHER.addURI(BrowserContract.TABS_AUTHORITY, "clients/#", CLIENTS_ID); 1.58 + 1.59 + HashMap<String, String> map; 1.60 + 1.61 + map = new HashMap<String, String>(); 1.62 + map.put(Tabs._ID, Tabs._ID); 1.63 + map.put(Tabs.TITLE, Tabs.TITLE); 1.64 + map.put(Tabs.URL, Tabs.URL); 1.65 + map.put(Tabs.HISTORY, Tabs.HISTORY); 1.66 + map.put(Tabs.FAVICON, Tabs.FAVICON); 1.67 + map.put(Tabs.LAST_USED, Tabs.LAST_USED); 1.68 + map.put(Tabs.POSITION, Tabs.POSITION); 1.69 + map.put(Clients.GUID, Clients.GUID); 1.70 + map.put(Clients.NAME, Clients.NAME); 1.71 + map.put(Clients.LAST_MODIFIED, Clients.LAST_MODIFIED); 1.72 + TABS_PROJECTION_MAP = Collections.unmodifiableMap(map); 1.73 + 1.74 + map = new HashMap<String, String>(); 1.75 + map.put(Clients.GUID, Clients.GUID); 1.76 + map.put(Clients.NAME, Clients.NAME); 1.77 + map.put(Clients.LAST_MODIFIED, Clients.LAST_MODIFIED); 1.78 + CLIENTS_PROJECTION_MAP = Collections.unmodifiableMap(map); 1.79 + } 1.80 + 1.81 + private static final String selectColumn(String table, String column) { 1.82 + return table + "." + column + " = ?"; 1.83 + } 1.84 + 1.85 + final class TabsDatabaseHelper extends SQLiteOpenHelper { 1.86 + public TabsDatabaseHelper(Context context, String databasePath) { 1.87 + super(context, databasePath, null, DATABASE_VERSION); 1.88 + } 1.89 + 1.90 + @Override 1.91 + public void onCreate(SQLiteDatabase db) { 1.92 + debug("Creating tabs.db: " + db.getPath()); 1.93 + debug("Creating " + TABLE_TABS + " table"); 1.94 + 1.95 + // Table for each tab on any client. 1.96 + db.execSQL("CREATE TABLE " + TABLE_TABS + "(" + 1.97 + Tabs._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 1.98 + Tabs.CLIENT_GUID + " TEXT," + 1.99 + Tabs.TITLE + " TEXT," + 1.100 + Tabs.URL + " TEXT," + 1.101 + Tabs.HISTORY + " TEXT," + 1.102 + Tabs.FAVICON + " TEXT," + 1.103 + Tabs.LAST_USED + " INTEGER," + 1.104 + Tabs.POSITION + " INTEGER" + 1.105 + ");"); 1.106 + 1.107 + // Indices on CLIENT_GUID and POSITION. 1.108 + db.execSQL("CREATE INDEX " + INDEX_TABS_GUID + 1.109 + " ON " + TABLE_TABS + "(" + Tabs.CLIENT_GUID + ")"); 1.110 + db.execSQL("CREATE INDEX " + INDEX_TABS_POSITION + 1.111 + " ON " + TABLE_TABS + "(" + Tabs.POSITION + ")"); 1.112 + 1.113 + debug("Creating " + TABLE_CLIENTS + " table"); 1.114 + 1.115 + // Table for client's name-guid mapping. 1.116 + db.execSQL("CREATE TABLE " + TABLE_CLIENTS + "(" + 1.117 + Clients.GUID + " TEXT PRIMARY KEY," + 1.118 + Clients.NAME + " TEXT," + 1.119 + Clients.LAST_MODIFIED + " INTEGER" + 1.120 + ");"); 1.121 + 1.122 + // Index on GUID. 1.123 + db.execSQL("CREATE INDEX " + INDEX_CLIENTS_GUID + 1.124 + " ON " + TABLE_CLIENTS + "(" + Clients.GUID + ")"); 1.125 + 1.126 + createLocalClient(db); 1.127 + } 1.128 + 1.129 + // Insert a client row for our local Fennec client. 1.130 + private void createLocalClient(SQLiteDatabase db) { 1.131 + debug("Inserting local Fennec client into " + TABLE_CLIENTS + " table"); 1.132 + 1.133 + ContentValues values = new ContentValues(); 1.134 + values.put(BrowserContract.Clients.LAST_MODIFIED, System.currentTimeMillis()); 1.135 + db.insertOrThrow(TABLE_CLIENTS, null, values); 1.136 + } 1.137 + 1.138 + @Override 1.139 + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 1.140 + debug("Upgrading tabs.db: " + db.getPath() + " from " + 1.141 + oldVersion + " to " + newVersion); 1.142 + 1.143 + // We have to do incremental upgrades until we reach the current 1.144 + // database schema version. 1.145 + for (int v = oldVersion + 1; v <= newVersion; v++) { 1.146 + switch(v) { 1.147 + case 2: 1.148 + createLocalClient(db); 1.149 + break; 1.150 + } 1.151 + } 1.152 + } 1.153 + 1.154 + @Override 1.155 + public void onOpen(SQLiteDatabase db) { 1.156 + debug("Opening tabs.db: " + db.getPath()); 1.157 + db.rawQuery("PRAGMA synchronous=OFF", null).close(); 1.158 + 1.159 + if (shouldUseTransactions()) { 1.160 + db.enableWriteAheadLogging(); 1.161 + db.setLockingEnabled(false); 1.162 + return; 1.163 + } 1.164 + 1.165 + // If we're not using transactions (in particular, prior to 1.166 + // Honeycomb), then we can do some lesser optimizations. 1.167 + db.rawQuery("PRAGMA journal_mode=PERSIST", null).close(); 1.168 + } 1.169 + } 1.170 + 1.171 + @Override 1.172 + public String getType(Uri uri) { 1.173 + final int match = URI_MATCHER.match(uri); 1.174 + 1.175 + trace("Getting URI type: " + uri); 1.176 + 1.177 + switch (match) { 1.178 + case TABS: 1.179 + trace("URI is TABS: " + uri); 1.180 + return Tabs.CONTENT_TYPE; 1.181 + 1.182 + case TABS_ID: 1.183 + trace("URI is TABS_ID: " + uri); 1.184 + return Tabs.CONTENT_ITEM_TYPE; 1.185 + 1.186 + case CLIENTS: 1.187 + trace("URI is CLIENTS: " + uri); 1.188 + return Clients.CONTENT_TYPE; 1.189 + 1.190 + case CLIENTS_ID: 1.191 + trace("URI is CLIENTS_ID: " + uri); 1.192 + return Clients.CONTENT_ITEM_TYPE; 1.193 + } 1.194 + 1.195 + debug("URI has unrecognized type: " + uri); 1.196 + 1.197 + return null; 1.198 + } 1.199 + 1.200 + @SuppressWarnings("fallthrough") 1.201 + public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) { 1.202 + trace("Calling delete in transaction on URI: " + uri); 1.203 + 1.204 + final int match = URI_MATCHER.match(uri); 1.205 + int deleted = 0; 1.206 + 1.207 + switch (match) { 1.208 + case CLIENTS_ID: 1.209 + trace("Delete on CLIENTS_ID: " + uri); 1.210 + selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_CLIENTS, Clients.ROWID)); 1.211 + selectionArgs = DBUtils.appendSelectionArgs(selectionArgs, 1.212 + new String[] { Long.toString(ContentUris.parseId(uri)) }); 1.213 + // fall through 1.214 + case CLIENTS: 1.215 + trace("Delete on CLIENTS: " + uri); 1.216 + // Delete from both TABLE_TABS and TABLE_CLIENTS. 1.217 + deleteValues(uri, selection, selectionArgs, TABLE_TABS); 1.218 + deleted = deleteValues(uri, selection, selectionArgs, TABLE_CLIENTS); 1.219 + break; 1.220 + 1.221 + case TABS_ID: 1.222 + trace("Delete on TABS_ID: " + uri); 1.223 + selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_TABS, Tabs._ID)); 1.224 + selectionArgs = DBUtils.appendSelectionArgs(selectionArgs, 1.225 + new String[] { Long.toString(ContentUris.parseId(uri)) }); 1.226 + // fall through 1.227 + case TABS: 1.228 + trace("Deleting on TABS: " + uri); 1.229 + deleted = deleteValues(uri, selection, selectionArgs, TABLE_TABS); 1.230 + break; 1.231 + 1.232 + default: 1.233 + throw new UnsupportedOperationException("Unknown delete URI " + uri); 1.234 + } 1.235 + 1.236 + debug("Deleted " + deleted + " rows for URI: " + uri); 1.237 + 1.238 + return deleted; 1.239 + } 1.240 + 1.241 + public Uri insertInTransaction(Uri uri, ContentValues values) { 1.242 + trace("Calling insert in transaction on URI: " + uri); 1.243 + 1.244 + final SQLiteDatabase db = getWritableDatabase(uri); 1.245 + int match = URI_MATCHER.match(uri); 1.246 + long id = -1; 1.247 + 1.248 + switch (match) { 1.249 + case CLIENTS: 1.250 + String guid = values.getAsString(Clients.GUID); 1.251 + debug("Inserting client in database with GUID: " + guid); 1.252 + id = db.insertOrThrow(TABLE_CLIENTS, Clients.GUID, values); 1.253 + break; 1.254 + 1.255 + case TABS: 1.256 + String url = values.getAsString(Tabs.URL); 1.257 + debug("Inserting tab in database with URL: " + url); 1.258 + id = db.insertOrThrow(TABLE_TABS, Tabs.TITLE, values); 1.259 + break; 1.260 + 1.261 + default: 1.262 + throw new UnsupportedOperationException("Unknown insert URI " + uri); 1.263 + } 1.264 + 1.265 + debug("Inserted ID in database: " + id); 1.266 + 1.267 + if (id >= 0) 1.268 + return ContentUris.withAppendedId(uri, id); 1.269 + 1.270 + return null; 1.271 + } 1.272 + 1.273 + public int updateInTransaction(Uri uri, ContentValues values, String selection, String[] selectionArgs) { 1.274 + trace("Calling update in transaction on URI: " + uri); 1.275 + 1.276 + int match = URI_MATCHER.match(uri); 1.277 + int updated = 0; 1.278 + 1.279 + switch (match) { 1.280 + case CLIENTS_ID: 1.281 + trace("Update on CLIENTS_ID: " + uri); 1.282 + selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_CLIENTS, Clients.ROWID)); 1.283 + selectionArgs = DBUtils.appendSelectionArgs(selectionArgs, 1.284 + new String[] { Long.toString(ContentUris.parseId(uri)) }); 1.285 + // fall through 1.286 + case CLIENTS: 1.287 + trace("Update on CLIENTS: " + uri); 1.288 + updated = updateValues(uri, values, selection, selectionArgs, TABLE_CLIENTS); 1.289 + break; 1.290 + 1.291 + case TABS_ID: 1.292 + trace("Update on TABS_ID: " + uri); 1.293 + selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_TABS, Tabs._ID)); 1.294 + selectionArgs = DBUtils.appendSelectionArgs(selectionArgs, 1.295 + new String[] { Long.toString(ContentUris.parseId(uri)) }); 1.296 + // fall through 1.297 + case TABS: 1.298 + trace("Update on TABS: " + uri); 1.299 + updated = updateValues(uri, values, selection, selectionArgs, TABLE_TABS); 1.300 + break; 1.301 + 1.302 + default: 1.303 + throw new UnsupportedOperationException("Unknown update URI " + uri); 1.304 + } 1.305 + 1.306 + debug("Updated " + updated + " rows for URI: " + uri); 1.307 + 1.308 + return updated; 1.309 + } 1.310 + 1.311 + @Override 1.312 + @SuppressWarnings("fallthrough") 1.313 + public Cursor query(Uri uri, String[] projection, String selection, 1.314 + String[] selectionArgs, String sortOrder) { 1.315 + SQLiteDatabase db = getReadableDatabase(uri); 1.316 + final int match = URI_MATCHER.match(uri); 1.317 + 1.318 + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 1.319 + String limit = uri.getQueryParameter(BrowserContract.PARAM_LIMIT); 1.320 + 1.321 + switch (match) { 1.322 + case TABS_ID: 1.323 + trace("Query is on TABS_ID: " + uri); 1.324 + selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_TABS, Tabs._ID)); 1.325 + selectionArgs = DBUtils.appendSelectionArgs(selectionArgs, 1.326 + new String[] { Long.toString(ContentUris.parseId(uri)) }); 1.327 + // fall through 1.328 + case TABS: 1.329 + trace("Query is on TABS: " + uri); 1.330 + if (TextUtils.isEmpty(sortOrder)) { 1.331 + sortOrder = DEFAULT_TABS_SORT_ORDER; 1.332 + } else { 1.333 + debug("Using sort order " + sortOrder + "."); 1.334 + } 1.335 + 1.336 + qb.setProjectionMap(TABS_PROJECTION_MAP); 1.337 + qb.setTables(TABLE_TABS + " LEFT OUTER JOIN " + TABLE_CLIENTS + " ON (" + TABLE_TABS + "." + Tabs.CLIENT_GUID + " = " + TABLE_CLIENTS + "." + Clients.GUID + ")"); 1.338 + break; 1.339 + 1.340 + case CLIENTS_ID: 1.341 + trace("Query is on CLIENTS_ID: " + uri); 1.342 + selection = DBUtils.concatenateWhere(selection, selectColumn(TABLE_CLIENTS, Clients.ROWID)); 1.343 + selectionArgs = DBUtils.appendSelectionArgs(selectionArgs, 1.344 + new String[] { Long.toString(ContentUris.parseId(uri)) }); 1.345 + // fall through 1.346 + case CLIENTS: 1.347 + trace("Query is on CLIENTS: " + uri); 1.348 + if (TextUtils.isEmpty(sortOrder)) { 1.349 + sortOrder = DEFAULT_CLIENTS_SORT_ORDER; 1.350 + } else { 1.351 + debug("Using sort order " + sortOrder + "."); 1.352 + } 1.353 + 1.354 + qb.setProjectionMap(CLIENTS_PROJECTION_MAP); 1.355 + qb.setTables(TABLE_CLIENTS); 1.356 + break; 1.357 + 1.358 + default: 1.359 + throw new UnsupportedOperationException("Unknown query URI " + uri); 1.360 + } 1.361 + 1.362 + trace("Running built query."); 1.363 + final Cursor cursor = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder, limit); 1.364 + cursor.setNotificationUri(getContext().getContentResolver(), BrowserContract.TABS_AUTHORITY_URI); 1.365 + 1.366 + return cursor; 1.367 + } 1.368 + 1.369 + int updateValues(Uri uri, ContentValues values, String selection, String[] selectionArgs, String table) { 1.370 + trace("Updating tabs on URI: " + uri); 1.371 + 1.372 + final SQLiteDatabase db = getWritableDatabase(uri); 1.373 + beginWrite(db); 1.374 + return db.update(table, values, selection, selectionArgs); 1.375 + } 1.376 + 1.377 + int deleteValues(Uri uri, String selection, String[] selectionArgs, String table) { 1.378 + debug("Deleting tabs for URI: " + uri); 1.379 + 1.380 + final SQLiteDatabase db = getWritableDatabase(uri); 1.381 + beginWrite(db); 1.382 + return db.delete(table, selection, selectionArgs); 1.383 + } 1.384 + 1.385 + @Override 1.386 + protected TabsDatabaseHelper createDatabaseHelper(Context context, String databasePath) { 1.387 + return new TabsDatabaseHelper(context, databasePath); 1.388 + } 1.389 + 1.390 + @Override 1.391 + protected String getDatabaseName() { 1.392 + return DATABASE_NAME; 1.393 + } 1.394 +}