From 9a0fd32cf6e2ada37675bc743532c5004b16e5e3 Mon Sep 17 00:00:00 2001 From: Ruben Rodriguez Date: Sun, 8 Mar 2015 15:14:03 +0000 Subject: Added patches to fix build bugs for Windows, MacOS and Android --- ...-part-1-prompt-for-master-password-unlock.patch | 60 + ...-part-2-Store-sensitive-FxA-credentials-i.patch | 551 +++++++++ ...-part-3-only-migrate-data-into-the-loginm.patch | 194 ++++ ...part-4b-browserid_identity-and-sync-chang.patch | 286 +++++ ...-part-5-stop-disabling-the-password-engin.patch | 519 +++++++++ .../disable/0001-upgrade-mobile-fxa-to-v32.patch | 992 ++++++++++++++++ .../disable/0002-update-mobile-sqlite-to-v35.patch | 246 ++++ .../disable/0003-upgrade-mobile-sync-to-v32.patch | 992 ++++++++++++++++ data/patches/gnuzilla-bug-1010972.patch | 11 + data/patches/gnuzilla-bug-1025689.patch | 19 + data/patches/gnuzilla-bug-1030899-1.patch | 60 + data/patches/gnuzilla-bug-1030899-2.patch | 59 + data/patches/gnuzilla-bug-1032460.patch | 16 + data/patches/gnuzilla-bug-1034167-1.patch | 93 ++ data/patches/gnuzilla-bug-1034167-2.patch | 1205 ++++++++++++++++++++ data/patches/gnuzilla-bug-1036286-1.patch | 188 +++ data/patches/gnuzilla-bug-1036286-2.patch | 105 ++ data/patches/gnuzilla-bug-1050780.patch | 53 + data/patches/gnuzilla-bug-1055166.patch | 33 + data/patches/gnuzilla-bug-1058778.patch | 10 + data/patches/gnuzilla-bug-1089931.patch | 40 + data/patches/gnuzilla-bug-1091987.patch | 40 + data/patches/gnuzilla-bug-1095298.patch | 30 + data/patches/reduceGeckoPriority.patch | 18 + ...le_android_base_android-services_mozbuild.patch | 34 + data/patches/revert-gnuzilla-bug-973138.patch | 25 + data/patches/torbrowser-bug-12811-2.patch | 33 + data/patches/torbrowser-bug-12811.patch | 19 + data/patches/torbrowser-bug-9837.patch | 22 + 29 files changed, 5953 insertions(+) create mode 100644 data/patches/0001-Bug-1013064-part-1-prompt-for-master-password-unlock.patch create mode 100644 data/patches/0002-Bug-1013064-part-2-Store-sensitive-FxA-credentials-i.patch create mode 100644 data/patches/0003-Bug-1013064-part-3-only-migrate-data-into-the-loginm.patch create mode 100644 data/patches/0004-Bug-1013064-part-4b-browserid_identity-and-sync-chang.patch create mode 100644 data/patches/0005-Bug-1013064-part-5-stop-disabling-the-password-engin.patch create mode 100644 data/patches/disable/0001-upgrade-mobile-fxa-to-v32.patch create mode 100644 data/patches/disable/0002-update-mobile-sqlite-to-v35.patch create mode 100644 data/patches/disable/0003-upgrade-mobile-sync-to-v32.patch create mode 100644 data/patches/gnuzilla-bug-1010972.patch create mode 100644 data/patches/gnuzilla-bug-1025689.patch create mode 100644 data/patches/gnuzilla-bug-1030899-1.patch create mode 100644 data/patches/gnuzilla-bug-1030899-2.patch create mode 100644 data/patches/gnuzilla-bug-1032460.patch create mode 100644 data/patches/gnuzilla-bug-1034167-1.patch create mode 100644 data/patches/gnuzilla-bug-1034167-2.patch create mode 100644 data/patches/gnuzilla-bug-1036286-1.patch create mode 100644 data/patches/gnuzilla-bug-1036286-2.patch create mode 100644 data/patches/gnuzilla-bug-1050780.patch create mode 100644 data/patches/gnuzilla-bug-1055166.patch create mode 100644 data/patches/gnuzilla-bug-1058778.patch create mode 100644 data/patches/gnuzilla-bug-1089931.patch create mode 100644 data/patches/gnuzilla-bug-1091987.patch create mode 100644 data/patches/gnuzilla-bug-1095298.patch create mode 100644 data/patches/reduceGeckoPriority.patch create mode 100644 data/patches/reorder-mobile_android_base_android-services_mozbuild.patch create mode 100644 data/patches/revert-gnuzilla-bug-973138.patch create mode 100644 data/patches/torbrowser-bug-12811-2.patch create mode 100644 data/patches/torbrowser-bug-12811.patch create mode 100644 data/patches/torbrowser-bug-9837.patch diff --git a/data/patches/0001-Bug-1013064-part-1-prompt-for-master-password-unlock.patch b/data/patches/0001-Bug-1013064-part-1-prompt-for-master-password-unlock.patch new file mode 100644 index 0000000..1880604 --- /dev/null +++ b/data/patches/0001-Bug-1013064-part-1-prompt-for-master-password-unlock.patch @@ -0,0 +1,60 @@ +From 18f9d132020afee6004e63c9006245e4d3b04e18 Mon Sep 17 00:00:00 2001 +From: Mark Hammond +Date: Wed, 11 Jun 2014 17:34:24 +1000 +Subject: Bug 1013064 (part 1) - prompt for master-password unlock when + interacting with about:accounts. r=ttaubert + +--- + browser/base/content/aboutaccounts/aboutaccounts.js | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/browser/base/content/aboutaccounts/aboutaccounts.js b/browser/base/content/aboutaccounts/aboutaccounts.js +index 5cceae15..af31be9 100644 +--- a/browser/base/content/aboutaccounts/aboutaccounts.js ++++ b/browser/base/content/aboutaccounts/aboutaccounts.js +@@ -7,16 +7,19 @@ + const {classes: Cc, interfaces: Ci, utils: Cu} = Components; + + Cu.import("resource://gre/modules/Services.jsm"); + Cu.import("resource://gre/modules/FxAccounts.jsm"); + + let fxAccountsCommon = {}; + Cu.import("resource://gre/modules/FxAccountsCommon.js", fxAccountsCommon); + ++// for master-password utilities ++Cu.import("resource://services-sync/util.js"); ++ + const PREF_LAST_FXA_USER = "identity.fxaccounts.lastSignedInUserHash"; + const PREF_SYNC_SHOW_CUSTOMIZATION = "services.sync.ui.showCustomizationDialog"; + + const OBSERVER_TOPICS = [ + fxAccountsCommon.ONVERIFIED_NOTIFICATION, + fxAccountsCommon.ONLOGOUT_NOTIFICATION, + ]; + +@@ -99,16 +102,22 @@ let wrapper = { + .wrappedJSObject; + + // Don't show about:accounts with FxA disabled. + if (!weave.fxAccountsEnabled) { + document.body.remove(); + return; + } + ++ // If a master-password is enabled, we want to encourage the user to ++ // unlock it. Things still work if not, but the user will probably need ++ // to re-auth next startup (in which case we will get here again and ++ // re-prompt) ++ Utils.ensureMPUnlocked(); ++ + let iframe = document.getElementById("remote"); + this.iframe = iframe; + iframe.addEventListener("load", this); + + try { + iframe.src = url || fxAccounts.getAccountsSignUpURI(); + } catch (e) { + error("Couldn't init Firefox Account wrapper: " + e.message); +-- +1.8.3.msysgit.0 + diff --git a/data/patches/0002-Bug-1013064-part-2-Store-sensitive-FxA-credentials-i.patch b/data/patches/0002-Bug-1013064-part-2-Store-sensitive-FxA-credentials-i.patch new file mode 100644 index 0000000..bbd6f8d --- /dev/null +++ b/data/patches/0002-Bug-1013064-part-2-Store-sensitive-FxA-credentials-i.patch @@ -0,0 +1,551 @@ +From 4a92f9ee1ba35989f82a24bba18806f8616a5be8 Mon Sep 17 00:00:00 2001 +From: Mark Hammond +Date: Sat, 14 Jun 2014 14:33:20 +1000 +Subject: Bug 1013064 (part 2) - Store sensitive FxA credentials in the login + manager. r=ckarlof,dveditz + +--- + services/fxaccounts/FxAccounts.jsm | 170 ++++++++++++++++++ + services/fxaccounts/FxAccountsCommon.js | 13 ++ + services/fxaccounts/moz.build | 5 +- + .../tests/xpcshell/test_loginmgr_storage.js | 196 +++++++++++++++++++++ + services/fxaccounts/tests/xpcshell/xpcshell.ini | 2 + + services/sync/modules/util.js | 12 +- + 6 files changed, 396 insertions(+), 2 deletions(-) + create mode 100644 services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js + +diff --git a/services/fxaccounts/FxAccounts.jsm b/services/fxaccounts/FxAccounts.jsm +index cc521b8..e9328dd 100644 +--- a/services/fxaccounts/FxAccounts.jsm ++++ b/services/fxaccounts/FxAccounts.jsm +@@ -298,17 +298,22 @@ function FxAccountsInternal() { + // able to abort all work on the first sign-in process. The currentTimer and + // currentAccountState are used for this purpose. + // (XXX - should the timer be directly on the currentAccountState?) + this.currentTimer = null; + this.currentAccountState = new AccountState(this); + + // We don't reference |profileDir| in the top-level module scope + // as we may be imported before we know where it is. ++ // We only want the fancy new LoginManagerStorage on desktop. ++#if defined(MOZ_B2G) + this.signedInUserStorage = new JSONStorage({ ++#else ++ this.signedInUserStorage = new LoginManagerStorage({ ++#endif + filename: DEFAULT_STORAGE_FILENAME, + baseDir: OS.Constants.Path.profileDir, + }); + } + + /** + * The internal API's prototype. + */ +@@ -896,16 +901,181 @@ JSONStorage.prototype = { + .then(CommonUtils.writeJSON.bind(null, contents, this.path)); + }, + + get: function() { + return CommonUtils.readJSON(this.path); + } + }; + ++/** ++ * LoginManagerStorage constructor that creates instances that may set/get ++ * from a combination of a clear-text JSON file and stored securely in ++ * the nsILoginManager. ++ * ++ * @param options { ++ * filename: of the plain-text file to write to ++ * baseDir: directory where the file resides ++ * } ++ * @return instance ++ */ ++ ++function LoginManagerStorage(options) { ++ // we reuse the JSONStorage for writing the plain-text stuff. ++ this.jsonStorage = new JSONStorage(options); ++} ++ ++LoginManagerStorage.prototype = { ++ // The fields in the credentials JSON object that are stored in plain-text ++ // in the profile directory. All other fields are stored in the login manager, ++ // and thus are only available when the master-password is unlocked. ++ ++ // a hook point for testing. ++ get _isLoggedIn() { ++ return Services.logins.isLoggedIn; ++ }, ++ ++ // Clear any data from the login manager. Returns true if the login manager ++ // was unlocked (even if no existing logins existed) or false if it was ++ // locked (meaning we don't even know if it existed or not.) ++ _clearLoginMgrData: Task.async(function* () { ++ try { // Services.logins might be third-party and broken... ++ yield Services.logins.initializationPromise; ++ if (!this._isLoggedIn) { ++ return false; ++ } ++ let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM); ++ for (let login of logins) { ++ Services.logins.removeLogin(login); ++ } ++ return true; ++ } catch (ex) { ++ log.error("Failed to clear login data: ${}", ex); ++ return false; ++ } ++ }), ++ ++ set: Task.async(function* (contents) { ++ if (!contents) { ++ // User is signing out - write the null to the json file. ++ yield this.jsonStorage.set(contents); ++ ++ // And nuke it from the login manager. ++ let cleared = yield this._clearLoginMgrData(); ++ if (!cleared) { ++ // just log a message - we verify that the email address matches when ++ // we reload it, so having a stale entry doesn't really hurt. ++ log.info("not removing credentials from login manager - not logged in"); ++ } ++ return; ++ } ++ ++ // We are saving actual data. ++ // Split the data into 2 chunks - one to go to the plain-text, and the ++ // other to write to the login manager. ++ let toWriteJSON = {version: contents.version}; ++ let accountDataJSON = toWriteJSON.accountData = {}; ++ let toWriteLoginMgr = {version: contents.version}; ++ let accountDataLoginMgr = toWriteLoginMgr.accountData = {}; ++ for (let [name, value] of Iterator(contents.accountData)) { ++ if (FXA_PWDMGR_PLAINTEXT_FIELDS.indexOf(name) >= 0) { ++ accountDataJSON[name] = value; ++ } else { ++ accountDataLoginMgr[name] = value; ++ } ++ } ++ yield this.jsonStorage.set(toWriteJSON); ++ ++ try { // Services.logins might be third-party and broken... ++ // and the stuff into the login manager. ++ yield Services.logins.initializationPromise; ++ // If MP is locked we silently fail - the user may need to re-auth ++ // next startup. ++ if (!this._isLoggedIn) { ++ log.info("not saving credentials to login manager - not logged in"); ++ return; ++ } ++ // write the rest of the data to the login manager. ++ let loginInfo = new Components.Constructor( ++ "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init"); ++ let login = new loginInfo(FXA_PWDMGR_HOST, ++ null, // aFormSubmitURL, ++ FXA_PWDMGR_REALM, // aHttpRealm, ++ contents.accountData.email, // aUsername ++ JSON.stringify(toWriteLoginMgr), // aPassword ++ "", // aUsernameField ++ "");// aPasswordField ++ ++ let existingLogins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, ++ FXA_PWDMGR_REALM); ++ if (existingLogins.length) { ++ Services.logins.modifyLogin(existingLogins[0], login); ++ } else { ++ Services.logins.addLogin(login); ++ } ++ } catch (ex) { ++ log.error("Failed to save data to the login manager: ${}", ex); ++ } ++ }), ++ ++ get: Task.async(function* () { ++ // we need to suck some data from the .json file in the profile dir and ++ // some other from the login manager. ++ let data = yield this.jsonStorage.get(); ++ if (!data) { ++ // no user logged in, nuke the storage data incase we couldn't remove ++ // it previously and then we are done. ++ yield this._clearLoginMgrData(); ++ return null; ++ } ++ ++ // if we have encryption keys it must have been saved before we ++ // used the login manager, so re-save it. ++ if (data.accountData.kA || data.accountData.kB || data.keyFetchToken) { ++ log.info("account data needs migration to the login manager."); ++ yield this.set(data); ++ } ++ ++ try { // Services.logins might be third-party and broken... ++ // read the data from the login manager and merge it for return. ++ yield Services.logins.initializationPromise; ++ ++ if (!this._isLoggedIn) { ++ log.info("returning partial account data as the login manager is locked."); ++ return data; ++ } ++ ++ let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM); ++ if (logins.length == 0) { ++ // This could happen if the MP was locked when we wrote the data. ++ log.info("Can't find the rest of the credentials in the login manager"); ++ return data; ++ } ++ let login = logins[0]; ++ if (login.username == data.accountData.email) { ++ let lmData = JSON.parse(login.password); ++ if (lmData.version == data.version) { ++ // Merge the login manager data ++ copyObjectProperties(lmData.accountData, data.accountData); ++ } else { ++ log.info("version field in the login manager doesn't match - ignoring it"); ++ yield this._clearLoginMgrData(); ++ } ++ } else { ++ log.info("username in the login manager doesn't match - ignoring it"); ++ yield this._clearLoginMgrData(); ++ } ++ } catch (ex) { ++ log.error("Failed to get data from the login manager: ${}", ex); ++ } ++ return data; ++ }), ++ ++} ++ + // A getter for the instance to export + XPCOMUtils.defineLazyGetter(this, "fxAccounts", function() { + let a = new FxAccounts(); + + // XXX Bug 947061 - We need a strategy for resuming email verification after + // browser restart + a.loadAndPoll(); + +diff --git a/services/fxaccounts/FxAccountsCommon.js b/services/fxaccounts/FxAccountsCommon.js +index 2bef093..476e528 100644 +--- a/services/fxaccounts/FxAccountsCommon.js ++++ b/services/fxaccounts/FxAccountsCommon.js +@@ -170,10 +170,23 @@ SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_AUTH_NONCE] = ERROR_INVALID_AUTH + SERVER_ERRNO_TO_ERROR[ERRNO_ENDPOINT_NO_LONGER_SUPPORTED] = ERROR_ENDPOINT_NO_LONGER_SUPPORTED; + SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_LOGIN_METHOD] = ERROR_INCORRECT_LOGIN_METHOD; + SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_KEY_RETRIEVAL_METHOD] = ERROR_INCORRECT_KEY_RETRIEVAL_METHOD; + SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_API_VERSION] = ERROR_INCORRECT_API_VERSION; + SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_EMAIL_CASE] = ERROR_INCORRECT_EMAIL_CASE; + SERVER_ERRNO_TO_ERROR[ERRNO_SERVICE_TEMP_UNAVAILABLE] = ERROR_SERVICE_TEMP_UNAVAILABLE; + SERVER_ERRNO_TO_ERROR[ERRNO_UNKNOWN_ERROR] = ERROR_UNKNOWN; + ++// FxAccounts has the ability to "split" the credentials between a plain-text ++// JSON file in the profile dir and in the login manager. ++// These constants relate to that. ++ ++// The fields we save in the plaintext JSON. ++// See bug 1013064 comments 23-25 for why the sessionToken is "safe" ++this.FXA_PWDMGR_PLAINTEXT_FIELDS = ["email", "verified", "authAt", ++ "sessionToken", "uid"]; ++// The pseudo-host we use in the login manager ++this.FXA_PWDMGR_HOST = "chrome://FirefoxAccounts"; ++// The realm we use in the login manager. ++this.FXA_PWDMGR_REALM = "Firefox Accounts credentials"; ++ + // Allow this file to be imported via Components.utils.import(). + this.EXPORTED_SYMBOLS = Object.keys(this); +diff --git a/services/fxaccounts/moz.build b/services/fxaccounts/moz.build +index f959714..1bb8c09 100644 +--- a/services/fxaccounts/moz.build ++++ b/services/fxaccounts/moz.build +@@ -5,16 +5,19 @@ + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + + PARALLEL_DIRS += ['interfaces'] + + TEST_DIRS += ['tests'] + + EXTRA_JS_MODULES += [ + 'Credentials.jsm', +- 'FxAccounts.jsm', + 'FxAccountsClient.jsm', + 'FxAccountsCommon.js' + ] + ++EXTRA_PP_JS_MODULES += [ ++ 'FxAccounts.jsm', ++] ++ + # For now, we will only be using the FxA manager in B2G. + if CONFIG['MOZ_B2G']: + EXTRA_JS_MODULES += ['FxAccountsManager.jsm'] +diff --git a/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js b/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js +new file mode 100644 +index 0000000..297b256 +--- /dev/null ++++ b/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js +@@ -0,0 +1,196 @@ ++/* Any copyright is dedicated to the Public Domain. ++ * http://creativecommons.org/publicdomain/zero/1.0/ */ ++ ++"use strict"; ++ ++// Tests for FxAccounts, storage and the master password. ++ ++// Stop us hitting the real auth server. ++Services.prefs.setCharPref("identity.fxaccounts.auth.uri", "http://localhost"); ++ ++Cu.import("resource://gre/modules/Services.jsm"); ++Cu.import("resource://gre/modules/FxAccounts.jsm"); ++Cu.import("resource://gre/modules/FxAccountsClient.jsm"); ++Cu.import("resource://gre/modules/FxAccountsCommon.js"); ++Cu.import("resource://gre/modules/osfile.jsm"); ++Cu.import("resource://services-common/utils.js"); ++Cu.import("resource://gre/modules/FxAccountsCommon.js"); ++ ++initTestLogging("Trace"); ++// See verbose logging from FxAccounts.jsm ++Services.prefs.setCharPref("identity.fxaccounts.loglevel", "DEBUG"); ++ ++function run_test() { ++ run_next_test(); ++} ++ ++function getLoginMgrData() { ++ let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM); ++ if (logins.length == 0) { ++ return null; ++ } ++ Assert.equal(logins.length, 1, "only 1 login available"); ++ return logins[0]; ++} ++ ++add_task(function test_simple() { ++ let fxa = new FxAccounts({}); ++ ++ let creds = { ++ email: "test@example.com", ++ sessionToken: "sessionToken", ++ kA: "the kA value", ++ kB: "the kB value", ++ verified: true ++ }; ++ yield fxa.setSignedInUser(creds); ++ ++ // This should have stored stuff in both the .json file in the profile ++ // dir, and the login dir. ++ let path = OS.Path.join(OS.Constants.Path.profileDir, "signedInUser.json"); ++ let data = yield CommonUtils.readJSON(path); ++ ++ Assert.strictEqual(data.accountData.email, creds.email, "correct email in the clear text"); ++ Assert.strictEqual(data.accountData.sessionToken, creds.sessionToken, "correct sessionToken in the clear text"); ++ Assert.strictEqual(data.accountData.verified, creds.verified, "correct verified flag"); ++ ++ Assert.ok(!("kA" in data.accountData), "kA not stored in clear text"); ++ Assert.ok(!("kB" in data.accountData), "kB not stored in clear text"); ++ ++ let login = getLoginMgrData(); ++ Assert.strictEqual(login.username, creds.email, "email matches"); ++ let loginData = JSON.parse(login.password); ++ Assert.strictEqual(loginData.version, data.version, "same version flag in both places"); ++ Assert.strictEqual(loginData.accountData.kA, creds.kA, "correct kA in the login mgr"); ++ Assert.strictEqual(loginData.accountData.kB, creds.kB, "correct kB in the login mgr"); ++ ++ Assert.ok(!("email" in loginData), "email not stored in the login mgr json"); ++ Assert.ok(!("sessionToken" in loginData), "sessionToken not stored in the login mgr json"); ++ Assert.ok(!("verified" in loginData), "verified not stored in the login mgr json"); ++ ++ yield fxa.signOut(/* localOnly = */ true); ++ Assert.strictEqual(getLoginMgrData(), null, "login mgr data deleted on logout"); ++}); ++ ++add_task(function test_MPLocked() { ++ let fxa = new FxAccounts({}); ++ ++ let creds = { ++ email: "test@example.com", ++ sessionToken: "sessionToken", ++ kA: "the kA value", ++ kB: "the kB value", ++ verified: true ++ }; ++ ++ // tell the storage that the MP is locked. ++ fxa.internal.signedInUserStorage.__defineGetter__("_isLoggedIn", function() false); ++ yield fxa.setSignedInUser(creds); ++ ++ // This should have stored stuff in the .json, and the login manager stuff ++ // will not exist. ++ let path = OS.Path.join(OS.Constants.Path.profileDir, "signedInUser.json"); ++ let data = yield CommonUtils.readJSON(path); ++ ++ Assert.strictEqual(data.accountData.email, creds.email, "correct email in the clear text"); ++ Assert.strictEqual(data.accountData.sessionToken, creds.sessionToken, "correct sessionToken in the clear text"); ++ Assert.strictEqual(data.accountData.verified, creds.verified, "correct verified flag"); ++ ++ Assert.ok(!("kA" in data.accountData), "kA not stored in clear text"); ++ Assert.ok(!("kB" in data.accountData), "kB not stored in clear text"); ++ ++ Assert.strictEqual(getLoginMgrData(), null, "login mgr data doesn't exist"); ++ yield fxa.signOut(/* localOnly = */ true) ++}); ++ ++add_task(function test_consistentWithMPEdgeCases() { ++ let fxa = new FxAccounts({}); ++ ++ let creds1 = { ++ email: "test@example.com", ++ sessionToken: "sessionToken", ++ kA: "the kA value", ++ kB: "the kB value", ++ verified: true ++ }; ++ ++ let creds2 = { ++ email: "test2@example.com", ++ sessionToken: "sessionToken2", ++ kA: "the kA value2", ++ kB: "the kB value2", ++ verified: false, ++ }; ++ ++ // Log a user in while MP is unlocked. ++ yield fxa.setSignedInUser(creds1); ++ ++ // tell the storage that the MP is locked - this will prevent logout from ++ // being able to clear the data. ++ fxa.internal.signedInUserStorage.__defineGetter__("_isLoggedIn", function() false); ++ ++ // now set the second credentials. ++ yield fxa.setSignedInUser(creds2); ++ ++ // We should still have creds1 data in the login manager. ++ let login = getLoginMgrData(); ++ Assert.strictEqual(login.username, creds1.email); ++ // and that we do have the first kA in the login manager. ++ Assert.strictEqual(JSON.parse(login.password).accountData.kA, creds1.kA, ++ "stale data still in login mgr"); ++ ++ // Make a new FxA instance (otherwise the values in memory will be used.) ++ // Because we haven't overridden _isLoggedIn for this new instance it will ++ // treat the MP as unlocked. ++ let fxa = new FxAccounts({}); ++ ++ let accountData = yield fxa.getSignedInUser(); ++ Assert.strictEqual(accountData.email, creds2.email); ++ // we should have no kA at all. ++ Assert.strictEqual(accountData.kA, undefined, "stale kA wasn't used"); ++ yield fxa.signOut(/* localOnly = */ true) ++}); ++ ++add_task(function test_migration() { ++ // manually write out the full creds data to the JSON - this will look like ++ // old data that needs migration. ++ let creds = { ++ email: "test@example.com", ++ sessionToken: "sessionToken", ++ kA: "the kA value", ++ kB: "the kB value", ++ verified: true ++ }; ++ let toWrite = { ++ version: 1, ++ accountData: creds, ++ }; ++ ++ let path = OS.Path.join(OS.Constants.Path.profileDir, "signedInUser.json"); ++ let data = yield CommonUtils.writeJSON(toWrite, path); ++ ++ // Create an FxA object - and tell it to load the data. ++ let fxa = new FxAccounts({}); ++ data = yield fxa.getSignedInUser(); ++ ++ Assert.deepEqual(data, creds, "we should have everything available"); ++ ++ // now sniff the data on disk - it should have been magically migrated. ++ data = yield CommonUtils.readJSON(path); ++ ++ Assert.strictEqual(data.accountData.email, creds.email, "correct email in the clear text"); ++ Assert.strictEqual(data.accountData.sessionToken, creds.sessionToken, "correct sessionToken in the clear text"); ++ Assert.strictEqual(data.accountData.verified, creds.verified, "correct verified flag"); ++ ++ Assert.ok(!("kA" in data.accountData), "kA not stored in clear text"); ++ Assert.ok(!("kB" in data.accountData), "kB not stored in clear text"); ++ ++ // and it should magically be in the login manager. ++ let login = getLoginMgrData(); ++ Assert.strictEqual(login.username, creds.email); ++ // and that we do have the first kA in the login manager. ++ Assert.strictEqual(JSON.parse(login.password).accountData.kA, creds.kA, ++ "kA was migrated"); ++ ++ yield fxa.signOut(/* localOnly = */ true) ++}); +diff --git a/services/fxaccounts/tests/xpcshell/xpcshell.ini b/services/fxaccounts/tests/xpcshell/xpcshell.ini +index 9d9bffe..4448d76 100644 +--- a/services/fxaccounts/tests/xpcshell/xpcshell.ini ++++ b/services/fxaccounts/tests/xpcshell/xpcshell.ini +@@ -1,10 +1,12 @@ + [DEFAULT] + head = head.js ../../../common/tests/unit/head_helpers.js ../../../common/tests/unit/head_http.js + tail = + + [test_accounts.js] + [test_client.js] + [test_credentials.js] ++[test_loginmgr_storage.js] ++skip-if = appname == 'b2g' # login manager storage only used on desktop. + [test_manager.js] + run-if = appname == 'b2g' + reason = FxAccountsManager is only available for B2G for now +diff --git a/services/sync/modules/util.js b/services/sync/modules/util.js +index 194e0b2..4691428 100644 +--- a/services/sync/modules/util.js ++++ b/services/sync/modules/util.js +@@ -14,16 +14,23 @@ Cu.import("resource://services-common/async.js", this); + Cu.import("resource://services-crypto/utils.js"); + Cu.import("resource://services-sync/constants.js"); + Cu.import("resource://gre/modules/Preferences.jsm"); + Cu.import("resource://gre/modules/Services.jsm", this); + Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); + Cu.import("resource://gre/modules/osfile.jsm", this); + Cu.import("resource://gre/modules/Task.jsm", this); + ++// FxAccountsCommon.js doesn't use a "namespace", so create one here. ++XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function() { ++ let FxAccountsCommon = {}; ++ Cu.import("resource://gre/modules/FxAccountsCommon.js", FxAccountsCommon); ++ return FxAccountsCommon; ++}); ++ + /* + * Utility functions + */ + + this.Utils = { + // Alias in functions from CommonUtils. These previously were defined here. + // In the ideal world, references to these would be removed. + nextTick: CommonUtils.nextTick, +@@ -589,18 +596,21 @@ this.Utils = { + * reset when we drop sync credentials, etc. + */ + getSyncCredentialsHosts: function() { + // This is somewhat expensive and the result static, so we cache the result. + if (this._syncCredentialsHosts) { + return this._syncCredentialsHosts; + } + let result = new Set(); +- // the legacy sync host. ++ // the legacy sync host + result.add(PWDMGR_HOST); ++ // the FxA host ++ result.add(FxAccountsCommon.FXA_PWDMGR_HOST); ++ // + // The FxA hosts - these almost certainly all have the same hostname, but + // better safe than sorry... + for (let prefName of ["identity.fxaccounts.remote.force_auth.uri", + "identity.fxaccounts.remote.signup.uri", + "identity.fxaccounts.remote.signin.uri", + "identity.fxaccounts.settings.uri"]) { + let prefVal; + try { +-- +1.8.3.msysgit.0 + diff --git a/data/patches/0003-Bug-1013064-part-3-only-migrate-data-into-the-loginm.patch b/data/patches/0003-Bug-1013064-part-3-only-migrate-data-into-the-loginm.patch new file mode 100644 index 0000000..063a031 --- /dev/null +++ b/data/patches/0003-Bug-1013064-part-3-only-migrate-data-into-the-loginm.patch @@ -0,0 +1,194 @@ +From 86c67f9b9081ce905442c86b38575b3422c8dce3 Mon Sep 17 00:00:00 2001 +From: Mark Hammond +Date: Wed, 18 Jun 2014 15:07:41 +1000 +Subject: Bug 1013064 (part 3) - only migrate data into the loginmgr when it is + unlocked. r=ckarlof + +--- + services/fxaccounts/FxAccounts.jsm | 25 ++++- + .../tests/xpcshell/test_loginmgr_storage.js | 113 +++++++++++++++++++++ + 2 files changed, 137 insertions(+), 1 deletion(-) + +diff --git a/services/fxaccounts/FxAccounts.jsm b/services/fxaccounts/FxAccounts.jsm +index e9328dd..14d71fc 100644 +--- a/services/fxaccounts/FxAccounts.jsm ++++ b/services/fxaccounts/FxAccounts.jsm +@@ -1025,17 +1025,40 @@ LoginManagerStorage.prototype = { + // it previously and then we are done. + yield this._clearLoginMgrData(); + return null; + } + + // if we have encryption keys it must have been saved before we + // used the login manager, so re-save it. + if (data.accountData.kA || data.accountData.kB || data.keyFetchToken) { +- log.info("account data needs migration to the login manager."); ++ // We need to migrate, but the MP might be locked (eg, on the first run ++ // with this enabled, we will get here very soon after startup, so will ++ // certainly be locked.) This means we can't actually store the data in ++ // the login manager (and thus might lose it if we migrated now) ++ // So if the MP is locked, we *don't* migrate, but still just return ++ // the subset of data we now store in the JSON. ++ // This will cause sync to notice the lack of keys, force an unlock then ++ // re-fetch the account data to see if the keys are there. At *that* ++ // point we will end up back here, but because the MP is now unlocked ++ // we can actually perform the migration. ++ if (!this._isLoggedIn) { ++ // return the "safe" subset but leave the storage alone. ++ log.info("account data needs migration to the login manager but the MP is locked."); ++ let result = { ++ version: data.version, ++ accountData: {}, ++ }; ++ for (let fieldName of FXA_PWDMGR_PLAINTEXT_FIELDS) { ++ result.accountData[fieldName] = data.accountData[fieldName]; ++ } ++ return result; ++ } ++ // actually migrate - just calling .set() will split everything up. ++ log.info("account data is being migrated to the login manager."); + yield this.set(data); + } + + try { // Services.logins might be third-party and broken... + // read the data from the login manager and merge it for return. + yield Services.logins.initializationPromise; + + if (!this._isLoggedIn) { +diff --git a/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js b/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js +index 297b256..a9cf5f4 100644 +--- a/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js ++++ b/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js +@@ -98,16 +98,129 @@ add_task(function test_MPLocked() { + + Assert.ok(!("kA" in data.accountData), "kA not stored in clear text"); + Assert.ok(!("kB" in data.accountData), "kB not stored in clear text"); + + Assert.strictEqual(getLoginMgrData(), null, "login mgr data doesn't exist"); + yield fxa.signOut(/* localOnly = */ true) + }); + ++add_task(function test_migrationMPUnlocked() { ++ // first manually save a signedInUser.json to simulate a first-run with ++ // pre-migrated data. ++ let fxa = new FxAccounts({}); ++ ++ let creds = { ++ email: "test@example.com", ++ sessionToken: "sessionToken", ++ kA: "the kA value", ++ kB: "the kB value", ++ verified: true ++ }; ++ let toWrite = { ++ version: fxa.version, ++ accountData: creds, ++ } ++ ++ let path = OS.Path.join(OS.Constants.Path.profileDir, "signedInUser.json"); ++ yield CommonUtils.writeJSON(toWrite, path); ++ ++ // now load it - it should migrate. ++ let data = yield fxa.getSignedInUser(); ++ Assert.deepEqual(data, creds, "we got all the data back"); ++ ++ // and verify it was actually migrated - re-read signedInUser back. ++ let data = yield CommonUtils.readJSON(path); ++ ++ Assert.strictEqual(data.accountData.email, creds.email, "correct email in the clear text"); ++ Assert.strictEqual(data.accountData.sessionToken, creds.sessionToken, "correct sessionToken in the clear text"); ++ Assert.strictEqual(data.accountData.verified, creds.verified, "correct verified flag"); ++ ++ Assert.ok(!("kA" in data.accountData), "kA not stored in clear text"); ++ Assert.ok(!("kB" in data.accountData), "kB not stored in clear text"); ++ ++ let login = getLoginMgrData(); ++ Assert.strictEqual(login.username, creds.email, "email matches"); ++ let loginData = JSON.parse(login.password); ++ Assert.strictEqual(loginData.version, data.version, "same version flag in both places"); ++ Assert.strictEqual(loginData.accountData.kA, creds.kA, "correct kA in the login mgr"); ++ Assert.strictEqual(loginData.accountData.kB, creds.kB, "correct kB in the login mgr"); ++ ++ Assert.ok(!("email" in loginData), "email not stored in the login mgr json"); ++ Assert.ok(!("sessionToken" in loginData), "sessionToken not stored in the login mgr json"); ++ Assert.ok(!("verified" in loginData), "verified not stored in the login mgr json"); ++ ++ yield fxa.signOut(/* localOnly = */ true); ++ Assert.strictEqual(getLoginMgrData(), null, "login mgr data deleted on logout"); ++}); ++ ++add_task(function test_migrationMPLocked() { ++ // first manually save a signedInUser.json to simulate a first-run with ++ // pre-migrated data. ++ let fxa = new FxAccounts({}); ++ ++ let creds = { ++ email: "test@example.com", ++ sessionToken: "sessionToken", ++ kA: "the kA value", ++ kB: "the kB value", ++ verified: true ++ }; ++ let toWrite = { ++ version: fxa.version, ++ accountData: creds, ++ } ++ ++ let path = OS.Path.join(OS.Constants.Path.profileDir, "signedInUser.json"); ++ yield CommonUtils.writeJSON(toWrite, path); ++ ++ // pretend the MP is locked. ++ fxa.internal.signedInUserStorage.__defineGetter__("_isLoggedIn", function() false); ++ ++ // now load it - it should *not* migrate, but should only give the JSON-safe ++ // data back. ++ let data = yield fxa.getSignedInUser(); ++ Assert.ok(!data.kA); ++ Assert.ok(!data.kB); ++ ++ // and verify the data on disk wan't migrated. ++ data = yield CommonUtils.readJSON(path); ++ Assert.deepEqual(data, toWrite); ++ ++ // Now "unlock" and re-ask for the signedInUser - it should migrate. ++ fxa.internal.signedInUserStorage.__defineGetter__("_isLoggedIn", function() true); ++ data = yield fxa.getSignedInUser(); ++ // this time we should have got all the data, not just the JSON-safe fields. ++ Assert.strictEqual(data.kA, creds.kA); ++ Assert.strictEqual(data.kB, creds.kB); ++ ++ // And verify the data in the JSON was migrated ++ data = yield CommonUtils.readJSON(path); ++ Assert.strictEqual(data.accountData.email, creds.email, "correct email in the clear text"); ++ Assert.strictEqual(data.accountData.sessionToken, creds.sessionToken, "correct sessionToken in the clear text"); ++ Assert.strictEqual(data.accountData.verified, creds.verified, "correct verified flag"); ++ ++ Assert.ok(!("kA" in data.accountData), "kA not stored in clear text"); ++ Assert.ok(!("kB" in data.accountData), "kB not stored in clear text"); ++ ++ let login = getLoginMgrData(); ++ Assert.strictEqual(login.username, creds.email, "email matches"); ++ let loginData = JSON.parse(login.password); ++ Assert.strictEqual(loginData.version, data.version, "same version flag in both places"); ++ Assert.strictEqual(loginData.accountData.kA, creds.kA, "correct kA in the login mgr"); ++ Assert.strictEqual(loginData.accountData.kB, creds.kB, "correct kB in the login mgr"); ++ ++ Assert.ok(!("email" in loginData), "email not stored in the login mgr json"); ++ Assert.ok(!("sessionToken" in loginData), "sessionToken not stored in the login mgr json"); ++ Assert.ok(!("verified" in loginData), "verified not stored in the login mgr json"); ++ ++ yield fxa.signOut(/* localOnly = */ true); ++ Assert.strictEqual(getLoginMgrData(), null, "login mgr data deleted on logout"); ++}); ++ + add_task(function test_consistentWithMPEdgeCases() { + let fxa = new FxAccounts({}); + + let creds1 = { + email: "test@example.com", + sessionToken: "sessionToken", + kA: "the kA value", + kB: "the kB value", +-- +1.8.3.msysgit.0 + diff --git a/data/patches/0004-Bug-1013064-part-4b-browserid_identity-and-sync-chang.patch b/data/patches/0004-Bug-1013064-part-4b-browserid_identity-and-sync-chang.patch new file mode 100644 index 0000000..369bde6 --- /dev/null +++ b/data/patches/0004-Bug-1013064-part-4b-browserid_identity-and-sync-chang.patch @@ -0,0 +1,286 @@ +From 9717484083e66b78eedfa14e539d51382aba760f Mon Sep 17 00:00:00 2001 +From: Mark Hammond +Date: Sat, 14 Jun 2014 14:33:56 +1000 +Subject: Bug 1013064 (part 4) - browserid_identity and sync changes to support + FxA credentials in the login manager. r=ckarlof,rnewman + +--- + services/sync/modules/browserid_identity.js | 61 ++++++++++++++++++++-- + services/sync/modules/identity.js | 19 +++++++ + services/sync/modules/service.js | 20 ++++--- + .../sync/tests/unit/test_browserid_identity.js | 15 ++++++ + 4 files changed, 102 insertions(+), 13 deletions(-) + +diff --git a/services/sync/modules/browserid_identity.js b/services/sync/modules/browserid_identity.js +--- a/services/sync/modules/browserid_identity.js ++++ b/services/sync/modules/browserid_identity.js +@@ -394,41 +394,83 @@ this.BrowserIDManager.prototype = { + this._syncKeyUpdated = true; + this._shouldHaveSyncKeyBundle = false; + }, + + /** + * The current state of the auth credentials. + * + * This essentially validates that enough credentials are available to use +- * Sync. ++ * Sync, although it effectively ignores the state of the master-password - ++ * if that's locked and that's the only problem we can see, say everything ++ * is OK - unlockAndVerifyAuthState will be used to perform the unlock ++ * and re-verification if necessary. + */ + get currentAuthState() { + if (this._authFailureReason) { + this._log.info("currentAuthState returning " + this._authFailureReason + + " due to previous failure"); + return this._authFailureReason; + } + // TODO: need to revisit this. Currently this isn't ready to go until + // both the username and syncKeyBundle are both configured and having no + // username seems to make things fail fast so that's good. + if (!this.username) { + return LOGIN_FAILED_NO_USERNAME; + } + + // No need to check this.syncKey as our getter for that attribute + // uses this.syncKeyBundle +- // If bundle creation started, but failed. +- if (this._shouldHaveSyncKeyBundle && !this.syncKeyBundle) { +- return LOGIN_FAILED_NO_PASSPHRASE; ++ // If bundle creation started, but failed due to any reason other than ++ // the MP being locked... ++ if (this._shouldHaveSyncKeyBundle && !this.syncKeyBundle && !Utils.mpLocked()) { ++ // Return a state that says a re-auth is necessary so we can get keys. ++ return LOGIN_FAILED_LOGIN_REJECTED; + } + + return STATUS_OK; + }, + ++ // Do we currently have keys, or do we have enough that we should be able ++ // to successfully fetch them? ++ _canFetchKeys: function() { ++ let userData = this._signedInUser; ++ // a keyFetchToken means we can almost certainly grab them. ++ // kA and kB means we already have them. ++ return userData && (userData.keyFetchToken || (userData.kA && userData.kB)); ++ }, ++ ++ /** ++ * Verify the current auth state, unlocking the master-password if necessary. ++ * ++ * Returns a promise that resolves with the current auth state after ++ * attempting to unlock. ++ */ ++ unlockAndVerifyAuthState: function() { ++ if (this._canFetchKeys()) { ++ return Promise.resolve(STATUS_OK); ++ } ++ // so no keys - ensure MP unlocked. ++ if (!Utils.ensureMPUnlocked()) { ++ // user declined to unlock, so we don't know if they are stored there. ++ return Promise.resolve(MASTER_PASSWORD_LOCKED); ++ } ++ // now we are unlocked we must re-fetch the user data as we may now have ++ // the details that were previously locked away. ++ return this._fxaService.getSignedInUser().then( ++ accountData => { ++ this._updateSignedInUser(accountData); ++ // If we still can't get keys it probably means the user authenticated ++ // without unlocking the MP or cleared the saved logins, so we've now ++ // lost them - the user will need to reauth before continuing. ++ return this._canFetchKeys() ? STATUS_OK : LOGIN_FAILED_LOGIN_REJECTED; ++ } ++ ); ++ }, ++ + /** + * Do we have a non-null, not yet expired token for the user currently + * signed in? + */ + hasValidToken: function() { + if (!this._token) { + return false; + } +@@ -444,16 +486,24 @@ this.BrowserIDManager.prototype = { + if (tokenServerURI.endsWith("/")) { // trailing slashes cause problems... + tokenServerURI = tokenServerURI.slice(0, -1); + } + let log = this._log; + let client = this._tokenServerClient; + let fxa = this._fxaService; + let userData = this._signedInUser; + ++ // We need kA and kB for things to work. If we don't have them, just ++ // return null for the token - sync calling unlockAndVerifyAuthState() ++ // before actually syncing will setup the error states if necessary. ++ if (!this._canFetchKeys()) { ++ log.info("_fetchTokenForUser has no keys to use."); ++ return null; ++ } ++ + log.info("Fetching assertion and token from: " + tokenServerURI); + + let maybeFetchKeys = () => { + // This is called at login time and every time we need a new token - in + // the latter case we already have kA and kB, so optimise that case. + if (userData.kA && userData.kB) { + return; + } +@@ -519,17 +569,18 @@ this.BrowserIDManager.prototype = { + // TODO: write tests to make sure that different auth error cases are handled here + // properly: auth error getting assertion, auth error getting token (invalid generation + // and client-state error) + if (err instanceof AuthenticationError) { + this._log.error("Authentication error in _fetchTokenForUser: " + err); + // set it to the "fatal" LOGIN_FAILED_LOGIN_REJECTED reason. + this._authFailureReason = LOGIN_FAILED_LOGIN_REJECTED; + } else { +- this._log.error("Non-authentication error in _fetchTokenForUser: " + err.message); ++ this._log.error("Non-authentication error in _fetchTokenForUser: " ++ + (err.message || err)); + // for now assume it is just a transient network related problem. + this._authFailureReason = LOGIN_FAILED_NETWORK_ERROR; + } + // Drop the sync key bundle, but still expect to have one. + // This will arrange for us to be in the right 'currentAuthState' + // such that UI will show the right error. + this._shouldHaveSyncKeyBundle = true; + Weave.Status.login = this._authFailureReason; +diff --git a/services/sync/modules/identity.js b/services/sync/modules/identity.js +--- a/services/sync/modules/identity.js ++++ b/services/sync/modules/identity.js +@@ -373,16 +373,35 @@ IdentityManager.prototype = { + if (!this.syncKeyBundle) { + return LOGIN_FAILED_INVALID_PASSPHRASE; + } + + return STATUS_OK; + }, + + /** ++ * Verify the current auth state, unlocking the master-password if necessary. ++ * ++ * Returns a promise that resolves with the current auth state after ++ * attempting to unlock. ++ */ ++ unlockAndVerifyAuthState: function() { ++ // Try to fetch the passphrase - this will prompt for MP unlock as a ++ // side-effect... ++ try { ++ this.syncKey; ++ } catch (ex) { ++ this._log.debug("Fetching passphrase threw " + ex + ++ "; assuming master password locked."); ++ return Promise.resolve(MASTER_PASSWORD_LOCKED); ++ } ++ return Promise.resolve(STATUS_OK); ++ }, ++ ++ /** + * Persist credentials to password store. + * + * When credentials are updated, they are changed in memory only. This will + * need to be called to save them to the underlying password store. + * + * If the password store is locked (e.g. if the master password hasn't been + * entered), this could throw an exception. + */ +diff --git a/services/sync/modules/service.js b/services/sync/modules/service.js +--- a/services/sync/modules/service.js ++++ b/services/sync/modules/service.js +@@ -679,27 +679,31 @@ Sync11Service.prototype = { + } + + if (!this.identity.username) { + this._log.warn("No username in verifyLogin."); + this.status.login = LOGIN_FAILED_NO_USERNAME; + return false; + } + +- // Unlock master password, or return. + // Attaching auth credentials to a request requires access to + // passwords, which means that Resource.get can throw MP-related + // exceptions! +- // Try to fetch the passphrase first, while we still have control. +- try { +- this.identity.syncKey; +- } catch (ex) { +- this._log.debug("Fetching passphrase threw " + ex + +- "; assuming master password locked."); +- this.status.login = MASTER_PASSWORD_LOCKED; ++ // So we ask the identity to verify the login state after unlocking the ++ // master password (ie, this call is expected to prompt for MP unlock ++ // if necessary) while we still have control. ++ let cb = Async.makeSpinningCallback(); ++ this.identity.unlockAndVerifyAuthState().then( ++ result => cb(null, result), ++ cb ++ ); ++ let unlockedState = cb.wait(); ++ this._log.debug("Fetching unlocked auth state returned " + unlockedState); ++ if (unlockedState != STATUS_OK) { ++ this.status.login = unlockedState; + return false; + } + + try { + // Make sure we have a cluster to verify against. + // This is a little weird, if we don't get a node we pretend + // to succeed, since that probably means we just don't have storage. + if (this.clusterURL == "" && !this._clusterManager.setCluster()) { +diff --git a/services/sync/tests/unit/test_browserid_identity.js b/services/sync/tests/unit/test_browserid_identity.js +--- a/services/sync/tests/unit/test_browserid_identity.js ++++ b/services/sync/tests/unit/test_browserid_identity.js +@@ -78,19 +78,33 @@ add_task(function test_initialializeWith + browseridManager.initializeWithCurrentIdentity(); + yield browseridManager.whenReadyToAuthenticate.promise; + do_check_true(!!browseridManager._token); + do_check_true(browseridManager.hasValidToken()); + do_check_eq(browseridManager.account, identityConfig.fxaccount.user.email); + } + ); + ++add_task(function test_initialializeWithNoKeys() { ++ _("Verify start after initializeWithCurrentIdentity without kA, kB or keyFetchToken"); ++ let identityConfig = makeIdentityConfig(); ++ delete identityConfig.fxaccount.user.kA; ++ delete identityConfig.fxaccount.user.kB; ++ // there's no keyFetchToken by default, so the initialize should fail. ++ configureFxAccountIdentity(browseridManager, identityConfig); ++ ++ yield browseridManager.initializeWithCurrentIdentity(); ++ yield browseridManager.whenReadyToAuthenticate.promise; ++ do_check_eq(Status.login, LOGIN_SUCCEEDED, "login succeeded even without keys"); ++ do_check_false(browseridManager._canFetchKeys(), "_canFetchKeys reflects lack of keys"); ++}); + + add_test(function test_getResourceAuthenticator() { + _("BrowserIDManager supplies a Resource Authenticator callback which returns a Hawk header."); ++ configureFxAccountIdentity(browseridManager); + let authenticator = browseridManager.getResourceAuthenticator(); + do_check_true(!!authenticator); + let req = {uri: CommonUtils.makeURI( + "https://example.net/somewhere/over/the/rainbow"), + method: 'GET'}; + let output = authenticator(req, 'GET'); + do_check_true('headers' in output); + do_check_true('authorization' in output.headers); +@@ -235,16 +249,17 @@ add_test(function test_RESTResourceAuthe + (getTimestampDelta(authHeader, now) - 12 * HOUR_MS) < 2 * MINUTE_MS); + + run_next_test(); + }); + + add_task(function test_ensureLoggedIn() { + configureFxAccountIdentity(browseridManager); + yield browseridManager.initializeWithCurrentIdentity(); ++ yield browseridManager.whenReadyToAuthenticate.promise; + Assert.equal(Status.login, LOGIN_SUCCEEDED, "original initialize worked"); + yield browseridManager.ensureLoggedIn(); + Assert.equal(Status.login, LOGIN_SUCCEEDED, "original ensureLoggedIn worked"); + Assert.ok(browseridManager._shouldHaveSyncKeyBundle, + "_shouldHaveSyncKeyBundle should always be true after ensureLogin completes."); + + // arrange for no logged in user. + let fxa = browseridManager._fxaService diff --git a/data/patches/0005-Bug-1013064-part-5-stop-disabling-the-password-engin.patch b/data/patches/0005-Bug-1013064-part-5-stop-disabling-the-password-engin.patch new file mode 100644 index 0000000..394d27c --- /dev/null +++ b/data/patches/0005-Bug-1013064-part-5-stop-disabling-the-password-engin.patch @@ -0,0 +1,519 @@ +From 07aa9cc1fcd5479976effe29f6adf5ad5ba7a8f8 Mon Sep 17 00:00:00 2001 +From: Mark Hammond +Date: Thu, 12 Jun 2014 18:19:49 +1000 +Subject: Bug 1013064 (part 5) - stop disabling the password engine when MP + enabled. r=ttaubert + +diff --git a/browser/base/content/sync/customize.js b/browser/base/content/sync/customize.js +--- a/browser/base/content/sync/customize.js ++++ b/browser/base/content/sync/customize.js +@@ -1,25 +1,11 @@ + /* 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/. */ + + "use strict"; + +-const {classes: Cc, interfaces: Ci, utils: Cu} = Components; +- +-Cu.import("resource://gre/modules/Services.jsm"); +- +-let service = Cc["@mozilla.org/weave/service;1"] +- .getService(Ci.nsISupports) +- .wrappedJSObject; +- +-if (!service.allowPasswordsEngine) { +- let checkbox = document.getElementById("fxa-pweng-chk"); +- checkbox.checked = false; +- checkbox.disabled = true; +-} +- + addEventListener("dialogaccept", function () { + let pane = document.getElementById("sync-customize-pane"); + pane.writePreferences(true); + window.arguments[0].accepted = true; + }); +diff --git a/browser/base/content/sync/customize.xul b/browser/base/content/sync/customize.xul +--- a/browser/base/content/sync/customize.xul ++++ b/browser/base/content/sync/customize.xul +@@ -40,18 +40,17 @@ + + + + +- + + +diff --git a/browser/base/content/sync/utils.js b/browser/base/content/sync/utils.js +--- a/browser/base/content/sync/utils.js ++++ b/browser/base/content/sync/utils.js +@@ -82,22 +82,16 @@ let gSyncUtils = { + this._openLink(Weave.Svc.Prefs.get(root + "termsURL")); + }, + + openPrivacyPolicy: function () { + let root = this.fxAccountsEnabled ? "fxa." : ""; + this._openLink(Weave.Svc.Prefs.get(root + "privacyURL")); + }, + +- openMPInfoPage: function (event) { +- event.stopPropagation(); +- let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL"); +- this._openLink(baseURL + "sync-master-password"); +- }, +- + openFirstSyncProgressPage: function () { + this._openLink("about:sync-progress"); + }, + + /** + * Prepare an invisible iframe with the passphrase backup document. + * Used by both the print and saving methods. + * +diff --git a/browser/components/preferences/in-content/sync.js b/browser/components/preferences/in-content/sync.js +--- a/browser/components/preferences/in-content/sync.js ++++ b/browser/components/preferences/in-content/sync.js +@@ -149,27 +149,16 @@ let gSyncPane = { + document.getElementById("fxaEmailAddress1").textContent = data.email; + document.getElementById("fxaEmailAddress2").textContent = data.email; + document.getElementById("fxaEmailAddress3").textContent = data.email; + document.getElementById("fxaSyncComputerName").value = Weave.Service.clientsEngine.localName; + let engines = document.getElementById("fxaSyncEngines") + for (let checkbox of engines.querySelectorAll("checkbox")) { + checkbox.disabled = enginesListDisabled; + } +- +- let checkbox = document.getElementById("fxa-pweng-chk"); +- let help = document.getElementById("fxa-pweng-help"); +- let allowPasswordsEngine = service.allowPasswordsEngine; +- +- if (!allowPasswordsEngine) { +- checkbox.checked = false; +- } +- +- checkbox.disabled = !allowPasswordsEngine || enginesListDisabled; +- help.hidden = allowPasswordsEngine || enginesListDisabled; + }); + // If fxAccountEnabled is false and we are in a "not configured" state, + // then fxAccounts is probably fully disabled rather than just unconfigured, + // so handle this case. This block can be removed once we remove support + // for fxAccounts being disabled. + } else if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED || + Weave.Svc.Prefs.get("firstSync", "") == "notReady") { + this.page = PAGE_NO_ACCOUNT; +diff --git a/browser/components/preferences/in-content/sync.xul b/browser/components/preferences/in-content/sync.xul +--- a/browser/components/preferences/in-content/sync.xul ++++ b/browser/components/preferences/in-content/sync.xul +@@ -278,30 +278,19 @@ + + + + +- +- +- +- +- +- +- +- +- +- +- ++ + + + + + + +- +- +- +- +- +- +- +- +- +- +- ++ + + + label:first-child { + margin-bottom: 0.6em; + } + +-#fxa-pweng-help-link > label { +- margin: 0; +-} +- +-#fxa-pweng-help-link > image { +- list-style-image: url("chrome://global/skin/icons/question-16.png"); +-} +- + %endif +diff --git a/browser/themes/osx/preferences/preferences.css b/browser/themes/osx/preferences/preferences.css +--- a/browser/themes/osx/preferences/preferences.css ++++ b/browser/themes/osx/preferences/preferences.css +@@ -228,25 +228,9 @@ html|a.inline-link:-moz-focusring { + margin: 12px 4px; + line-height: 1.2em; + } + + #noFxaAccount > label:first-child { + margin-bottom: 0.6em; + } + +-#fxa-pweng-help-link > label { +- margin: 0; +-} +- +-#fxa-pweng-help-link > image { +- width: 16px; +- height: 16px; +- list-style-image: url("chrome://global/skin/icons/question-16.png"); +-} +- +-@media (min-resolution: 2dppx) { +- #fxa-pweng-help-link > image { +- list-style-image: url("chrome://global/skin/icons/question-32.png"); +- } +-} +- + %endif +diff --git a/browser/themes/windows/preferences/preferences.css b/browser/themes/windows/preferences/preferences.css +--- a/browser/themes/windows/preferences/preferences.css ++++ b/browser/themes/windows/preferences/preferences.css +@@ -156,17 +156,9 @@ label.small { + margin: 6px; + line-height: 1.2em; + } + + #noFxaAccount > label:first-child { + margin-bottom: 0.6em; + } + +-#fxa-pweng-help-link > label { +- margin: 0; +-} +- +-#fxa-pweng-help-link > image { +- list-style-image: url("chrome://global/skin/icons/question-16.png"); +-} +- + %endif +diff --git a/services/sync/Weave.js b/services/sync/Weave.js +--- a/services/sync/Weave.js ++++ b/services/sync/Weave.js +@@ -104,26 +104,16 @@ WeaveService.prototype = { + let username = Services.prefs.getCharPref(SYNC_PREFS_BRANCH + "username"); + return !username || username.contains('@'); + } catch (_) { + return true; // No username == only allow FxA to be configured. + } + }, + + /** +- * Returns whether the password engine is allowed. We explicitly disallow +- * the password engine when a master password is used to ensure those can't +- * be accessed without the master key. +- */ +- get allowPasswordsEngine() { +- // This doesn't apply to old-style sync, it's only an issue for FxA. +- return !this.fxAccountsEnabled || !Utils.mpEnabled(); +- }, +- +- /** + * Whether Sync appears to be enabled. + * + * This returns true if all the Sync preferences for storing account + * and server configuration are populated. + * + * It does *not* perform a robust check to see if the client is working. + * For that, you'll want to check Weave.Status.checkSetup(). + */ +diff --git a/services/sync/modules/engines/passwords.js b/services/sync/modules/engines/passwords.js +--- a/services/sync/modules/engines/passwords.js ++++ b/services/sync/modules/engines/passwords.js +@@ -31,38 +31,16 @@ this.PasswordEngine = function PasswordE + } + PasswordEngine.prototype = { + __proto__: SyncEngine.prototype, + _storeObj: PasswordStore, + _trackerObj: PasswordTracker, + _recordObj: LoginRec, + applyIncomingBatchSize: PASSWORDS_STORE_BATCH_SIZE, + +- get isAllowed() { +- return Cc["@mozilla.org/weave/service;1"] +- .getService(Ci.nsISupports) +- .wrappedJSObject +- .allowPasswordsEngine; +- }, +- +- get enabled() { +- // If we are disabled due to !isAllowed(), we must take care to ensure the +- // engine has actually had the enabled setter called which reflects this state. +- let prefVal = SyncEngine.prototype.__lookupGetter__("enabled").call(this); +- let newVal = this.isAllowed && prefVal; +- if (newVal != prefVal) { +- this.enabled = newVal; +- } +- return newVal; +- }, +- +- set enabled(val) { +- SyncEngine.prototype.__lookupSetter__("enabled").call(this, this.isAllowed && val); +- }, +- + _syncFinish: function _syncFinish() { + SyncEngine.prototype._syncFinish.call(this); + + // Delete the weave credentials from the server once + if (!Svc.Prefs.get("deletePwdFxA", false)) { + try { + let ids = []; + for (let host of Utils.getSyncCredentialsHosts()) { +diff --git a/services/sync/tests/unit/test_password_mpenabled.js b/services/sync/tests/unit/test_password_mpenabled.js +deleted file mode 100644 +--- a/services/sync/tests/unit/test_password_mpenabled.js ++++ /dev/null +@@ -1,137 +0,0 @@ +-/* Any copyright is dedicated to the Public Domain. +- http://creativecommons.org/publicdomain/zero/1.0/ */ +- +-Cu.import("resource://gre/modules/Log.jsm"); +-Cu.import("resource://services-sync/stages/enginesync.js"); +-Cu.import("resource://services-sync/util.js"); +-Cu.import("resource://services-sync/engines/passwords.js"); +-Cu.import("resource://services-sync/service.js"); +-Cu.import("resource://testing-common/services/sync/utils.js"); +- +-function run_test() { +- initTestLogging("Trace"); +- run_next_test(); +-} +- +-add_test(function test_simple() { +- ensureLegacyIdentityManager(); +- // Stub fxAccountsEnabled +- let xpcs = Cc["@mozilla.org/weave/service;1"] +- .getService(Components.interfaces.nsISupports) +- .wrappedJSObject; +- let fxaEnabledGetter = xpcs.__lookupGetter__("fxAccountsEnabled"); +- xpcs.__defineGetter__("fxAccountsEnabled", () => true); +- +- // Stub mpEnabled. +- let mpEnabledF = Utils.mpEnabled; +- let mpEnabled = false; +- Utils.mpEnabled = function() mpEnabled; +- +- let manager = Service.engineManager; +- +- Service.engineManager.register(PasswordEngine); +- let engine = Service.engineManager.get("passwords"); +- let wipeCount = 0; +- let engineWipeServerF = engine.wipeServer; +- engine.wipeServer = function() { +- ++wipeCount; +- } +- +- // A server for the metadata. +- let server = new SyncServer(); +- let johndoe = server.registerUser("johndoe", "password"); +- johndoe.createContents({ +- meta: {global: {engines: {passwords: {version: engine.version, +- syncID: engine.syncID}}}}, +- crypto: {}, +- clients: {} +- }); +- server.start(); +- setBasicCredentials("johndoe", "password", "abcdeabcdeabcdeabcdeabcdea"); +- Service.serverURL = server.baseURI; +- Service.clusterURL = server.baseURI; +- +- let engineSync = new EngineSynchronizer(Service); +- engineSync._log.level = Log.Level.Trace; +- +- function assertEnabled(expected, message) { +- Assert.strictEqual(engine.enabled, expected, message); +- // The preference *must* reflect the actual state. +- Assert.strictEqual(Svc.Prefs.get("engine." + engine.prefName), expected, +- message + " (pref should match enabled state)"); +- } +- +- try { +- assertEnabled(true, "password engine should be enabled by default") +- let engineMeta = Service.recordManager.get(engine.metaURL); +- // This engine should be in the meta/global +- Assert.notStrictEqual(engineMeta.payload.engines[engine.name], undefined, +- "The engine should appear in the metadata"); +- Assert.ok(!engineMeta.changed, "the metadata for the password engine hasn't changed"); +- +- // (pretend to) enable a master-password +- mpEnabled = true; +- // The password engine should be locally disabled... +- assertEnabled(false, "if mp is locked the engine should be disabled"); +- // ...but not declined. +- Assert.ok(!manager.isDeclined("passwords"), "password engine is not declined"); +- // Next time a sync would happen, we call _updateEnabledEngines(), which +- // would remove the engine from the metadata - call that now. +- engineSync._updateEnabledEngines(); +- // The global meta should no longer list the engine. +- engineMeta = Service.recordManager.get(engine.metaURL); +- Assert.strictEqual(engineMeta.payload.engines[engine.name], undefined, +- "The engine should have vanished"); +- // And we should have wiped the server data. +- Assert.strictEqual(wipeCount, 1, "wipeServer should have been called"); +- +- // Now simulate an incoming meta/global indicating the engine should be +- // enabled. We should fail to actually enable it - the pref should remain +- // false and we wipe the server for anything another device might have +- // stored. +- let meta = { +- payload: { +- engines: { +- "passwords": {"version":1,"syncID":"yfBi2v7PpFO2"}, +- }, +- }, +- }; +- engineSync._updateEnabledFromMeta(meta, 3, manager); +- Assert.strictEqual(wipeCount, 2, "wipeServer should have been called"); +- Assert.ok(!manager.isDeclined("passwords"), "password engine is not declined"); +- assertEnabled(false, "engine still not enabled locally"); +- +- // Let's turn the MP off - but *not* re-enable it locally. +- mpEnabled = false; +- // Just disabling the MP isn't enough to force it back to enabled. +- assertEnabled(false, "engine still not enabled locally"); +- // Another incoming metadata record with the engine enabled should cause +- // it to be enabled locally. +- meta = { +- payload: { +- engines: { +- "passwords": 1, +- }, +- }, +- }; +- engineSync._updateEnabledFromMeta(meta, 3, manager); +- Assert.strictEqual(wipeCount, 2, "wipeServer should *not* have been called again"); +- Assert.ok(!manager.isDeclined("passwords"), "password engine is not declined"); +- // It should be enabled locally. +- assertEnabled(true, "engine now enabled locally"); +- // Next time a sync starts it should magically re-appear in our meta/global +- engine._syncStartup(); +- //engineSync._updateEnabledEngines(); +- engineMeta = Service.recordManager.get(engine.metaURL); +- Assert.equal(engineMeta.payload.engines[engine.name].version, engine.version, +- "The engine should re-appear in the metadata"); +- } finally { +- // restore the damage we did above... +- engine.wipeServer = engineWipeServerF; +- engine._store.wipe(); +- // Un-stub mpEnabled and fxAccountsEnabled +- Utils.mpEnabled = mpEnabledF; +- xpcs.__defineGetter__("fxAccountsEnabled", fxaEnabledGetter); +- server.stop(run_next_test); +- } +-}); +diff --git a/services/sync/tests/unit/xpcshell.ini b/services/sync/tests/unit/xpcshell.ini +--- a/services/sync/tests/unit/xpcshell.ini ++++ b/services/sync/tests/unit/xpcshell.ini +@@ -164,10 +164,8 @@ skip-if = debug + [test_prefs_store.js] + [test_prefs_tracker.js] + [test_tab_engine.js] + [test_tab_store.js] + [test_tab_tracker.js] + + [test_healthreport.js] + skip-if = ! healthreport +- +-[test_password_mpenabled.js] diff --git a/data/patches/disable/0001-upgrade-mobile-fxa-to-v32.patch b/data/patches/disable/0001-upgrade-mobile-fxa-to-v32.patch new file mode 100644 index 0000000..e420727 --- /dev/null +++ b/data/patches/disable/0001-upgrade-mobile-fxa-to-v32.patch @@ -0,0 +1,992 @@ +diff -ruN mozilla-esr31/mobile/android/base/fxa/FirefoxAccounts.java mozilla-release/mobile/android/base/fxa/FirefoxAccounts.java +--- mozilla-esr31/mobile/android/base/fxa/FirefoxAccounts.java 2015-02-25 19:21:06.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/FirefoxAccounts.java 2014-09-24 03:05:32.000000000 +0200 +@@ -6,13 +6,18 @@ + + import java.io.File; + import java.util.EnumSet; ++import java.util.Locale; + import java.util.concurrent.CountDownLatch; + ++import org.mozilla.gecko.AppConstants; ++import org.mozilla.gecko.R; + import org.mozilla.gecko.background.common.log.Logger; + import org.mozilla.gecko.fxa.authenticator.AccountPickler; + import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; ++import org.mozilla.gecko.fxa.login.State; + import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter; + import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper; ++import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender; + import org.mozilla.gecko.sync.ThreadPool; + import org.mozilla.gecko.sync.Utils; + +@@ -20,6 +25,7 @@ + import android.accounts.AccountManager; + import android.content.ContentResolver; + import android.content.Context; ++import android.content.res.Resources; + import android.os.Bundle; + + /** +@@ -152,6 +158,38 @@ + return null; + } + ++ /** ++ * @return ++ * the {@link State} instance associated with the current account, or null if ++ * no accounts exist. ++ */ ++ public static State getFirefoxAccountState(final Context context) { ++ final Account account = getFirefoxAccount(context); ++ if (account == null) { ++ return null; ++ } ++ ++ final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account); ++ try { ++ return fxAccount.getState(); ++ } catch (final Exception ex) { ++ Logger.warn(LOG_TAG, "Could not get FX account state.", ex); ++ return null; ++ } ++ } ++ ++ /* ++ * @param context Android context ++ * @return the email address associated with the configured Firefox account if one exists; null otherwise. ++ */ ++ public static String getFirefoxAccountEmail(final Context context) { ++ final Account account = getFirefoxAccount(context); ++ if (account == null) { ++ return null; ++ } ++ return account.name; ++ } ++ + protected static void putHintsToSync(final Bundle extras, EnumSet syncHints) { + // stagesToSync and stagesToSkip are allowed to be null. + if (syncHints == null) { +@@ -264,4 +302,33 @@ + // stopObserving null-checks its argument. + FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusListener); + } ++ ++ public static String getOldSyncUpgradeURL(final Resources res, final Locale locale) { ++ final String VERSION = AppConstants.MOZ_APP_VERSION; ++ final String OS = AppConstants.OS_TARGET; ++ final String LOCALE = Utils.getLanguageTag(locale); ++ return res.getString(R.string.fxaccount_link_old_firefox, VERSION, OS, LOCALE); ++ } ++ ++ /** ++ * Resends the account verification email, and displays an appropriate ++ * toast on both send success and failure. Note that because the underlying implementation ++ * uses {@link AsyncTask}, the provided context must be UI-capable, and this ++ * method called from the UI thread (see ++ * {@link org.mozilla.gecko.fxa.tasks.FxAccountCodeResender#resendCode(Context, AndroidFxAccount)} ++ * for more). ++ * ++ * @param context a UI-capable Android context. ++ * @return true if an account exists, false otherwise. ++ */ ++ public static boolean resendVerificationEmail(final Context context) { ++ final Account account = getFirefoxAccount(context); ++ if (account == null) { ++ return false; ++ } ++ ++ final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account); ++ FxAccountCodeResender.resendCode(context, fxAccount); ++ return true; ++ } + } +diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java mozilla-release/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java +--- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java 2015-02-25 19:21:06.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java 2014-09-24 03:05:32.000000000 +0200 +@@ -19,10 +19,10 @@ + import org.mozilla.gecko.background.fxa.PasswordStretcher; + import org.mozilla.gecko.background.fxa.QuickPasswordStretcher; + import org.mozilla.gecko.fxa.FxAccountConstants; +-import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.ProgressDisplay; + import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; + import org.mozilla.gecko.fxa.login.Engaged; + import org.mozilla.gecko.fxa.login.State; ++import org.mozilla.gecko.fxa.tasks.FxAccountSetupTask.ProgressDisplay; + import org.mozilla.gecko.sync.SyncConfiguration; + import org.mozilla.gecko.sync.setup.Constants; + import org.mozilla.gecko.sync.setup.activities.ActivityUtils; +diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java mozilla-release/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java +--- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java 2015-02-25 19:21:06.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java 2014-09-24 03:05:32.000000000 +0200 +@@ -4,21 +4,15 @@ + + package org.mozilla.gecko.fxa.activities; + +-import java.util.concurrent.Executor; +-import java.util.concurrent.Executors; +- + import org.mozilla.gecko.R; + import org.mozilla.gecko.background.common.log.Logger; +-import org.mozilla.gecko.background.fxa.FxAccountClient; +-import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; +-import org.mozilla.gecko.background.fxa.FxAccountClient20; +-import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; + import org.mozilla.gecko.fxa.FirefoxAccounts; + import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; + import org.mozilla.gecko.fxa.login.Engaged; + import org.mozilla.gecko.fxa.login.State; + import org.mozilla.gecko.fxa.login.State.Action; + import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper; ++import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender; + import org.mozilla.gecko.sync.setup.activities.ActivityUtils; + + import android.accounts.Account; +@@ -28,7 +22,6 @@ + import android.view.View; + import android.view.View.OnClickListener; + import android.widget.TextView; +-import android.widget.Toast; + + /** + * Activity which displays account created successfully screen to the user, and +@@ -164,76 +157,8 @@ + resendLink.setClickable(resendLinkShouldBeEnabled); + } + +- public static class FxAccountResendCodeTask extends FxAccountSetupTask { +- protected static final String LOG_TAG = FxAccountResendCodeTask.class.getSimpleName(); +- +- protected final byte[] sessionToken; +- +- public FxAccountResendCodeTask(Context context, byte[] sessionToken, FxAccountClient client, RequestDelegate delegate) { +- super(context, null, client, delegate); +- this.sessionToken = sessionToken; +- } +- +- @Override +- protected InnerRequestDelegate doInBackground(Void... arg0) { +- try { +- client.resendCode(sessionToken, innerDelegate); +- latch.await(); +- return innerDelegate; +- } catch (Exception e) { +- Logger.error(LOG_TAG, "Got exception signing in.", e); +- delegate.handleError(e); +- } +- return null; +- } +- } +- +- protected static class ResendCodeDelegate implements RequestDelegate { +- public final Context context; +- +- public ResendCodeDelegate(Context context) { +- this.context = context; +- } +- +- @Override +- public void handleError(Exception e) { +- Logger.warn(LOG_TAG, "Got exception requesting fresh confirmation link; ignoring.", e); +- Toast.makeText(context, R.string.fxaccount_confirm_account_verification_link_not_sent, Toast.LENGTH_LONG).show(); +- } +- +- @Override +- public void handleFailure(FxAccountClientRemoteException e) { +- handleError(e); +- } +- +- @Override +- public void handleSuccess(Void result) { +- Toast.makeText(context, R.string.fxaccount_confirm_account_verification_link_sent, Toast.LENGTH_SHORT).show(); +- } +- } +- +- public static void resendCode(Context context, AndroidFxAccount fxAccount) { +- RequestDelegate delegate = new ResendCodeDelegate(context); +- +- byte[] sessionToken; +- try { +- sessionToken = ((Engaged) fxAccount.getState()).getSessionToken(); +- } catch (Exception e) { +- delegate.handleError(e); +- return; +- } +- if (sessionToken == null) { +- delegate.handleError(new IllegalStateException("sessionToken should not be null")); +- return; +- } +- +- Executor executor = Executors.newSingleThreadExecutor(); +- FxAccountClient client = new FxAccountClient20(fxAccount.getAccountServerURI(), executor); +- new FxAccountResendCodeTask(context, sessionToken, client, delegate).execute(); +- } +- + @Override + public void onClick(View v) { +- resendCode(this, fxAccount); ++ FxAccountCodeResender.resendCode(this, fxAccount); + } + } +diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java mozilla-release/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java +--- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java 2015-02-25 19:21:06.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java 2014-09-24 03:05:32.000000000 +0200 +@@ -21,7 +21,7 @@ + import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; + import org.mozilla.gecko.background.fxa.PasswordStretcher; + import org.mozilla.gecko.fxa.FxAccountConstants; +-import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountCreateAccountTask; ++import org.mozilla.gecko.fxa.tasks.FxAccountCreateAccountTask; + + import android.app.AlertDialog; + import android.app.Dialog; +diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java mozilla-release/mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java +--- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java 2015-02-25 19:21:06.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java 2014-09-24 03:05:32.000000000 +0200 +@@ -6,13 +6,11 @@ + + import java.util.Locale; + +-import org.mozilla.gecko.AppConstants; + import org.mozilla.gecko.R; + import org.mozilla.gecko.background.common.log.Logger; + import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper; + import org.mozilla.gecko.fxa.FirefoxAccounts; + import org.mozilla.gecko.fxa.FxAccountConstants; +-import org.mozilla.gecko.sync.Utils; + import org.mozilla.gecko.sync.setup.activities.ActivityUtils; + import org.mozilla.gecko.sync.setup.activities.LocaleAware; + +@@ -109,11 +107,7 @@ + protected void linkifyOldFirefoxLink() { + TextView oldFirefox = (TextView) findViewById(R.id.old_firefox); + String text = getResources().getString(R.string.fxaccount_getting_started_old_firefox); +- String VERSION = AppConstants.MOZ_APP_VERSION; +- String OS = AppConstants.OS_TARGET; +- +- String LOCALE = Utils.getLanguageTag(Locale.getDefault()); +- String url = getResources().getString(R.string.fxaccount_link_old_firefox, VERSION, OS, LOCALE); ++ final String url = FirefoxAccounts.getOldSyncUpgradeURL(getResources(), Locale.getDefault()); + FxAccountConstants.pii(LOG_TAG, "Old Firefox url is: " + url); // Don't want to leak locale in particular. + ActivityUtils.linkTextView(oldFirefox, text, url); + } +diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountSetupTask.java mozilla-release/mobile/android/base/fxa/activities/FxAccountSetupTask.java +--- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountSetupTask.java 2015-02-25 19:21:06.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/activities/FxAccountSetupTask.java 1970-01-01 01:00:00.000000000 +0100 +@@ -1,172 +0,0 @@ +-/* 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.fxa.activities; +- +-import java.io.UnsupportedEncodingException; +-import java.util.concurrent.CountDownLatch; +- +-import org.mozilla.gecko.background.common.log.Logger; +-import org.mozilla.gecko.background.fxa.FxAccountClient; +-import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; +-import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse; +-import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; +-import org.mozilla.gecko.background.fxa.PasswordStretcher; +-import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.InnerRequestDelegate; +- +-import android.content.Context; +-import android.os.AsyncTask; +- +-/** +- * An AsyncTask wrapper around signing up for, and signing in to, a +- * Firefox Account. +- *

