Bug 941318 - Create content provider to store home lists data. r=wesj

This commit is contained in:
Margaret Leibovic 2013-12-19 14:51:09 -08:00
parent 7660bbfd17
commit a7d8706c72
5 changed files with 453 additions and 0 deletions

View File

@ -274,6 +274,10 @@
android:authorities="@ANDROID_PACKAGE_NAME@.db.tabs"
android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
<provider android:name="org.mozilla.gecko.db.HomeListsProvider"
android:authorities="@ANDROID_PACKAGE_NAME@.db.homelists"
android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
<service
android:exported="false"
android:name="org.mozilla.gecko.updater.UpdateService"

View File

@ -24,6 +24,9 @@ public class BrowserContract {
public static final String TABS_AUTHORITY = AppConstants.ANDROID_PACKAGE_NAME + ".db.tabs";
public static final Uri TABS_AUTHORITY_URI = Uri.parse("content://" + TABS_AUTHORITY);
public static final String HOME_LISTS_AUTHORITY = AppConstants.ANDROID_PACKAGE_NAME + ".db.homelists";
public static final Uri HOME_LISTS_AUTHORITY_URI = Uri.parse("content://" + HOME_LISTS_AUTHORITY);
public static final String PARAM_PROFILE = "profile";
public static final String PARAM_PROFILE_PATH = "profilePath";
public static final String PARAM_LIMIT = "limit";
@ -284,4 +287,17 @@ public class BrowserContract {
// timestamp provided by Sync during insertion.
public static final String LAST_MODIFIED = "last_modified";
}
// Data storage for custom lists on about:home
@RobocopTarget
public static final class HomeListItems implements CommonColumns, URLColumns {
private HomeListItems() {}
public static final Uri CONTENT_FAKE_URI = Uri.withAppendedPath(HOME_LISTS_AUTHORITY_URI, "items/fake");
public static final Uri CONTENT_URI = Uri.withAppendedPath(HOME_LISTS_AUTHORITY_URI, "items");
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/homelistitem";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/homelistitem";
public static final String PROVIDER_ID = "provider_id";
}
}

View File

@ -0,0 +1,48 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.db;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.db.BrowserContract.HomeListItems;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
final class HomeListsDatabaseHelper extends SQLiteOpenHelper {
static final int DATABASE_VERSION = 1;
static final String DATABASE_NAME = "homelists.db";
static final String TABLE_ITEMS = "items";
public HomeListsDatabaseHelper(Context context, String databasePath) {
super(context, databasePath, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE_ITEMS + "(" +
HomeListItems._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
HomeListItems.PROVIDER_ID + " TEXT," +
HomeListItems.TITLE + " TEXT," +
HomeListItems.URL + " TEXT)"
);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Unimpelemented
}
@Override
public void onOpen(SQLiteDatabase db) {
// Unimplemented
}
}

View File

