Bug 1046709 - Part 3: Synthesize visits when importing history from Android r=nalexander,rnewman
MozReview-Commit-ID: Fcw5lygXbem
--- a/mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
@@ -86,16 +86,21 @@ public class LocalBrowserDB implements B
// Map of folder GUIDs to IDs. Used for caching.
private final HashMap<String, Long> mFolderIdMap;
// Use wrapped Boolean so that we can have a null state
private volatile Boolean mDesktopBookmarksExist;
private volatile SuggestedSites mSuggestedSites;
+ // Constants used when importing history data from legacy browser.
+ public static String HISTORY_VISITS_DATE = "date";
+ public static String HISTORY_VISITS_COUNT = "visits";
+ public static String HISTORY_VISITS_URL = "url";
+
private final Uri mBookmarksUriWithProfile;
private final Uri mParentsUriWithProfile;
private final Uri mHistoryUriWithProfile;
private final Uri mHistoryExpireUriWithProfile;
private final Uri mCombinedUriWithProfile;
private final Uri mUpdateHistoryUriWithProfile;
private final Uri mFaviconsUriWithProfile;
private final Uri mThumbnailsUriWithProfile;
@@ -1481,16 +1486,82 @@ public class LocalBrowserDB implements B
// Queue the operation
operations.add(builder.build());
} finally {
cursor.close();
}
}
+ /**
+ * Utility method used by AndroidImport to insert visit data for history records that were just imported.
+ * Uses batch operations.
+ *
+ * @param cr <code>ContentResolver</code> used to query history table and bulkInsert visit records
+ * @param operations Collection of operations for queueing inserts
+ * @param visitsToSynthesize List of ContentValues describing visit information for each history record:
+ * (History URL, LAST DATE VISITED, VISIT COUNT)
+ */
+ public void insertVisitsFromImportHistoryInBatch(ContentResolver cr,
+ Collection<ContentProviderOperation> operations,
+ ArrayList<ContentValues> visitsToSynthesize) {
+ // If for any reason we fail to obtain history GUID for a tuple we're processing,
+ // let's just ignore it. It's possible that the "best-effort" history import
+ // did not fully succeed, so we could be missing some of the records.
+ int historyGUIDCol = -1;
+ for (ContentValues visitsInformation : visitsToSynthesize) {
+ final Cursor cursor = cr.query(mHistoryUriWithProfile,
+ new String[] {History.GUID},
+ History.URL + " = ?",
+ new String[] {visitsInformation.getAsString(HISTORY_VISITS_URL)},
+ null);
+ if (cursor == null) {
+ continue;
+ }
+
+ final String historyGUID;
+
+ try {
+ if (!cursor.moveToFirst()) {
+ continue;
+ }
+ if (historyGUIDCol == -1) {
+ historyGUIDCol = cursor.getColumnIndexOrThrow(History.GUID);
+ }
+
+ historyGUID = cursor.getString(historyGUIDCol);
+ } finally {
+ // We "continue" on a null cursor above, so it's safe to act upon it without checking.
+ cursor.close();
+ }
+ if (historyGUID == null) {
+ continue;
+ }
+
+ // This fakes the individual visit records, using last visited date as the starting point.
+ for (int i = 0; i < visitsInformation.getAsInteger(HISTORY_VISITS_COUNT); i++) {
+ // We rely on database defaults for IS_LOCAL and VISIT_TYPE.
+ final ContentValues visitToInsert = new ContentValues();
+ visitToInsert.put(BrowserContract.Visits.HISTORY_GUID, historyGUID);
+
+ // Visit timestamps are stored in microseconds, while Android Browser visit timestmaps
+ // are in milliseconds. This is the conversion point for imports.
+ visitToInsert.put(BrowserContract.Visits.DATE_VISITED,
+ (visitsInformation.getAsLong(HISTORY_VISITS_DATE) - i) * 1000);
+
+ final ContentProviderOperation.Builder builder =
+ ContentProviderOperation.newInsert(BrowserContract.Visits.CONTENT_URI);
+ builder.withValues(visitToInsert);
+
+ // Queue the insert operation
+ operations.add(builder.build());
+ }
+ }
+ }
+
@Override
public void updateBookmarkInBatch(ContentResolver cr,
Collection<ContentProviderOperation> operations,
String url, String title, String guid,
long parent, long added,
long modified, long position,
String keyword, int type) {
ContentValues values = new ContentValues();
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/AndroidImport.java
+++ b/mobile/android/base/java/org/mozilla/gecko/preferences/AndroidImport.java
@@ -1,15 +1,16 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* 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.preferences;
+import android.content.ContentValues;
import android.os.Build;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserContract.Bookmarks;
import org.mozilla.gecko.db.LocalBrowserDB;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
@@ -110,16 +111,17 @@ public class AndroidImport implements Ru
if (cursor != null)
cursor.close();
}
flushBatchOperations();
}
public void mergeHistory() {
+ ArrayList<ContentValues> visitsToSynthesize = new ArrayList<>();
Cursor cursor = null;
try {
cursor = query (LegacyBrowserProvider.BOOKMARKS_URI,
SAMSUNG_HISTORY_URI,
LegacyBrowserProvider.BookmarkColumns.BOOKMARK + " = 0 AND " +
LegacyBrowserProvider.BookmarkColumns.VISITS + " > 0");
if (cursor != null) {
@@ -135,25 +137,36 @@ public class AndroidImport implements Ru
String title = cursor.getString(titleCol);
long date = cursor.getLong(dateCol);
int visits = cursor.getInt(visitsCol);
byte[] data = cursor.getBlob(faviconCol);
mDB.updateHistoryInBatch(mCr, mOperations, url, title, date, visits);
if (data != null) {
mDB.updateFaviconInBatch(mCr, mOperations, url, null, null, data);
}
+ ContentValues visitData = new ContentValues();
+ visitData.put(LocalBrowserDB.HISTORY_VISITS_DATE, date);
+ visitData.put(LocalBrowserDB.HISTORY_VISITS_URL, url);
+ visitData.put(LocalBrowserDB.HISTORY_VISITS_COUNT, visits);
+ visitsToSynthesize.add(visitData);
cursor.moveToNext();
}
}
} finally {
if (cursor != null)
cursor.close();
}
flushBatchOperations();
+
+ // Now that we have flushed history records, we need to synthesize individual visits. We have
+ // gathered information about all of the visits we need to synthesize into visitsForSynthesis.
+ mDB.insertVisitsFromImportHistoryInBatch(mCr, mOperations, visitsToSynthesize);
+
+ flushBatchOperations();
}
protected Cursor query(Uri mainUri, Uri fallbackUri, String condition) {
final Cursor cursor = mCr.query(mainUri, null, condition, null, null);
if (Build.MANUFACTURER.equals(SAMSUNG_MANUFACTURER) && (cursor == null || cursor.getCount() == 0)) {
if (cursor != null) {
cursor.close();
}