+- * It's strange to add explicit blocking to callback-threading code, but we do +- * it here to take advantage of Android's built in support for background work. +- * We really want to avoid making a threading mistake that brings down the whole +- * process. +- */ +-abstract class FxAccountSetupTask extends AsyncTask> { +- private static final String LOG_TAG = FxAccountSetupTask.class.getSimpleName(); +- +- public interface ProgressDisplay { +- public void showProgress(); +- public void dismissProgress(); +- } +- +- protected final Context context; +- protected final FxAccountClient client; +- protected final ProgressDisplay progressDisplay; +- +- // Initialized lazily. +- protected byte[] quickStretchedPW; +- +- // AsyncTask's are one-time-use, so final members are fine. +- protected final CountDownLatch latch = new CountDownLatch(1); +- protected final InnerRequestDelegate innerDelegate = new InnerRequestDelegate(latch); +- +- protected final RequestDelegate delegate; +- +- public FxAccountSetupTask(Context context, ProgressDisplay progressDisplay, FxAccountClient client, RequestDelegate delegate) { +- this.context = context; +- this.client = client; +- this.delegate = delegate; +- this.progressDisplay = progressDisplay; +- } +- +- @Override +- protected void onPreExecute() { +- if (progressDisplay != null) { +- progressDisplay.showProgress(); +- } +- } +- +- @Override +- protected void onPostExecute(InnerRequestDelegate result) { +- if (progressDisplay != null) { +- progressDisplay.dismissProgress(); +- } +- +- // We are on the UI thread, and need to invoke these callbacks here to allow UI updating. +- if (innerDelegate.failure != null) { +- delegate.handleFailure(innerDelegate.failure); +- } else if (innerDelegate.exception != null) { +- delegate.handleError(innerDelegate.exception); +- } else { +- delegate.handleSuccess(result.response); +- } +- } +- +- @Override +- protected void onCancelled(InnerRequestDelegate result) { +- if (progressDisplay != null) { +- progressDisplay.dismissProgress(); +- } +- delegate.handleError(new IllegalStateException("Task was cancelled.")); +- } +- +- protected static class InnerRequestDelegate implements RequestDelegate { +- protected final CountDownLatch latch; +- public T response = null; +- public Exception exception = null; +- public FxAccountClientRemoteException failure = null; +- +- protected InnerRequestDelegate(CountDownLatch latch) { +- this.latch = latch; +- } +- +- @Override +- public void handleError(Exception e) { +- Logger.error(LOG_TAG, "Got exception."); +- this.exception = e; +- latch.countDown(); +- } +- +- @Override +- public void handleFailure(FxAccountClientRemoteException e) { +- Logger.warn(LOG_TAG, "Got failure."); +- this.failure = e; +- latch.countDown(); +- } +- +- @Override +- public void handleSuccess(T result) { +- Logger.info(LOG_TAG, "Got success."); +- this.response = result; +- latch.countDown(); +- } +- } +- +- public static class FxAccountCreateAccountTask extends FxAccountSetupTask { +- private static final String LOG_TAG = FxAccountCreateAccountTask.class.getSimpleName(); +- +- protected final byte[] emailUTF8; +- protected final PasswordStretcher passwordStretcher; +- +- public FxAccountCreateAccountTask(Context context, ProgressDisplay progressDisplay, String email, PasswordStretcher passwordStretcher, FxAccountClient client, RequestDelegate delegate) throws UnsupportedEncodingException { +- super(context, progressDisplay, client, delegate); +- this.emailUTF8 = email.getBytes("UTF-8"); +- this.passwordStretcher = passwordStretcher; +- } +- +- @Override +- protected InnerRequestDelegate doInBackground(Void... arg0) { +- try { +- client.createAccountAndGetKeys(emailUTF8, passwordStretcher, innerDelegate); +- latch.await(); +- return innerDelegate; +- } catch (Exception e) { +- Logger.error(LOG_TAG, "Got exception logging in.", e); +- delegate.handleError(e); +- } +- return null; +- } +- } +- +- public static class FxAccountSignInTask extends FxAccountSetupTask { +- protected static final String LOG_TAG = FxAccountSignInTask.class.getSimpleName(); +- +- protected final byte[] emailUTF8; +- protected final PasswordStretcher passwordStretcher; +- +- public FxAccountSignInTask(Context context, ProgressDisplay progressDisplay, String email, PasswordStretcher passwordStretcher, FxAccountClient client, RequestDelegate delegate) throws UnsupportedEncodingException { +- super(context, progressDisplay, client, delegate); +- this.emailUTF8 = email.getBytes("UTF-8"); +- this.passwordStretcher = passwordStretcher; +- } +- +- @Override +- protected InnerRequestDelegate doInBackground(Void... arg0) { +- try { +- client.loginAndGetKeys(emailUTF8, passwordStretcher, innerDelegate); +- latch.await(); +- return innerDelegate; +- } catch (Exception e) { +- Logger.error(LOG_TAG, "Got exception signing in.", e); +- delegate.handleError(e); +- } +- return null; +- } +- } +-} +diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountSignInActivity.java mozilla-release/mobile/android/base/fxa/activities/FxAccountSignInActivity.java +--- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountSignInActivity.java 2015-02-25 19:21:06.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/activities/FxAccountSignInActivity.java 2014-09-24 03:05:32.000000000 +0200 +@@ -16,7 +16,7 @@ + import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; + import org.mozilla.gecko.background.fxa.PasswordStretcher; + import org.mozilla.gecko.fxa.FxAccountConstants; +-import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountSignInTask; ++import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask; + import org.mozilla.gecko.sync.setup.activities.ActivityUtils; + + import android.content.Intent; +diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountStatusFragment.java mozilla-release/mobile/android/base/fxa/activities/FxAccountStatusFragment.java +--- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountStatusFragment.java 2015-02-25 19:21:06.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/activities/FxAccountStatusFragment.java 2014-09-24 03:05:32.000000000 +0200 +@@ -17,6 +17,8 @@ + import org.mozilla.gecko.fxa.login.Married; + import org.mozilla.gecko.fxa.login.State; + import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper; ++import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender; ++import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate; + import org.mozilla.gecko.sync.SyncConfiguration; + + import android.accounts.Account; +@@ -27,10 +29,13 @@ + import android.os.Bundle; + import android.os.Handler; + import android.preference.CheckBoxPreference; ++import android.preference.EditTextPreference; + import android.preference.Preference; ++import android.preference.Preference.OnPreferenceChangeListener; + import android.preference.Preference.OnPreferenceClickListener; + import android.preference.PreferenceCategory; + import android.preference.PreferenceScreen; ++import android.text.TextUtils; + + /** + * A fragment that displays the status of an AndroidFxAccount. +@@ -38,7 +43,9 @@ + * The owning activity is responsible for providing an AndroidFxAccount at + * appropriate times. + */ +-public class FxAccountStatusFragment extends PreferenceFragment implements OnPreferenceClickListener { ++public class FxAccountStatusFragment ++ extends PreferenceFragment ++ implements OnPreferenceClickListener, OnPreferenceChangeListener { + private static final String LOG_TAG = FxAccountStatusFragment.class.getSimpleName(); + + // When a checkbox is toggled, wait 5 seconds (for other checkbox actions) +@@ -64,7 +71,12 @@ + protected CheckBoxPreference tabsPreference; + protected CheckBoxPreference passwordsPreference; + ++ protected EditTextPreference deviceNamePreference; ++ + protected volatile AndroidFxAccount fxAccount; ++ // The contract is: when fxAccount is non-null, then clientsDataDelegate is ++ // non-null. If violated then an IllegalStateException is thrown. ++ protected volatile SharedPreferencesClientsDataDelegate clientsDataDelegate; + + // Used to post delayed sync requests. + protected Handler handler; +@@ -87,6 +99,10 @@ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); ++ addPreferences(); ++ } ++ ++ protected void addPreferences() { + addPreferencesFromResource(R.xml.fxaccount_status_prefscreen); + + emailPreference = ensureFindPreference("email"); +@@ -118,6 +134,9 @@ + historyPreference.setOnPreferenceClickListener(this); + tabsPreference.setOnPreferenceClickListener(this); + passwordsPreference.setOnPreferenceClickListener(this); ++ ++ deviceNamePreference = (EditTextPreference) ensureFindPreference("device_name"); ++ deviceNamePreference.setOnPreferenceChangeListener(this); + } + + /** +@@ -142,7 +161,7 @@ + } + + if (preference == needsVerificationPreference) { +- FxAccountConfirmAccountActivity.resendCode(getActivity().getApplicationContext(), fxAccount); ++ FxAccountCodeResender.resendCode(getActivity().getApplicationContext(), fxAccount); + + Intent intent = new Intent(getActivity(), FxAccountConfirmAccountActivity.class); + // Per http://stackoverflow.com/a/8992365, this triggers a known bug with +@@ -176,6 +195,8 @@ + historyPreference.setEnabled(enabled); + tabsPreference.setEnabled(enabled); + passwordsPreference.setEnabled(enabled); ++ // Since we can't sync, we can't update our remote client record. ++ deviceNamePreference.setEnabled(enabled); + } + + /** +@@ -293,6 +314,14 @@ + throw new IllegalArgumentException("fxAccount must not be null"); + } + this.fxAccount = fxAccount; ++ try { ++ this.clientsDataDelegate = new SharedPreferencesClientsDataDelegate(fxAccount.getSyncPrefs()); ++ } catch (Exception e) { ++ Logger.error(LOG_TAG, "Got exception fetching Sync prefs associated to Firefox Account; aborting.", e); ++ // Something is terribly wrong; best to get a stack trace rather than ++ // continue with a null clients delegate. ++ throw new IllegalStateException(e); ++ } + + handler = new Handler(); // Attached to current (assumed to be UI) thread. + +@@ -318,6 +347,17 @@ + FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusDelegate); + } + ++ protected void hardRefresh() { ++ // This is the only way to guarantee that the EditText dialogs created by ++ // EditTextPreferences are re-created. This works around the issue described ++ // at http://androiddev.orkitra.com/?p=112079. ++ final PreferenceScreen statusScreen = (PreferenceScreen) ensureFindPreference("status_screen"); ++ statusScreen.removeAll(); ++ addPreferences(); ++ ++ refresh(); ++ } ++ + protected void refresh() { + // refresh is called from our onResume, which can happen before the owning + // Activity tells us about an account (via our public +@@ -371,6 +411,10 @@ + // No matter our state, we should update the checkboxes. + updateSelectedEngines(); + } ++ ++ final String clientName = clientsDataDelegate.getClientName(); ++ deviceNamePreference.setSummary(clientName); ++ deviceNamePreference.setText(clientName); + } + + /** +@@ -570,4 +614,22 @@ + button.setOnPreferenceClickListener(listener); + } + } ++ ++ @Override ++ public boolean onPreferenceChange(Preference preference, Object newValue) { ++ if (preference == deviceNamePreference) { ++ String newClientName = (String) newValue; ++ if (TextUtils.isEmpty(newClientName)) { ++ newClientName = clientsDataDelegate.getDefaultClientName(); ++ } ++ final long now = System.currentTimeMillis(); ++ clientsDataDelegate.setClientName(newClientName, now); ++ requestDelayedSync(); // Try to update our remote client record. ++ hardRefresh(); // Updates the value displayed to the user, among other things. ++ return true; ++ } ++ ++ // For everything else, accept the change. ++ return true; ++ } + } +diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java mozilla-release/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java +--- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java 2015-02-25 19:21:06.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java 2014-09-24 03:05:32.000000000 +0200 +@@ -18,11 +18,11 @@ + import org.mozilla.gecko.background.fxa.PasswordStretcher; + import org.mozilla.gecko.fxa.FirefoxAccounts; + import org.mozilla.gecko.fxa.FxAccountConstants; +-import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountSignInTask; + import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; + import org.mozilla.gecko.fxa.login.Engaged; + import org.mozilla.gecko.fxa.login.State; + import org.mozilla.gecko.fxa.login.State.StateLabel; ++import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask; + import org.mozilla.gecko.sync.setup.activities.ActivityUtils; + + import android.os.Bundle; +diff -ruN mozilla-esr31/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java mozilla-release/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java +--- mozilla-esr31/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java 2015-02-25 19:21:06.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java 2014-09-24 03:05:33.000000000 +0200 +@@ -357,7 +357,11 @@ + + FxAccountGlobalSession globalSession = null; + try { +- ClientsDataDelegate clientsDataDelegate = new SharedPreferencesClientsDataDelegate(sharedPrefs); ++ final ClientsDataDelegate clientsDataDelegate = new SharedPreferencesClientsDataDelegate(sharedPrefs); ++ if (FxAccountConstants.LOG_PERSONAL_INFORMATION) { ++ FxAccountConstants.pii(LOG_TAG, "Client device name is: '" + clientsDataDelegate.getClientName() + "'."); ++ FxAccountConstants.pii(LOG_TAG, "Client device data last modified: " + clientsDataDelegate.getLastModifiedTimestamp()); ++ } + + // We compute skew over time using SkewHandler. This yields an unchanging + // skew adjustment that the HawkAuthHeaderProvider uses to adjust its +diff -ruN mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountCodeResender.java mozilla-release/mobile/android/base/fxa/tasks/FxAccountCodeResender.java +--- mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountCodeResender.java 1970-01-01 01:00:00.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/tasks/FxAccountCodeResender.java 2014-09-24 03:05:33.000000000 +0200 +@@ -0,0 +1,108 @@ ++/* 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.fxa.tasks; ++ ++import java.util.concurrent.Executor; ++import java.util.concurrent.Executors; ++ ++import org.mozilla.gecko.R; ++import org.mozilla.gecko.background.common.log.Logger; ++import org.mozilla.gecko.background.fxa.FxAccountClient; ++import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; ++import org.mozilla.gecko.background.fxa.FxAccountClient20; ++import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; ++import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; ++import org.mozilla.gecko.fxa.login.Engaged; ++ ++import android.content.Context; ++import android.widget.Toast; ++ ++/** ++ * A helper class that provides a simple interface for requesting ++ * a Firefox Account verification email to be resent. ++ */ ++public class FxAccountCodeResender { ++ private static final String LOG_TAG = FxAccountCodeResender.class.getSimpleName(); ++ ++ private static class FxAccountResendCodeTask extends FxAccountSetupTask { ++ protected static final String LOG_TAG = FxAccountResendCodeTask.class.getSimpleName(); ++ ++ protected final byte[] sessionToken; ++ ++ public FxAccountResendCodeTask(Context context, byte[] sessionToken, FxAccountClient client, RequestDelegate delegate) { ++ super(context, null, client, delegate); ++ this.sessionToken = sessionToken; ++ } ++ ++ @Override ++ protected InnerRequestDelegate doInBackground(Void... arg0) { ++ try { ++ client.resendCode(sessionToken, innerDelegate); ++ latch.await(); ++ return innerDelegate; ++ } catch (Exception e) { ++ Logger.error(LOG_TAG, "Got exception signing in.", e); ++ delegate.handleError(e); ++ } ++ return null; ++ } ++ } ++ ++ private static class ResendCodeDelegate implements RequestDelegate { ++ public final Context context; ++ ++ public ResendCodeDelegate(Context context) { ++ this.context = context; ++ } ++ ++ @Override ++ public void handleError(Exception e) { ++ Logger.warn(LOG_TAG, "Got exception requesting fresh confirmation link; ignoring.", e); ++ Toast.makeText(context, R.string.fxaccount_confirm_account_verification_link_not_sent, Toast.LENGTH_LONG).show(); ++ } ++ ++ @Override ++ public void handleFailure(FxAccountClientRemoteException e) { ++ handleError(e); ++ } ++ ++ @Override ++ public void handleSuccess(Void result) { ++ Toast.makeText(context, R.string.fxaccount_confirm_account_verification_link_sent, Toast.LENGTH_SHORT).show(); ++ } ++ } ++ ++ /** ++ * Resends the account verification email, and displays an appropriate ++ * toast on both send success and failure. Note that because the underlying implementation ++ * uses {@link AsyncTask}, the provided context must be UI-capable and ++ * this method called from the UI thread. ++ * ++ * Note that it may actually be possible to run this (and the {@link AsyncTask}) method ++ * from a background thread - but this hasn't been tested. ++ * ++ * @param context A UI-capable Android context. ++ * @param fxAccount The Firefox Account to resend the code to. ++ */ ++ public static void resendCode(Context context, AndroidFxAccount fxAccount) { ++ RequestDelegate delegate = new ResendCodeDelegate(context); ++ ++ byte[] sessionToken; ++ try { ++ sessionToken = ((Engaged) fxAccount.getState()).getSessionToken(); ++ } catch (Exception e) { ++ delegate.handleError(e); ++ return; ++ } ++ if (sessionToken == null) { ++ delegate.handleError(new IllegalStateException("sessionToken should not be null")); ++ return; ++ } ++ ++ Executor executor = Executors.newSingleThreadExecutor(); ++ FxAccountClient client = new FxAccountClient20(fxAccount.getAccountServerURI(), executor); ++ new FxAccountResendCodeTask(context, sessionToken, client, delegate).execute(); ++ } ++} +diff -ruN mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountCreateAccountTask.java mozilla-release/mobile/android/base/fxa/tasks/FxAccountCreateAccountTask.java +--- mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountCreateAccountTask.java 1970-01-01 01:00:00.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/tasks/FxAccountCreateAccountTask.java 2014-09-24 03:05:33.000000000 +0200 +@@ -0,0 +1,41 @@ ++/* 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.fxa.tasks; ++ ++import java.io.UnsupportedEncodingException; ++ ++import org.mozilla.gecko.background.common.log.Logger; ++import org.mozilla.gecko.background.fxa.FxAccountClient; ++import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; ++import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse; ++import org.mozilla.gecko.background.fxa.PasswordStretcher; ++ ++import android.content.Context; ++ ++public class FxAccountCreateAccountTask extends FxAccountSetupTask { ++ private static final String LOG_TAG = FxAccountCreateAccountTask.class.getSimpleName(); ++ ++ protected final byte[] emailUTF8; ++ protected final PasswordStretcher passwordStretcher; ++ ++ public FxAccountCreateAccountTask(Context context, ProgressDisplay progressDisplay, String email, PasswordStretcher passwordStretcher, FxAccountClient client, RequestDelegate delegate) throws UnsupportedEncodingException { ++ super(context, progressDisplay, client, delegate); ++ this.emailUTF8 = email.getBytes("UTF-8"); ++ this.passwordStretcher = passwordStretcher; ++ } ++ ++ @Override ++ protected InnerRequestDelegate doInBackground(Void... arg0) { ++ try { ++ client.createAccountAndGetKeys(emailUTF8, passwordStretcher, innerDelegate); ++ latch.await(); ++ return innerDelegate; ++ } catch (Exception e) { ++ Logger.error(LOG_TAG, "Got exception logging in.", e); ++ delegate.handleError(e); ++ } ++ return null; ++ } ++} +diff -ruN mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountSetupTask.java mozilla-release/mobile/android/base/fxa/tasks/FxAccountSetupTask.java +--- mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountSetupTask.java 1970-01-01 01:00:00.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/tasks/FxAccountSetupTask.java 2014-09-24 03:05:33.000000000 +0200 +@@ -0,0 +1,117 @@ ++/* 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.fxa.tasks; ++ ++import java.util.concurrent.CountDownLatch; ++ ++import org.mozilla.gecko.background.common.log.Logger; ++import org.mozilla.gecko.background.fxa.FxAccountClient; ++import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; ++import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; ++import org.mozilla.gecko.fxa.tasks.FxAccountSetupTask.InnerRequestDelegate; ++ ++import android.content.Context; ++import android.os.AsyncTask; ++ ++/** ++ * An AsyncTask wrapper around signing up for, and signing in to, a ++ * Firefox Account. ++ *

++ * It's strange to add explicit blocking to callback-threading code, but we do ++ * it here to take advantage of Android's built in support for background work. ++ * We really want to avoid making a threading mistake that brings down the whole ++ * process. ++ */ ++public abstract class FxAccountSetupTask extends AsyncTask> { ++ private static final String LOG_TAG = FxAccountSetupTask.class.getSimpleName(); ++ ++ public interface ProgressDisplay { ++ public void showProgress(); ++ public void dismissProgress(); ++ } ++ ++ protected final Context context; ++ protected final FxAccountClient client; ++ protected final ProgressDisplay progressDisplay; ++ ++ // Initialized lazily. ++ protected byte[] quickStretchedPW; ++ ++ // AsyncTask's are one-time-use, so final members are fine. ++ protected final CountDownLatch latch = new CountDownLatch(1); ++ protected final InnerRequestDelegate innerDelegate = new InnerRequestDelegate(latch); ++ ++ protected final RequestDelegate delegate; ++ ++ public FxAccountSetupTask(Context context, ProgressDisplay progressDisplay, FxAccountClient client, RequestDelegate delegate) { ++ this.context = context; ++ this.client = client; ++ this.delegate = delegate; ++ this.progressDisplay = progressDisplay; ++ } ++ ++ @Override ++ protected void onPreExecute() { ++ if (progressDisplay != null) { ++ progressDisplay.showProgress(); ++ } ++ } ++ ++ @Override ++ protected void onPostExecute(InnerRequestDelegate result) { ++ if (progressDisplay != null) { ++ progressDisplay.dismissProgress(); ++ } ++ ++ // We are on the UI thread, and need to invoke these callbacks here to allow UI updating. ++ if (innerDelegate.failure != null) { ++ delegate.handleFailure(innerDelegate.failure); ++ } else if (innerDelegate.exception != null) { ++ delegate.handleError(innerDelegate.exception); ++ } else { ++ delegate.handleSuccess(result.response); ++ } ++ } ++ ++ @Override ++ protected void onCancelled(InnerRequestDelegate result) { ++ if (progressDisplay != null) { ++ progressDisplay.dismissProgress(); ++ } ++ delegate.handleError(new IllegalStateException("Task was cancelled.")); ++ } ++ ++ protected static class InnerRequestDelegate implements RequestDelegate { ++ protected final CountDownLatch latch; ++ public T response = null; ++ public Exception exception = null; ++ public FxAccountClientRemoteException failure = null; ++ ++ protected InnerRequestDelegate(CountDownLatch latch) { ++ this.latch = latch; ++ } ++ ++ @Override ++ public void handleError(Exception e) { ++ Logger.error(LOG_TAG, "Got exception."); ++ this.exception = e; ++ latch.countDown(); ++ } ++ ++ @Override ++ public void handleFailure(FxAccountClientRemoteException e) { ++ Logger.warn(LOG_TAG, "Got failure."); ++ this.failure = e; ++ latch.countDown(); ++ } ++ ++ @Override ++ public void handleSuccess(T result) { ++ Logger.info(LOG_TAG, "Got success."); ++ this.response = result; ++ latch.countDown(); ++ } ++ } ++} +diff -ruN mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountSignInTask.java mozilla-release/mobile/android/base/fxa/tasks/FxAccountSignInTask.java +--- mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountSignInTask.java 1970-01-01 01:00:00.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/tasks/FxAccountSignInTask.java 2014-09-24 03:05:33.000000000 +0200 +@@ -0,0 +1,41 @@ ++/* 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.fxa.tasks; ++ ++import java.io.UnsupportedEncodingException; ++ ++import org.mozilla.gecko.background.common.log.Logger; ++import org.mozilla.gecko.background.fxa.FxAccountClient; ++import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; ++import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse; ++import org.mozilla.gecko.background.fxa.PasswordStretcher; ++ ++import android.content.Context; ++ ++public class FxAccountSignInTask extends FxAccountSetupTask { ++ protected static final String LOG_TAG = FxAccountSignInTask.class.getSimpleName(); ++ ++ protected final byte[] emailUTF8; ++ protected final PasswordStretcher passwordStretcher; ++ ++ public FxAccountSignInTask(Context context, ProgressDisplay progressDisplay, String email, PasswordStretcher passwordStretcher, FxAccountClient client, RequestDelegate delegate) throws UnsupportedEncodingException { ++ super(context, progressDisplay, client, delegate); ++ this.emailUTF8 = email.getBytes("UTF-8"); ++ this.passwordStretcher = passwordStretcher; ++ } ++ ++ @Override ++ protected InnerRequestDelegate doInBackground(Void... arg0) { ++ try { ++ client.loginAndGetKeys(emailUTF8, passwordStretcher, innerDelegate); ++ latch.await(); ++ return innerDelegate; ++ } catch (Exception e) { ++ Logger.error(LOG_TAG, "Got exception signing in.", e); ++ delegate.handleError(e); ++ } ++ return null; ++ } ++} +--- mozilla-esr31/mobile/android/base/android-services.mozbuild 2015-02-25 19:21:05.000000000 +0100 ++++ mozilla-release/mobile/android/base/android-services.mozbuild 2014-09-24 03:05:32.000000000 +0200 +@@ -555,7 +557,6 @@ + 'fxa/activities/FxAccountCreateAccountActivity.java', + 'fxa/activities/FxAccountCreateAccountNotAllowedActivity.java', + 'fxa/activities/FxAccountGetStartedActivity.java', +- 'fxa/activities/FxAccountSetupTask.java', + 'fxa/activities/FxAccountSignInActivity.java', + 'fxa/activities/FxAccountStatusActivity.java', + 'fxa/activities/FxAccountStatusFragment.java', +@@ -589,6 +590,10 @@ + 'fxa/sync/FxAccountSyncService.java', + 'fxa/sync/FxAccountSyncStatusHelper.java', + 'fxa/sync/SchedulePolicy.java', ++ 'fxa/tasks/FxAccountCodeResender.java', ++ 'fxa/tasks/FxAccountCreateAccountTask.java', ++ 'fxa/tasks/FxAccountSetupTask.java', ++ 'fxa/tasks/FxAccountSignInTask.java', + 'sync/AlreadySyncingException.java', + 'sync/BackoffHandler.java', + 'sync/BadRequiredFieldJSONException.java', diff --git a/data/patches/disable/0002-update-mobile-sqlite-to-v35.patch b/data/patches/disable/0002-update-mobile-sqlite-to-v35.patch new file mode 100644 index 0000000..9a99ea2 --- /dev/null +++ b/data/patches/disable/0002-update-mobile-sqlite-to-v35.patch @@ -0,0 +1,246 @@ +diff -ru mobile/android/base/sqlite/ByteBufferInputStream.java /tmp/mozilla-release/mobile/android/base/sqlite/ByteBufferInputStream.java +--- a/mobile/android/base/sqlite/ByteBufferInputStream.java 2015-02-23 22:40:38.708934982 +0100 ++++ b/mobile/android/base/sqlite/ByteBufferInputStream.java 2015-01-23 07:00:04.000000000 +0100 +@@ -14,7 +14,7 @@ + * easier to use. + */ + public class ByteBufferInputStream extends InputStream { +- private ByteBuffer mByteBuffer; ++ private final ByteBuffer mByteBuffer; + + public ByteBufferInputStream(ByteBuffer aByteBuffer) { + mByteBuffer = aByteBuffer; +diff -ru mobile/android/base/sqlite/MatrixBlobCursor.java /tmp/mozilla-release/mobile/android/base/sqlite/MatrixBlobCursor.java +--- a/mobile/android/base/sqlite/MatrixBlobCursor.java 2015-02-23 22:40:38.720934982 +0100 ++++ b/mobile/android/base/sqlite/MatrixBlobCursor.java 2015-01-23 07:00:04.000000000 +0100 +@@ -17,34 +17,37 @@ + + package org.mozilla.gecko.sqlite; + ++import java.nio.ByteBuffer; ++import java.util.ArrayList; ++ ++import org.mozilla.gecko.AppConstants; + import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI; + + import android.database.AbstractCursor; + import android.database.CursorIndexOutOfBoundsException; ++import android.util.Log; + +-import java.nio.ByteBuffer; +-import java.util.ArrayList; +- +-/* +- * Android's AbstractCursor throws on getBlob() +- * and MatrixCursor forgot to override it. This was fixed +- * at some point but old devices are still SOL. +- * Oh, and everything in MatrixCursor is private instead of +- * protected, so we need to entirely duplicate it here, +- * instad of just being able to add the missing method. +- */ + /** + * A mutable cursor implementation backed by an array of {@code Object}s. Use + * {@link #newRow()} to add rows. Automatically expands internal capacity + * as needed. ++ * ++ * This class provides one missing feature from Android's MatrixCursor: ++ * the implementation of getBlob that was inadvertently omitted from API 9 (and ++ * perhaps later; it's present in 14). ++ * ++ * MatrixCursor is all private, so we entirely duplicate it here. + */ + public class MatrixBlobCursor extends AbstractCursor { ++ private static final String LOGTAG = "GeckoMatrixCursor"; + + private final String[] columnNames; +- private Object[] data; +- private int rowCount = 0; + private final int columnCount; + ++ private int rowCount; ++ ++ Object[] data; ++ + /** + * Constructs a new cursor with the given initial capacity. + * +@@ -140,17 +143,18 @@ + */ + @WrapElementForJNI + public void addRow(Iterable columnValues) { +- int start = rowCount * columnCount; +- int end = start + columnCount; +- ensureCapacity(end); ++ final int start = rowCount * columnCount; + + if (columnValues instanceof ArrayList) { + addRow((ArrayList) columnValues, start); + return; + } + ++ final int end = start + columnCount; + int current = start; +- Object[] localData = data; ++ ++ ensureCapacity(end); ++ final Object[] localData = data; + for (Object columnValue : columnValues) { + if (current == end) { + // TODO: null out row? +@@ -173,39 +177,47 @@ + /** Optimization for {@link ArrayList}. */ + @WrapElementForJNI + private void addRow(ArrayList columnValues, int start) { +- int size = columnValues.size(); ++ final int size = columnValues.size(); + if (size != columnCount) { + throw new IllegalArgumentException("columnNames.length = " + + columnCount + ", columnValues.size() = " + size); + } + +- rowCount++; +- Object[] localData = data; ++ final int end = start + columnCount; ++ ensureCapacity(end); ++ ++ // Take a reference just in case someone calls ensureCapacity ++ // and `data` gets replaced by a new array! ++ final Object[] localData = data; + for (int i = 0; i < size; i++) { + localData[start + i] = columnValues.get(i); + } ++ ++ rowCount++; + } + +- /** Ensures that this cursor has enough capacity. */ +- private void ensureCapacity(int size) { +- if (size > data.length) { +- Object[] oldData = this.data; +- int newSize = data.length * 2; +- if (newSize < size) { +- newSize = size; +- } +- this.data = new Object[newSize]; +- System.arraycopy(oldData, 0, this.data, 0, oldData.length); ++ /** ++ * Ensures that this cursor has enough capacity. If it needs to allocate ++ * a new array, the existing capacity will be at least doubled. ++ */ ++ private void ensureCapacity(final int size) { ++ if (size <= data.length) { ++ return; + } ++ ++ final Object[] oldData = this.data; ++ this.data = new Object[Math.max(size, data.length * 2)]; ++ System.arraycopy(oldData, 0, this.data, 0, oldData.length); + } + + /** + * Builds a row, starting from the left-most column and adding one column + * value at a time. Follows the same ordering as the column names specified + * at cursor construction time. ++ * ++ * Not thread-safe. + */ + public class RowBuilder { +- + private int index; + private final int endIndex; + +@@ -221,10 +233,9 @@ + * values + * @return this builder to support chaining + */ +- public RowBuilder add(Object columnValue) { ++ public RowBuilder add(final Object columnValue) { + if (index == endIndex) { +- throw new CursorIndexOutOfBoundsException( +- "No more columns left."); ++ throw new CursorIndexOutOfBoundsException("No more columns left."); + } + + data[index++] = columnValue; +@@ -232,6 +243,9 @@ + } + } + ++ /** ++ * Not thread safe. ++ */ + public void set(int column, Object value) { + if (column < 0 || column >= columnCount) { + throw new CursorIndexOutOfBoundsException("Requested column: " +@@ -266,7 +280,7 @@ + + @Override + public short getShort(int column) { +- Object value = get(column); ++ final Object value = get(column); + if (value == null) return 0; + if (value instanceof Number) return ((Number) value).shortValue(); + return Short.parseShort(value.toString()); +@@ -311,10 +325,11 @@ + if (value instanceof byte[]) { + return (byte[]) value; + } ++ + if (value instanceof ByteBuffer) { +- ByteBuffer data = (ByteBuffer)value; +- byte[] byteArray = new byte[data.remaining()]; +- data.get(byteArray); ++ final ByteBuffer bytes = (ByteBuffer) value; ++ byte[] byteArray = new byte[bytes.remaining()]; ++ bytes.get(byteArray); + return byteArray; + } + throw new UnsupportedOperationException("BLOB Object not of known type"); +@@ -324,4 +339,15 @@ + public boolean isNull(int column) { + return get(column) == null; + } ++ ++ @Override ++ protected void finalize() { ++ if (AppConstants.DEBUG_BUILD) { ++ if (!isClosed()) { ++ Log.e(LOGTAG, "Cursor finalized without being closed", new RuntimeException("stack")); ++ } ++ } ++ ++ super.finalize(); ++ } + } +diff -ru mobile/android/base/sqlite/SQLiteBridge.java /tmp/mozilla-release/mobile/android/base/sqlite/SQLiteBridge.java +--- a/mobile/android/base/sqlite/SQLiteBridge.java 2015-02-23 22:40:38.716934982 +0100 ++++ b/mobile/android/base/sqlite/SQLiteBridge.java 2015-01-23 07:00:04.000000000 +0100 +@@ -26,16 +26,16 @@ + private static final String LOGTAG = "SQLiteBridge"; + + // Path to the database. If this database was not opened with openDatabase, we reopen it every query. +- private String mDb; ++ private final String mDb; + + // Pointer to the database if it was opened with openDatabase. 0 implies closed. +- protected volatile long mDbPointer = 0L; ++ protected volatile long mDbPointer; + + // Values remembered after a query. + private long[] mQueryResults; + +- private boolean mTransactionSuccess = false; +- private boolean mInTransaction = false; ++ private boolean mTransactionSuccess; ++ private boolean mInTransaction; + + private static final int RESULT_INSERT_ROW_ID = 0; + private static final int RESULT_ROWS_CHANGED = 1; +@@ -227,6 +227,7 @@ + cursor.moveToFirst(); + String version = cursor.getString(0); + ret = Integer.parseInt(version); ++ cursor.close(); + } + return ret; + } diff --git a/data/patches/disable/0003-upgrade-mobile-sync-to-v32.patch b/data/patches/disable/0003-upgrade-mobile-sync-to-v32.patch new file mode 100644 index 0000000..e420727 --- /dev/null +++ b/data/patches/disable/0003-upgrade-mobile-sync-to-v32.patch @@ -0,0 +1,992 @@ +diff -ruN mozilla-esr31/mobile/android/base/fxa/FirefoxAccounts.java mozilla-release/mobile/android/base/fxa/FirefoxAccounts.java +--- mozilla-esr31/mobile/android/base/fxa/FirefoxAccounts.java 2015-02-25 19:21:06.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/FirefoxAccounts.java 2014-09-24 03:05:32.000000000 +0200 +@@ -6,13 +6,18 @@ + + import java.io.File; + import java.util.EnumSet; ++import java.util.Locale; + import java.util.concurrent.CountDownLatch; + ++import org.mozilla.gecko.AppConstants; ++import org.mozilla.gecko.R; + import org.mozilla.gecko.background.common.log.Logger; + import org.mozilla.gecko.fxa.authenticator.AccountPickler; + import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; ++import org.mozilla.gecko.fxa.login.State; + import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter; + import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper; ++import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender; + import org.mozilla.gecko.sync.ThreadPool; + import org.mozilla.gecko.sync.Utils; + +@@ -20,6 +25,7 @@ + import android.accounts.AccountManager; + import android.content.ContentResolver; + import android.content.Context; ++import android.content.res.Resources; + import android.os.Bundle; + + /** +@@ -152,6 +158,38 @@ + return null; + } + ++ /** ++ * @return ++ * the {@link State} instance associated with the current account, or null if ++ * no accounts exist. ++ */ ++ public static State getFirefoxAccountState(final Context context) { ++ final Account account = getFirefoxAccount(context); ++ if (account == null) { ++ return null; ++ } ++ ++ final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account); ++ try { ++ return fxAccount.getState(); ++ } catch (final Exception ex) { ++ Logger.warn(LOG_TAG, "Could not get FX account state.", ex); ++ return null; ++ } ++ } ++ ++ /* ++ * @param context Android context ++ * @return the email address associated with the configured Firefox account if one exists; null otherwise. ++ */ ++ public static String getFirefoxAccountEmail(final Context context) { ++ final Account account = getFirefoxAccount(context); ++ if (account == null) { ++ return null; ++ } ++ return account.name; ++ } ++ + protected static void putHintsToSync(final Bundle extras, EnumSet syncHints) { + // stagesToSync and stagesToSkip are allowed to be null. + if (syncHints == null) { +@@ -264,4 +302,33 @@ + // stopObserving null-checks its argument. + FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusListener); + } ++ ++ public static String getOldSyncUpgradeURL(final Resources res, final Locale locale) { ++ final String VERSION = AppConstants.MOZ_APP_VERSION; ++ final String OS = AppConstants.OS_TARGET; ++ final String LOCALE = Utils.getLanguageTag(locale); ++ return res.getString(R.string.fxaccount_link_old_firefox, VERSION, OS, LOCALE); ++ } ++ ++ /** ++ * Resends the account verification email, and displays an appropriate ++ * toast on both send success and failure. Note that because the underlying implementation ++ * uses {@link AsyncTask}, the provided context must be UI-capable, and this ++ * method called from the UI thread (see ++ * {@link org.mozilla.gecko.fxa.tasks.FxAccountCodeResender#resendCode(Context, AndroidFxAccount)} ++ * for more). ++ * ++ * @param context a UI-capable Android context. ++ * @return true if an account exists, false otherwise. ++ */ ++ public static boolean resendVerificationEmail(final Context context) { ++ final Account account = getFirefoxAccount(context); ++ if (account == null) { ++ return false; ++ } ++ ++ final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account); ++ FxAccountCodeResender.resendCode(context, fxAccount); ++ return true; ++ } + } +diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java mozilla-release/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java +--- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java 2015-02-25 19:21:06.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java 2014-09-24 03:05:32.000000000 +0200 +@@ -19,10 +19,10 @@ + import org.mozilla.gecko.background.fxa.PasswordStretcher; + import org.mozilla.gecko.background.fxa.QuickPasswordStretcher; + import org.mozilla.gecko.fxa.FxAccountConstants; +-import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.ProgressDisplay; + import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; + import org.mozilla.gecko.fxa.login.Engaged; + import org.mozilla.gecko.fxa.login.State; ++import org.mozilla.gecko.fxa.tasks.FxAccountSetupTask.ProgressDisplay; + import org.mozilla.gecko.sync.SyncConfiguration; + import org.mozilla.gecko.sync.setup.Constants; + import org.mozilla.gecko.sync.setup.activities.ActivityUtils; +diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java mozilla-release/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java +--- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java 2015-02-25 19:21:06.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java 2014-09-24 03:05:32.000000000 +0200 +@@ -4,21 +4,15 @@ + + package org.mozilla.gecko.fxa.activities; + +-import java.util.concurrent.Executor; +-import java.util.concurrent.Executors; +- + import org.mozilla.gecko.R; + import org.mozilla.gecko.background.common.log.Logger; +-import org.mozilla.gecko.background.fxa.FxAccountClient; +-import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; +-import org.mozilla.gecko.background.fxa.FxAccountClient20; +-import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; + import org.mozilla.gecko.fxa.FirefoxAccounts; + import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; + import org.mozilla.gecko.fxa.login.Engaged; + import org.mozilla.gecko.fxa.login.State; + import org.mozilla.gecko.fxa.login.State.Action; + import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper; ++import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender; + import org.mozilla.gecko.sync.setup.activities.ActivityUtils; + + import android.accounts.Account; +@@ -28,7 +22,6 @@ + import android.view.View; + import android.view.View.OnClickListener; + import android.widget.TextView; +-import android.widget.Toast; + + /** + * Activity which displays account created successfully screen to the user, and +@@ -164,76 +157,8 @@ + resendLink.setClickable(resendLinkShouldBeEnabled); + } + +- public static class FxAccountResendCodeTask extends FxAccountSetupTask { +- protected static final String LOG_TAG = FxAccountResendCodeTask.class.getSimpleName(); +- +- protected final byte[] sessionToken; +- +- public FxAccountResendCodeTask(Context context, byte[] sessionToken, FxAccountClient client, RequestDelegate delegate) { +- super(context, null, client, delegate); +- this.sessionToken = sessionToken; +- } +- +- @Override +- protected InnerRequestDelegate doInBackground(Void... arg0) { +- try { +- client.resendCode(sessionToken, innerDelegate); +- latch.await(); +- return innerDelegate; +- } catch (Exception e) { +- Logger.error(LOG_TAG, "Got exception signing in.", e); +- delegate.handleError(e); +- } +- return null; +- } +- } +- +- protected static class ResendCodeDelegate implements RequestDelegate { +- public final Context context; +- +- public ResendCodeDelegate(Context context) { +- this.context = context; +- } +- +- @Override +- public void handleError(Exception e) { +- Logger.warn(LOG_TAG, "Got exception requesting fresh confirmation link; ignoring.", e); +- Toast.makeText(context, R.string.fxaccount_confirm_account_verification_link_not_sent, Toast.LENGTH_LONG).show(); +- } +- +- @Override +- public void handleFailure(FxAccountClientRemoteException e) { +- handleError(e); +- } +- +- @Override +- public void handleSuccess(Void result) { +- Toast.makeText(context, R.string.fxaccount_confirm_account_verification_link_sent, Toast.LENGTH_SHORT).show(); +- } +- } +- +- public static void resendCode(Context context, AndroidFxAccount fxAccount) { +- RequestDelegate delegate = new ResendCodeDelegate(context); +- +- byte[] sessionToken; +- try { +- sessionToken = ((Engaged) fxAccount.getState()).getSessionToken(); +- } catch (Exception e) { +- delegate.handleError(e); +- return; +- } +- if (sessionToken == null) { +- delegate.handleError(new IllegalStateException("sessionToken should not be null")); +- return; +- } +- +- Executor executor = Executors.newSingleThreadExecutor(); +- FxAccountClient client = new FxAccountClient20(fxAccount.getAccountServerURI(), executor); +- new FxAccountResendCodeTask(context, sessionToken, client, delegate).execute(); +- } +- + @Override + public void onClick(View v) { +- resendCode(this, fxAccount); ++ FxAccountCodeResender.resendCode(this, fxAccount); + } + } +diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java mozilla-release/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java +--- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java 2015-02-25 19:21:06.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java 2014-09-24 03:05:32.000000000 +0200 +@@ -21,7 +21,7 @@ + import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; + import org.mozilla.gecko.background.fxa.PasswordStretcher; + import org.mozilla.gecko.fxa.FxAccountConstants; +-import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountCreateAccountTask; ++import org.mozilla.gecko.fxa.tasks.FxAccountCreateAccountTask; + + import android.app.AlertDialog; + import android.app.Dialog; +diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java mozilla-release/mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java +--- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java 2015-02-25 19:21:06.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java 2014-09-24 03:05:32.000000000 +0200 +@@ -6,13 +6,11 @@ + + import java.util.Locale; + +-import org.mozilla.gecko.AppConstants; + import org.mozilla.gecko.R; + import org.mozilla.gecko.background.common.log.Logger; + import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper; + import org.mozilla.gecko.fxa.FirefoxAccounts; + import org.mozilla.gecko.fxa.FxAccountConstants; +-import org.mozilla.gecko.sync.Utils; + import org.mozilla.gecko.sync.setup.activities.ActivityUtils; + import org.mozilla.gecko.sync.setup.activities.LocaleAware; + +@@ -109,11 +107,7 @@ + protected void linkifyOldFirefoxLink() { + TextView oldFirefox = (TextView) findViewById(R.id.old_firefox); + String text = getResources().getString(R.string.fxaccount_getting_started_old_firefox); +- String VERSION = AppConstants.MOZ_APP_VERSION; +- String OS = AppConstants.OS_TARGET; +- +- String LOCALE = Utils.getLanguageTag(Locale.getDefault()); +- String url = getResources().getString(R.string.fxaccount_link_old_firefox, VERSION, OS, LOCALE); ++ final String url = FirefoxAccounts.getOldSyncUpgradeURL(getResources(), Locale.getDefault()); + FxAccountConstants.pii(LOG_TAG, "Old Firefox url is: " + url); // Don't want to leak locale in particular. + ActivityUtils.linkTextView(oldFirefox, text, url); + } +diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountSetupTask.java mozilla-release/mobile/android/base/fxa/activities/FxAccountSetupTask.java +--- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountSetupTask.java 2015-02-25 19:21:06.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/activities/FxAccountSetupTask.java 1970-01-01 01:00:00.000000000 +0100 +@@ -1,172 +0,0 @@ +-/* 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.fxa.activities; +- +-import java.io.UnsupportedEncodingException; +-import java.util.concurrent.CountDownLatch; +- +-import org.mozilla.gecko.background.common.log.Logger; +-import org.mozilla.gecko.background.fxa.FxAccountClient; +-import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; +-import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse; +-import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; +-import org.mozilla.gecko.background.fxa.PasswordStretcher; +-import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.InnerRequestDelegate; +- +-import android.content.Context; +-import android.os.AsyncTask; +- +-/** +- * An AsyncTask wrapper around signing up for, and signing in to, a +- * Firefox Account. +- *

+- * It's strange to add explicit blocking to callback-threading code, but we do +- * it here to take advantage of Android's built in support for background work. +- * We really want to avoid making a threading mistake that brings down the whole +- * process. +- */ +-abstract class FxAccountSetupTask extends AsyncTask> { +- private static final String LOG_TAG = FxAccountSetupTask.class.getSimpleName(); +- +- public interface ProgressDisplay { +- public void showProgress(); +- public void dismissProgress(); +- } +- +- protected final Context context; +- protected final FxAccountClient client; +- protected final ProgressDisplay progressDisplay; +- +- // Initialized lazily. +- protected byte[] quickStretchedPW; +- +- // AsyncTask's are one-time-use, so final members are fine. +- protected final CountDownLatch latch = new CountDownLatch(1); +- protected final InnerRequestDelegate innerDelegate = new InnerRequestDelegate(latch); +- +- protected final RequestDelegate delegate; +- +- public FxAccountSetupTask(Context context, ProgressDisplay progressDisplay, FxAccountClient client, RequestDelegate delegate) { +- this.context = context; +- this.client = client; +- this.delegate = delegate; +- this.progressDisplay = progressDisplay; +- } +- +- @Override +- protected void onPreExecute() { +- if (progressDisplay != null) { +- progressDisplay.showProgress(); +- } +- } +- +- @Override +- protected void onPostExecute(InnerRequestDelegate result) { +- if (progressDisplay != null) { +- progressDisplay.dismissProgress(); +- } +- +- // We are on the UI thread, and need to invoke these callbacks here to allow UI updating. +- if (innerDelegate.failure != null) { +- delegate.handleFailure(innerDelegate.failure); +- } else if (innerDelegate.exception != null) { +- delegate.handleError(innerDelegate.exception); +- } else { +- delegate.handleSuccess(result.response); +- } +- } +- +- @Override +- protected void onCancelled(InnerRequestDelegate result) { +- if (progressDisplay != null) { +- progressDisplay.dismissProgress(); +- } +- delegate.handleError(new IllegalStateException("Task was cancelled.")); +- } +- +- protected static class InnerRequestDelegate implements RequestDelegate { +- protected final CountDownLatch latch; +- public T response = null; +- public Exception exception = null; +- public FxAccountClientRemoteException failure = null; +- +- protected InnerRequestDelegate(CountDownLatch latch) { +- this.latch = latch; +- } +- +- @Override +- public void handleError(Exception e) { +- Logger.error(LOG_TAG, "Got exception."); +- this.exception = e; +- latch.countDown(); +- } +- +- @Override +- public void handleFailure(FxAccountClientRemoteException e) { +- Logger.warn(LOG_TAG, "Got failure."); +- this.failure = e; +- latch.countDown(); +- } +- +- @Override +- public void handleSuccess(T result) { +- Logger.info(LOG_TAG, "Got success."); +- this.response = result; +- latch.countDown(); +- } +- } +- +- public static class FxAccountCreateAccountTask extends FxAccountSetupTask { +- private static final String LOG_TAG = FxAccountCreateAccountTask.class.getSimpleName(); +- +- protected final byte[] emailUTF8; +- protected final PasswordStretcher passwordStretcher; +- +- public FxAccountCreateAccountTask(Context context, ProgressDisplay progressDisplay, String email, PasswordStretcher passwordStretcher, FxAccountClient client, RequestDelegate delegate) throws UnsupportedEncodingException { +- super(context, progressDisplay, client, delegate); +- this.emailUTF8 = email.getBytes("UTF-8"); +- this.passwordStretcher = passwordStretcher; +- } +- +- @Override +- protected InnerRequestDelegate doInBackground(Void... arg0) { +- try { +- client.createAccountAndGetKeys(emailUTF8, passwordStretcher, innerDelegate); +- latch.await(); +- return innerDelegate; +- } catch (Exception e) { +- Logger.error(LOG_TAG, "Got exception logging in.", e); +- delegate.handleError(e); +- } +- return null; +- } +- } +- +- public static class FxAccountSignInTask extends FxAccountSetupTask { +- protected static final String LOG_TAG = FxAccountSignInTask.class.getSimpleName(); +- +- protected final byte[] emailUTF8; +- protected final PasswordStretcher passwordStretcher; +- +- public FxAccountSignInTask(Context context, ProgressDisplay progressDisplay, String email, PasswordStretcher passwordStretcher, FxAccountClient client, RequestDelegate delegate) throws UnsupportedEncodingException { +- super(context, progressDisplay, client, delegate); +- this.emailUTF8 = email.getBytes("UTF-8"); +- this.passwordStretcher = passwordStretcher; +- } +- +- @Override +- protected InnerRequestDelegate doInBackground(Void... arg0) { +- try { +- client.loginAndGetKeys(emailUTF8, passwordStretcher, innerDelegate); +- latch.await(); +- return innerDelegate; +- } catch (Exception e) { +- Logger.error(LOG_TAG, "Got exception signing in.", e); +- delegate.handleError(e); +- } +- return null; +- } +- } +-} +diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountSignInActivity.java mozilla-release/mobile/android/base/fxa/activities/FxAccountSignInActivity.java +--- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountSignInActivity.java 2015-02-25 19:21:06.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/activities/FxAccountSignInActivity.java 2014-09-24 03:05:32.000000000 +0200 +@@ -16,7 +16,7 @@ + import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; + import org.mozilla.gecko.background.fxa.PasswordStretcher; + import org.mozilla.gecko.fxa.FxAccountConstants; +-import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountSignInTask; ++import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask; + import org.mozilla.gecko.sync.setup.activities.ActivityUtils; + + import android.content.Intent; +diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountStatusFragment.java mozilla-release/mobile/android/base/fxa/activities/FxAccountStatusFragment.java +--- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountStatusFragment.java 2015-02-25 19:21:06.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/activities/FxAccountStatusFragment.java 2014-09-24 03:05:32.000000000 +0200 +@@ -17,6 +17,8 @@ + import org.mozilla.gecko.fxa.login.Married; + import org.mozilla.gecko.fxa.login.State; + import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper; ++import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender; ++import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate; + import org.mozilla.gecko.sync.SyncConfiguration; + + import android.accounts.Account; +@@ -27,10 +29,13 @@ + import android.os.Bundle; + import android.os.Handler; + import android.preference.CheckBoxPreference; ++import android.preference.EditTextPreference; + import android.preference.Preference; ++import android.preference.Preference.OnPreferenceChangeListener; + import android.preference.Preference.OnPreferenceClickListener; + import android.preference.PreferenceCategory; + import android.preference.PreferenceScreen; ++import android.text.TextUtils; + + /** + * A fragment that displays the status of an AndroidFxAccount. +@@ -38,7 +43,9 @@ + * The owning activity is responsible for providing an AndroidFxAccount at + * appropriate times. + */ +-public class FxAccountStatusFragment extends PreferenceFragment implements OnPreferenceClickListener { ++public class FxAccountStatusFragment ++ extends PreferenceFragment ++ implements OnPreferenceClickListener, OnPreferenceChangeListener { + private static final String LOG_TAG = FxAccountStatusFragment.class.getSimpleName(); + + // When a checkbox is toggled, wait 5 seconds (for other checkbox actions) +@@ -64,7 +71,12 @@ + protected CheckBoxPreference tabsPreference; + protected CheckBoxPreference passwordsPreference; + ++ protected EditTextPreference deviceNamePreference; ++ + protected volatile AndroidFxAccount fxAccount; ++ // The contract is: when fxAccount is non-null, then clientsDataDelegate is ++ // non-null. If violated then an IllegalStateException is thrown. ++ protected volatile SharedPreferencesClientsDataDelegate clientsDataDelegate; + + // Used to post delayed sync requests. + protected Handler handler; +@@ -87,6 +99,10 @@ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); ++ addPreferences(); ++ } ++ ++ protected void addPreferences() { + addPreferencesFromResource(R.xml.fxaccount_status_prefscreen); + + emailPreference = ensureFindPreference("email"); +@@ -118,6 +134,9 @@ + historyPreference.setOnPreferenceClickListener(this); + tabsPreference.setOnPreferenceClickListener(this); + passwordsPreference.setOnPreferenceClickListener(this); ++ ++ deviceNamePreference = (EditTextPreference) ensureFindPreference("device_name"); ++ deviceNamePreference.setOnPreferenceChangeListener(this); + } + + /** +@@ -142,7 +161,7 @@ + } + + if (preference == needsVerificationPreference) { +- FxAccountConfirmAccountActivity.resendCode(getActivity().getApplicationContext(), fxAccount); ++ FxAccountCodeResender.resendCode(getActivity().getApplicationContext(), fxAccount); + + Intent intent = new Intent(getActivity(), FxAccountConfirmAccountActivity.class); + // Per http://stackoverflow.com/a/8992365, this triggers a known bug with +@@ -176,6 +195,8 @@ + historyPreference.setEnabled(enabled); + tabsPreference.setEnabled(enabled); + passwordsPreference.setEnabled(enabled); ++ // Since we can't sync, we can't update our remote client record. ++ deviceNamePreference.setEnabled(enabled); + } + + /** +@@ -293,6 +314,14 @@ + throw new IllegalArgumentException("fxAccount must not be null"); + } + this.fxAccount = fxAccount; ++ try { ++ this.clientsDataDelegate = new SharedPreferencesClientsDataDelegate(fxAccount.getSyncPrefs()); ++ } catch (Exception e) { ++ Logger.error(LOG_TAG, "Got exception fetching Sync prefs associated to Firefox Account; aborting.", e); ++ // Something is terribly wrong; best to get a stack trace rather than ++ // continue with a null clients delegate. ++ throw new IllegalStateException(e); ++ } + + handler = new Handler(); // Attached to current (assumed to be UI) thread. + +@@ -318,6 +347,17 @@ + FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusDelegate); + } + ++ protected void hardRefresh() { ++ // This is the only way to guarantee that the EditText dialogs created by ++ // EditTextPreferences are re-created. This works around the issue described ++ // at http://androiddev.orkitra.com/?p=112079. ++ final PreferenceScreen statusScreen = (PreferenceScreen) ensureFindPreference("status_screen"); ++ statusScreen.removeAll(); ++ addPreferences(); ++ ++ refresh(); ++ } ++ + protected void refresh() { + // refresh is called from our onResume, which can happen before the owning + // Activity tells us about an account (via our public +@@ -371,6 +411,10 @@ + // No matter our state, we should update the checkboxes. + updateSelectedEngines(); + } ++ ++ final String clientName = clientsDataDelegate.getClientName(); ++ deviceNamePreference.setSummary(clientName); ++ deviceNamePreference.setText(clientName); + } + + /** +@@ -570,4 +614,22 @@ + button.setOnPreferenceClickListener(listener); + } + } ++ ++ @Override ++ public boolean onPreferenceChange(Preference preference, Object newValue) { ++ if (preference == deviceNamePreference) { ++ String newClientName = (String) newValue; ++ if (TextUtils.isEmpty(newClientName)) { ++ newClientName = clientsDataDelegate.getDefaultClientName(); ++ } ++ final long now = System.currentTimeMillis(); ++ clientsDataDelegate.setClientName(newClientName, now); ++ requestDelayedSync(); // Try to update our remote client record. ++ hardRefresh(); // Updates the value displayed to the user, among other things. ++ return true; ++ } ++ ++ // For everything else, accept the change. ++ return true; ++ } + } +diff -ruN mozilla-esr31/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java mozilla-release/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java +--- mozilla-esr31/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java 2015-02-25 19:21:06.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java 2014-09-24 03:05:32.000000000 +0200 +@@ -18,11 +18,11 @@ + import org.mozilla.gecko.background.fxa.PasswordStretcher; + import org.mozilla.gecko.fxa.FirefoxAccounts; + import org.mozilla.gecko.fxa.FxAccountConstants; +-import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountSignInTask; + import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; + import org.mozilla.gecko.fxa.login.Engaged; + import org.mozilla.gecko.fxa.login.State; + import org.mozilla.gecko.fxa.login.State.StateLabel; ++import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask; + import org.mozilla.gecko.sync.setup.activities.ActivityUtils; + + import android.os.Bundle; +diff -ruN mozilla-esr31/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java mozilla-release/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java +--- mozilla-esr31/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java 2015-02-25 19:21:06.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java 2014-09-24 03:05:33.000000000 +0200 +@@ -357,7 +357,11 @@ + + FxAccountGlobalSession globalSession = null; + try { +- ClientsDataDelegate clientsDataDelegate = new SharedPreferencesClientsDataDelegate(sharedPrefs); ++ final ClientsDataDelegate clientsDataDelegate = new SharedPreferencesClientsDataDelegate(sharedPrefs); ++ if (FxAccountConstants.LOG_PERSONAL_INFORMATION) { ++ FxAccountConstants.pii(LOG_TAG, "Client device name is: '" + clientsDataDelegate.getClientName() + "'."); ++ FxAccountConstants.pii(LOG_TAG, "Client device data last modified: " + clientsDataDelegate.getLastModifiedTimestamp()); ++ } + + // We compute skew over time using SkewHandler. This yields an unchanging + // skew adjustment that the HawkAuthHeaderProvider uses to adjust its +diff -ruN mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountCodeResender.java mozilla-release/mobile/android/base/fxa/tasks/FxAccountCodeResender.java +--- mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountCodeResender.java 1970-01-01 01:00:00.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/tasks/FxAccountCodeResender.java 2014-09-24 03:05:33.000000000 +0200 +@@ -0,0 +1,108 @@ ++/* 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.fxa.tasks; ++ ++import java.util.concurrent.Executor; ++import java.util.concurrent.Executors; ++ ++import org.mozilla.gecko.R; ++import org.mozilla.gecko.background.common.log.Logger; ++import org.mozilla.gecko.background.fxa.FxAccountClient; ++import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; ++import org.mozilla.gecko.background.fxa.FxAccountClient20; ++import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; ++import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; ++import org.mozilla.gecko.fxa.login.Engaged; ++ ++import android.content.Context; ++import android.widget.Toast; ++ ++/** ++ * A helper class that provides a simple interface for requesting ++ * a Firefox Account verification email to be resent. ++ */ ++public class FxAccountCodeResender { ++ private static final String LOG_TAG = FxAccountCodeResender.class.getSimpleName(); ++ ++ private static class FxAccountResendCodeTask extends FxAccountSetupTask { ++ protected static final String LOG_TAG = FxAccountResendCodeTask.class.getSimpleName(); ++ ++ protected final byte[] sessionToken; ++ ++ public FxAccountResendCodeTask(Context context, byte[] sessionToken, FxAccountClient client, RequestDelegate delegate) { ++ super(context, null, client, delegate); ++ this.sessionToken = sessionToken; ++ } ++ ++ @Override ++ protected InnerRequestDelegate doInBackground(Void... arg0) { ++ try { ++ client.resendCode(sessionToken, innerDelegate); ++ latch.await(); ++ return innerDelegate; ++ } catch (Exception e) { ++ Logger.error(LOG_TAG, "Got exception signing in.", e); ++ delegate.handleError(e); ++ } ++ return null; ++ } ++ } ++ ++ private static class ResendCodeDelegate implements RequestDelegate { ++ public final Context context; ++ ++ public ResendCodeDelegate(Context context) { ++ this.context = context; ++ } ++ ++ @Override ++ public void handleError(Exception e) { ++ Logger.warn(LOG_TAG, "Got exception requesting fresh confirmation link; ignoring.", e); ++ Toast.makeText(context, R.string.fxaccount_confirm_account_verification_link_not_sent, Toast.LENGTH_LONG).show(); ++ } ++ ++ @Override ++ public void handleFailure(FxAccountClientRemoteException e) { ++ handleError(e); ++ } ++ ++ @Override ++ public void handleSuccess(Void result) { ++ Toast.makeText(context, R.string.fxaccount_confirm_account_verification_link_sent, Toast.LENGTH_SHORT).show(); ++ } ++ } ++ ++ /** ++ * Resends the account verification email, and displays an appropriate ++ * toast on both send success and failure. Note that because the underlying implementation ++ * uses {@link AsyncTask}, the provided context must be UI-capable and ++ * this method called from the UI thread. ++ * ++ * Note that it may actually be possible to run this (and the {@link AsyncTask}) method ++ * from a background thread - but this hasn't been tested. ++ * ++ * @param context A UI-capable Android context. ++ * @param fxAccount The Firefox Account to resend the code to. ++ */ ++ public static void resendCode(Context context, AndroidFxAccount fxAccount) { ++ RequestDelegate delegate = new ResendCodeDelegate(context); ++ ++ byte[] sessionToken; ++ try { ++ sessionToken = ((Engaged) fxAccount.getState()).getSessionToken(); ++ } catch (Exception e) { ++ delegate.handleError(e); ++ return; ++ } ++ if (sessionToken == null) { ++ delegate.handleError(new IllegalStateException("sessionToken should not be null")); ++ return; ++ } ++ ++ Executor executor = Executors.newSingleThreadExecutor(); ++ FxAccountClient client = new FxAccountClient20(fxAccount.getAccountServerURI(), executor); ++ new FxAccountResendCodeTask(context, sessionToken, client, delegate).execute(); ++ } ++} +diff -ruN mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountCreateAccountTask.java mozilla-release/mobile/android/base/fxa/tasks/FxAccountCreateAccountTask.java +--- mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountCreateAccountTask.java 1970-01-01 01:00:00.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/tasks/FxAccountCreateAccountTask.java 2014-09-24 03:05:33.000000000 +0200 +@@ -0,0 +1,41 @@ ++/* 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.fxa.tasks; ++ ++import java.io.UnsupportedEncodingException; ++ ++import org.mozilla.gecko.background.common.log.Logger; ++import org.mozilla.gecko.background.fxa.FxAccountClient; ++import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; ++import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse; ++import org.mozilla.gecko.background.fxa.PasswordStretcher; ++ ++import android.content.Context; ++ ++public class FxAccountCreateAccountTask extends FxAccountSetupTask { ++ private static final String LOG_TAG = FxAccountCreateAccountTask.class.getSimpleName(); ++ ++ protected final byte[] emailUTF8; ++ protected final PasswordStretcher passwordStretcher; ++ ++ public FxAccountCreateAccountTask(Context context, ProgressDisplay progressDisplay, String email, PasswordStretcher passwordStretcher, FxAccountClient client, RequestDelegate delegate) throws UnsupportedEncodingException { ++ super(context, progressDisplay, client, delegate); ++ this.emailUTF8 = email.getBytes("UTF-8"); ++ this.passwordStretcher = passwordStretcher; ++ } ++ ++ @Override ++ protected InnerRequestDelegate doInBackground(Void... arg0) { ++ try { ++ client.createAccountAndGetKeys(emailUTF8, passwordStretcher, innerDelegate); ++ latch.await(); ++ return innerDelegate; ++ } catch (Exception e) { ++ Logger.error(LOG_TAG, "Got exception logging in.", e); ++ delegate.handleError(e); ++ } ++ return null; ++ } ++} +diff -ruN mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountSetupTask.java mozilla-release/mobile/android/base/fxa/tasks/FxAccountSetupTask.java +--- mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountSetupTask.java 1970-01-01 01:00:00.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/tasks/FxAccountSetupTask.java 2014-09-24 03:05:33.000000000 +0200 +@@ -0,0 +1,117 @@ ++/* 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.fxa.tasks; ++ ++import java.util.concurrent.CountDownLatch; ++ ++import org.mozilla.gecko.background.common.log.Logger; ++import org.mozilla.gecko.background.fxa.FxAccountClient; ++import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; ++import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException; ++import org.mozilla.gecko.fxa.tasks.FxAccountSetupTask.InnerRequestDelegate; ++ ++import android.content.Context; ++import android.os.AsyncTask; ++ ++/** ++ * An AsyncTask wrapper around signing up for, and signing in to, a ++ * Firefox Account. ++ *

++ * It's strange to add explicit blocking to callback-threading code, but we do ++ * it here to take advantage of Android's built in support for background work. ++ * We really want to avoid making a threading mistake that brings down the whole ++ * process. ++ */ ++public abstract class FxAccountSetupTask extends AsyncTask> { ++ private static final String LOG_TAG = FxAccountSetupTask.class.getSimpleName(); ++ ++ public interface ProgressDisplay { ++ public void showProgress(); ++ public void dismissProgress(); ++ } ++ ++ protected final Context context; ++ protected final FxAccountClient client; ++ protected final ProgressDisplay progressDisplay; ++ ++ // Initialized lazily. ++ protected byte[] quickStretchedPW; ++ ++ // AsyncTask's are one-time-use, so final members are fine. ++ protected final CountDownLatch latch = new CountDownLatch(1); ++ protected final InnerRequestDelegate innerDelegate = new InnerRequestDelegate(latch); ++ ++ protected final RequestDelegate delegate; ++ ++ public FxAccountSetupTask(Context context, ProgressDisplay progressDisplay, FxAccountClient client, RequestDelegate delegate) { ++ this.context = context; ++ this.client = client; ++ this.delegate = delegate; ++ this.progressDisplay = progressDisplay; ++ } ++ ++ @Override ++ protected void onPreExecute() { ++ if (progressDisplay != null) { ++ progressDisplay.showProgress(); ++ } ++ } ++ ++ @Override ++ protected void onPostExecute(InnerRequestDelegate result) { ++ if (progressDisplay != null) { ++ progressDisplay.dismissProgress(); ++ } ++ ++ // We are on the UI thread, and need to invoke these callbacks here to allow UI updating. ++ if (innerDelegate.failure != null) { ++ delegate.handleFailure(innerDelegate.failure); ++ } else if (innerDelegate.exception != null) { ++ delegate.handleError(innerDelegate.exception); ++ } else { ++ delegate.handleSuccess(result.response); ++ } ++ } ++ ++ @Override ++ protected void onCancelled(InnerRequestDelegate result) { ++ if (progressDisplay != null) { ++ progressDisplay.dismissProgress(); ++ } ++ delegate.handleError(new IllegalStateException("Task was cancelled.")); ++ } ++ ++ protected static class InnerRequestDelegate implements RequestDelegate { ++ protected final CountDownLatch latch; ++ public T response = null; ++ public Exception exception = null; ++ public FxAccountClientRemoteException failure = null; ++ ++ protected InnerRequestDelegate(CountDownLatch latch) { ++ this.latch = latch; ++ } ++ ++ @Override ++ public void handleError(Exception e) { ++ Logger.error(LOG_TAG, "Got exception."); ++ this.exception = e; ++ latch.countDown(); ++ } ++ ++ @Override ++ public void handleFailure(FxAccountClientRemoteException e) { ++ Logger.warn(LOG_TAG, "Got failure."); ++ this.failure = e; ++ latch.countDown(); ++ } ++ ++ @Override ++ public void handleSuccess(T result) { ++ Logger.info(LOG_TAG, "Got success."); ++ this.response = result; ++ latch.countDown(); ++ } ++ } ++} +diff -ruN mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountSignInTask.java mozilla-release/mobile/android/base/fxa/tasks/FxAccountSignInTask.java +--- mozilla-esr31/mobile/android/base/fxa/tasks/FxAccountSignInTask.java 1970-01-01 01:00:00.000000000 +0100 ++++ mozilla-release/mobile/android/base/fxa/tasks/FxAccountSignInTask.java 2014-09-24 03:05:33.000000000 +0200 +@@ -0,0 +1,41 @@ ++/* 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.fxa.tasks; ++ ++import java.io.UnsupportedEncodingException; ++ ++import org.mozilla.gecko.background.common.log.Logger; ++import org.mozilla.gecko.background.fxa.FxAccountClient; ++import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate; ++import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse; ++import org.mozilla.gecko.background.fxa.PasswordStretcher; ++ ++import android.content.Context; ++ ++public class FxAccountSignInTask extends FxAccountSetupTask { ++ protected static final String LOG_TAG = FxAccountSignInTask.class.getSimpleName(); ++ ++ protected final byte[] emailUTF8; ++ protected final PasswordStretcher passwordStretcher; ++ ++ public FxAccountSignInTask(Context context, ProgressDisplay progressDisplay, String email, PasswordStretcher passwordStretcher, FxAccountClient client, RequestDelegate delegate) throws UnsupportedEncodingException { ++ super(context, progressDisplay, client, delegate); ++ this.emailUTF8 = email.getBytes("UTF-8"); ++ this.passwordStretcher = passwordStretcher; ++ } ++ ++ @Override ++ protected InnerRequestDelegate doInBackground(Void... arg0) { ++ try { ++ client.loginAndGetKeys(emailUTF8, passwordStretcher, innerDelegate); ++ latch.await(); ++ return innerDelegate; ++ } catch (Exception e) { ++ Logger.error(LOG_TAG, "Got exception signing in.", e); ++ delegate.handleError(e); ++ } ++ return null; ++ } ++} +--- mozilla-esr31/mobile/android/base/android-services.mozbuild 2015-02-25 19:21:05.000000000 +0100 ++++ mozilla-release/mobile/android/base/android-services.mozbuild 2014-09-24 03:05:32.000000000 +0200 +@@ -555,7 +557,6 @@ + 'fxa/activities/FxAccountCreateAccountActivity.java', + 'fxa/activities/FxAccountCreateAccountNotAllowedActivity.java', + 'fxa/activities/FxAccountGetStartedActivity.java', +- 'fxa/activities/FxAccountSetupTask.java', + 'fxa/activities/FxAccountSignInActivity.java', + 'fxa/activities/FxAccountStatusActivity.java', + 'fxa/activities/FxAccountStatusFragment.java', +@@ -589,6 +590,10 @@ + 'fxa/sync/FxAccountSyncService.java', + 'fxa/sync/FxAccountSyncStatusHelper.java', + 'fxa/sync/SchedulePolicy.java', ++ 'fxa/tasks/FxAccountCodeResender.java', ++ 'fxa/tasks/FxAccountCreateAccountTask.java', ++ 'fxa/tasks/FxAccountSetupTask.java', ++ 'fxa/tasks/FxAccountSignInTask.java', + 'sync/AlreadySyncingException.java', + 'sync/BackoffHandler.java', + 'sync/BadRequiredFieldJSONException.java', diff --git a/data/patches/gnuzilla-bug-1010972.patch b/data/patches/gnuzilla-bug-1010972.patch new file mode 100644 index 0000000..94eaf2b --- /dev/null +++ b/data/patches/gnuzilla-bug-1010972.patch @@ -0,0 +1,11 @@ +--- a/media/libyuv/source/cpu_id.cc 2015-01-27 19:15:52.069520584 +0100 ++++ b/media/libyuv/source/cpu_id.cc 2015-01-30 00:50:10.425853894 +0100 +@@ -97,7 +97,7 @@ + uint32 xcr0 = 0u; + #if defined(_MSC_VER) && defined(_XCR_XFEATURE_ENABLED_MASK) + xcr0 = (uint32)(_xgetbv(_XCR_XFEATURE_ENABLED_MASK)); +-#elif defined(_M_IX86) ++#elif defined(_MSC_VER) && defined(_M_IX86) + __asm { + xor ecx, ecx // xcr 0 + _asm _emit 0x0f _asm _emit 0x01 _asm _emit 0xd0 // For VS2010 and earlier. diff --git a/data/patches/gnuzilla-bug-1025689.patch b/data/patches/gnuzilla-bug-1025689.patch new file mode 100644 index 0000000..9364624 --- /dev/null +++ b/data/patches/gnuzilla-bug-1025689.patch @@ -0,0 +1,19 @@ +diff -ru mozilla-esr31/media/libopus/moz.build icecat-31.4.0/media/libopus/moz.build +--- a/media/libopus/moz.build 2015-01-06 06:08:03.000000000 +0100 ++++ b/media/libopus/moz.build 2015-02-02 02:37:44.416803188 +0100 +@@ -23,10 +23,11 @@ + DEFINES['OPUS_ARM_ASM'] = True + DEFINES['OPUS_ARM_EXTERNAL_ASM'] = True + DEFINES['OPUS_ARM_INLINE_ASM'] = True +- DEFINES['OPUS_ARM_INLINE_EDSP'] = True +- DEFINES['OPUS_ARM_MAY_HAVE_EDSP'] = True +- DEFINES['OPUS_ARM_MAY_HAVE_MEDIA'] = True +- DEFINES['OPUS_ARM_MAY_HAVE_NEON'] = True ++ if int(CONFIG['ARM_ARCH']) >= 6: ++ DEFINES['OPUS_ARM_INLINE_EDSP'] = True ++ DEFINES['OPUS_ARM_MAY_HAVE_EDSP'] = True ++ DEFINES['OPUS_ARM_MAY_HAVE_MEDIA'] = True ++ DEFINES['OPUS_ARM_MAY_HAVE_NEON'] = True + + if CONFIG['MOZ_DEBUG']: + DEFINES['ENABLE_ASSERTIONS'] = True diff --git a/data/patches/gnuzilla-bug-1030899-1.patch b/data/patches/gnuzilla-bug-1030899-1.patch new file mode 100644 index 0000000..5f3d3ba --- /dev/null +++ b/data/patches/gnuzilla-bug-1030899-1.patch @@ -0,0 +1,60 @@ +--- a/ipc/chromium/src/third_party/libevent-dont-use-issetugid-on-android.patch ++++ a/ipc/chromium/src/third_party/libevent-dont-use-issetugid-on-android.patch +@@ -0,0 +1,22 @@ ++diff --git a/ipc/chromium/src/third_party/libevent/android/event2/event-config.h b/ipc/chromium/src/third_party/libevent/android/event2/event-config.h ++--- a/ipc/chromium/src/third_party/libevent/android/event2/event-config.h +++++ b/ipc/chromium/src/third_party/libevent/android/event2/event-config.h ++@@ -119,17 +119,17 @@ ++ ++ /* Define to 1 if you have the `inet_pton' function. */ ++ #define _EVENT_HAVE_INET_PTON 1 ++ ++ /* Define to 1 if you have the header file. */ ++ #define _EVENT_HAVE_INTTYPES_H 1 ++ ++ /* Define to 1 if you have the `issetugid' function. */ ++-#define _EVENT_HAVE_ISSETUGID 1 +++/* #undef _EVENT_HAVE_ISSETUGID */ ++ ++ /* Define to 1 if you have the `kqueue' function. */ ++ /* #undef _EVENT_HAVE_KQUEUE */ ++ ++ /* Define if the system has zlib */ ++ #define _EVENT_HAVE_LIBZ 1 ++ ++ /* Define to 1 if you have the header file. */ +--- a/ipc/chromium/src/third_party/libevent/README.mozilla ++++ a/ipc/chromium/src/third_party/libevent/README.mozilla +@@ -10,8 +10,10 @@ These files are taken from libevent-2.0. + + 2. This is ugly, prepare yourself. OS X has a weird problem with how the "TAILQ_END(head)" is used, causing a linking error. Just replace all use of the "TAILQ_END(head)" macro with "NULL". + + 3. Apply "add mac-arc4random-buf.patch", which removes some bad OS X compatibility code. This will allow libevent to compile on all supported versions of OS X. + + 4. Apply "openbsd-no-arc4random_addrandom.patch", which fixes the build on OpenBSD (which doesnt provide arc4random_addrandom anymore, see #931354) + + 5. Apply "libevent-use-non-deprecated-syscalls.patch", which fixes the build on AArch64 architecture (which does not provide deprecated syscalls) ++ ++6. Apply "libevent-dont-use-issetugid-on-android.patch'. which fixes the build on Android L preview +--- a/ipc/chromium/src/third_party/libevent/android/event2/event-config.h ++++ a/ipc/chromium/src/third_party/libevent/android/event2/event-config.h +@@ -119,17 +119,17 @@ + + /* Define to 1 if you have the `inet_pton' function. */ + #define _EVENT_HAVE_INET_PTON 1 + + /* Define to 1 if you have the header file. */ + #define _EVENT_HAVE_INTTYPES_H 1 + + /* Define to 1 if you have the `issetugid' function. */ +-#define _EVENT_HAVE_ISSETUGID 1 ++/* #undef _EVENT_HAVE_ISSETUGID */ + + /* Define to 1 if you have the `kqueue' function. */ + /* #undef _EVENT_HAVE_KQUEUE */ + + /* Define if the system has zlib */ + #define _EVENT_HAVE_LIBZ 1 + + /* Define to 1 if you have the header file. */ + diff --git a/data/patches/gnuzilla-bug-1030899-2.patch b/data/patches/gnuzilla-bug-1030899-2.patch new file mode 100644 index 0000000..55728ab --- /dev/null +++ b/data/patches/gnuzilla-bug-1030899-2.patch @@ -0,0 +1,59 @@ +--- a/memory/mozjemalloc/jemalloc.c ++++ a/memory/mozjemalloc/jemalloc.c +@@ -220,17 +220,16 @@ + + #ifdef MALLOC_PAGEFILE + /* Write size when initializing a page file. */ + # define MALLOC_PAGEFILE_WRITE_SIZE 512 + #endif + + #if defined(MOZ_MEMORY_LINUX) && !defined(MOZ_MEMORY_ANDROID) + #define _GNU_SOURCE /* For mremap(2). */ +-#define issetugid() 0 + #if 0 /* Enable in order to test decommit code on Linux. */ + # define MALLOC_DECOMMIT + #endif + #endif + + #include + + #include +@@ -261,17 +260,16 @@ + #define vsnprintf _vsnprintf + + #ifndef NO_TLS + static unsigned long tlsIndex = 0xffffffff; + #endif + + #define __thread + #define _pthread_self() __threadid() +-#define issetugid() 0 + + /* use MSVC intrinsics */ + #pragma intrinsic(_BitScanForward) + static __forceinline int + ffs(int x) + { + unsigned long i; + +@@ -5675,18 +5673,17 @@ malloc_init_hard(void) + #endif + { + /* No configuration specified. */ + buf[0] = '\0'; + opts = buf; + } + break; + case 1: +- if (issetugid() == 0 && (opts = +- getenv("MALLOC_OPTIONS")) != NULL) { ++ if ((opts = getenv("MALLOC_OPTIONS")) != NULL) { + /* + * Do nothing; opts is already initialized to + * the value of the MALLOC_OPTIONS environment + * variable. + */ + } else { + /* No configuration specified. */ + buf[0] = '\0'; + diff --git a/data/patches/gnuzilla-bug-1032460.patch b/data/patches/gnuzilla-bug-1032460.patch new file mode 100644 index 0000000..08f37c3 --- /dev/null +++ b/data/patches/gnuzilla-bug-1032460.patch @@ -0,0 +1,16 @@ +https://bugzilla.mozilla.org/show_bug.cgi?id=1032460 + +diff -ru icecat-31.4.0.orig/widget/android/AndroidBridge.cpp icecat-31.4.0/widget/android/AndroidBridge.cpp +--- icecat-31.4.0.orig/widget/android/AndroidBridge.cpp 2015-02-22 19:11:11.834816575 +0100 ++++ icecat-31.4.0/widget/android/AndroidBridge.cpp 2015-02-22 19:16:16.226810788 +0100 +@@ -199,7 +199,9 @@ + + jclass eglClass = getClassGlobalRef("com/google/android/gles_jni/EGLSurfaceImpl"); + if (eglClass) { +- jEGLSurfacePointerField = getField("mEGLSurface", "I"); ++ // The pointer type moved to a 'long' in Android L, API version 20 ++ const char* jniType = mAPIVersion >= 20 ? "J" : "I"; ++ jEGLSurfacePointerField = getField("mEGLSurface", jniType); + } else { + jEGLSurfacePointerField = 0; + } diff --git a/data/patches/gnuzilla-bug-1034167-1.patch b/data/patches/gnuzilla-bug-1034167-1.patch new file mode 100644 index 0000000..410b2cf --- /dev/null +++ b/data/patches/gnuzilla-bug-1034167-1.patch @@ -0,0 +1,93 @@ +# HG changeset patch +# User Lucas Rocha + +Bug 1034167 - Part 2: Explicit set scrollbars in TabsTray (r=mfinkle) + +diff --git a/mobile/android/base/resources/values-land/styles.xml b/mobile/android/base/resources/values-land/styles.xml +index 774c48e..6769b1e 100644 +--- a/mobile/android/base/resources/values-land/styles.xml ++++ b/mobile/android/base/resources/values-land/styles.xml +@@ -2,16 +2,17 @@ + + + + + + + + + + + + +diff --git a/mobile/android/base/resources/values-large-v11/styles.xml b/mobile/android/base/resources/values-large-v11/styles.xml +index 7a22789..a6a6221 100644 +--- a/mobile/android/base/resources/values-large-v11/styles.xml ++++ b/mobile/android/base/resources/values-large-v11/styles.xml +@@ -25,16 +25,17 @@ + 6dp + 6dp + + horizontal + + + + + + + + + + + + +