Bug 1360359 - WIP Part 1: Simple FormAutofill migration and ContentProvider draft
authorGrigory Kruglov <gkruglov@mozilla.com>
Thu, 27 Apr 2017 20:54:14 -0400
changeset 569899 0a88136718ce880f439de383dad0d429f8b4ad20
parent 568252 3f0c8da53c5cb015933b10b52ded3f30432b378a
child 569900 fef12de1388baf87cb78f15d1d9ead6a9eb977cd
push id56303
push userbmo:gkruglov@mozilla.com
push dateFri, 28 Apr 2017 01:01:51 +0000
bugs1360359
milestone55.0a1
Bug 1360359 - WIP Part 1: Simple FormAutofill migration and ContentProvider MozReview-Commit-ID: C1bpK6M0876
mobile/android/base/java/org/mozilla/gecko/db/BrowserContract.java
mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java
mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserContract.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserContract.java
@@ -37,16 +37,19 @@ public class BrowserContract {
     public static final Uri READING_LIST_AUTHORITY_URI = Uri.parse("content://" + READING_LIST_AUTHORITY);
 
     public static final String SEARCH_HISTORY_AUTHORITY = AppConstants.ANDROID_PACKAGE_NAME + ".db.searchhistory";
     public static final Uri SEARCH_HISTORY_AUTHORITY_URI = Uri.parse("content://" + SEARCH_HISTORY_AUTHORITY);
 
     public static final String LOGINS_AUTHORITY = AppConstants.ANDROID_PACKAGE_NAME + ".db.logins";
     public static final Uri LOGINS_AUTHORITY_URI = Uri.parse("content://" + LOGINS_AUTHORITY);
 
+    public static final String FORM_AUTOFILL_AUTHORITY = AppConstants.ANDROID_PACKAGE_NAME + ".db.formautofill";
+    public static final Uri FORM_AUTOFILL_AUTHORITY_URI = Uri.parse("content://" + FORM_AUTOFILL_AUTHORITY);
+
     public static final String PARAM_PROFILE = "profile";
     public static final String PARAM_PROFILE_PATH = "profilePath";
     public static final String PARAM_LIMIT = "limit";
     public static final String PARAM_SUGGESTEDSITES_LIMIT = "suggestedsites_limit";
     public static final String PARAM_TOPSITES_EXCLUDE_REMOTE_ONLY = "topsites_exclude_remote_only";
     public static final String PARAM_IS_SYNC = "sync";
     public static final String PARAM_SHOW_DELETED = "show_deleted";
     public static final String PARAM_IS_TEST = "test";
@@ -398,16 +401,23 @@ public class BrowserContract {
         public static final String FIELD_NAME = "fieldname";
         public static final String VALUE = "value";
         public static final String TIMES_USED = "timesUsed";
         public static final String FIRST_USED = "firstUsed";
         public static final String LAST_USED = "lastUsed";
         public static final String GUID = "guid";
     }
 
+    public static final class FormAutofill implements CommonColumns, SyncColumns {
+        private FormAutofill() {}
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(FORM_AUTOFILL_AUTHORITY_URI, "formautofill");
+        public static final String TABLE_NAME = "form_autofill";
+        public static final String JSON = "json";
+    }
+
     public static final class DeletedFormHistory implements DeletedColumns {
         private DeletedFormHistory() {}
         public static final Uri CONTENT_URI = Uri.withAppendedPath(FORM_HISTORY_AUTHORITY_URI, "deleted-formhistory");
         public static final String CONTENT_TYPE = "vnd.android.cursor.dir/deleted-formhistory";
     }
 
     @RobocopTarget
     public static final class Tabs implements CommonColumns {
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java
@@ -27,16 +27,17 @@ import org.mozilla.gecko.db.BrowserContr
 import org.mozilla.gecko.db.BrowserContract.History;
 import org.mozilla.gecko.db.BrowserContract.Visits;
 import org.mozilla.gecko.db.BrowserContract.PageMetadata;
 import org.mozilla.gecko.db.BrowserContract.Numbers;
 import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
 import org.mozilla.gecko.db.BrowserContract.SearchHistory;
 import org.mozilla.gecko.db.BrowserContract.Thumbnails;
 import org.mozilla.gecko.db.BrowserContract.UrlAnnotations;
+import org.mozilla.gecko.db.BrowserContract.FormAutofill;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.reader.SavedReaderViewHelper;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.repositories.android.RepoUtils;
 import org.mozilla.gecko.util.FileUtils;
 
 import static org.mozilla.gecko.db.DBUtils.qualifyColumn;
 
@@ -55,17 +56,17 @@ import android.util.Log;
 
 
 // public for robocop testing
 public final class BrowserDatabaseHelper extends SQLiteOpenHelper {
     private static final String LOGTAG = "GeckoBrowserDBHelper";
 
     // Replace the Bug number below with your Bug that is conducting a DB upgrade, as to force a merge conflict with any
     // other patches that require a DB upgrade.
-    public static final int DATABASE_VERSION = 36; // Bug 1301717
+    public static final int DATABASE_VERSION = 37; // Bug 1360359
     public static final String DATABASE_NAME = "browser.db";
 
     final protected Context mContext;
 
     static final String TABLE_BOOKMARKS = Bookmarks.TABLE_NAME;
     static final String TABLE_HISTORY = History.TABLE_NAME;
     static final String TABLE_VISITS = Visits.TABLE_NAME;
     static final String TABLE_PAGE_METADATA = PageMetadata.TABLE_NAME;
@@ -73,16 +74,17 @@ public final class BrowserDatabaseHelper
     static final String TABLE_THUMBNAILS = Thumbnails.TABLE_NAME;
     static final String TABLE_READING_LIST = ReadingListItems.TABLE_NAME;
     static final String TABLE_TABS = TabsProvider.TABLE_TABS;
     static final String TABLE_CLIENTS = TabsProvider.TABLE_CLIENTS;
     static final String TABLE_LOGINS = BrowserContract.Logins.TABLE_LOGINS;
     static final String TABLE_DELETED_LOGINS = BrowserContract.DeletedLogins.TABLE_DELETED_LOGINS;
     static final String TABLE_DISABLED_HOSTS = BrowserContract.LoginsDisabledHosts.TABLE_DISABLED_HOSTS;
     static final String TABLE_ANNOTATIONS = UrlAnnotations.TABLE_NAME;
+    static final String TABLE_FORM_AUTOFILL = FormAutofill.TABLE_NAME;
 
     static final String VIEW_COMBINED = Combined.VIEW_NAME;
     static final String VIEW_BOOKMARKS_WITH_FAVICONS = Bookmarks.VIEW_WITH_FAVICONS;
     static final String VIEW_BOOKMARKS_WITH_ANNOTATIONS = Bookmarks.VIEW_WITH_ANNOTATIONS;
     static final String VIEW_HISTORY_WITH_FAVICONS = History.VIEW_WITH_FAVICONS;
     static final String VIEW_COMBINED_WITH_FAVICONS = Combined.VIEW_WITH_FAVICONS;
 
     static final String TABLE_BOOKMARKS_JOIN_FAVICONS = TABLE_BOOKMARKS + " LEFT OUTER JOIN " +
@@ -294,16 +296,31 @@ public final class BrowserDatabaseHelper
                 BrowserContract.Tabs.POSITION + " INTEGER, " +
                 "FOREIGN KEY (" + BrowserContract.Tabs.CLIENT_GUID + ") REFERENCES " +
                 TABLE_CLIENTS + "(" + BrowserContract.Clients.GUID + ") ON DELETE CASCADE" +
                 ");");
 
         didCreateTabsTable = true;
     }
 
+    private void createFormAutofillTable(SQLiteDatabase db) {
+        debug("Creating " + TABLE_FORM_AUTOFILL + " table");
+        db.execSQL("CREATE TABLE " + TABLE_FORM_AUTOFILL + "(" +
+                FormAutofill._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+                FormAutofill.GUID + " TEXT NOT NULL," +
+                FormAutofill.JSON + " TEXT, " +
+                FormAutofill.IS_DELETED + " TINYINT NOT NULL DEFAULT 0, " +
+                FormAutofill.DATE_CREATED + " INTEGER NOT NULL, " +
+                FormAutofill.DATE_MODIFIED + " INTEGER NOT NULL" +
+                ");");
+
+        db.execSQL("CREATE UNIQUE INDEX form_autofill_guid_index ON " + TABLE_FORM_AUTOFILL + '('
+                + FormAutofill.GUID + ')');
+    }
+
     private void createTabsTableIndices(SQLiteDatabase db, final String tableName) {
         // Indices on CLIENT_GUID and POSITION.
         db.execSQL("CREATE INDEX " + TabsProvider.INDEX_TABS_GUID +
                 " ON " + tableName + "(" + BrowserContract.Tabs.CLIENT_GUID + ")");
         db.execSQL("CREATE INDEX " + TabsProvider.INDEX_TABS_POSITION +
                 " ON " + tableName + "(" + BrowserContract.Tabs.POSITION + ")");
     }
 
@@ -746,16 +763,18 @@ public final class BrowserDatabaseHelper
         createBookmarksWithAnnotationsView(db);
 
         createVisitsTable(db);
         createCombinedViewOn34(db);
 
         createActivityStreamBlocklistTable(db);
 
         createPageMetadataTable(db);
+
+        createFormAutofillTable(db);
     }
 
     /**
      * Copies the tabs and clients tables out of the given tabs.db file and into the destinationDB.
      *
      * @param tabsDBFile Path to existing tabs.db.
      * @param destinationDB The destination database.
      */
