Wed, 31 Dec 2014 07:22:50 +0100
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 | } |