diff options
Diffstat (limited to 'b2g/components/UpdatePrompt.js')
-rw-r--r-- | b2g/components/UpdatePrompt.js | 783 |
1 files changed, 0 insertions, 783 deletions
diff --git a/b2g/components/UpdatePrompt.js b/b2g/components/UpdatePrompt.js deleted file mode 100644 index 1df07204c..000000000 --- a/b2g/components/UpdatePrompt.js +++ /dev/null @@ -1,783 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- - * vim: sw=2 ts=8 et : - */ -/* 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/. */ - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cu = Components.utils; -const Cr = Components.results; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/AppConstants.jsm"); - -const VERBOSE = 1; -var log = - VERBOSE ? - function log_dump(msg) { dump("UpdatePrompt: "+ msg +"\n"); } : - function log_noop(msg) { }; - -const PREF_APPLY_PROMPT_TIMEOUT = "b2g.update.apply-prompt-timeout"; -const PREF_APPLY_IDLE_TIMEOUT = "b2g.update.apply-idle-timeout"; -const PREF_DOWNLOAD_WATCHDOG_TIMEOUT = "b2g.update.download-watchdog-timeout"; -const PREF_DOWNLOAD_WATCHDOG_MAX_RETRIES = "b2g.update.download-watchdog-max-retries"; - -const NETWORK_ERROR_OFFLINE = 111; -const HTTP_ERROR_OFFSET = 1000; - -const STATE_DOWNLOADING = 'downloading'; - -XPCOMUtils.defineLazyServiceGetter(Services, "aus", - "@mozilla.org/updates/update-service;1", - "nsIApplicationUpdateService"); - -XPCOMUtils.defineLazyServiceGetter(Services, "um", - "@mozilla.org/updates/update-manager;1", - "nsIUpdateManager"); - -XPCOMUtils.defineLazyServiceGetter(Services, "idle", - "@mozilla.org/widget/idleservice;1", - "nsIIdleService"); - -XPCOMUtils.defineLazyServiceGetter(Services, "settings", - "@mozilla.org/settingsService;1", - "nsISettingsService"); - -XPCOMUtils.defineLazyServiceGetter(Services, 'env', - '@mozilla.org/process/environment;1', - 'nsIEnvironment'); - -function useSettings() { - // When we're running in the real phone, then we can use settings. - // But when we're running as part of xpcshell, there is no settings database - // and trying to use settings in this scenario causes lots of weird - // assertions at shutdown time. - if (typeof useSettings.result === "undefined") { - useSettings.result = !Services.env.get("XPCSHELL_TEST_PROFILE_DIR"); - } - return useSettings.result; -} - -XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy", - "resource://gre/modules/SystemAppProxy.jsm"); - -function UpdateCheckListener(updatePrompt) { - this._updatePrompt = updatePrompt; -} - -UpdateCheckListener.prototype = { - QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdateCheckListener]), - - _updatePrompt: null, - - onCheckComplete: function UCL_onCheckComplete(request, updates, updateCount) { - if (Services.um.activeUpdate) { - // We're actively downloading an update, that's the update the user should - // see, even if a newer update is available. - this._updatePrompt.setUpdateStatus("active-update"); - this._updatePrompt.showUpdateAvailable(Services.um.activeUpdate); - return; - } - - if (updateCount == 0) { - this._updatePrompt.setUpdateStatus("no-updates"); - - if (this._updatePrompt._systemUpdateListener) { - this._updatePrompt._systemUpdateListener.onError("no-updates"); - } - - return; - } - - let update = Services.aus.selectUpdate(updates, updateCount); - if (!update) { - this._updatePrompt.setUpdateStatus("already-latest-version"); - - if (this._updatePrompt._systemUpdateListener) { - this._updatePrompt._systemUpdateListener.onError("already-latest-version"); - } - - return; - } - - this._updatePrompt.setUpdateStatus("check-complete"); - this._updatePrompt.showUpdateAvailable(update); - }, - - onError: function UCL_onError(request, update) { - // nsIUpdate uses a signed integer for errorCode while any platform errors - // require all 32 bits. - let errorCode = update.errorCode >>> 0; - let isNSError = (errorCode >>> 31) == 1; - let errorMsg = "check-error-"; - - if (errorCode == NETWORK_ERROR_OFFLINE) { - errorMsg = "retry-when-online"; - this._updatePrompt.setUpdateStatus(errorMsg); - } else if (isNSError) { - errorMsg = "check-error-" + errorCode; - this._updatePrompt.setUpdateStatus(errorMsg); - } else if (errorCode > HTTP_ERROR_OFFSET) { - let httpErrorCode = errorCode - HTTP_ERROR_OFFSET; - errorMsg = "check-error-http-" + httpErrorCode; - this._updatePrompt.setUpdateStatus(errorMsg); - } - - if (this._updatePrompt._systemUpdateListener) { - this._updatePrompt._systemUpdateListener.onError(errorMsg); - } - - Services.aus.QueryInterface(Ci.nsIUpdateCheckListener); - Services.aus.onError(request, update); - } -}; - -function UpdatePrompt() { - this.wrappedJSObject = this; - this._updateCheckListener = new UpdateCheckListener(this); -} - -UpdatePrompt.prototype = { - classID: Components.ID("{88b3eb21-d072-4e3b-886d-f89d8c49fe59}"), - QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePrompt, - Ci.nsIUpdateCheckListener, - Ci.nsIRequestObserver, - Ci.nsIProgressEventSink, - Ci.nsIObserver, - Ci.nsISystemUpdateProvider]), - _xpcom_factory: XPCOMUtils.generateSingletonFactory(UpdatePrompt), - - _update: null, - _applyPromptTimer: null, - _waitingForIdle: false, - _updateCheckListner: null, - _systemUpdateListener: null, - _availableParameters: { - "deviceinfo.last_updated": null, - "gecko.updateStatus": null, - "app.update.channel": null, - "app.update.interval": null, - "app.update.url": null, - }, - _pendingUpdateAvailablePackageInfo: null, - _isPendingUpdateReady: false, - _updateErrorQueue: [ ], - _receivedUpdatePromptReady: false, - - // nsISystemUpdateProvider - checkForUpdate: function() { - this.forceUpdateCheck(); - }, - - startDownload: function() { - this.downloadUpdate(this._update); - }, - - stopDownload: function() { - this.handleDownloadCancel(); - }, - - applyUpdate: function() { - this.handleApplyPromptResult({result: "restart"}); - }, - - setParameter: function(aName, aValue) { - if (!this._availableParameters.hasOwnProperty(aName)) { - return false; - } - - this._availableParameters[aName] = aValue; - - switch (aName) { - case "app.update.channel": - case "app.update.url": - Services.prefs.setCharPref(aName, aValue); - break; - case "app.update.interval": - Services.prefs.setIntPref(aName, parseInt(aValue, 10)); - break; - } - - return true; - }, - - getParameter: function(aName) { - if (!this._availableParameters.hasOwnProperty(aName)) { - return null; - } - - return this._availableParameters[aName]; - }, - - setListener: function(aListener) { - this._systemUpdateListener = aListener; - - // If an update is available or ready, trigger the event right away at this point. - if (this._pendingUpdateAvailablePackageInfo) { - this._systemUpdateListener.onUpdateAvailable(this._pendingUpdateAvailablePackageInfo.type, - this._pendingUpdateAvailablePackageInfo.version, - this._pendingUpdateAvailablePackageInfo.description, - this._pendingUpdateAvailablePackageInfo.buildDate, - this._pendingUpdateAvailablePackageInfo.size); - // Set null when the listener is attached. - this._pendingUpdateAvailablePackageInfo = null; - } - - if (this._isPendingUpdateReady) { - this._systemUpdateListener.onUpdateReady(); - this._isPendingUpdateReady = false; - } - }, - - unsetListener: function(aListener) { - this._systemUpdateListener = null; - }, - - get applyPromptTimeout() { - return Services.prefs.getIntPref(PREF_APPLY_PROMPT_TIMEOUT); - }, - - get applyIdleTimeout() { - return Services.prefs.getIntPref(PREF_APPLY_IDLE_TIMEOUT); - }, - - handleContentStart: function UP_handleContentStart() { - SystemAppProxy.addEventListener("mozContentEvent", this); - }, - - // nsIUpdatePrompt - - // FIXME/bug 737601: we should have users opt-in to downloading - // updates when on a billed pipe. Initially, opt-in for 3g, but - // that doesn't cover all cases. - checkForUpdates: function UP_checkForUpdates() { }, - - showUpdateAvailable: function UP_showUpdateAvailable(aUpdate) { - let packageInfo = {}; - packageInfo.version = aUpdate.displayVersion; - packageInfo.description = aUpdate.statusText; - packageInfo.buildDate = aUpdate.buildID; - - let patch = aUpdate.selectedPatch; - if (!patch && aUpdate.patchCount > 0) { - // For now we just check the first patch to get size information if a - // patch hasn't been selected yet. - patch = aUpdate.getPatchAt(0); - } - - if (patch) { - packageInfo.size = patch.size; - packageInfo.type = patch.type; - } else { - log("Warning: no patches available in update"); - } - - this._pendingUpdateAvailablePackageInfo = packageInfo; - - if (this._systemUpdateListener) { - this._systemUpdateListener.onUpdateAvailable(packageInfo.type, - packageInfo.version, - packageInfo.description, - packageInfo.buildDate, - packageInfo.size); - // Set null since the event is fired. - this._pendingUpdateAvailablePackageInfo = null; - } - - if (!this.sendUpdateEvent("update-available", aUpdate)) { - - log("Unable to prompt for available update, forcing download"); - this.downloadUpdate(aUpdate); - } - }, - - showUpdateDownloaded: function UP_showUpdateDownloaded(aUpdate, aBackground) { - if (this._systemUpdateListener) { - this._systemUpdateListener.onUpdateReady(); - } else { - this._isPendingUpdateReady = true; - } - - // The update has been downloaded and staged. We send the update-downloaded - // event right away. After the user has been idle for a while, we send the - // update-prompt-restart event, increasing the chances that we can apply the - // update quietly without user intervention. - this.sendUpdateEvent("update-downloaded", aUpdate); - - if (Services.idle.idleTime >= this.applyIdleTimeout) { - this.showApplyPrompt(aUpdate); - return; - } - - let applyIdleTimeoutSeconds = this.applyIdleTimeout / 1000; - // We haven't been idle long enough, so register an observer - log("Update is ready to apply, registering idle timeout of " + - applyIdleTimeoutSeconds + " seconds before prompting."); - - this._update = aUpdate; - this.waitForIdle(); - }, - - storeUpdateError: function UP_storeUpdateError(aUpdate) { - log("Storing update error for later use"); - this._updateErrorQueue.push(aUpdate); - }, - - sendStoredUpdateError: function UP_sendStoredUpdateError() { - log("Sending stored update error"); - this._updateErrorQueue.forEach(aUpdate => { - this.sendUpdateEvent("update-error", aUpdate); - }); - this._updateErrorQueue = [ ]; - }, - - showUpdateError: function UP_showUpdateError(aUpdate) { - log("Update error, state: " + aUpdate.state + ", errorCode: " + - aUpdate.errorCode); - if (this._systemUpdateListener) { - this._systemUpdateListener.onError("update-error: " + aUpdate.errorCode + " " + aUpdate.statusText); - } - - if (!this._receivedUpdatePromptReady) { - this.storeUpdateError(aUpdate); - } else { - this.sendUpdateEvent("update-error", aUpdate); - } - - this.setUpdateStatus(aUpdate.statusText); - }, - - showUpdateHistory: function UP_showUpdateHistory(aParent) { }, - showUpdateInstalled: function UP_showUpdateInstalled() { - this.setParameter("deviceinfo.last_updated", Date.now()); - - if (useSettings()) { - let lock = Services.settings.createLock(); - lock.set("deviceinfo.last_updated", Date.now(), null, null); - } - }, - - // Custom functions - - waitForIdle: function UP_waitForIdle() { - if (this._waitingForIdle) { - return; - } - - this._waitingForIdle = true; - Services.idle.addIdleObserver(this, this.applyIdleTimeout / 1000); - Services.obs.addObserver(this, "quit-application", false); - }, - - setUpdateStatus: function UP_setUpdateStatus(aStatus) { - this.setParameter("gecko.updateStatus", aStatus); - - if (useSettings()) { - log("Setting gecko.updateStatus: " + aStatus); - - let lock = Services.settings.createLock(); - lock.set("gecko.updateStatus", aStatus, null); - } - }, - - showApplyPrompt: function UP_showApplyPrompt(aUpdate) { - // Notify update package is ready to apply - if (this._systemUpdateListener) { - this._systemUpdateListener.onUpdateReady(); - } else { - // Set the flag to true and fire the onUpdateReady event when the listener is attached. - this._isPendingUpdateReady = true; - } - - if (!this.sendUpdateEvent("update-prompt-apply", aUpdate)) { - log("Unable to prompt, forcing restart"); - this.restartProcess(); - return; - } - - if (AppConstants.MOZ_B2G_RIL) { - let window = Services.wm.getMostRecentWindow("navigator:browser"); - let pinReq = window.navigator.mozIccManager.getCardLock("pin"); - pinReq.onsuccess = function(e) { - if (e.target.result.enabled) { - // The SIM is pin locked. Don't use a fallback timer. This means that - // the user has to press Install to apply the update. If we use the - // timer, and the timer reboots the phone, then the phone will be - // unusable until the SIM is unlocked. - log("SIM is pin locked. Not starting fallback timer."); - } else { - // This means that no pin lock is enabled, so we go ahead and start - // the fallback timer. - this._applyPromptTimer = this.createTimer(this.applyPromptTimeout); - } - }.bind(this); - pinReq.onerror = function(e) { - this._applyPromptTimer = this.createTimer(this.applyPromptTimeout); - }.bind(this); - } else { - // Schedule a fallback timeout in case the UI is unable to respond or show - // a prompt for some reason. - this._applyPromptTimer = this.createTimer(this.applyPromptTimeout); - } - }, - - _copyProperties: ["appVersion", "buildID", "detailsURL", "displayVersion", - "errorCode", "isOSUpdate", "platformVersion", - "previousAppVersion", "state", "statusText"], - - sendUpdateEvent: function UP_sendUpdateEvent(aType, aUpdate) { - let detail = {}; - for (let property of this._copyProperties) { - detail[property] = aUpdate[property]; - } - - let patch = aUpdate.selectedPatch; - if (!patch && aUpdate.patchCount > 0) { - // For now we just check the first patch to get size information if a - // patch hasn't been selected yet. - patch = aUpdate.getPatchAt(0); - } - - if (patch) { - detail.size = patch.size; - detail.updateType = patch.type; - } else { - log("Warning: no patches available in update"); - } - - this._update = aUpdate; - return this.sendChromeEvent(aType, detail); - }, - - sendChromeEvent: function UP_sendChromeEvent(aType, aDetail) { - let detail = aDetail || {}; - detail.type = aType; - - let sent = SystemAppProxy.dispatchEvent(detail); - if (!sent) { - log("Warning: Couldn't send update event " + aType + - ": no content browser. Will send again when content becomes available."); - return false; - } - return true; - }, - - handleAvailableResult: function UP_handleAvailableResult(aDetail) { - // If the user doesn't choose "download", the updater will implicitly call - // showUpdateAvailable again after a certain period of time - switch (aDetail.result) { - case "download": - this.downloadUpdate(this._update); - break; - } - }, - - handleApplyPromptResult: function UP_handleApplyPromptResult(aDetail) { - if (this._applyPromptTimer) { - this._applyPromptTimer.cancel(); - this._applyPromptTimer = null; - } - - switch (aDetail.result) { - // Battery not okay, do not wait for idle to re-prompt - case "low-battery": - break; - case "wait": - // Wait until the user is idle before prompting to apply the update - this.waitForIdle(); - break; - case "restart": - this.finishUpdate(); - this._update = null; - break; - } - }, - - downloadUpdate: function UP_downloadUpdate(aUpdate) { - if (!aUpdate) { - aUpdate = Services.um.activeUpdate; - if (!aUpdate) { - log("No active update found to download"); - return; - } - } - - let status = Services.aus.downloadUpdate(aUpdate, true); - if (status == STATE_DOWNLOADING) { - Services.aus.addDownloadListener(this); - return; - } - - // If the update has already been downloaded and applied, then - // Services.aus.downloadUpdate will return immediately and not - // call showUpdateDownloaded, so we detect this. - if (aUpdate.state == "applied" && aUpdate.errorCode == 0) { - this.showUpdateDownloaded(aUpdate, true); - return; - } - - log("Error downloading update " + aUpdate.name + ": " + aUpdate.errorCode); - let errorCode = aUpdate.errorCode >>> 0; - if (errorCode == Cr.NS_ERROR_FILE_TOO_BIG) { - aUpdate.statusText = "file-too-big"; - } - this.showUpdateError(aUpdate); - }, - - handleDownloadCancel: function UP_handleDownloadCancel() { - log("Pausing download"); - Services.aus.pauseDownload(); - }, - - finishUpdate: function UP_finishUpdate() { - if (!this._update.isOSUpdate) { - // Standard gecko+gaia updates will just need to restart the process - this.restartProcess(); - return; - } - - try { - Services.aus.applyOsUpdate(this._update); - } - catch (e) { - this._update.errorCode = Cr.NS_ERROR_FAILURE; - this.showUpdateError(this._update); - } - }, - - restartProcess: function UP_restartProcess() { - log("Update downloaded, restarting to apply it"); - - let callbackAfterSet = function() { - if (AppConstants.platform !== "gonk") { - let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"] - .getService(Ci.nsIAppStartup); - appStartup.quit(appStartup.eForceQuit | appStartup.eRestart); - } else { - // NB: on Gonk, we rely on the system process manager to restart us. - let pmService = Cc["@mozilla.org/power/powermanagerservice;1"] - .getService(Ci.nsIPowerManagerService); - pmService.restart(); - } - } - - if (useSettings()) { - // Save current os version in deviceinfo.previous_os - let lock = Services.settings.createLock({ - handle: callbackAfterSet, - handleAbort: function(error) { - log("Abort callback when trying to set previous_os: " + error); - callbackAfterSet(); - } - }); - lock.get("deviceinfo.os", { - handle: function(name, value) { - log("Set previous_os to: " + value); - lock.set("deviceinfo.previous_os", value, null, null); - } - }); - } - }, - - forceUpdateCheck: function UP_forceUpdateCheck() { - log("Forcing update check"); - - let checker = Cc["@mozilla.org/updates/update-checker;1"] - .createInstance(Ci.nsIUpdateChecker); - checker.checkForUpdates(this._updateCheckListener, true); - }, - - handleEvent: function UP_handleEvent(evt) { - if (evt.type !== "mozContentEvent") { - return; - } - - let detail = evt.detail; - if (!detail) { - return; - } - - switch (detail.type) { - case "force-update-check": - this.forceUpdateCheck(); - break; - case "update-available-result": - this.handleAvailableResult(detail); - // If we started the apply prompt timer, this means that we're waiting - // for the user to press Later or Install Now. In this situation we - // don't want to clear this._update, becuase handleApplyPromptResult - // needs it. - if (this._applyPromptTimer == null && !this._waitingForIdle) { - this._update = null; - } - break; - case "update-download-cancel": - this.handleDownloadCancel(); - break; - case "update-prompt-apply-result": - this.handleApplyPromptResult(detail); - break; - case "update-prompt-ready": - this._receivedUpdatePromptReady = true; - this.sendStoredUpdateError(); - break; - } - }, - - // nsIObserver - - observe: function UP_observe(aSubject, aTopic, aData) { - switch (aTopic) { - case "idle": - this._waitingForIdle = false; - this.showApplyPrompt(this._update); - // Fall through - case "quit-application": - Services.idle.removeIdleObserver(this, this.applyIdleTimeout / 1000); - Services.obs.removeObserver(this, "quit-application"); - break; - } - }, - - // nsITimerCallback - - notify: function UP_notify(aTimer) { - if (aTimer == this._applyPromptTimer) { - log("Timed out waiting for result, restarting"); - this._applyPromptTimer = null; - this.finishUpdate(); - this._update = null; - return; - } - if (aTimer == this._watchdogTimer) { - log("Download watchdog fired"); - this._watchdogTimer = null; - this._autoRestartDownload = true; - Services.aus.pauseDownload(); - return; - } - }, - - createTimer: function UP_createTimer(aTimeoutMs) { - let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); - timer.initWithCallback(this, aTimeoutMs, timer.TYPE_ONE_SHOT); - return timer; - }, - - // nsIRequestObserver - - _startedSent: false, - - _watchdogTimer: null, - - _autoRestartDownload: false, - _autoRestartCount: 0, - - startWatchdogTimer: function UP_startWatchdogTimer() { - let watchdogTimeout = 120000; // 120 seconds - try { - watchdogTimeout = Services.prefs.getIntPref(PREF_DOWNLOAD_WATCHDOG_TIMEOUT); - } catch (e) { - // This means that the preference doesn't exist. watchdogTimeout will - // retain its default assigned above. - } - if (watchdogTimeout <= 0) { - // 0 implies don't bother using the watchdog timer at all. - this._watchdogTimer = null; - return; - } - if (this._watchdogTimer) { - this._watchdogTimer.cancel(); - } else { - this._watchdogTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); - } - this._watchdogTimer.initWithCallback(this, watchdogTimeout, - Ci.nsITimer.TYPE_ONE_SHOT); - }, - - stopWatchdogTimer: function UP_stopWatchdogTimer() { - if (this._watchdogTimer) { - this._watchdogTimer.cancel(); - this._watchdogTimer = null; - } - }, - - touchWatchdogTimer: function UP_touchWatchdogTimer() { - this.startWatchdogTimer(); - }, - - onStartRequest: function UP_onStartRequest(aRequest, aContext) { - // Wait until onProgress to send the update-download-started event, in case - // this request turns out to fail for some reason - this._startedSent = false; - this.startWatchdogTimer(); - }, - - onStopRequest: function UP_onStopRequest(aRequest, aContext, aStatusCode) { - this.stopWatchdogTimer(); - Services.aus.removeDownloadListener(this); - let paused = !Components.isSuccessCode(aStatusCode); - if (!paused) { - // The download was successful, no need to restart - this._autoRestartDownload = false; - } - if (this._autoRestartDownload) { - this._autoRestartDownload = false; - let watchdogMaxRetries = Services.prefs.getIntPref(PREF_DOWNLOAD_WATCHDOG_MAX_RETRIES); - this._autoRestartCount++; - if (this._autoRestartCount > watchdogMaxRetries) { - log("Download - retry count exceeded - error"); - // We exceeded the max retries. Treat the download like an error, - // which will give the user a chance to restart manually later. - this._autoRestartCount = 0; - if (Services.um.activeUpdate) { - this.showUpdateError(Services.um.activeUpdate); - } - return; - } - log("Download - restarting download - attempt " + this._autoRestartCount); - this.downloadUpdate(null); - return; - } - this._autoRestartCount = 0; - this.sendChromeEvent("update-download-stopped", { - paused: paused - }); - }, - - // nsIProgressEventSink - - onProgress: function UP_onProgress(aRequest, aContext, aProgress, - aProgressMax) { - if (this._systemUpdateListener) { - this._systemUpdateListener.onProgress(aProgress, aProgressMax); - } - - if (aProgress == aProgressMax) { - // The update.mar validation done by onStopRequest may take - // a while before the onStopRequest callback is made, so stop - // the timer now. - this.stopWatchdogTimer(); - } else { - this.touchWatchdogTimer(); - } - if (!this._startedSent) { - this.sendChromeEvent("update-download-started", { - total: aProgressMax - }); - this._startedSent = true; - } - - this.sendChromeEvent("update-download-progress", { - progress: aProgress, - total: aProgressMax - }); - }, - - onStatus: function UP_onStatus(aRequest, aUpdate, aStatus, aStatusArg) { } -}; - -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([UpdatePrompt]); |