@@ -1975,16 +1994,20 @@ public final class BrowserDatabaseHelper
     private void upgradeDatabaseFrom34to35(final SQLiteDatabase db) {
         createActivityStreamBlocklistTable(db);
     }
 
     private void upgradeDatabaseFrom35to36(final SQLiteDatabase db) {
         createPageMetadataTable(db);
     }
 
+    private void upgradeDatabaseFrom36to37(final SQLiteDatabase db) {
+        createFormAutofillTable(db);
+    }
+
     private void createV33CombinedView(final SQLiteDatabase db) {
         db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED);
         db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED_WITH_FAVICONS);
 
         createCombinedViewOn33(db);
     }
 
     private void createV34CombinedView(final SQLiteDatabase db) {
@@ -2110,16 +2133,20 @@ public final class BrowserDatabaseHelper
 
                 case 35:
                     upgradeDatabaseFrom34to35(db);
                     break;
 
                 case 36:
                     upgradeDatabaseFrom35to36(db);
                     break;
+
+                case 37:
+                    upgradeDatabaseFrom36to37(db);
+                    break;
             }
         }
 
         for (Table table : BrowserProvider.sTables) {
             table.onUpgrade(db, oldVersion, newVersion);
         }
 
         // Delete the obsolete favicon database after all other upgrades complete.
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
@@ -24,16 +24,17 @@ import org.mozilla.gecko.db.BrowserContr
 import org.mozilla.gecko.db.BrowserContract.History;
 import org.mozilla.gecko.db.BrowserContract.Visits;
 import org.mozilla.gecko.db.BrowserContract.Schema;
 import org.mozilla.gecko.db.BrowserContract.Tabs;
 import org.mozilla.gecko.db.BrowserContract.Thumbnails;
 import org.mozilla.gecko.db.BrowserContract.TopSites;
 import org.mozilla.gecko.db.BrowserContract.UrlAnnotations;
 import org.mozilla.gecko.db.BrowserContract.PageMetadata;