@ -0,0 +1,383 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.db;
import org.mozilla.gecko.db.BrowserContract.HomeListItems;
import org.mozilla.gecko.db.PerProfileDatabases.DatabaseHelperFactory;
import org.mozilla.gecko.mozglue.RobocopTarget;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MatrixCursor.RowBuilder;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
public class HomeListsProvider extends ContentProvider {
private static final String LOGTAG = "GeckoHomeListsProvider";
// Flag to disable DB usage. This prevents us from creating (and later needing to upgrade) a DB.
private static final boolean DB_DISABLED = true;
private PerProfileDatabases<HomeListsDatabaseHelper> mDatabases;
// Endpoint to return static fake data.
static final int ITEMS_FAKE = 100;
static final int ITEMS = 101;
static final int ITEMS_ID = 102;
static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
static {
URI_MATCHER.addURI(BrowserContract.HOME_LISTS_AUTHORITY, "items/fake", ITEMS_FAKE);
URI_MATCHER.addURI(BrowserContract.HOME_LISTS_AUTHORITY, "items", ITEMS);
URI_MATCHER.addURI(BrowserContract.HOME_LISTS_AUTHORITY, "items/#", ITEMS_ID);
}
private static boolean logDebug = Log.isLoggable(LOGTAG, Log.DEBUG);
private static boolean logVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE);
protected static void trace(String message) {
if (logVerbose) {
Log.v(LOGTAG, message);
}
}
protected static void debug(String message) {
if (logDebug) {
Log.d(LOGTAG, message);
}
}
private boolean isTest(Uri uri) {
String isTest = uri.getQueryParameter(BrowserContract.PARAM_IS_TEST);
return !TextUtils.isEmpty(isTest);
}
private SQLiteDatabase getReadableDatabase(Uri uri) {
if (DB_DISABLED) {
throw new UnsupportedOperationException("Database operations are disabled!");
}
String profile = null;
if (uri != null) {
profile = uri.getQueryParameter(BrowserContract.PARAM_PROFILE);
}
return mDatabases.getDatabaseHelperForProfile(profile, isTest(uri)).getReadableDatabase();
}
private SQLiteDatabase getWritableDatabase(Uri uri) {
if (DB_DISABLED) {
throw new UnsupportedOperationException("Database operations are disabled!");
}
String profile = null;
if (uri != null) {
profile = uri.getQueryParameter(BrowserContract.PARAM_PROFILE);
}
return mDatabases.getDatabaseHelperForProfile(profile, isTest(uri)).getWritableDatabase();
}
@Override
public boolean onCreate() {
if (DB_DISABLED) {
return true;
}
synchronized (this) {
mDatabases = new PerProfileDatabases<HomeListsDatabaseHelper>(getContext(), HomeListsDatabaseHelper.DATABASE_NAME,
new DatabaseHelperFactory<HomeListsDatabaseHelper>() {
@Override
public HomeListsDatabaseHelper makeDatabaseHelper(Context context, String databasePath) {
return new HomeListsDatabaseHelper(context, databasePath);
}
});
}
return true;
}
@Override
public String getType(Uri uri) {
trace("Getting URI type: " + uri);
final int match = URI_MATCHER.match(uri);
switch (match) {
case ITEMS_FAKE: {
trace("URI is ITEMS_FAKE: " + uri);
return HomeListItems.CONTENT_TYPE;
}
case ITEMS: {
trace("URI is ITEMS: " + uri);
return HomeListItems.CONTENT_TYPE;
}
case ITEMS_ID: {
trace("URI is ITEMS_ID: " + uri);
return HomeListItems.CONTENT_ITEM_TYPE;
}
}
debug("URI has unrecognized type: " + uri);
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
trace("Calling delete on URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
int deleted = 0;
if (Build.VERSION.SDK_INT >= 11) {
trace("Beginning delete transaction: " + uri);
db.beginTransaction();
try {
deleted = deleteInTransaction(uri, selection, selectionArgs);
db.setTransactionSuccessful();
trace("Successful delete transaction: " + uri);
} finally {
db.endTransaction();
}
} else {
deleted = deleteInTransaction(uri, selection, selectionArgs);
}
if (deleted > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return deleted;
}
public int deleteInTransaction(Uri uri, String selection, String[] selectionArgs) {
trace("Calling delete in transaction on URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
int deleted = 0;
final int match = URI_MATCHER.match(uri);
switch (match) {
case ITEMS: {
trace("Delete on ITEMS: " + uri);
deleted = db.delete(HomeListsDatabaseHelper.TABLE_ITEMS, selection, selectionArgs);
break;
}
case ITEMS_ID: {
trace("Delete on ITEMS: " + uri);
// Not implemented
break;
}
default:
throw new UnsupportedOperationException("Unknown delete URI " + uri);
}
debug("Deleted " + deleted + " rows for URI: " + uri);
return deleted;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
trace("Calling insert on URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
Uri result = null;
if (Build.VERSION.SDK_INT >= 11) {
trace("Beginning insert transaction: " + uri);
db.beginTransaction();
try {
result = insertInTransaction(uri, values);
db.setTransactionSuccessful();
trace("Successful insert transaction: " + uri);
} finally {
db.endTransaction();
}
} else {
result = insertInTransaction(uri, values);
}
if (result != null) {
getContext().getContentResolver().notifyChange(uri, null);
}
return result;
}
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
if (values == null) {
return 0;
}
final SQLiteDatabase db = getWritableDatabase(uri);
final int numValues = values.length;
int successes = 0;
db.beginTransaction();
try {
for (int i = 0; i < numValues; i++) {
try {
insertInTransaction(uri, values[i]);
successes++;
} catch (SQLException e) {
Log.e(LOGTAG, "SQLException in bulkInsert", e);
// Restart the transaction to continue insertions.
db.setTransactionSuccessful();
db.endTransaction();
db.beginTransaction();
}
}
trace("Flushing DB bulkinsert...");
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
if (successes > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return successes;
}
public Uri insertInTransaction(Uri uri, ContentValues values) {
trace("Calling insert in transaction on URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
long id = -1;
final int match = URI_MATCHER.match(uri);
switch (match) {
case ITEMS: {
trace("Insert on ITEMS: " + uri);
id = db.insertOrThrow(HomeListsDatabaseHelper.TABLE_ITEMS, null, values);
break;
}
case ITEMS_ID: {
trace("Insert on ITEMS_ID: " + uri);
// Not implemented
break;
}
default:
throw new UnsupportedOperationException("Unknown insert URI " + uri);
}
if (id >= 0) {
debug("Inserted ID in database: " + id);
return ContentUris.withAppendedId(uri, id);
}
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
trace("Calling update on URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
int updated = 0;
if (Build.VERSION.SDK_INT >= 11) {
trace("Beginning update transaction: " + uri);
db.beginTransaction();
try {
updated = updateInTransaction(uri, values, selection, selectionArgs);
db.setTransactionSuccessful();
trace("Successful update transaction: " + uri);
} finally {
db.endTransaction();
}
} else {
updated = updateInTransaction(uri, values, selection, selectionArgs);
}
if (updated > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
return updated;
}
public int updateInTransaction(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
trace("Calling update in transaction on URI: " + uri);
final SQLiteDatabase db = getWritableDatabase(uri);
int updated = 0;
final int match = URI_MATCHER.match(uri);
switch (match) {
case ITEMS: {
trace("Update on ITEMS: " + uri);
updated = db.update(HomeListsDatabaseHelper.TABLE_ITEMS, values, selection, selectionArgs);
break;
}
case ITEMS_ID: {
trace("Update on ITEMS_ID: " + uri);
// Not implemented
break;
}
default:
throw new UnsupportedOperationException("Unknown update URI " + uri);
}
debug("Updated " + updated + " rows for URI: " + uri);
return updated;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
final int match = URI_MATCHER.match(uri);
// If we're querying the fake items, don't try to get the database.
if (match == ITEMS_FAKE) {
trace("Query on ITEMS_FAKE: " + uri);
return queryFakeItems(uri, projection, selection, selectionArgs, sortOrder);
}
final SQLiteDatabase db = getReadableDatabase(uri);
Cursor cursor = null;
switch (match) {
case ITEMS: {
trace("Query on ITEMS: " + uri);
cursor = db.query(HomeListsDatabaseHelper.TABLE_ITEMS, projection, selection, selectionArgs, null, null, sortOrder, null);
break;
}
case ITEMS_ID: {
trace("Query on ITEMS_ID: " + uri);
// Not implemented
break;
}
default:
throw new UnsupportedOperationException("Unknown query URI " + uri);
}
return cursor;
}
/**
* Returns a cursor populated with static fake data.
*/
private Cursor queryFakeItems(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
final String[] itemsColumns = new String[] {
HomeListItems._ID,
HomeListItems.PROVIDER_ID,
HomeListItems.URL,
HomeListItems.TITLE
};
// XXX: Return more items (from JSON file?) and filter fake items by provider specified in selection
final MatrixCursor c = new MatrixCursor(itemsColumns);
c.addRow(new Object[] { 1, "fake-provider", "http://example.com", "Example" });
c.addRow(new Object[] { 2, "fake-provider", "http://mozilla.org", "Mozilla" });
return c;
}
}

View File

@ -114,6 +114,8 @@ gbjar.sources += [
'db/BrowserProvider.java',
'db/DBUtils.java',
'db/FormHistoryProvider.java',
'db/HomeListsDatabaseHelper.java',
'db/HomeListsProvider.java',
'db/LocalBrowserDB.java',
'db/PasswordsProvider.java',
'db/PerProfileContentProvider.java',