diff options
author | Ruben Rodriguez <ruben@gnu.org> | 2015-03-08 15:14:03 +0000 |
---|---|---|
committer | Ruben Rodriguez <ruben@gnu.org> | 2015-03-08 15:14:03 +0000 |
commit | 9a0fd32cf6e2ada37675bc743532c5004b16e5e3 (patch) | |
tree | d66faa3279486d2e8ad72bf70e7ef522460372a0 /data/patches | |
parent | 2732de330618bc29b7ab07f4004d8e84f698cf95 (diff) |
Added patches to fix build bugs for Windows, MacOS and Android
Diffstat (limited to 'data/patches')
29 files changed, 5953 insertions, 0 deletions
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 <mhammond@skippinet.com.au> +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 <mhammond@skippinet.com.au> +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 <mhammond@skippinet.com.au> +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 <mhammond@skippinet.com.au> +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 <mhammond@skippinet.com.au> +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 @@ + + <vbox align="start"> + <checkbox label="&engine.tabs.label;" + accesskey="&engine.tabs.accesskey;" + preference="engine.tabs"/> + <checkbox label="&engine.bookmarks.label;" + accesskey="&engine.bookmarks.accesskey;" + preference="engine.bookmarks"/> +- <checkbox id="fxa-pweng-chk" +- label="&engine.passwords.label;" ++ <checkbox label="&engine.passwords.label;" + accesskey="&engine.passwords.accesskey;" + preference="engine.passwords"/> + <checkbox label="&engine.history.label;" + accesskey="&engine.history.accesskey;" + preference="engine.history"/> + <checkbox label="&engine.addons.label;" + accesskey="&engine.addons.accesskey;" + preference="engine.addons"/> +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 @@ + <hbox id="fxaSyncEngines"> + <vbox> + <checkbox label="&engine.tabs.label;" + accesskey="&engine.tabs.accesskey;" + preference="engine.tabs"/> + <checkbox label="&engine.bookmarks.label;" + accesskey="&engine.bookmarks.accesskey;" + preference="engine.bookmarks"/> +- <hbox> +- <checkbox id="fxa-pweng-chk" +- label="&engine.passwords.label;" +- accesskey="&engine.passwords.accesskey;" +- preference="engine.passwords"/> +- +- <vbox id="fxa-pweng-help"> +- <spacer flex="1"/> +- <hbox id="fxa-pweng-help-link"> +- <image onclick="gSyncUtils.openMPInfoPage(event);" /> +- </hbox> +- <spacer flex="1"/> +- </vbox> +- </hbox> ++ <checkbox label="&engine.passwords.label;" ++ accesskey="&engine.passwords.accesskey;" ++ preference="engine.passwords"/> + <checkbox label="&engine.history.label;" + accesskey="&engine.history.accesskey;" + preference="engine.history"/> + <checkbox label="&engine.addons.label;" + accesskey="&engine.addons.accesskey;" + preference="engine.addons"/> + <checkbox label="&engine.prefs.label;" + accesskey="&engine.prefs.accesskey;" +diff --git a/browser/components/preferences/sync.js b/browser/components/preferences/sync.js +--- a/browser/components/preferences/sync.js ++++ b/browser/components/preferences/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/sync.xul b/browser/components/preferences/sync.xul +--- a/browser/components/preferences/sync.xul ++++ b/browser/components/preferences/sync.xul +@@ -260,30 +260,19 @@ + <hbox id="fxaSyncEngines"> + <vbox> + <checkbox label="&engine.tabs.label;" + accesskey="&engine.tabs.accesskey;" + preference="engine.tabs"/> + <checkbox label="&engine.bookmarks.label;" + accesskey="&engine.bookmarks.accesskey;" + preference="engine.bookmarks"/> +- <hbox> +- <checkbox id="fxa-pweng-chk" +- label="&engine.passwords.label;" +- accesskey="&engine.passwords.accesskey;" +- preference="engine.passwords"/> +- +- <vbox id="fxa-pweng-help"> +- <spacer flex="1"/> +- <hbox id="fxa-pweng-help-link"> +- <image onclick="gSyncUtils.openMPInfoPage(event);" /> +- </hbox> +- <spacer flex="1"/> +- </vbox> +- </hbox> ++ <checkbox label="&engine.passwords.label;" ++ accesskey="&engine.passwords.accesskey;" ++ preference="engine.passwords"/> + <checkbox label="&engine.history.label;" + accesskey="&engine.history.accesskey;" + preference="engine.history"/> + <checkbox label="&engine.addons.label;" + accesskey="&engine.addons.accesskey;" + preference="engine.addons"/> + <checkbox label="&engine.prefs.label;" + accesskey="&engine.prefs.accesskey;" +diff --git a/browser/themes/linux/preferences/preferences.css b/browser/themes/linux/preferences/preferences.css +--- a/browser/themes/linux/preferences/preferences.css ++++ b/browser/themes/linux/preferences/preferences.css +@@ -166,17 +166,9 @@ label.small { + margin: 5px; + 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/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 <code>null</code> 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<SyncHint> 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<Void> { +- protected static final String LOG_TAG = FxAccountResendCodeTask.class.getSimpleName(); +- +- protected final byte[] sessionToken; +- +- public FxAccountResendCodeTask(Context context, byte[] sessionToken, FxAccountClient client, RequestDelegate<Void> delegate) { +- super(context, null, client, delegate); +- this.sessionToken = sessionToken; +- } +- +- @Override +- protected InnerRequestDelegate<Void> 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<Void> { +- 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<Void> 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 <code>AsyncTask</code> wrapper around signing up for, and signing in to, a +- * Firefox Account. +- * <p> +- * 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<T> extends AsyncTask<Void, Void, InnerRequestDelegate<T>> { +- 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<T> innerDelegate = new InnerRequestDelegate<T>(latch); +- +- protected final RequestDelegate<T> delegate; +- +- public FxAccountSetupTask(Context context, ProgressDisplay progressDisplay, FxAccountClient client, RequestDelegate<T> 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<T> 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<T> result) { +- if (progressDisplay != null) { +- progressDisplay.dismissProgress(); +- } +- delegate.handleError(new IllegalStateException("Task was cancelled.")); +- } +- +- protected static class InnerRequestDelegate<T> implements RequestDelegate<T> { +- 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<LoginResponse> { +- 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<LoginResponse> delegate) throws UnsupportedEncodingException { +- super(context, progressDisplay, client, delegate); +- this.emailUTF8 = email.getBytes("UTF-8"); +- this.passwordStretcher = passwordStretcher; +- } +- +- @Override +- protected InnerRequestDelegate<LoginResponse> 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<LoginResponse> { +- 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<LoginResponse> delegate) throws UnsupportedEncodingException { +- super(context, progressDisplay, client, delegate); +- this.emailUTF8 = email.getBytes("UTF-8"); +- this.passwordStretcher = passwordStretcher; +- } +- +- @Override +- protected InnerRequestDelegate<LoginResponse> 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<Void> { ++ protected static final String LOG_TAG = FxAccountResendCodeTask.class.getSimpleName(); ++ ++ protected final byte[] sessionToken; ++ ++ public FxAccountResendCodeTask(Context context, byte[] sessionToken, FxAccountClient client, RequestDelegate<Void> delegate) { ++ super(context, null, client, delegate); ++ this.sessionToken = sessionToken; ++ } ++ ++ @Override ++ protected InnerRequestDelegate<Void> 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<Void> { ++ 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<Void> 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<LoginResponse> { ++ 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<LoginResponse> delegate) throws UnsupportedEncodingException { ++ super(context, progressDisplay, client, delegate); ++ this.emailUTF8 = email.getBytes("UTF-8"); ++ this.passwordStretcher = passwordStretcher; ++ } ++ ++ @Override ++ protected InnerRequestDelegate<LoginResponse> 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 <code>AsyncTask</code> wrapper around signing up for, and signing in to, a ++ * Firefox Account. ++ * <p> ++ * 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<T> extends AsyncTask<Void, Void, InnerRequestDelegate<T>> { ++ 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<T> innerDelegate = new InnerRequestDelegate<T>(latch); ++ ++ protected final RequestDelegate<T> delegate; ++ ++ public FxAccountSetupTask(Context context, ProgressDisplay progressDisplay, FxAccountClient client, RequestDelegate<T> 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<T> 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<T> result) { ++ if (progressDisplay != null) { ++ progressDisplay.dismissProgress(); ++ } ++ delegate.handleError(new IllegalStateException("Task was cancelled.")); ++ } ++ ++ protected static class InnerRequestDelegate<T> implements RequestDelegate<T> { ++ 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<LoginResponse> { ++ 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<LoginResponse> delegate) throws UnsupportedEncodingException { ++ super(context, progressDisplay, client, delegate); ++ this.emailUTF8 = email.getBytes("UTF-8"); ++ this.passwordStretcher = passwordStretcher; ++ } ++ ++ @Override ++ protected InnerRequestDelegate<LoginResponse> 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 <code>null</code> 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<SyncHint> 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<Void> { +- protected static final String LOG_TAG = FxAccountResendCodeTask.class.getSimpleName(); +- +- protected final byte[] sessionToken; +- +- public FxAccountResendCodeTask(Context context, byte[] sessionToken, FxAccountClient client, RequestDelegate<Void> delegate) { +- super(context, null, client, delegate); +- this.sessionToken = sessionToken; +- } +- +- @Override +- protected InnerRequestDelegate<Void> 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<Void> { +- 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<Void> 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 <code>AsyncTask</code> wrapper around signing up for, and signing in to, a +- * Firefox Account. +- * <p> +- * 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<T> extends AsyncTask<Void, Void, InnerRequestDelegate<T>> { +- 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<T> innerDelegate = new InnerRequestDelegate<T>(latch); +- +- protected final RequestDelegate<T> delegate; +- +- public FxAccountSetupTask(Context context, ProgressDisplay progressDisplay, FxAccountClient client, RequestDelegate<T> 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<T> 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<T> result) { +- if (progressDisplay != null) { +- progressDisplay.dismissProgress(); +- } +- delegate.handleError(new IllegalStateException("Task was cancelled.")); +- } +- +- protected static class InnerRequestDelegate<T> implements RequestDelegate<T> { +- 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<LoginResponse> { +- 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<LoginResponse> delegate) throws UnsupportedEncodingException { +- super(context, progressDisplay, client, delegate); +- this.emailUTF8 = email.getBytes("UTF-8"); +- this.passwordStretcher = passwordStretcher; +- } +- +- @Override +- protected InnerRequestDelegate<LoginResponse> 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<LoginResponse> { +- 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<LoginResponse> delegate) throws UnsupportedEncodingException { +- super(context, progressDisplay, client, delegate); +- this.emailUTF8 = email.getBytes("UTF-8"); +- this.passwordStretcher = passwordStretcher; +- } +- +- @Override +- protected InnerRequestDelegate<LoginResponse> 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<Void> { ++ protected static final String LOG_TAG = FxAccountResendCodeTask.class.getSimpleName(); ++ ++ protected final byte[] sessionToken; ++ ++ public FxAccountResendCodeTask(Context context, byte[] sessionToken, FxAccountClient client, RequestDelegate<Void> delegate) { ++ super(context, null, client, delegate); ++ this.sessionToken = sessionToken; ++ } ++ ++ @Override ++ protected InnerRequestDelegate<Void> 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<Void> { ++ 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<Void> 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<LoginResponse> { ++ 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<LoginResponse> delegate) throws UnsupportedEncodingException { ++ super(context, progressDisplay, client, delegate); ++ this.emailUTF8 = email.getBytes("UTF-8"); ++ this.passwordStretcher = passwordStretcher; ++ } ++ ++ @Override ++ protected InnerRequestDelegate<LoginResponse> 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 <code>AsyncTask</code> wrapper around signing up for, and signing in to, a ++ * Firefox Account. ++ * <p> ++ * 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<T> extends AsyncTask<Void, Void, InnerRequestDelegate<T>> { ++ 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<T> innerDelegate = new InnerRequestDelegate<T>(latch); ++ ++ protected final RequestDelegate<T> delegate; ++ ++ public FxAccountSetupTask(Context context, ProgressDisplay progressDisplay, FxAccountClient client, RequestDelegate<T> 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<T> 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<T> result) { ++ if (progressDisplay != null) { ++ progressDisplay.dismissProgress(); ++ } ++ delegate.handleError(new IllegalStateException("Task was cancelled.")); ++ } ++ ++ protected static class InnerRequestDelegate<T> implements RequestDelegate<T> { ++ 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<LoginResponse> { ++ 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<LoginResponse> delegate) throws UnsupportedEncodingException { ++ super(context, progressDisplay, client, delegate); ++ this.emailUTF8 = email.getBytes("UTF-8"); ++ this.passwordStretcher = passwordStretcher; ++ } ++ ++ @Override ++ protected InnerRequestDelegate<LoginResponse> 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 <inttypes.h> 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 <memory.h> 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 <inttypes.h> 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 <memory.h> 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 <sys/types.h> + + #include <errno.h> +@@ -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 <lucasr@mozilla.com> + +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 @@ + <!-- 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/. --> + + <resources> + + <style name="TabsList" parent="TabsListBase"> + <item name="android:orientation">horizontal</item> ++ <item name="android:scrollbars">horizontal</item> + </style> + + <style name="TabsItem"> + <item name="android:nextFocusDown">@+id/close</item> + </style> + + <style name="TabsItemClose"> + <item name="android:nextFocusUp">@+id/info</item> +diff --git a/mobile/android/base/resources/values-large-land-v11/styles.xml b/mobile/android/base/resources/values-large-land-v11/styles.xml +index 557b659..1468914 100644 +--- a/mobile/android/base/resources/values-large-land-v11/styles.xml ++++ b/mobile/android/base/resources/values-large-land-v11/styles.xml +@@ -2,16 +2,17 @@ + <!-- 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/. --> + + <resources> + + <style name="TabsList" parent="TabsListBase"> + <item name="android:orientation">vertical</item> ++ <item name="android:scrollbars">vertical</item> + </style> + + <style name="Widget.BookmarkFolderView" parent="Widget.TwoLinePageRow.Title"> + <item name="android:paddingLeft">60dip</item> + <item name="android:drawablePadding">10dip</item> + <item name="android:drawableLeft">@drawable/bookmark_folder</item> + </style> + +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 @@ + <item name="android:layout_marginTop">6dp</item> + <item name="android:layout_marginBottom">6dp</item> + <!-- Start with forward hidden --> + <item name="android:orientation">horizontal</item> + </style> + + <style name="TabsList" parent="TabsListBase"> + <item name="android:orientation">horizontal</item> ++ <item name="android:scrollbars">horizontal</item> + </style> + + <style name="TabsItem"> + <item name="android:nextFocusDown">@+id/close</item> + </style> + + <style name="TabsItemClose"> + <item name="android:nextFocusUp">@+id/info</item> +diff --git a/mobile/android/base/resources/values/styles.xml b/mobile/android/base/resources/values/styles.xml +index a15f72e..f479180 100644 +--- a/mobile/android/base/resources/values/styles.xml ++++ b/mobile/android/base/resources/values/styles.xml +@@ -430,16 +430,17 @@ + <!-- TabsTray List --> + <style name="TabsListBase"> + <item name="android:background">@android:color/transparent</item> + <item name="android:listSelector">@android:color/transparent</item> + </style> + + <style name="TabsList" parent="TabsListBase"> + <item name="android:orientation">vertical</item> ++ <item name="android:scrollbars">vertical</item> + </style> + + <style name="TabsItem"> + <item name="android:nextFocusRight">@+id/close</item> + </style> + + <style name="TabsItemClose"> + <item name="android:nextFocusLeft">@+id/info</item> diff --git a/data/patches/gnuzilla-bug-1034167-2.patch b/data/patches/gnuzilla-bug-1034167-2.patch new file mode 100644 index 0000000..3df67a0 --- /dev/null +++ b/data/patches/gnuzilla-bug-1034167-2.patch @@ -0,0 +1,1205 @@ +# HG changeset patch +# User Lucas Rocha <lucasr@mozilla.com> + +Bug 1034167 - Part 1: Update TwoWayView from upstream (r=mfinkle) + +diff --git a/mobile/android/base/resources/values/attrs.xml b/mobile/android/base/resources/values/attrs.xml +index 0c27b87..7140292 100644 +--- a/mobile/android/base/resources/values/attrs.xml ++++ b/mobile/android/base/resources/values/attrs.xml +@@ -117,91 +117,20 @@ + + <declare-styleable name="LightweightTheme"> + <attr name="state_light" format="boolean"/> + <attr name="state_dark" format="boolean"/> + <attr name="autoUpdateTheme" format="boolean"/> + </declare-styleable> + + <declare-styleable name="TwoWayView"> +- <!-- Imported from View --> +- <attr name="android:id"/> +- <attr name="android:tag"/> +- <attr name="android:scrollX"/> +- <attr name="android:scrollY"/> +- <attr name="android:background"/> +- <attr name="android:padding"/> +- <attr name="android:paddingLeft"/> +- <attr name="android:paddingTop"/> +- <attr name="android:paddingRight"/> +- <attr name="android:paddingBottom"/> +- <attr name="android:paddingStart"/> +- <attr name="android:paddingEnd"/> +- <attr name="android:focusable"/> +- <attr name="android:focusableInTouchMode"/> +- <attr name="android:visibility"/> +- <attr name="android:fitsSystemWindows"/> +- <attr name="android:scrollbars"/> +- <attr name="android:scrollbarStyle"/> +- <attr name="android:isScrollContainer"/> +- <attr name="android:fadeScrollbars"/> +- <attr name="android:scrollbarFadeDuration"/> +- <attr name="android:scrollbarDefaultDelayBeforeFade"/> +- <attr name="android:scrollbarSize"/> +- <attr name="android:scrollbarThumbHorizontal"/> +- <attr name="android:scrollbarThumbVertical"/> +- <attr name="android:scrollbarTrackHorizontal"/> +- <attr name="android:scrollbarTrackVertical"/> +- <attr name="android:scrollbarAlwaysDrawHorizontalTrack"/> +- <attr name="android:scrollbarAlwaysDrawVerticalTrack"/> +- <attr name="android:fadingEdge"/> +- <attr name="android:requiresFadingEdge"/> +- <attr name="android:fadingEdgeLength"/> +- <attr name="android:nextFocusLeft"/> +- <attr name="android:nextFocusRight"/> +- <attr name="android:nextFocusUp"/> +- <attr name="android:nextFocusDown"/> +- <attr name="android:nextFocusForward"/> +- <attr name="android:clickable"/> +- <attr name="android:longClickable"/> +- <attr name="android:saveEnabled"/> +- <attr name="android:filterTouchesWhenObscured"/> +- <attr name="android:drawingCacheQuality"/> +- <attr name="android:keepScreenOn"/> +- <attr name="android:duplicateParentState"/> +- <attr name="android:minHeight"/> +- <attr name="android:minWidth"/> +- <attr name="android:soundEffectsEnabled"/> +- <attr name="android:hapticFeedbackEnabled"/> +- <attr name="android:contentDescription"/> +- <attr name="android:onClick"/> +- <attr name="android:overScrollMode"/> +- <attr name="android:alpha"/> +- <attr name="android:translationX"/> +- <attr name="android:translationY"/> +- <attr name="android:transformPivotX"/> +- <attr name="android:transformPivotY"/> +- <attr name="android:rotation"/> +- <attr name="android:rotationX"/> +- <attr name="android:rotationY"/> +- <attr name="android:scaleX"/> +- <attr name="android:scaleY"/> +- <attr name="android:verticalScrollbarPosition"/> +- <attr name="android:layerType"/> +- <attr name="android:layoutDirection"/> +- <attr name="android:textDirection"/> +- <attr name="android:textAlignment"/> +- +- <!-- Imported from Android --> + <attr name="android:orientation"/> +- +- <!-- Imported from AbsListView --> + <attr name="android:choiceMode"/> +- <attr name="android:drawSelectorOnTop"/> + <attr name="android:listSelector"/> ++ <attr name="android:drawSelectorOnTop"/> + </declare-styleable> + + <declare-styleable name="HomeListView"> + <!-- Draws a divider on top of the list, if true. Defaults to false. --> + <attr name="topDivider" format="boolean"/> + </declare-styleable> + + <declare-styleable name="HomePagerTabStrip"> +--- a/mobile/android/base/widget/TwoWayView.java 2015-01-06 05:07:55.000000000 +0000 ++++ b/mobile/android/base/widget/TwoWayView.java 2015-02-23 19:46:01.737065730 +0000 +@@ -23,6 +23,9 @@ + + import org.mozilla.gecko.R; + ++import java.util.ArrayList; ++import java.util.List; ++ + import android.annotation.TargetApi; + import android.content.Context; + import android.content.res.TypedArray; +@@ -67,8 +70,7 @@ + import android.widget.ListAdapter; + import android.widget.Scroller; + +-import java.util.ArrayList; +-import java.util.List; ++import static android.os.Build.VERSION_CODES.HONEYCOMB; + + /* + * Implementation Notes: +@@ -136,8 +138,8 @@ + + public static enum Orientation { + HORIZONTAL, +- VERTICAL; +- }; ++ VERTICAL ++ } + + private ListAdapter mAdapter; + +@@ -266,7 +268,7 @@ + * Callback method to be invoked while the list view or grid view is being scrolled. If the + * view is being scrolled, this method will be called before the next frame of the scroll is + * rendered. In particular, it will be called before any calls to +- * {@link Adapter#getView(int, View, ViewGroup)}. ++ * {@link android.widget.Adapter#getView(int, View, ViewGroup)}. + * + * @param view The view whose scroll state is being reported + * +@@ -388,7 +390,6 @@ + ViewCompat.setOverScrollMode(this, ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TwoWayView, defStyle, 0); +- initializeScrollbars(a); + + mDrawSelectorOnTop = a.getBoolean( + R.styleable.TwoWayView_android_drawSelectorOnTop, false); +@@ -409,19 +410,16 @@ + } + + a.recycle(); +- +- updateScrollbarsDirection(); + } + + public void setOrientation(Orientation orientation) { +- final boolean isVertical = (orientation.compareTo(Orientation.VERTICAL) == 0); ++ final boolean isVertical = (orientation == Orientation.VERTICAL); + if (mIsVertical == isVertical) { + return; + } + + mIsVertical = isVertical; + +- updateScrollbarsDirection(); + resetState(); + mRecycler.clear(); + +@@ -441,6 +439,7 @@ + requestLayout(); + } + ++ @SuppressWarnings("unused") + public int getItemMargin() { + return mItemMargin; + } +@@ -451,6 +450,7 @@ + * + * @param itemsCanFocus true if items can get focus, false otherwise + */ ++ @SuppressWarnings("unused") + public void setItemsCanFocus(boolean itemsCanFocus) { + mItemsCanFocus = itemsCanFocus; + if (!itemsCanFocus) { +@@ -462,6 +462,7 @@ + * @return Whether the views created by the ListAdapter can contain focusable + * items. + */ ++ @SuppressWarnings("unused") + public boolean getItemsCanFocus() { + return mItemsCanFocus; + } +@@ -481,7 +482,7 @@ + * the recycler for later reuse. This listener can be used to free resources + * associated to the View. + * +- * @param listener The recycler listener to be notified of views set aside ++ * @param l The recycler listener to be notified of views set aside + * in the recycler. + * + * @see TwoWayView.RecycleBin +@@ -495,11 +496,12 @@ + * Controls whether the selection highlight drawable should be drawn on top of the item or + * behind it. + * +- * @param onTop If true, the selector will be drawn on the item it is highlighting. The default +- * is false. ++ * @param drawSelectorOnTop If true, the selector will be drawn on the item it is highlighting. ++ * The default is false. + * + * @attr ref android.R.styleable#AbsListView_drawSelectorOnTop + */ ++ @SuppressWarnings("unused") + public void setDrawSelectorOnTop(boolean drawSelectorOnTop) { + mDrawSelectorOnTop = drawSelectorOnTop; + } +@@ -511,6 +513,7 @@ + * + * @attr ref android.R.styleable#AbsListView_listSelector + */ ++ @SuppressWarnings("unused") + public void setSelector(int resID) { + setSelector(getResources().getDrawable(resID)); + } +@@ -542,6 +545,7 @@ + * + * @return the drawable used to display the selector + */ ++ @SuppressWarnings("unused") + public Drawable getSelector() { + return mSelector; + } +@@ -564,7 +568,7 @@ + + /** + * Returns the number of items currently selected. This will only be valid +- * if the choice mode is not {@link #CHOICE_MODE_NONE} (default). ++ * if the choice mode is not {@link ChoiceMode#NONE} (default). + * + * <p>To determine the specific items that are currently selected, use one of + * the <code>getChecked*</code> methods. +@@ -575,23 +579,24 @@ + * @see #getCheckedItemPositions() + * @see #getCheckedItemIds() + */ ++ @SuppressWarnings("unused") + public int getCheckedItemCount() { + return mCheckedItemCount; + } + + /** + * Returns the checked state of the specified position. The result is only +- * valid if the choice mode has been set to {@link #CHOICE_MODE_SINGLE} +- * or {@link #CHOICE_MODE_MULTIPLE}. ++ * valid if the choice mode has been set to {@link ChoiceMode#SINGLE} ++ * or {@link ChoiceMode#MULTIPLE}. + * + * @param position The item whose checked state to return + * @return The item's checked state or <code>false</code> if choice mode + * is invalid + * +- * @see #setChoiceMode(int) ++ * @see #setChoiceMode(ChoiceMode) + */ + public boolean isItemChecked(int position) { +- if (mChoiceMode.compareTo(ChoiceMode.NONE) == 0 && mCheckStates != null) { ++ if (mChoiceMode == ChoiceMode.NONE && mCheckStates != null) { + return mCheckStates.get(position); + } + +@@ -600,16 +605,15 @@ + + /** + * Returns the currently checked item. The result is only valid if the choice +- * mode has been set to {@link #CHOICE_MODE_SINGLE}. ++ * mode has been set to {@link ChoiceMode#SINGLE}. + * + * @return The position of the currently checked item or + * {@link #INVALID_POSITION} if nothing is selected + * +- * @see #setChoiceMode(int) ++ * @see #setChoiceMode(ChoiceMode) + */ + public int getCheckedItemPosition() { +- if (mChoiceMode.compareTo(ChoiceMode.SINGLE) == 0 && +- mCheckStates != null && mCheckStates.size() == 1) { ++ if (mChoiceMode == ChoiceMode.SINGLE && mCheckStates != null && mCheckStates.size() == 1) { + return mCheckStates.keyAt(0); + } + +@@ -618,15 +622,15 @@ + + /** + * Returns the set of checked items in the list. The result is only valid if +- * the choice mode has not been set to {@link #CHOICE_MODE_NONE}. ++ * the choice mode has not been set to {@link ChoiceMode#NONE}. + * + * @return A SparseBooleanArray which will return true for each call to + * get(int position) where position is a position in the list, + * or <code>null</code> if the choice mode is set to +- * {@link #CHOICE_MODE_NONE}. ++ * {@link ChoiceMode#NONE}. + */ + public SparseBooleanArray getCheckedItemPositions() { +- if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0) { ++ if (mChoiceMode != ChoiceMode.NONE) { + return mCheckStates; + } + +@@ -635,15 +639,14 @@ + + /** + * Returns the set of checked items ids. The result is only valid if the +- * choice mode has not been set to {@link #CHOICE_MODE_NONE} and the adapter ++ * choice mode has not been set to {@link ChoiceMode#NONE} and the adapter + * has stable IDs. ({@link ListAdapter#hasStableIds()} == {@code true}) + * + * @return A new array which contains the id of each checked item in the + * list. + */ + public long[] getCheckedItemIds() { +- if (mChoiceMode.compareTo(ChoiceMode.NONE) == 0 || +- mCheckedIdStates == null || mAdapter == null) { ++ if (mChoiceMode == ChoiceMode.NONE || mCheckedIdStates == null || mAdapter == null) { + return new long[0]; + } + +@@ -660,18 +663,19 @@ + + /** + * Sets the checked state of the specified position. The is only valid if +- * the choice mode has been set to {@link #CHOICE_MODE_SINGLE} or +- * {@link #CHOICE_MODE_MULTIPLE}. ++ * the choice mode has been set to {@link ChoiceMode#SINGLE} or ++ * {@link ChoiceMode#MULTIPLE}. + * + * @param position The item whose checked state is to be checked + * @param value The new checked state for the item + */ ++ @SuppressWarnings("unused") + public void setItemChecked(int position, boolean value) { +- if (mChoiceMode.compareTo(ChoiceMode.NONE) == 0) { ++ if (mChoiceMode == ChoiceMode.NONE) { + return; + } + +- if (mChoiceMode.compareTo(ChoiceMode.MULTIPLE) == 0) { ++ if (mChoiceMode == ChoiceMode.MULTIPLE) { + boolean oldValue = mCheckStates.get(position); + mCheckStates.put(position, value); + +@@ -729,6 +733,7 @@ + /** + * Clear any choices previously set + */ ++ @SuppressWarnings("unused") + public void clearChoices() { + if (mCheckStates != null) { + mCheckStates.clear(); +@@ -742,27 +747,28 @@ + } + + /** +- * @see #setChoiceMode(int) ++ * @see #setChoiceMode(ChoiceMode) + * + * @return The current choice mode + */ ++ @SuppressWarnings("unused") + public ChoiceMode getChoiceMode() { + return mChoiceMode; + } + + /** + * Defines the choice behavior for the List. By default, Lists do not have any choice behavior +- * ({@link #CHOICE_MODE_NONE}). By setting the choiceMode to {@link #CHOICE_MODE_SINGLE}, the ++ * ({@link ChoiceMode#NONE}). By setting the choiceMode to {@link ChoiceMode#SINGLE}, the + * List allows up to one item to be in a chosen state. By setting the choiceMode to +- * {@link #CHOICE_MODE_MULTIPLE}, the list allows any number of items to be chosen. ++ * {@link ChoiceMode#MULTIPLE}, the list allows any number of items to be chosen. + * +- * @param choiceMode One of {@link #CHOICE_MODE_NONE}, {@link #CHOICE_MODE_SINGLE}, or +- * {@link #CHOICE_MODE_MULTIPLE} ++ * @param choiceMode One of {@link ChoiceMode#NONE}, {@link ChoiceMode#SINGLE}, or ++ * {@link ChoiceMode#MULTIPLE} + */ + public void setChoiceMode(ChoiceMode choiceMode) { + mChoiceMode = choiceMode; + +- if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0) { ++ if (mChoiceMode != ChoiceMode.NONE) { + if (mCheckStates == null) { + mCheckStates = new SparseBooleanArray(); + } +@@ -813,8 +819,7 @@ + mHasStableIds = adapter.hasStableIds(); + mAreAllItemsSelectable = adapter.areAllItemsEnabled(); + +- if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0 && mHasStableIds && +- mCheckedIdStates == null) { ++ if (mChoiceMode != ChoiceMode.NONE && mHasStableIds && mCheckedIdStates == null) { + mCheckedIdStates = new LongSparseArray<Integer>(); + } + +@@ -1268,7 +1273,7 @@ + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { +- if (!mIsAttached) { ++ if (!mIsAttached || mAdapter == null) { + return false; + } + +@@ -1354,7 +1359,7 @@ + return isClickable() || isLongClickable(); + } + +- if (!mIsAttached) { ++ if (!mIsAttached || mAdapter == null) { + return false; + } + +@@ -1477,7 +1482,7 @@ + final float x = ev.getX(); + final float y = ev.getY(); + +- boolean inList = false; ++ final boolean inList; + if (mIsVertical) { + inList = x > getPaddingLeft() && x < getWidth() - getPaddingRight(); + } else { +@@ -1731,7 +1736,7 @@ + } + + // TODO: Use some form of smooth scroll instead +- trackMotionScroll(viewportSize); ++ scrollListItemsBy(viewportSize); + return true; + } + return false; +@@ -1746,7 +1751,7 @@ + } + + // TODO: Use some form of smooth scroll instead +- trackMotionScroll(-viewportSize); ++ scrollListItemsBy(-viewportSize); + return true; + } + return false; +@@ -2077,7 +2082,7 @@ + newFocus = FocusFinder.getInstance().findNextFocus(this, oldFocus, direction); + } else { + if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) { +- final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); ++ final int start = getStartEdge(); + + final int selectedStart; + if (selectedView != null) { +@@ -2088,12 +2093,11 @@ + + searchPoint = Math.max(selectedStart, start); + } else { +- final int end = (mIsVertical ? getHeight() - getPaddingBottom() : +- getWidth() - getPaddingRight()); ++ final int end = getEndEdge(); + + final int selectedEnd; + if (selectedView != null) { +- selectedEnd = (mIsVertical ? selectedView.getBottom() : selectedView.getRight()); ++ selectedEnd = getChildEndEdge(selectedView); + } else { + selectedEnd = end; + } +@@ -2242,7 +2246,7 @@ + } + + if (amountToScroll > 0) { +- trackMotionScroll(direction == View.FOCUS_UP || direction == View.FOCUS_LEFT ? ++ scrollListItemsBy(direction == View.FOCUS_UP || direction == View.FOCUS_LEFT ? + amountToScroll : -amountToScroll); + needToRedraw = true; + } +@@ -2304,8 +2308,7 @@ + final int numChildren = getChildCount(); + + if (direction == View.FOCUS_DOWN || direction == View.FOCUS_RIGHT) { +- final int end = (mIsVertical ? getHeight() - getPaddingBottom() : +- getWidth() - getPaddingRight()); ++ final int end = getEndEdge(); + + int indexToMakeVisible = numChildren - 1; + if (nextSelectedPosition != INVALID_POSITION) { +@@ -2320,10 +2323,8 @@ + goalEnd -= getArrowScrollPreviewLength(); + } + +- final int viewToMakeVisibleStart = +- (mIsVertical ? viewToMakeVisible.getTop() : viewToMakeVisible.getLeft()); +- final int viewToMakeVisibleEnd = +- (mIsVertical ? viewToMakeVisible.getBottom() : viewToMakeVisible.getRight()); ++ final int viewToMakeVisibleStart = getChildStartEdge(viewToMakeVisible); ++ final int viewToMakeVisibleEnd = getChildEndEdge(viewToMakeVisible); + + if (viewToMakeVisibleEnd <= goalEnd) { + // Target item is fully visible +@@ -2339,8 +2340,7 @@ + int amountToScroll = (viewToMakeVisibleEnd - goalEnd); + + if (mFirstPosition + numChildren == mItemCount) { +- final View lastChild = getChildAt(numChildren - 1); +- final int lastChildEnd = (mIsVertical ? lastChild.getBottom() : lastChild.getRight()); ++ final int lastChildEnd = getChildEndEdge(getChildAt(numChildren - 1)); + + // Last is last in list -> Make sure we don't scroll past it + final int max = lastChildEnd - end; +@@ -2349,7 +2349,7 @@ + + return Math.min(amountToScroll, getMaxScrollAmount()); + } else { +- final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); ++ final int start = getStartEdge(); + + int indexToMakeVisible = 0; + if (nextSelectedPosition != INVALID_POSITION) { +@@ -2364,10 +2364,8 @@ + goalStart += getArrowScrollPreviewLength(); + } + +- final int viewToMakeVisibleStart = +- (mIsVertical ? viewToMakeVisible.getTop() : viewToMakeVisible.getLeft()); +- final int viewToMakeVisibleEnd = +- (mIsVertical ? viewToMakeVisible.getBottom() : viewToMakeVisible.getRight()); ++ final int viewToMakeVisibleStart = getChildStartEdge(viewToMakeVisible); ++ final int viewToMakeVisibleEnd = getChildEndEdge(viewToMakeVisible); + + if (viewToMakeVisibleStart >= goalStart) { + // Item is fully visible +@@ -2383,8 +2381,7 @@ + int amountToScroll = (goalStart - viewToMakeVisibleStart); + + if (mFirstPosition == 0) { +- final View firstChild = getChildAt(0); +- final int firstChildStart = (mIsVertical ? firstChild.getTop() : firstChild.getLeft()); ++ final int firstChildStart = getChildStartEdge(getChildAt(0)); + + // First is first in list -> make sure we don't scroll past it + final int max = start - firstChildStart; +@@ -2416,7 +2413,7 @@ + offsetDescendantRectToMyCoords(newFocus, mTempRect); + + if (direction == View.FOCUS_UP || direction == View.FOCUS_LEFT) { +- final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); ++ final int start = getStartEdge(); + final int newFocusStart = (mIsVertical ? mTempRect.top : mTempRect.left); + + if (newFocusStart < start) { +@@ -2426,8 +2423,7 @@ + } + } + } else { +- final int end = (mIsVertical ? getHeight() - getPaddingBottom() : +- getWidth() - getPaddingRight()); ++ final int end = getEndEdge(); + final int newFocusEnd = (mIsVertical ? mTempRect.bottom : mTempRect.right); + + if (newFocusEnd > end) { +@@ -2452,9 +2448,8 @@ + descendant.getDrawingRect(mTempRect); + offsetDescendantRectToMyCoords(descendant, mTempRect); + +- final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); +- final int end = (mIsVertical ? getHeight() - getPaddingBottom() : +- getWidth() - getPaddingRight()); ++ final int start = getStartEdge(); ++ final int end = getEndEdge(); + + final int viewStart = (mIsVertical ? mTempRect.top : mTempRect.left); + final int viewEnd = (mIsVertical ? mTempRect.bottom : mTempRect.right); +@@ -2741,7 +2736,7 @@ + motionViewPrevStart = (mIsVertical ? motionView.getTop() : motionView.getLeft()); + } + +- boolean atEdge = trackMotionScroll(delta); ++ boolean atEdge = scrollListItemsBy(delta); + + motionView = this.getChildAt(motionIndex); + if (motionView != null) { +@@ -2821,7 +2816,7 @@ + ViewCompat.postInvalidateOnAnimation(this); + } + +- trackMotionScroll(delta); ++ scrollListItemsBy(delta); + mTouchMode = TOUCH_MODE_DRAGGING; + + // We did not scroll the full amount. Treat this essentially like the +@@ -2901,10 +2896,8 @@ + } + + for (int i = 0; i < childCount; i++) { +- View v = getChildAt(i); +- +- if ((mIsVertical && motionPos <= v.getBottom()) || +- (!mIsVertical && motionPos <= v.getRight())) { ++ final View v = getChildAt(i); ++ if (motionPos <= getChildEndEdge(v)) { + return mFirstPosition + i; + } + } +@@ -2935,6 +2928,26 @@ + return vc.getScaledOverscrollDistance(); + } + ++ private int getStartEdge() { ++ return (mIsVertical ? getPaddingTop() : getPaddingLeft()); ++ } ++ ++ private int getEndEdge() { ++ if (mIsVertical) { ++ return (getHeight() - getPaddingBottom()); ++ } else { ++ return (getWidth() - getPaddingRight()); ++ } ++ } ++ ++ private int getChildStartEdge(View child) { ++ return (mIsVertical ? child.getTop() : child.getLeft()); ++ } ++ ++ private int getChildEndEdge(View child) { ++ return (mIsVertical ? child.getBottom() : child.getRight()); ++ } ++ + private boolean contentFits() { + final int childCount = getChildCount(); + if (childCount == 0) { +@@ -2948,18 +2961,8 @@ + View first = getChildAt(0); + View last = getChildAt(childCount - 1); + +- if (mIsVertical) { +- return first.getTop() >= getPaddingTop() && +- last.getBottom() <= getHeight() - getPaddingBottom(); +- } else { +- return first.getLeft() >= getPaddingLeft() && +- last.getRight() <= getWidth() - getPaddingRight(); +- } +- } +- +- private void updateScrollbarsDirection() { +- setHorizontalScrollBarEnabled(!mIsVertical); +- setVerticalScrollBarEnabled(mIsVertical); ++ return (getChildStartEdge(first) >= getStartEdge() && ++ getChildEndEdge(last) <= getEndEdge()); + } + + private void triggerCheckForTap() { +@@ -2997,17 +3000,14 @@ + removeCallbacks(mPendingCheckForLongPress); + } + +- boolean trackMotionScroll(int incrementalDelta) { ++ private boolean scrollListItemsBy(int incrementalDelta) { + final int childCount = getChildCount(); + if (childCount == 0) { + return true; + } + +- final View first = getChildAt(0); +- final int firstStart = (mIsVertical ? first.getTop() : first.getLeft()); +- +- final View last = getChildAt(childCount - 1); +- final int lastEnd = (mIsVertical ? last.getBottom() : last.getRight()); ++ final int firstStart = getChildStartEdge(getChildAt(0)); ++ final int lastEnd = getChildEndEdge(getChildAt(childCount - 1)); + + final int paddingTop = getPaddingTop(); + final int paddingBottom = getPaddingBottom(); +@@ -3017,8 +3017,7 @@ + final int paddingStart = (mIsVertical ? paddingTop : paddingLeft); + + final int spaceBefore = paddingStart - firstStart; +- final int end = (mIsVertical ? getHeight() - paddingBottom : +- getWidth() - paddingRight); ++ final int end = getEndEdge(); + final int spaceAfter = lastEnd - end; + + final int size; +@@ -3059,7 +3058,7 @@ + + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); +- final int childEnd = (mIsVertical ? child.getBottom() : child.getRight()); ++ final int childEnd = getChildEndEdge(child); + + if (childEnd >= childrenStart) { + break; +@@ -3073,7 +3072,7 @@ + + for (int i = childCount - 1; i >= 0; i--) { + final View child = getChildAt(i); +- final int childStart = (mIsVertical ? child.getTop() : child.getLeft()); ++ final int childStart = getChildStartEdge(child); + + if (childStart <= childrenEnd) { + break; +@@ -3140,11 +3139,7 @@ + + @TargetApi(5) + private boolean awakenScrollbarsInternal() { +- if (Build.VERSION.SDK_INT >= 5) { +- return super.awakenScrollBars(); +- } else { +- return false; +- } ++ return (Build.VERSION.SDK_INT >= 5) && super.awakenScrollBars(); + } + + @Override +@@ -3163,7 +3158,7 @@ + final int diff = (int) (pos - mLastTouchPos); + mLastTouchPos = pos; + +- final boolean stopped = trackMotionScroll(diff); ++ final boolean stopped = scrollListItemsBy(diff); + + if (!stopped && !mScroller.isFinished()) { + ViewCompat.postInvalidateOnAnimation(this); +@@ -3210,7 +3205,7 @@ + } + + final int restoreCount = canvas.save(); +- final int height = getHeight() - getPaddingTop() - getPaddingBottom(); ++ final int height = getHeight(); + + canvas.translate(0, height); + canvas.rotate(270); +@@ -3226,8 +3221,8 @@ + } + + final int restoreCount = canvas.save(); +- final int width = getWidth() - getPaddingLeft() - getPaddingRight(); +- final int height = getHeight() - getPaddingTop() - getPaddingBottom(); ++ final int width = getWidth(); ++ final int height = getHeight(); + + if (mIsVertical) { + canvas.translate(-width, height); +@@ -3683,6 +3678,10 @@ + } + } + ++ public void scrollBy(int offset) { ++ scrollListItemsBy(offset); ++ } ++ + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + // Dispatch in the normal way +@@ -3814,9 +3813,8 @@ + return; + } + +- final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); +- final int end = +- (mIsVertical ? getHeight() - getPaddingBottom() : getWidth() - getPaddingRight()); ++ final int start = getStartEdge(); ++ final int end = getEndEdge(); + + int childCount = getChildCount(); + int index = 0; +@@ -4084,8 +4082,8 @@ + int end) { + final int selectedPosition = mSelectedPosition; + +- final int oldSelectedStart = (mIsVertical ? oldSelected.getTop() : oldSelected.getLeft()); +- final int oldSelectedEnd = (mIsVertical ? oldSelected.getBottom() : oldSelected.getRight()); ++ final int oldSelectedStart = getChildStartEdge(oldSelected); ++ final int oldSelectedEnd = getChildEndEdge(oldSelected); + + View selected = null; + +@@ -4118,8 +4116,8 @@ + // Now put the new selection (B) below that + selected = makeAndAddView(selectedPosition, oldSelectedEnd + itemMargin, true, true); + +- final int selectedStart = (mIsVertical ? selected.getTop() : selected.getLeft()); +- final int selectedEnd = (mIsVertical ? selected.getBottom() : selected.getRight()); ++ final int selectedStart = getChildStartEdge(selected); ++ final int selectedEnd = getChildEndEdge(selected); + + // Some of the newly selected item extends below the bottom of the list + if (selectedEnd > end) { +@@ -4178,8 +4176,8 @@ + selected = makeAndAddView(selectedPosition, oldSelectedStart, false, true); + } + +- final int selectedStart = (mIsVertical ? selected.getTop() : selected.getLeft()); +- final int selectedEnd = (mIsVertical ? selected.getBottom() : selected.getRight()); ++ final int selectedStart = getChildStartEdge(selected); ++ final int selectedEnd = getChildEndEdge(selected); + + // Some of the newly selected item extends above the top of the list + if (selectedStart < start) { +@@ -4210,8 +4208,8 @@ + + selected = makeAndAddView(selectedPosition, oldSelectedStart, true, true); + +- final int selectedStart = (mIsVertical ? selected.getTop() : selected.getLeft()); +- final int selectedEnd = (mIsVertical ? selected.getBottom() : selected.getRight()); ++ final int selectedStart = getChildStartEdge(selected); ++ final int selectedEnd = getChildEndEdge(selected); + + // We're staying still... + if (oldSelectedStart < start) { +@@ -4272,7 +4270,7 @@ + } + + private void handleDataChanged() { +- if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0 && mAdapter != null && mAdapter.hasStableIds()) { ++ if (mChoiceMode != ChoiceMode.NONE && mAdapter != null && mAdapter.hasStableIds()) { + confirmCheckedPositionsById(); + } + +@@ -4406,9 +4404,8 @@ + int selectedStart = 0; + int selectedPosition; + +- final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); +- final int end = +- (mIsVertical ? getHeight() - getPaddingBottom() : getWidth() - getPaddingRight()); ++ final int start = getStartEdge(); ++ final int end = getEndEdge(); + + final int firstPosition = mFirstPosition; + final int toPosition = mResurrectToPosition; +@@ -4445,8 +4442,8 @@ + + for (int i = childCount - 1; i >= 0; i--) { + final View child = getChildAt(i); +- final int childStart = (mIsVertical ? child.getTop() : child.getLeft()); +- final int childEnd = (mIsVertical ? child.getBottom() : child.getRight()); ++ final int childStart = getChildStartEdge(child); ++ final int childEnd = getChildEndEdge(child); + + if (i == childCount - 1) { + selectedStart = childStart; +@@ -4718,7 +4715,7 @@ + recycleBin.addScrapView(child, -1); + } + +- returnedWidth += child.getMeasuredHeight(); ++ returnedWidth += child.getMeasuredWidth(); + + if (returnedWidth >= maxWidth) { + // We went over, figure out which width to return. If returnedWidth > maxWidth, +@@ -4810,11 +4807,10 @@ + child.setPressed(isPressed); + } + +- if (mChoiceMode.compareTo(ChoiceMode.NONE) != 0 && mCheckStates != null) { ++ if (mChoiceMode != ChoiceMode.NONE && mCheckStates != null) { + if (child instanceof Checkable) { + ((Checkable) child).setChecked(mCheckStates.get(position)); +- } else if (getContext().getApplicationInfo().targetSdkVersion +- >= Build.VERSION_CODES.HONEYCOMB) { ++ } else if (Build.VERSION.SDK_INT >= HONEYCOMB) { + child.setActivated(mCheckStates.get(position)); + } + } +@@ -4847,26 +4843,17 @@ + + if (down) { + final int paddingStart = (mIsVertical ? getPaddingTop() : getPaddingLeft()); +- +- final int lastEnd; +- if (mIsVertical) { +- lastEnd = getChildAt(childCount - 1).getBottom(); +- } else { +- lastEnd = getChildAt(childCount - 1).getRight(); +- } ++ final int lastEnd = getChildEndEdge(getChildAt(childCount - 1)); + + final int offset = (childCount > 0 ? lastEnd + mItemMargin : paddingStart); + fillAfter(mFirstPosition + childCount, offset); + correctTooHigh(getChildCount()); + } else { +- final int end; ++ final int end = getEndEdge(); + final int firstStart; +- + if (mIsVertical) { +- end = getHeight() - getPaddingBottom(); + firstStart = getChildAt(0).getTop(); + } else { +- end = getWidth() - getPaddingRight(); + firstStart = getChildAt(0).getLeft(); + } + +@@ -4879,7 +4866,7 @@ + private View fillBefore(int pos, int nextOffset) { + View selectedView = null; + +- final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); ++ final int start = getStartEdge(); + + while (nextOffset > start && pos >= 0) { + boolean isSelected = (pos == mSelectedPosition); +@@ -4906,19 +4893,13 @@ + private View fillAfter(int pos, int nextOffset) { + View selectedView = null; + +- final int end = +- (mIsVertical ? getHeight() - getPaddingBottom() : getWidth() - getPaddingRight()); ++ final int end = getEndEdge(); + + while (nextOffset < end && pos < mItemCount) { + boolean selected = (pos == mSelectedPosition); + + View child = makeAndAddView(pos, nextOffset, true, selected); +- +- if (mIsVertical) { +- nextOffset = child.getBottom() + mItemMargin; +- } else { +- nextOffset = child.getRight() + mItemMargin; +- } ++ nextOffset = getChildEndEdge(child) + mItemMargin; + + if (selected) { + selectedView = child; +@@ -4937,25 +4918,13 @@ + // Possibly changed again in fillBefore if we add rows above this one. + mFirstPosition = position; + +- final int itemMargin = mItemMargin; +- +- final int offsetBefore; +- if (mIsVertical) { +- offsetBefore = temp.getTop() - itemMargin; +- } else { +- offsetBefore = temp.getLeft() - itemMargin; +- } ++ final int offsetBefore = getChildStartEdge(temp) + mItemMargin; + final View before = fillBefore(position - 1, offsetBefore); + + // This will correct for the top of the first view not touching the top of the list + adjustViewsStartOrEnd(); + +- final int offsetAfter; +- if (mIsVertical) { +- offsetAfter = temp.getBottom() + itemMargin; +- } else { +- offsetAfter = temp.getRight() + itemMargin; +- } ++ final int offsetAfter = getChildEndEdge(temp) + mItemMargin; + final View after = fillAfter(position + 1, offsetAfter); + + final int childCount = getChildCount(); +@@ -5009,37 +4978,22 @@ + } + + private void fillBeforeAndAfter(View selected, int position) { +- final int itemMargin = mItemMargin; +- +- final int offsetBefore; +- if (mIsVertical) { +- offsetBefore = selected.getTop() - itemMargin; +- } else { +- offsetBefore = selected.getLeft() - itemMargin; +- } +- ++ final int offsetBefore = getChildStartEdge(selected) + mItemMargin; + fillBefore(position - 1, offsetBefore); + + adjustViewsStartOrEnd(); + +- final int offsetAfter; +- if (mIsVertical) { +- offsetAfter = selected.getBottom() + itemMargin; +- } else { +- offsetAfter = selected.getRight() + itemMargin; +- } +- ++ final int offsetAfter = getChildEndEdge(selected) + mItemMargin; + fillAfter(position + 1, offsetAfter); + } + + private View fillFromSelection(int selectedTop, int start, int end) { + final int selectedPosition = mSelectedPosition; +- View selected; + +- selected = makeAndAddView(selectedPosition, selectedTop, true, true); ++ View selected = makeAndAddView(selectedPosition, selectedTop, true, true); + +- final int selectedStart = (mIsVertical ? selected.getTop() : selected.getLeft()); +- final int selectedEnd = (mIsVertical ? selected.getBottom() : selected.getRight()); ++ final int selectedStart = getChildStartEdge(selected); ++ final int selectedEnd = getChildEndEdge(selected); + + // Some of the newly selected item extends below the bottom of the list + if (selectedEnd > end) { +@@ -5085,28 +5039,19 @@ + return; + } + +- // Get the last child ... +- final View lastChild = getChildAt(childCount - 1); +- +- // ... and its end edge +- final int lastEnd; +- if (mIsVertical) { +- lastEnd = lastChild.getBottom(); +- } else { +- lastEnd = lastChild.getRight(); +- } ++ // Get the last child end edge ++ final int lastEnd = getChildEndEdge(getChildAt(childCount - 1)); + + // This is bottom of our drawable area +- final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); +- final int end = +- (mIsVertical ? getHeight() - getPaddingBottom() : getWidth() - getPaddingRight()); ++ final int start = getStartEdge(); ++ final int end = getEndEdge(); + + // This is how far the end edge of the last view is from the end of the + // drawable area + int endOffset = end - lastEnd; + + View firstChild = getChildAt(0); +- int firstStart = (mIsVertical ? firstChild.getTop() : firstChild.getLeft()); ++ int firstStart = getChildStartEdge(firstChild); + + // Make sure we are 1) Too high, and 2) Either there are more rows above the + // first row or the first row is scrolled off the top of the drawable area +@@ -5120,7 +5065,7 @@ + offsetChildren(endOffset); + + if (mFirstPosition > 0) { +- firstStart = (mIsVertical ? firstChild.getTop() : firstChild.getLeft()); ++ firstStart = getChildStartEdge(firstChild); + + // Fill the gap that was opened above mFirstPosition with more rows, if + // possible +@@ -5142,21 +5087,15 @@ + final View first = getChildAt(0); + final int firstStart = (mIsVertical ? first.getTop() : first.getLeft()); + +- final int start = (mIsVertical ? getPaddingTop() : getPaddingLeft()); +- +- final int end; +- if (mIsVertical) { +- end = getHeight() - getPaddingBottom(); +- } else { +- end = getWidth() - getPaddingRight(); +- } ++ final int start = getStartEdge(); ++ final int end = getEndEdge(); + + // This is how far the start edge of the first view is from the start of the + // drawable area + int startOffset = firstStart - start; + + View last = getChildAt(childCount - 1); +- int lastEnd = (mIsVertical ? last.getBottom() : last.getRight()); ++ int lastEnd = getChildEndEdge(last); + + int lastPosition = mFirstPosition + childCount - 1; + +@@ -5174,7 +5113,7 @@ + offsetChildren(-startOffset); + + if (lastPosition < mItemCount - 1) { +- lastEnd = (mIsVertical ? last.getBottom() : last.getRight()); ++ lastEnd = getChildEndEdge(last); + + // Fill the gap that was opened below the last position with more rows, if + // possible +@@ -5366,6 +5305,8 @@ + } + + void resetState() { ++ mScroller.forceFinished(true); ++ + removeAllViewsInLayout(); + + mSelectedStart = 0; +@@ -5419,7 +5360,7 @@ + mSyncPosition = mFirstPosition; + + if (child != null) { +- mSpecificStart = child.getTop(); ++ mSpecificStart = (mIsVertical ? child.getTop() : child.getLeft()); + } + + mSyncMode = SYNC_FIRST_POSITION; +@@ -5435,16 +5376,13 @@ + final int firstPos = mFirstPosition; + final int count = getChildCount(); + +- final boolean useActivated = getContext().getApplicationInfo().targetSdkVersion +- >= Build.VERSION_CODES.HONEYCOMB; +- + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + final int position = firstPos + i; + + if (child instanceof Checkable) { + ((Checkable) child).setChecked(mCheckStates.get(position)); +- } else if (useActivated) { ++ } else if (Build.VERSION.SDK_INT >= HONEYCOMB) { + child.setActivated(mCheckStates.get(position)); + } + } +@@ -5454,7 +5392,7 @@ + public boolean performItemClick(View view, int position, long id) { + boolean checkedStateChanged = false; + +- if (mChoiceMode.compareTo(ChoiceMode.MULTIPLE) == 0) { ++ if (mChoiceMode == ChoiceMode.MULTIPLE) { + boolean checked = !mCheckStates.get(position, false); + mCheckStates.put(position, checked); + +@@ -5473,7 +5411,7 @@ + } + + checkedStateChanged = true; +- } else if (mChoiceMode.compareTo(ChoiceMode.SINGLE) == 0) { ++ } else if (mChoiceMode == ChoiceMode.SINGLE) { + boolean checked = !mCheckStates.get(position, false); + if (checked) { + mCheckStates.clear(); +@@ -5699,14 +5637,14 @@ + super(width, height); + + if (this.width == MATCH_PARENT) { +- Log.w(LOGTAG, "Constructing LayoutParams with width FILL_PARENT " + ++ Log.w(LOGTAG, "Constructing LayoutParams with width MATCH_PARENT " + + "does not make much sense as the view might change orientation. " + + "Falling back to WRAP_CONTENT"); + this.width = WRAP_CONTENT; + } + + if (this.height == MATCH_PARENT) { +- Log.w(LOGTAG, "Constructing LayoutParams with height FILL_PARENT " + ++ Log.w(LOGTAG, "Constructing LayoutParams with height MATCH_PARENT " + + "does not make much sense as the view might change orientation. " + + "Falling back to WRAP_CONTENT"); + this.height = WRAP_CONTENT; +@@ -5735,7 +5673,7 @@ + super(other); + + if (this.width == MATCH_PARENT) { +- Log.w(LOGTAG, "Constructing LayoutParams with height MATCH_PARENT - " + ++ Log.w(LOGTAG, "Constructing LayoutParams with width MATCH_PARENT - " + + "does not make much sense as the view might change orientation. " + + "Falling back to WRAP_CONTENT"); + this.width = WRAP_CONTENT; +@@ -5786,11 +5724,8 @@ + } else { + final int typeCount = mViewTypeCount; + for (int i = 0; i < typeCount; i++) { +- final ArrayList<View> scrap = mScrapViews[i]; +- final int scrapCount = scrap.size(); +- +- for (int j = 0; j < scrapCount; j++) { +- scrap.get(j).forceLayout(); ++ for (View scrap : mScrapViews[i]) { ++ scrap.forceLayout(); + } + } + } +@@ -6120,7 +6055,7 @@ + // Force one here to make sure that the state of the list matches + // the state of the adapter. + if (mDataChanged) { +- onLayout(false, getLeft(), getTop(), getRight(), getBottom()); ++ layout(getLeft(), getTop(), getRight(), getBottom()); + } + } else { + if (mEmptyView != null) { +@@ -6525,16 +6460,10 @@ + return false; + + case AccessibilityNodeInfoCompat.ACTION_CLICK: +- if (isClickable()) { +- return performItemClick(host, position, id); +- } +- return false; ++ return isClickable() && performItemClick(host, position, id); + + case AccessibilityNodeInfoCompat.ACTION_LONG_CLICK: +- if (isLongClickable()) { +- return performLongPress(host, position, id); +- } +- return false; ++ return isLongClickable() && performLongPress(host, position, id); + } + + return false; diff --git a/data/patches/gnuzilla-bug-1036286-1.patch b/data/patches/gnuzilla-bug-1036286-1.patch new file mode 100644 index 0000000..a5d3110 --- /dev/null +++ b/data/patches/gnuzilla-bug-1036286-1.patch @@ -0,0 +1,188 @@ +--- a/mozglue/linker/ElfLoader.cpp ++++ a/mozglue/linker/ElfLoader.cpp +@@ -960,28 +960,28 @@ static uint64_t ProcessTimeStamp_Now() + /* Data structure used to pass data to the temporary signal handler, + * as well as triggering a test crash. */ + struct TmpData { + volatile int crash_int; + volatile uint64_t crash_timestamp; + }; + + SEGVHandler::SEGVHandler() +-: registeredHandler(false), signalHandlingBroken(false) +-, signalHandlingSlow(false) ++: initialized(false), registeredHandler(false), signalHandlingBroken(true) ++, signalHandlingSlow(true) + { + /* Initialize oldStack.ss_flags to an invalid value when used to set + * an alternative stack, meaning we haven't got information about the +- * original alternative stack and thus don't mean to restore it */ ++ * original alternative stack and thus don't mean to restore it in ++ * the desctructor. */ + oldStack.ss_flags = SS_ONSTACK; +- if (!Divert(sigaction, __wrap_sigaction)) +- return; + + /* Get the current segfault signal handler. */ +- sys_sigaction(SIGSEGV, nullptr, &this->action); ++ struct sigaction old_action; ++ sys_sigaction(SIGSEGV, nullptr, &old_action); + + /* Some devices don't provide useful information to their SIGSEGV handlers, + * making it impossible for on-demand decompression to work. To check if + * we're on such a device, setup a temporary handler and deliberately + * trigger a segfault. The handler will set signalHandlingBroken if the + * provided information is bogus. + * Some other devices have a kernel option enabled that makes SIGSEGV handler + * have an overhead so high that it affects how on-demand decompression +@@ -1000,21 +1000,33 @@ SEGVHandler::SEGVHandler() + if (sys_sigaction(SIGSEGV, &action, nullptr)) + return; + + TmpData *data = reinterpret_cast<TmpData*>(stackPtr.get()); + data->crash_timestamp = ProcessTimeStamp_Now(); + mprotect(stackPtr, stackPtr.GetLength(), PROT_NONE); + data->crash_int = 123; + /* Restore the original segfault signal handler. */ +- sys_sigaction(SIGSEGV, &this->action, nullptr); ++ sys_sigaction(SIGSEGV, &old_action, nullptr); + stackPtr.Assign(MAP_FAILED, 0); ++} ++ ++void ++SEGVHandler::FinishInitialization() ++{ ++ /* Ideally, we'd need some locking here, but in practice, we're not ++ * going to race with another thread. */ ++ initialized = true; ++ + if (signalHandlingBroken || signalHandlingSlow) + return; + ++ if (!Divert(sigaction, __wrap_sigaction)) ++ return; ++ + /* Setup an alternative stack if the already existing one is not big + * enough, or if there is none. */ + if (sigaltstack(nullptr, &oldStack) == 0) { + if (oldStack.ss_flags == SS_ONSTACK) + oldStack.ss_flags = 0; + if (!oldStack.ss_sp || oldStack.ss_size < stackSize) { + stackPtr.Assign(MemoryRange::mmap(nullptr, stackSize, + PROT_READ | PROT_WRITE, +@@ -1028,17 +1040,17 @@ SEGVHandler::SEGVHandler() + if (sigaltstack(&stack, nullptr) != 0) + return; + } + } + /* Register our own handler, and store the already registered one in + * SEGVHandler's struct sigaction member */ + action.sa_sigaction = &SEGVHandler::handler; + action.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK; +- registeredHandler = !sys_sigaction(SIGSEGV, &action, nullptr); ++ registeredHandler = !sys_sigaction(SIGSEGV, &action, &this->action); + } + + SEGVHandler::~SEGVHandler() + { + /* Restore alternative stack for signals */ + if (oldStack.ss_flags != SS_ONSTACK) + sigaltstack(&oldStack, nullptr); + /* Restore original signal handler */ +@@ -1048,28 +1060,28 @@ SEGVHandler::~SEGVHandler() + + /* Test handler for a deliberately triggered SIGSEGV that determines whether + * useful information is provided to signal handlers, particularly whether + * si_addr is filled in properly, and whether the segfault handler is called + * quickly enough. */ + void SEGVHandler::test_handler(int signum, siginfo_t *info, void *context) + { + SEGVHandler &that = ElfLoader::Singleton; +- if (signum != SIGSEGV || +- info == nullptr || info->si_addr != that.stackPtr.get()) +- that.signalHandlingBroken = true; ++ if (signum == SIGSEGV && info && ++ info->si_addr == that.stackPtr.get()) ++ that.signalHandlingBroken = false; + mprotect(that.stackPtr, that.stackPtr.GetLength(), PROT_READ | PROT_WRITE); + TmpData *data = reinterpret_cast<TmpData*>(that.stackPtr.get()); + uint64_t latency = ProcessTimeStamp_Now() - data->crash_timestamp; + DEBUG_LOG("SEGVHandler latency: %" PRIu64, latency); + /* See bug 886736 for timings on different devices, 150 µs is reasonably above +- * the latency on "working" devices and seems to be reasonably fast to incur +- * a huge overhead to on-demand decompression. */ +- if (latency > 150000) +- that.signalHandlingSlow = true; ++ * the latency on "working" devices and seems to be reasonably fast not to ++ * incur a huge overhead to on-demand decompression. */ ++ if (latency <= 150000) ++ that.signalHandlingSlow = false; + } + + /* TODO: "properly" handle signal masks and flags */ + void SEGVHandler::handler(int signum, siginfo_t *info, void *context) + { + //ASSERT(signum == SIGSEGV); + DEBUG_LOG("Caught segmentation fault @%p", info->si_addr); + +--- a/mozglue/linker/ElfLoader.h ++++ a/mozglue/linker/ElfLoader.h +@@ -307,32 +307,40 @@ private: + * faults within the address space of the loaded libraries. It however + * allows a handler to be set for faults in other places, and redispatches + * to the handler set through signal() or sigaction(). + */ + class SEGVHandler + { + public: + bool hasRegisteredHandler() { ++ if (! initialized) ++ FinishInitialization(); + return registeredHandler; + } + + bool isSignalHandlingBroken() { + return signalHandlingBroken; + } + + protected: + SEGVHandler(); + ~SEGVHandler(); + + private: + static int __wrap_sigaction(int signum, const struct sigaction *act, + struct sigaction *oldact); + + /** ++ * The constructor doesn't do all initialization, and the tail is done ++ * at a later time. ++ */ ++ void FinishInitialization(); ++ ++ /** + * SIGSEGV handler registered with __wrap_signal or __wrap_sigaction. + */ + struct sigaction action; + + /** + * ElfLoader SIGSEGV handler. + */ + static void handler(int signum, siginfo_t *info, void *context); +@@ -354,16 +362,17 @@ private: + stack_t oldStack; + + /** + * Pointer to an alternative stack for signals. Only set if oldStack is + * not set or not big enough. + */ + MappedPtr stackPtr; + ++ bool initialized; + bool registeredHandler; + bool signalHandlingBroken; + bool signalHandlingSlow; + }; + + /** + * Elf Loader class in charge of loading and bookkeeping libraries. + */ diff --git a/data/patches/gnuzilla-bug-1036286-2.patch b/data/patches/gnuzilla-bug-1036286-2.patch new file mode 100644 index 0000000..6708062 --- /dev/null +++ b/data/patches/gnuzilla-bug-1036286-2.patch @@ -0,0 +1,105 @@ +--- a/mozglue/linker/CustomElf.cpp ++++ a/mozglue/linker/CustomElf.cpp +@@ -1,16 +1,17 @@ + /* 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/. */ + + #include <cstring> + #include <sys/mman.h> + #include <vector> + #include <dlfcn.h> ++#include <signal.h> + #include "CustomElf.h" + #include "Mappable.h" + #include "Logging.h" + + using namespace Elf; + using namespace mozilla; + + /* TODO: Fill ElfLoader::Singleton.lastError on errors. */ +@@ -348,16 +349,21 @@ CustomElf::GetSymbolPtrInDeps(const char + if (strcmp(symbol + 2, "dso_handle") == 0) + return const_cast<CustomElf *>(this); + if (strcmp(symbol + 2, "moz_linker_stats") == 0) + return FunctionPtr(&ElfLoader::stats); + #ifdef __ARM_EABI__ + if (strcmp(symbol + 2, "gnu_Unwind_Find_exidx") == 0) + return FunctionPtr(__wrap___gnu_Unwind_Find_exidx); + #endif ++ } else if (symbol[0] == 's' && symbol[1] == 'i') { ++ if (strcmp(symbol + 2, "gnal") == 0) ++ return FunctionPtr(signal); ++ if (strcmp(symbol + 2, "gaction") == 0) ++ return FunctionPtr(sigaction); + } + + #define MISSING_FLASH_SYMNAME_START "_ZN7android10VectorImpl19reservedVectorImpl" + + // Android changed some symbols that Flash depended on in 4.4, + // so stub those out here + if (strncmp(symbol, + MISSING_FLASH_SYMNAME_START, +--- a/mozglue/linker/ElfLoader.cpp ++++ a/mozglue/linker/ElfLoader.cpp +@@ -1014,17 +1014,59 @@ SEGVHandler::FinishInitialization() + { + /* Ideally, we'd need some locking here, but in practice, we're not + * going to race with another thread. */ + initialized = true; + + if (signalHandlingBroken || signalHandlingSlow) + return; + +- if (!Divert(sigaction, __wrap_sigaction)) ++ typedef int (*sigaction_func)(int, const struct sigaction *, ++ struct sigaction *); ++ ++ sigaction_func libc_sigaction; ++ ++#if defined(ANDROID) ++ /* Android > 4.4 comes with a sigaction wrapper in a LD_PRELOADed library ++ * (libsigchain) for ART. That wrapper kind of does the same trick as we ++ * do, so we need extra care in handling it. ++ * - Divert the libc's sigaction, assuming the LD_PRELOADed library uses ++ * it under the hood (which is more or less true according to the source ++ * of that library, since it's doing a lookup in RTLD_NEXT) ++ * - With the LD_PRELOADed library in place, all calls to sigaction from ++ * from system libraries will go to the LD_PRELOADed library. ++ * - The LD_PRELOADed library calls to sigaction go to our __wrap_sigaction. ++ * - The calls to sigaction from libraries faulty.lib loads are sent to ++ * the LD_PRELOADed library. ++ * In practice, for signal handling, this means: ++ * - The signal handler registered to the kernel is ours. ++ * - Our handler redispatches to the LD_PRELOADed library's if there's a ++ * segfault we don't handle. ++ * - The LD_PRELOADed library redispatches according to whatever system ++ * library or faulty.lib-loaded library set with sigaction. ++ * ++ * When there is no sigaction wrapper in place: ++ * - Divert the libc's sigaction. ++ * - Calls to sigaction from system library and faulty.lib-loaded libraries ++ * all go to the libc's sigaction, which end up in our __wrap_sigaction. ++ * - The signal handler registered to the kernel is ours. ++ * - Our handler redispatches according to whatever system library or ++ * faulty.lib-loaded library set with sigaction. ++ */ ++ void *libc = dlopen("libc.so", RTLD_GLOBAL | RTLD_LAZY); ++ if (libc) { ++ libc_sigaction = ++ reinterpret_cast<sigaction_func>(dlsym(libc, "sigaction")); ++ } else ++#endif ++ { ++ libc_sigaction = sigaction; ++ } ++ ++ if (!Divert(libc_sigaction, __wrap_sigaction)) + return; + + /* Setup an alternative stack if the already existing one is not big + * enough, or if there is none. */ + if (sigaltstack(nullptr, &oldStack) == 0) { + if (oldStack.ss_flags == SS_ONSTACK) + oldStack.ss_flags = 0; + if (!oldStack.ss_sp || oldStack.ss_size < stackSize) { diff --git a/data/patches/gnuzilla-bug-1050780.patch b/data/patches/gnuzilla-bug-1050780.patch new file mode 100644 index 0000000..b142dc7 --- /dev/null +++ b/data/patches/gnuzilla-bug-1050780.patch @@ -0,0 +1,53 @@ +# HG changeset patch +# User Lucas Rocha <lucasr@mozilla.com> + +Bug 1050780 - Avoid disabled items in GeckoMenu's adapter (r=margaret) + +diff --git a/mobile/android/base/menu/GeckoMenu.java b/mobile/android/base/menu/GeckoMenu.java +index eff2a51..cd2404b 100644 +--- a/mobile/android/base/menu/GeckoMenu.java ++++ b/mobile/android/base/menu/GeckoMenu.java +@@ -781,17 +781,19 @@ public class GeckoMenu extends ListView + public boolean areAllItemsEnabled() { + // Setting this to true is a workaround to fix disappearing + // dividers in the menu (bug 963249). + return true; + } + + @Override + public boolean isEnabled(int position) { +- return getItem(position).isEnabled(); ++ // Setting this to true is a workaround to fix disappearing ++ // dividers in the menu in L (bug 1050780). ++ return true; + } + + public void addMenuItem(GeckoMenuItem menuItem) { + if (mItems.contains(menuItem)) + return; + + // Insert it in proper order. + int index = 0; +diff --git a/mobile/android/base/resources/drawable/action_bar_button.xml b/mobile/android/base/resources/drawable/action_bar_button.xml +index 9cb7aa8..fe36bc4 100644 +--- a/mobile/android/base/resources/drawable/action_bar_button.xml ++++ b/mobile/android/base/resources/drawable/action_bar_button.xml +@@ -1,16 +1,17 @@ + <?xml version="1.0" encoding="utf-8"?> + <!-- 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/. --> + + <selector xmlns:android="http://schemas.android.com/apk/res/android"> + +- <item android:state_pressed="true"> ++ <item android:state_pressed="true" ++ android:state_enabled="true"> + <shape> + <solid android:color="@color/highlight" /> + </shape> + </item> + + <item android:state_focused="true" + android:state_pressed="false"> + <shape> diff --git a/data/patches/gnuzilla-bug-1055166.patch b/data/patches/gnuzilla-bug-1055166.patch new file mode 100644 index 0000000..51b8ee5 --- /dev/null +++ b/data/patches/gnuzilla-bug-1055166.patch @@ -0,0 +1,33 @@ +From: James Willcox <snorp@snorp.net> +Bug 1055166 - Catch IllegalStateException when recycling Message + + + +diff --git a/mobile/android/base/GeckoAppShell.java b/mobile/android/base/GeckoAppShell.java +index b06547f8c8135542d27713bb8e4c5d0e7496bb17..a50538e1b3449d3767ad78ed3fb8e596bc1acfbb 100644 +--- a/mobile/android/base/GeckoAppShell.java ++++ b/mobile/android/base/GeckoAppShell.java +@@ -2478,17 +2478,22 @@ public class GeckoAppShell + // Our "queue is empty" message; see runGecko() + msg.recycle(); + return false; + } + if (msg.getTarget() == null) + Looper.myLooper().quit(); + else + msg.getTarget().dispatchMessage(msg); +- msg.recycle(); ++ ++ try { ++ msg.recycle(); ++ } catch (IllegalStateException e) { ++ // There is nothing we can do here so just eat it ++ } + return true; + } + + @WrapElementForJNI + public static void notifyWakeLockChanged(String topic, String state) { + if (getGeckoInterface() != null) + getGeckoInterface().notifyWakeLockChanged(topic, state); + } diff --git a/data/patches/gnuzilla-bug-1058778.patch b/data/patches/gnuzilla-bug-1058778.patch new file mode 100644 index 0000000..27d5097 --- /dev/null +++ b/data/patches/gnuzilla-bug-1058778.patch @@ -0,0 +1,10 @@ +--- a/security/build/moz.build 2015-01-06 05:07:59.000000000 +0000 ++++ b/security/build/moz.build 2015-01-30 21:55:40.988341831 +0000 +@@ -10,5 +10,5 @@ + + FORCE_SHARED_LIB = True + +-if CONFIG['OS_TARGET'] == 'WINNT': +- DEFFILE = 'nss3.def' ++ if CONFIG['OS_TARGET'] == 'WINNT': ++ DEFFILE = 'nss3.def' diff --git a/data/patches/gnuzilla-bug-1089931.patch b/data/patches/gnuzilla-bug-1089931.patch new file mode 100644 index 0000000..cfd019f --- /dev/null +++ b/data/patches/gnuzilla-bug-1089931.patch @@ -0,0 +1,40 @@ +https://bugzilla.mozilla.org/show_bug.cgi?id=1089931 +--- a/toolkit/crashreporter/google-breakpad/src/common/android/include/ucontext.h ++++ a/toolkit/crashreporter/google-breakpad/src/common/android/include/ucontext.h +@@ -26,31 +26,30 @@ + // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + #ifndef GOOGLE_BREAKPAD_COMMON_ANDROID_INCLUDE_UCONTEXT_H + #define GOOGLE_BREAKPAD_COMMON_ANDROID_INCLUDE_UCONTEXT_H + + #include <sys/cdefs.h> ++#include <signal.h> + +-#ifdef __BIONIC_UCONTEXT_H +-#include <ucontext.h> ++#ifdef __BIONIC_HAVE_UCONTEXT_H ++# include_next <ucontext.h> + #else +- +-#include <sys/ucontext.h> ++# include <sys/ucontext.h> ++#endif // __BIONIC_UCONTEXT_H + + #ifdef __cplusplus + extern "C" { + #endif // __cplusplus + + // Provided by src/android/common/breakpad_getcontext.S + int breakpad_getcontext(ucontext_t* ucp); + + #define getcontext(x) breakpad_getcontext(x) + + #ifdef __cplusplus + } // extern "C" + #endif // __cplusplus + +-#endif // __BIONIC_UCONTEXT_H +- + #endif // GOOGLE_BREAKPAD_COMMON_ANDROID_INCLUDE_UCONTEXT_H diff --git a/data/patches/gnuzilla-bug-1091987.patch b/data/patches/gnuzilla-bug-1091987.patch new file mode 100644 index 0000000..d42b5df --- /dev/null +++ b/data/patches/gnuzilla-bug-1091987.patch @@ -0,0 +1,40 @@ +https://bugzilla.mozilla.org/show_bug.cgi?id=1091987 + +--- a/widget/android/AndroidJavaWrappers.h ++++ a/widget/android/AndroidJavaWrappers.h +@@ -228,16 +228,17 @@ private: + static jmethodID jBeginDrawingMethod; + static jmethodID jDrawBackgroundMethod; + static jmethodID jDrawForegroundMethod; + static jmethodID jEndDrawingMethod; + }; + + enum { + // These keycode masks are not defined in android/keycodes.h: ++#if __ANDROID_API__ < 15 + AKEYCODE_ESCAPE = 111, + AKEYCODE_FORWARD_DEL = 112, + AKEYCODE_CTRL_LEFT = 113, + AKEYCODE_CTRL_RIGHT = 114, + AKEYCODE_CAPS_LOCK = 115, + AKEYCODE_SCROLL_LOCK = 116, + AKEYCODE_META_LEFT = 117, + AKEYCODE_META_RIGHT = 118, +@@ -337,16 +338,17 @@ enum { + AKEYCODE_EISU = 212, + AKEYCODE_MUHENKAN = 213, + AKEYCODE_HENKAN = 214, + AKEYCODE_KATAKANA_HIRAGANA = 215, + AKEYCODE_YEN = 216, + AKEYCODE_RO = 217, + AKEYCODE_KANA = 218, + AKEYCODE_ASSIST = 219, ++#endif + + AMETA_FUNCTION_ON = 0x00000008, + AMETA_CTRL_ON = 0x00001000, + AMETA_CTRL_LEFT_ON = 0x00002000, + AMETA_CTRL_RIGHT_ON = 0x00004000, + AMETA_META_ON = 0x00010000, + AMETA_META_LEFT_ON = 0x00020000, + AMETA_META_RIGHT_ON = 0x00040000, diff --git a/data/patches/gnuzilla-bug-1095298.patch b/data/patches/gnuzilla-bug-1095298.patch new file mode 100644 index 0000000..aa075a2 --- /dev/null +++ b/data/patches/gnuzilla-bug-1095298.patch @@ -0,0 +1,30 @@ +--- a/intl/locale/src/nsLocaleService.cpp ++++ a/intl/locale/src/nsLocaleService.cpp +@@ -135,17 +135,25 @@ nsLocaleService::nsLocaleService(void) + + for( i = 0; i < LocaleListLength; i++ ) { + nsresult result; + // setlocale( , "") evaluates LC_* and LANG + char* lc_temp = setlocale(posix_locale_category[i], ""); + CopyASCIItoUTF16(LocaleList[i], category); + category_platform = category; + category_platform.AppendLiteral("##PLATFORM"); +- if (lc_temp != nullptr) { ++ ++ bool lc_temp_valid = lc_temp != nullptr; ++ ++#if defined(MOZ_WIDGET_ANDROID) ++ // Treat the "C" env as nothing useful. See Bug 1095298. ++ lc_temp_valid = lc_temp_valid && strcmp(lc_temp, "C") != 0; ++#endif ++ ++ if (lc_temp_valid) { + result = nsPosixLocale::GetXPLocale(lc_temp, xpLocale); + CopyASCIItoUTF16(lc_temp, platformLocale); + } else { + if ( lang == nullptr ) { + platformLocale.AssignLiteral("en_US"); + result = nsPosixLocale::GetXPLocale("en-US", xpLocale); + } else { + CopyASCIItoUTF16(lang, platformLocale); + diff --git a/data/patches/reduceGeckoPriority.patch b/data/patches/reduceGeckoPriority.patch new file mode 100644 index 0000000..843d2c8 --- /dev/null +++ b/data/patches/reduceGeckoPriority.patch @@ -0,0 +1,18 @@ +diff -ur icecat-31.4.0.orig/mobile/android/base/util/ThreadUtils.java icecat-31.4.0/mobile/android/base/util/ThreadUtils.java +--- icecat-31.4.0.orig/mobile/android/base/util/ThreadUtils.java 2015-02-22 18:35:21.022789023 +0000 ++++ icecat-31.4.0/mobile/android/base/util/ThreadUtils.java 2015-02-22 22:09:50.518544343 +0000 +@@ -179,7 +179,13 @@ + * @param timeout Timeout in ms after which the priority will be reset + */ + public static void reduceGeckoPriority(long timeout) { +- if (!sIsGeckoPriorityReduced) { ++ if (Runtime.getRuntime().availableProcessors() > 1) { ++ // Don't reduce priority for multicore devices. We use availableProcessors() ++ // for its fast performance. It may give false negatives (i.e. multicore ++ // detected as single-core), but we can tolerate this behavior. ++ return; ++ } ++ if (!sIsGeckoPriorityReduced && sGeckoThread != null) { + sIsGeckoPriorityReduced = true; + sGeckoThread.setPriority(Thread.MIN_PRIORITY); + getUiHandler().postDelayed(sPriorityResetRunnable, timeout); diff --git a/data/patches/reorder-mobile_android_base_android-services_mozbuild.patch b/data/patches/reorder-mobile_android_base_android-services_mozbuild.patch new file mode 100644 index 0000000..dd38815 --- /dev/null +++ b/data/patches/reorder-mobile_android_base_android-services_mozbuild.patch @@ -0,0 +1,34 @@ +--- a/mobile/android/base/android-services.mozbuild 2015-01-06 05:07:54.000000000 +0000 ++++ b/mobile/android/base/android-services.mozbuild 2015-02-02 02:59:30.180709918 +0000 +@@ -567,7 +567,7 @@ + 'fxa/authenticator/FxAccountAuthenticatorService.java', + 'fxa/authenticator/FxAccountLoginDelegate.java', + 'fxa/authenticator/FxAccountLoginException.java', +- 'fxa/FirefoxAccounts.java', ++ 'fxa/IceCatAccounts.java', + 'fxa/login/BaseRequestDelegate.java', + 'fxa/login/Cohabiting.java', + 'fxa/login/Doghouse.java', +@@ -709,8 +709,8 @@ + 'sync/repositories/android/CachedSQLiteOpenHelper.java', + 'sync/repositories/android/ClientsDatabase.java', + 'sync/repositories/android/ClientsDatabaseAccessor.java', +- 'sync/repositories/android/FennecTabsRepository.java', + 'sync/repositories/android/FormHistoryRepositorySession.java', ++ 'sync/repositories/android/IceCatMobileTabsRepository.java', + 'sync/repositories/android/PasswordsRepositorySession.java', + 'sync/repositories/android/RepoUtils.java', + 'sync/repositories/BookmarkNeedsReparentingException.java', +@@ -803,11 +803,11 @@ + 'sync/stage/CompletedStage.java', + 'sync/stage/EnsureClusterURLStage.java', + 'sync/stage/EnsureCrypto5KeysStage.java', +- 'sync/stage/FennecTabsServerSyncStage.java', + 'sync/stage/FetchInfoCollectionsStage.java', + 'sync/stage/FetchMetaGlobalStage.java', + 'sync/stage/FormHistoryServerSyncStage.java', + 'sync/stage/GlobalSyncStage.java', ++ 'sync/stage/IceCatMobileTabsServerSyncStage.java', + 'sync/stage/NoSuchStageException.java', + 'sync/stage/NoSyncIDException.java', + 'sync/stage/PasswordsServerSyncStage.java', diff --git a/data/patches/revert-gnuzilla-bug-973138.patch b/data/patches/revert-gnuzilla-bug-973138.patch new file mode 100644 index 0000000..ffd3a68 --- /dev/null +++ b/data/patches/revert-gnuzilla-bug-973138.patch @@ -0,0 +1,25 @@ +This patch reverts fix for gnuzilla bug: +https://bugzilla.mozilla.org/show_bug.cgi?id=973138 +which introduces: +https://trac.torproject.org/projects/tor/ticket/12811 +https://bugzilla.mozilla.org/show_bug.cgi?id=1049366 + +--- a/mozglue/build/WindowsDllBlocklist.cpp 2015-01-27 19:17:08.585519129 +0100 ++++ b/mozglue/build/WindowsDllBlocklist.cpp 2015-01-30 00:42:10.817863012 +0100 +@@ -551,7 +551,7 @@ + printf_stderr("LdrLoadDll: dll name '%s'\n", dllName); + #endif + +- // Block a suspicious binary that uses various 12-digit hex strings ++/* // Block a suspicious binary that uses various 12-digit hex strings + // e.g. MovieMode.48CA2AEFA22D.dll (bug 973138) + char * dot = strchr(dllName, '.'); + if (dot && (strchr(dot+1, '.') == dot+13)) { +@@ -561,6 +561,7 @@ + return STATUS_DLL_NOT_FOUND; + } + } ++*/ + + // then compare to everything on the blocklist + info = &sWindowsDllBlocklist[0]; diff --git a/data/patches/torbrowser-bug-12811-2.patch b/data/patches/torbrowser-bug-12811-2.patch new file mode 100644 index 0000000..e80e228 --- /dev/null +++ b/data/patches/torbrowser-bug-12811-2.patch @@ -0,0 +1,33 @@ +commit 73a628a5cebfa6dae1fca4c5fde072376ce4d06f +Author: Georg Koppen <gk at torproject.org> +Date: Fri Sep 12 09:37:39 2014 +0000 + + Bug 12811: Add MOZ_D3DCOMPILER_DLL iff available. + + It seems MOZ_D3DCOMPILER_DLL is not avilable when compiling with + mingw-w64. Trying to include it nevertheless breaks the packaging step + as `@BINPATH@/@MOZ_D3DCOMPILER_DLL@` resolves to `bin/` which results + in including all the things beginning with a `bin/` (again) which is + treated as an error in case they got already included. + + This got fixed more or less by accident by bug 999260 which did not make + it into ESR 31 (https://hg.mozilla.org/mozilla-central/rev/8b48386fc226). +--- + browser/installer/package-manifest.in | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in +index 521538f..c9117cf 100644 +--- a/browser/installer/package-manifest.in ++++ b/browser/installer/package-manifest.in +@@ -597,8 +597,10 @@ + #ifdef MOZ_ANGLE_RENDERER + @BINPATH@/libEGL.dll + @BINPATH@/libGLESv2.dll ++#ifdef MOZ_D3DCOMPILER_DLL_PATH + @BINPATH@/@MOZ_D3DCOMPILER_DLL@ + #endif ++#endif + + ; [Browser Chrome Files] + @BINPATH@/browser/chrome.manifest diff --git a/data/patches/torbrowser-bug-12811.patch b/data/patches/torbrowser-bug-12811.patch new file mode 100644 index 0000000..6b062f4 --- /dev/null +++ b/data/patches/torbrowser-bug-12811.patch @@ -0,0 +1,19 @@ + Bug 12811: WTypes.h -> wtypes.h +--- + .../google-breakpad/src/google_breakpad/common/breakpad_types.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/toolkit/crashreporter/google-breakpad/src/google_breakpad/common/breakpad_types.h b/toolkit/crashreporter/google-breakpad/src/google_breakpad/common/breakpad_types.h +index a60c5f3..e92436f 100644 +--- a/toolkit/crashreporter/google-breakpad/src/google_breakpad/common/breakpad_types.h ++++ b/toolkit/crashreporter/google-breakpad/src/google_breakpad/common/breakpad_types.h +@@ -58,7 +58,7 @@ + */ + #include BREAKPAD_CUSTOM_STDINT_H + #else +-#include <WTypes.h> ++#include <wtypes.h> + + typedef unsigned __int8 uint8_t; + typedef unsigned __int16 uint16_t; + diff --git a/data/patches/torbrowser-bug-9837.patch b/data/patches/torbrowser-bug-9837.patch new file mode 100644 index 0000000..f62f0cb --- /dev/null +++ b/data/patches/torbrowser-bug-9837.patch @@ -0,0 +1,22 @@ + Disable uninstall helper.exe on Win32. + + Reworked for Icecat + + Fixes 'make package' breakage on Windows builds. See + https://trac.torproject.org/projects/tor/ticket/9837 +--- + browser/installer/package-manifest.in | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +--- a/browser/installer/package-manifest.in 2015-01-30 23:33:42.296298458 +0100 ++++ b/browser/installer/package-manifest.in 2015-02-02 03:22:28.480752158 +0100 +@@ -43,7 +43,7 @@ + @BINPATH@/browser/@PREF_DIR@/firefox-l10n.js + @BINPATH@/browser/searchplugins/* + #ifdef XP_WIN32 +-@BINPATH@/uninstall/helper.exe ++;@BINPATH@/uninstall/helper.exe + #endif + #ifdef MOZ_UPDATER + @BINPATH@/update.locale + |