+import org.mozilla.gecko.db.BrowserContract.FormAutofill;
 import org.mozilla.gecko.db.DBUtils.UpdateOperation;
 import org.mozilla.gecko.icons.IconsHelper;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.BroadcastReceiver;
 import android.content.ContentProviderOperation;
@@ -89,16 +90,17 @@ public class BrowserProvider extends Sha
     static final String TABLE_HISTORY = History.TABLE_NAME;
     static final String TABLE_VISITS = Visits.TABLE_NAME;
     static final String TABLE_FAVICONS = Favicons.TABLE_NAME;
     static final String TABLE_THUMBNAILS = Thumbnails.TABLE_NAME;
     static final String TABLE_TABS = Tabs.TABLE_NAME;
     static final String TABLE_URL_ANNOTATIONS = UrlAnnotations.TABLE_NAME;
     static final String TABLE_ACTIVITY_STREAM_BLOCKLIST = ActivityStreamBlocklist.TABLE_NAME;
     static final String TABLE_PAGE_METADATA = PageMetadata.TABLE_NAME;
+    static final String TABLE_FORM_AUTOFILL = FormAutofill.TABLE_NAME;
 
     static final String VIEW_COMBINED = Combined.VIEW_NAME;
     static final String VIEW_BOOKMARKS_WITH_FAVICONS = Bookmarks.VIEW_WITH_FAVICONS;
     static final String VIEW_BOOKMARKS_WITH_ANNOTATIONS = Bookmarks.VIEW_WITH_ANNOTATIONS;
     static final String VIEW_HISTORY_WITH_FAVICONS = History.VIEW_WITH_FAVICONS;
     static final String VIEW_COMBINED_WITH_FAVICONS = Combined.VIEW_WITH_FAVICONS;
 
     // Bookmark matches
