diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index 318bc9573cc..7624aa9e296 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -1649,6 +1649,10 @@ abstract public class GeckoApp mBrowserToolbar.updateTabCount(1); } + // Start migrating as early as possible, can do this in + // parallel with Gecko load. + checkMigrateProfile(); + Uri data = intent.getData(); if (data != null && "http".equals(data.getScheme()) && isHostOnPrefetchWhitelist(data.getHost())) { @@ -1771,6 +1775,9 @@ abstract public class GeckoApp GeckoAppShell.getHandler().postDelayed(new Runnable() { public void run() { Log.w(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - pre checkLaunchState"); + // Sync settings need Gecko to be loaded, so + // no hurry in starting this. + checkMigrateSync(); /* XXXX see bug 635342 @@ -1784,8 +1791,6 @@ abstract public class GeckoApp if (!checkLaunchState(LaunchState.Launched)) { return; } - - checkMigrateProfile(); } }, 50); } @@ -2278,30 +2283,49 @@ abstract public class GeckoApp } private void checkMigrateProfile() { - File profileDir = getProfile().getDir(); - long currentTime = SystemClock.uptimeMillis(); + final File profileDir = getProfile().getDir(); + final long currentTime = SystemClock.uptimeMillis(); if (profileDir != null) { - Log.i(LOGTAG, "checking profile migration in: " + profileDir.getAbsolutePath()); + Log.i(LOGTAG, "Checking profile migration in: " + profileDir.getAbsolutePath()); final GeckoApp app = GeckoApp.mAppContext; - ProfileMigrator profileMigrator = + final ProfileMigrator profileMigrator = new ProfileMigrator(app, profileDir); // Do a migration run on the first start after an upgrade. if (!profileMigrator.hasMigrationRun()) { final SetupScreen setupScreen = new SetupScreen(app); - // don't show unless this take a while + // Don't show unless this take a while. setupScreen.showDelayed(mMainHandler); - profileMigrator.launch(); - setupScreen.dismiss(); - // Update about:home with the new information. - updateAboutHomeTopSites(); + GeckoAppShell.getHandler().post(new Runnable() { + public void run() { + profileMigrator.launchPlaces(); + setupScreen.dismiss(); + + long timeDiff = SystemClock.uptimeMillis() - currentTime; + Log.i(LOGTAG, "Profile migration took " + timeDiff + " ms"); + + // Update about:home with the new information. + updateAboutHomeTopSites(); + } + }); + } + } + } + + private void checkMigrateSync() { + final File profileDir = getProfile().getDir(); + if (profileDir != null) { + final GeckoApp app = GeckoApp.mAppContext; + ProfileMigrator profileMigrator = + new ProfileMigrator(app, profileDir); + if (!profileMigrator.hasSyncMigrated()) { + Log.i(LOGTAG, "Checking Sync settings in: " + profileDir.getAbsolutePath()); + profileMigrator.launchSyncPrefs(); } } - long timeDiff = SystemClock.uptimeMillis() - currentTime; - Log.i(LOGTAG, "Profile migration took " + timeDiff + " ms"); } /** diff --git a/mobile/android/base/ProfileMigrator.java b/mobile/android/base/ProfileMigrator.java index 05851087798..4725dadd20a 100644 --- a/mobile/android/base/ProfileMigrator.java +++ b/mobile/android/base/ProfileMigrator.java @@ -42,11 +42,15 @@ import org.mozilla.gecko.db.BrowserContract.Bookmarks; import org.mozilla.gecko.db.BrowserContract.History; import org.mozilla.gecko.db.BrowserContract.ImageColumns; import org.mozilla.gecko.db.BrowserContract.Images; +import org.mozilla.gecko.db.BrowserContract.Passwords; import org.mozilla.gecko.db.BrowserContract.URLColumns; import org.mozilla.gecko.db.BrowserContract.SyncColumns; import org.mozilla.gecko.sqlite.SQLiteBridge; import org.mozilla.gecko.sqlite.SQLiteBridgeException; +import org.mozilla.gecko.sync.setup.SyncAccounts; +import org.mozilla.gecko.sync.setup.SyncAccounts.SyncAccountParameters; +import android.accounts.Account; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; @@ -61,11 +65,12 @@ import android.database.sqlite.SQLiteConstraintException; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.AsyncTask; import android.os.RemoteException; import android.provider.Browser; +import android.text.TextUtils; import android.util.Log; -import android.net.Uri; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -83,6 +88,10 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.json.JSONArray; +import org.json.JSONObject; +import org.json.JSONException; + public class ProfileMigrator { private static final String LOGTAG = "ProfileMigrator"; private static final String PREFS_NAME = "ProfileMigrator"; @@ -101,6 +110,7 @@ public class ProfileMigrator { private static final String PREFS_MIGRATE_HISTORY_DONE = "history_done"; // Number of history entries already migrated. private static final String PREFS_MIGRATE_HISTORY_COUNT = "history_count"; + private static final String PREFS_MIGRATE_SYNC_DONE = "sync_done"; /* These queries are derived from the low-level Places schema @@ -199,13 +209,34 @@ public class ProfileMigrator { private final String kHistoryDate = "h_date"; private final String kHistoryVisits = "h_visits"; + /* + Sync settings to get from prefs.js. + */ + private final String[] kSyncSettingsList = new String[] { + "services.sync.account", + "services.sync.client.name", + "services.sync.client.GUID", + "services.sync.serverURL", + "services.sync.clusterURL" + }; + + /* + Sync settings to get from password manager. + */ + private final String kSyncHostName = "chrome://weave"; + private final String[] kSyncRealmList = new String[] { + "Mozilla Services Password", + "Mozilla Services Encryption Passphrase" + }; + + public ProfileMigrator(Context context, File profileDir) { mProfileDir = profileDir; mContext = context; mCr = mContext.getContentResolver(); } - public void launch() { + public void launchPlaces() { boolean timeThisRun = false; Telemetry.Timer timer = null; // First run, time things @@ -213,15 +244,22 @@ public class ProfileMigrator { timeThisRun = true; timer = new Telemetry.Timer("BROWSERPROVIDER_XUL_IMPORT_TIME"); } - launch(DEFAULT_HISTORY_MIGRATE_COUNT); + launchPlaces(DEFAULT_HISTORY_MIGRATE_COUNT); if (timeThisRun) timer.stop(); } - public void launch(int maxEntries) { + public void launchPlaces(int maxEntries) { + // Places migration is heavy on the phone, allow it to block + // other processing. new PlacesRunnable(maxEntries).run(); } + public void launchSyncPrefs() { + // Sync settings will post a runnable, no need for a seperate thread. + new SyncTask().run(); + } + public boolean areBookmarksMigrated() { return getPreferences().getBoolean(PREFS_MIGRATE_BOOKMARKS_DONE, false); } @@ -230,6 +268,11 @@ public class ProfileMigrator { return getPreferences().getBoolean(PREFS_MIGRATE_HISTORY_DONE, false); } + // Have Sync settings been transferred? + public boolean hasSyncMigrated() { + return getPreferences().getBoolean(PREFS_MIGRATE_SYNC_DONE, false); + } + // Has migration run before? protected boolean hasMigrationRun() { return areBookmarksMigrated() && (getMigratedHistoryEntries() > 0); @@ -266,6 +309,206 @@ public class ProfileMigrator { editor.commit(); } + protected void setMigratedSync() { + SharedPreferences.Editor editor = getPreferences().edit(); + editor.putBoolean(PREFS_MIGRATE_SYNC_DONE, true); + editor.commit(); + } + + private class SyncTask implements Runnable, GeckoEventListener { + private List mSyncSettingsList; + private Map mSyncSettingsMap; + + // Initialize preferences by sending the "Preferences:Get" command to Gecko + protected void requestValues() { + mSyncSettingsList = Arrays.asList(kSyncSettingsList); + mSyncSettingsMap = new HashMap(); + JSONArray jsonPrefs = new JSONArray(mSyncSettingsList); + Log.d(LOGTAG, "Sending: " + jsonPrefs.toString()); + GeckoEvent event = + GeckoEvent.createBroadcastEvent("Preferences:Get", + jsonPrefs.toString()); + GeckoAppShell.sendEventToGecko(event); + } + + // Receive settings reply from Gecko, do the rest of the setup + public void handleMessage(String event, JSONObject message) { + Log.d(LOGTAG, "Received event: " + event); + try { + if (event.equals("Preferences:Data")) { + // Receive most settings from Gecko's service. + // This includes personal info, so don't log. + // Log.d(LOGTAG, "Message: " + message.toString()); + JSONArray jsonPrefs = message.getJSONArray("preferences"); + parsePrefs(jsonPrefs); + GeckoAppShell.unregisterGeckoEventListener("Preferences:Data", + (GeckoEventListener)this); + + // Now call the password provider to fill in the rest. + for (String location: kSyncRealmList) { + Log.d(LOGTAG, "Checking: " + location); + String passwd = getPassword(location); + if (!TextUtils.isEmpty(passwd)) { + Log.d(LOGTAG, "Got password"); + mSyncSettingsMap.put(location, passwd); + } else { + Log.d(LOGTAG, "No password found"); + mSyncSettingsMap.put(location, null); + } + } + + // Call Sync and transfer settings. + configureSync(); + } + } catch (Exception e) { + Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e); + } + } + + protected String getPassword(String realm) { + Cursor cursor = null; + String result = null; + try { + cursor = mCr.query(Passwords.CONTENT_URI, + null, + Passwords.HOSTNAME + " = ? AND " + + Passwords.HTTP_REALM + " = ?", + new String[] { kSyncHostName, realm }, + null); + + if (cursor != null) { + final int userCol = + cursor.getColumnIndexOrThrow(Passwords.ENCRYPTED_USERNAME); + final int passCol = + cursor.getColumnIndexOrThrow(Passwords.ENCRYPTED_PASSWORD); + + if (cursor.moveToFirst()) { + String user = cursor.getString(userCol); + String pass = cursor.getString(passCol); + result = pass; + } else { + Log.i(LOGTAG, "No password found for realm = " + realm); + } + } + } finally { + if (cursor != null) + cursor.close(); + } + + return result; + } + + protected void parsePrefs(JSONArray jsonPrefs) { + try { + final int length = jsonPrefs.length(); + for (int i = 0; i < length; i++) { + JSONObject jPref = jsonPrefs.getJSONObject(i); + final String prefName = jPref.getString("name"); + final String prefType = jPref.getString("type"); + if ("bool".equals(prefType)) { + final boolean value = jPref.getBoolean("value"); + mSyncSettingsMap.put(prefName, value ? "1" : "0"); + } else { + final String value = jPref.getString("value"); + if (!TextUtils.isEmpty(value)) { + mSyncSettingsMap.put(prefName, value); + } else { + Log.w(LOGTAG, "Could not recover setting for = " + prefName); + mSyncSettingsMap.put(prefName, null); + } + } + } + } catch (JSONException e) { + Log.e(LOGTAG, "Exception handling preferences answer: " + + e.getMessage()); + } + } + + protected void configureSync() { + final String userName = mSyncSettingsMap.get("services.sync.account"); + final String syncKey = mSyncSettingsMap.get("Mozilla Services Password"); + final String syncPass = mSyncSettingsMap.get("Mozilla Services Encryption Passphrase"); + final String serverURL = mSyncSettingsMap.get("services.sync.serverURL"); + final String clusterURL = mSyncSettingsMap.get("services.sync.clusterURL"); + final String clientName = mSyncSettingsMap.get("services.sync.client.name"); + final String clientGuid = mSyncSettingsMap.get("services.sync.client.GUID"); + + if (userName == null || syncKey == null || syncPass == null) { + // This isn't going to work. Give up. + Log.e(LOGTAG, "Profile has incomplete Sync config. Not migrating."); + setMigratedSync(); + return; + } + + final SyncAccountParameters params = + new SyncAccountParameters(mContext, null, + userName, syncKey, + syncPass, serverURL, clusterURL, + clientName, clientGuid); + + new SyncAccounts.CreateSyncAccountTask() { + @Override + protected void onPostExecute(Account account) { + if (account == null) { + Log.e(LOGTAG, "Failed to migrate Sync account."); + } else { + Log.i(LOGTAG, "Migrating Sync account succeeded."); + } + setMigratedSync(); + } + }.execute(params); + } + + protected void registerAndRequest() { + GeckoAppShell.getHandler().post(new Runnable() { + public void run() { + GeckoAppShell.registerGeckoEventListener("Preferences:Data", + SyncTask.this); + requestValues(); + } + }); + } + + @Override + public void run() { + // Run only if no Sync accounts exist. + new SyncAccounts.AccountsExistTask() { + @Override + protected void onPostExecute(Boolean result) { + if (result.booleanValue()) { + Log.i(LOGTAG, "Sync account already configured, skipping."); + setMigratedSync(); + } else { + // No account configured, fire up. + registerAndRequest(); + } + } + }.execute(mContext); + } + } + + private class MiscTask implements Runnable { + protected void cleanupXULLibCache() { + File cacheFile = GeckoAppShell.getCacheDir(mContext); + File[] files = cacheFile.listFiles(); + if (files != null) { + Iterator cacheFiles = Arrays.asList(files).iterator(); + while (cacheFiles.hasNext()) { + File libFile = (File)cacheFiles.next(); + if (libFile.getName().endsWith(".so")) { + libFile.delete(); + } + } + } + } + + @Override + public void run() { + // XXX: Land dependent bugs (732069) first + // cleanupXULLibCache(); + } + } + private class PlacesRunnable implements Runnable { private Map mRerootMap; private ArrayList mOperations; @@ -878,25 +1121,9 @@ public class ProfileMigrator { } } - protected void cleanupXULLibCache() { - File cacheFile = GeckoAppShell.getCacheDir(mContext); - File[] files = cacheFile.listFiles(); - if (files != null) { - Iterator cacheFiles = Arrays.asList(files).iterator(); - while (cacheFiles.hasNext()) { - File libFile = cacheFiles.next(); - if (libFile.getName().endsWith(".so")) { - libFile.delete(); - } - } - } - } - @Override public void run() { migratePlaces(mProfileDir); - // XXX: Land dependent bugs first - // cleanupXULLibCache(); } } } diff --git a/mobile/android/base/db/BrowserProvider.java.in b/mobile/android/base/db/BrowserProvider.java.in index b782e8dd426..c3cbb1ee04e 100644 --- a/mobile/android/base/db/BrowserProvider.java.in +++ b/mobile/android/base/db/BrowserProvider.java.in @@ -1497,7 +1497,7 @@ public class BrowserProvider extends ContentProvider { boolean needHistory = wantHistory && !migrator.isHistoryMigrated(); if (needBookmarks || needHistory) { - migrator.launch(); + migrator.launchPlaces(); needBookmarks = wantBookmarks && !migrator.areBookmarksMigrated(); needHistory = wantHistory && !migrator.isHistoryMigrated();