diff --git a/mobile/android/base/AndroidManifest.xml.in b/mobile/android/base/AndroidManifest.xml.in
index dda190a7675c..c5485eeddfef 100644
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -274,6 +274,10 @@
android:authorities="@ANDROID_PACKAGE_NAME@.db.tabs"
android:permission="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
+
+
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(getContext(), HomeListsDatabaseHelper.DATABASE_NAME,
+ new DatabaseHelperFactory() {
+ @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;
+ }
+}
diff --git a/mobile/android/base/moz.build b/mobile/android/base/moz.build
index 767d7738d7b1..3e091f242722 100644
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -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',