@@ -142,16 +144,18 @@ public class BrowserProvider extends Sha
     static final int METADATA = 1200;
 
     static final int HIGHLIGHT_CANDIDATES = 1300;
 
     static final int ACTIVITY_STREAM_BLOCKLIST = 1400;
 
     static final int PAGE_METADATA = 1500;
 
+    static final int FORM_AUTOFILL = 1600;
+
     static final String DEFAULT_BOOKMARKS_SORT_ORDER = Bookmarks.TYPE
             + " ASC, " + Bookmarks.POSITION + " ASC, " + Bookmarks._ID
             + " ASC";
 
     static final String DEFAULT_HISTORY_SORT_ORDER = History.DATE_LAST_VISITED + " DESC";
     static final String DEFAULT_VISITS_SORT_ORDER = Visits.DATE_VISITED + " DESC";
 
     static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
@@ -160,16 +164,17 @@ public class BrowserProvider extends Sha
     static final Map<String, String> HISTORY_PROJECTION_MAP;
     static final Map<String, String> COMBINED_PROJECTION_MAP;
     static final Map<String, String> SCHEMA_PROJECTION_MAP;
     static final Map<String, String> FAVICONS_PROJECTION_MAP;
     static final Map<String, String> THUMBNAILS_PROJECTION_MAP;
     static final Map<String, String> URL_ANNOTATIONS_PROJECTION_MAP;
     static final Map<String, String> VISIT_PROJECTION_MAP;
     static final Map<String, String> PAGE_METADATA_PROJECTION_MAP;
+    static final Map<String, String> FORM_AUTOFILL_PROJECTION_MAP;
     static final Table[] sTables;
 
     static {
         sTables = new Table[] {
             // See awful shortcut assumption hack in getURLImageDataTable.
             new URLImageDataTable()
         };
         // We will reuse this.
@@ -296,16 +301,26 @@ public class BrowserProvider extends Sha
         map.put(PageMetadata.HISTORY_GUID, PageMetadata.HISTORY_GUID);
         map.put(PageMetadata.DATE_CREATED, PageMetadata.DATE_CREATED);
         map.put(PageMetadata.HAS_IMAGE, PageMetadata.HAS_IMAGE);
         map.put(PageMetadata.JSON, PageMetadata.JSON);
         PAGE_METADATA_PROJECTION_MAP = Collections.unmodifiableMap(map);
 
         URI_MATCHER.addURI(BrowserContract.AUTHORITY, "page_metadata", PAGE_METADATA);
 
+        map = new HashMap<>();
+        map.put(FormAutofill._ID, FormAutofill._ID);
+        map.put(FormAutofill.GUID, FormAutofill.GUID);
+        map.put(FormAutofill.JSON, FormAutofill.JSON);
+        map.put(FormAutofill.DATE_CREATED, FormAutofill.DATE_CREATED);
+        map.put(FormAutofill.DATE_MODIFIED, FormAutofill.DATE_MODIFIED);
+        FORM_AUTOFILL_PROJECTION_MAP = Collections.unmodifiableMap(map);
+
+        URI_MATCHER.addURI(BrowserContract.AUTHORITY, "form_autofill", FORM_AUTOFILL);
+
         // Schema
         URI_MATCHER.addURI(BrowserContract.AUTHORITY, "schema", SCHEMA);
 
         map = new HashMap<String, String>();
         map.put(Schema.VERSION, Schema.VERSION);
         SCHEMA_PROJECTION_MAP = Collections.unmodifiableMap(map);
 
 
@@ -644,16 +659,21 @@ public class BrowserProvider extends Sha
                 deleteUrlAnnotation(uri, selection, selectionArgs);
                 break;
 
             case PAGE_METADATA:
                 trace("Delete on PAGE_METADATA: " + uri);
                 deleted = deletePageMetadata(uri, selection, selectionArgs);
                 break;
 
+            case FORM_AUTOFILL:
+                trace("Delete on FORM_AUTOFILL: " + uri);
+                deleted = deleteFormAutofill(uri, selection, selectionArgs);
+                break;
+
             default: {
                 Table table = findTableFor(match);
                 if (table == null) {
                     throw new UnsupportedOperationException("Unknown delete URI " + uri);
                 }
                 trace("Deleting TABLE: " + uri);
                 beginWrite(db);
                 deleted = table.delete(db, uri, match, selection, selectionArgs);
@@ -716,16 +736,22 @@ public class BrowserProvider extends Sha
             }
 
             case PAGE_METADATA: {
                 trace("Insert on PAGE_METADATA: " + uri);
                 id = insertPageMetadata(uri, values);
                 break;
             }
 
+            case FORM_AUTOFILL: {
+                trace("Insert on FORM_AUTOFILL: " + uri);
+                id = insertFormAutofill(uri, values);
+                break;
+            }
+
             default: {
                 Table table = findTableFor(match);
                 if (table == null) {
                     throw new UnsupportedOperationException("Unknown insert URI " + uri);
                 }
 
                 trace("Insert on TABLE: " + uri);
                 final SQLiteDatabase db = getWritableDatabase(uri);
@@ -851,16 +877,19 @@ public class BrowserProvider extends Sha
                 }
                 break;
             }
 
             case URL_ANNOTATIONS:
                 updateUrlAnnotation(uri, values, selection, selectionArgs);
                 break;
 
+            case FORM_AUTOFILL:
+                throw new UnsupportedOperationException("Updating FormAutofill records is not supported");
+
             default: {
                 Table table = findTableFor(match);
                 if (table == null) {
                     throw new UnsupportedOperationException("Unknown update URI " + uri);
                 }
                 trace("Update TABLE: " + uri);
 
                 beginWrite(db);
@@ -1386,16 +1415,28 @@ public class BrowserProvider extends Sha
             case PAGE_METADATA: {
                 debug("PageMetadata query: " + uri);
 
                 qb.setProjectionMap(PAGE_METADATA_PROJECTION_MAP);
                 qb.setTables(TABLE_PAGE_METADATA);
                 break;
             }
 
+            case FORM_AUTOFILL: {
+                if (!isCallerSync(uri)) {
+                    throw new UnsupportedOperationException("Querying FormAutofill data outside of Sync is not supported.");
+                }
+
+                debug("FormAutofill query: " + uri);
+
+                qb.setProjectionMap(FORM_AUTOFILL_PROJECTION_MAP);
+                qb.setTables(TABLE_FORM_AUTOFILL);
+                break;
+            }
+
             default: {
                 Table table = findTableFor(match);
                 if (table == null) {
                     throw new UnsupportedOperationException("Unknown query URI " + uri);
                 }
                 trace("Update TABLE: " + uri);
                 return table.query(db, uri, match, projection, selection, selectionArgs, sortOrder, groupBy, limit);
             }
@@ -1992,16 +2033,35 @@ public class BrowserProvider extends Sha
         beginWrite(db);
 
         // Perform INSERT OR REPLACE, there might be page metadata present and we want to replace it.
         // Depends on a conflict arising from unique foreign key (history_guid) constraint violation.
         return db.insertWithOnConflict(
                 TABLE_PAGE_METADATA, null, values, SQLiteDatabase.CONFLICT_REPLACE);
     }
 
+    private long insertFormAutofill(final Uri uri, final ContentValues values) {
+        if (!isCallerSync(uri)) {
+            throw new UnsupportedOperationException("Querying FormAutofill data outside of Sync is not supported.");
+        }
+
+        final SQLiteDatabase db = getWritableDatabase(uri);
+
+        if (!values.containsKey(FormAutofill.DATE_CREATED)) {
+            values.put(FormAutofill.DATE_CREATED, System.currentTimeMillis());
+        }
+
+        beginWrite(db);
+
+        // Perform INSERT OR REPLACE. Local modification is not allowed, and so we're just replacing
+        // whatever we already have. Depends on a conflict arising from unique GUID constraint violation.
+        return db.insertWithOnConflict(
+                TABLE_PAGE_METADATA, null, values, SQLiteDatabase.CONFLICT_REPLACE);
+    }
+
     private long insertUrlAnnotation(final Uri uri, final ContentValues values) {
         final String url = values.getAsString(UrlAnnotations.URL);
         trace("Inserting url annotations for URL: " + url);
 
         final SQLiteDatabase db = getWritableDatabase(uri);
         beginWrite(db);
         return db.insertOrThrow(TABLE_URL_ANNOTATIONS, null, values);
     }
@@ -2015,16 +2075,27 @@ public class BrowserProvider extends Sha
 
     private int deletePageMetadata(final Uri uri, final String selection, final String[] selectionArgs) {
         trace("Deleting page metadata for URI: " + uri);
 
         final SQLiteDatabase db = getWritableDatabase(uri);
         return db.delete(TABLE_PAGE_METADATA, selection, selectionArgs);
     }
 
+    private int deleteFormAutofill(final Uri uri, final String selection, final String[] selectionArgs) {
+        if (!isCallerSync(uri)) {
+            throw new UnsupportedOperationException("Querying FormAutofill data outside of Sync is not supported.");
+        }
+
+        trace("Deleting form autofill for URI: " + uri);
+
+        final SQLiteDatabase db = getWritableDatabase(uri);
+        return db.delete(TABLE_FORM_AUTOFILL, selection, selectionArgs);
+    }
+
     private void updateUrlAnnotation(final Uri uri, final ContentValues values, final String selection, final String[] selectionArgs) {
         trace("Updating url annotation for URI: " + uri);
 
         final SQLiteDatabase db = getWritableDatabase(uri);
         db.update(TABLE_URL_ANNOTATIONS, values, selection, selectionArgs);
     }
 
     private int updateOrInsertThumbnail(Uri uri, ContentValues values, String selection,