/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Update Prompt. * * The Initial Developer of the Original Code is Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Mark Finkle * Alex Pakhotin * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ const Cc = Components.classes; const Ci = Components.interfaces; const Cu = Components.utils; const UPDATE_NOTIFICATION_NAME = "update-app"; const UPDATE_NOTIFICATION_ICON = "drawable://alert_download_progress"; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyGetter(this, "gUpdateBundle", function aus_gUpdateBundle() { return Services.strings.createBundle("chrome://mozapps/locale/update/updates.properties"); }); XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function aus_gBrandBundle() { return Services.strings.createBundle("chrome://branding/locale/brand.properties"); }); XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function aus_gBrowserBundle() { return Services.strings.createBundle("chrome://browser/locale/browser.properties"); }); XPCOMUtils.defineLazyGetter(this, "AddonManager", function() { Cu.import("resource://gre/modules/AddonManager.jsm"); return AddonManager; }); XPCOMUtils.defineLazyGetter(this, "LocaleRepository", function() { Cu.import("resource://gre/modules/LocaleRepository.jsm"); return LocaleRepository; }); function getPref(func, preference, defaultValue) { try { return Services.prefs[func](preference); } catch (e) {} return defaultValue; } function sendMessageToJava(aMsg) { let data = Cc["@mozilla.org/android/bridge;1"].getService(Ci.nsIAndroidBridge).handleGeckoMessage(JSON.stringify(aMsg)); return JSON.parse(data); } // ----------------------------------------------------------------------- // Update Prompt // ----------------------------------------------------------------------- function UpdatePrompt() { } UpdatePrompt.prototype = { classID: Components.ID("{88b3eb21-d072-4e3b-886d-f89d8c49fe59}"), QueryInterface: XPCOMUtils.generateQI([Ci.nsIUpdatePrompt, Ci.nsIRequestObserver, Ci.nsIProgressEventSink]), get _enabled() { return !getPref("getBoolPref", "app.update.silent", false); }, _showNotification: function UP__showNotif(aUpdate, aTitle, aText, aImageUrl, aMode) { let observer = { updatePrompt: this, observe: function (aSubject, aTopic, aData) { switch (aTopic) { case "alertclickcallback": this.updatePrompt._handleUpdate(aUpdate, aMode); break; } } }; let notifier = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService); notifier.showAlertNotification(aImageUrl, aTitle, aText, true, "", observer, UPDATE_NOTIFICATION_NAME); }, _handleUpdate: function UP__handleUpdate(aUpdate, aMode) { if (aMode == "available") { let window = Services.wm.getMostRecentWindow("navigator:browser"); let title = gUpdateBundle.GetStringFromName("updatesfound_" + aUpdate.type + ".title"); let brandName = gBrandBundle.GetStringFromName("brandShortName"); // Unconditionally use the "major" type here as for now it is always a new version // without additional description required for a minor update message let message = gUpdateBundle.formatStringFromName("intro_major", [brandName, aUpdate.displayVersion], 2); let button0 = gUpdateBundle.GetStringFromName("okButton"); let button1 = gUpdateBundle.GetStringFromName("askLaterButton"); let prompt = Services.prompt; let flags = prompt.BUTTON_POS_0 * prompt.BUTTON_TITLE_IS_STRING + prompt.BUTTON_POS_1 * prompt.BUTTON_TITLE_IS_STRING; let download = (prompt.confirmEx(window, title, message, flags, button0, button1, null, null, {value: false}) == 0); if (download) { // Start downloading the update in the background let aus = Cc["@mozilla.org/updates/update-service;1"].getService(Ci.nsIApplicationUpdateService); if (aus.downloadUpdate(aUpdate, true) != "failed") { let title = gBrowserBundle.formatStringFromName("alertDownloadsStart", [aUpdate.name], 1); this._showNotification(aUpdate, title, "", UPDATE_NOTIFICATION_ICON, "download"); // Add this UI as a listener for active downloads aus.addDownloadListener(this); } } } else if(aMode == "downloaded") { // Notify all windows that an application quit has been requested let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool); Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart"); // If nothing aborted, restart the app if (cancelQuit.data == false) { sendMessageToJava({ gecko: { type: "Update:Restart" } }); } } }, _updateDownloadProgress: function UP__updateDownloadProgress(aProgress, aTotal) { let alertsService = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService); let progressListener = alertsService.QueryInterface(Ci.nsIAlertsProgressListener); if (progressListener) progressListener.onProgress(UPDATE_NOTIFICATION_NAME, aProgress, aTotal); }, // ------------------------- // nsIUpdatePrompt interface // ------------------------- // Right now this is used only to check for updates in progress checkForUpdates: function UP_checkForUpdates() { if (!this._enabled) return; let aus = Cc["@mozilla.org/updates/update-service;1"].getService(Ci.nsIApplicationUpdateService); if (!aus.isDownloading) return; let updateManager = Cc["@mozilla.org/updates/update-manager;1"].getService(Ci.nsIUpdateManager); let updateName = updateManager.activeUpdate ? updateManager.activeUpdate.name : gBrandBundle.GetStringFromName("brandShortName"); let title = gBrowserBundle.formatStringFromName("alertDownloadsStart", [updateName], 1); this._showNotification(updateManager.activeUpdate, title, "", UPDATE_NOTIFICATION_ICON, "downloading"); aus.removeDownloadListener(this); // just in case it's already added aus.addDownloadListener(this); }, showUpdateAvailable: function UP_showUpdateAvailable(aUpdate) { if (!this._enabled) return; const PREF_APP_UPDATE_SKIPNOTIFICATION = "app.update.skipNotification"; if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_SKIPNOTIFICATION) && Services.prefs.getBoolPref(PREF_APP_UPDATE_SKIPNOTIFICATION)) { Services.prefs.setBoolPref(PREF_APP_UPDATE_SKIPNOTIFICATION, false); // Notification was already displayed and clicked, so jump to the next step: // ask the user about downloading update this._handleUpdate(aUpdate, "available"); return; } let stringsPrefix = "updateAvailable_" + aUpdate.type + "."; let title = gUpdateBundle.formatStringFromName(stringsPrefix + "title", [aUpdate.name], 1); let text = gUpdateBundle.GetStringFromName(stringsPrefix + "text"); let imageUrl = ""; this._showNotification(aUpdate, title, text, imageUrl, "available"); }, showUpdateDownloaded: function UP_showUpdateDownloaded(aUpdate, aBackground) { if (!this._enabled) return; // uninstall all installed locales AddonManager.getAddonsByTypes(["locale"], (function (aAddons) { if (aAddons.length > 0) { let listener = this.getAddonListener(aUpdate, this); AddonManager.addAddonListener(listener); aAddons.forEach(function(aAddon) { listener._uninstalling.push(aAddon.id); aAddon.uninstall(); }, this); } else { this._showDownloadedNotification(aUpdate); } }).bind(this)); }, _showDownloadedNotification: function UP_showDlNotification(aUpdate) { let stringsPrefix = "updateDownloaded_" + aUpdate.type + "."; let title = gUpdateBundle.formatStringFromName(stringsPrefix + "title", [aUpdate.name], 1); let text = gUpdateBundle.GetStringFromName(stringsPrefix + "text"); let imageUrl = ""; this._showNotification(aUpdate, title, text, imageUrl, "downloaded"); }, _uninstalling: [], _installing: [], _currentUpdate: null, _reinstallLocales: function UP_reinstallLocales(aUpdate, aListener, aPending) { LocaleRepository.getLocales((function(aLocales) { aLocales.forEach(function(aLocale, aIndex, aArray) { let index = aPending.indexOf(aLocale.addon.id); if (index > -1) { aListener._installing.push(aLocale.addon.id); aLocale.addon.install.install(); } }, this); // store the buildid of these locales so that we can disable locales when the // user updates through a non-updater channel Services.prefs.setCharPref("extensions.compatability.locales.buildid", aUpdate.buildID); }).bind(this), { buildID: aUpdate.buildID }); }, showUpdateInstalled: function UP_showUpdateInstalled() { if (!this._enabled || !getPref("getBoolPref", "app.update.showInstalledUI", false)) return; let title = gBrandBundle.GetStringFromName("brandShortName"); let text = gUpdateBundle.GetStringFromName("installSuccess"); let imageUrl = ""; this._showNotification(aUpdate, title, text, imageUrl, "installed"); }, showUpdateError: function UP_showUpdateError(aUpdate) { if (!this._enabled) return; if (aUpdate.state == "failed") { var title = gBrandBundle.GetStringFromName("brandShortName"); let text = gUpdateBundle.GetStringFromName("updaterIOErrorTitle"); let imageUrl = ""; this._showNotification(aUpdate, title, text, imageUrl, "error"); } }, showUpdateHistory: function UP_showUpdateHistory(aParent) { // NOT IMPL }, // ---------------------------- // nsIRequestObserver interface // ---------------------------- // When the data transfer begins onStartRequest: function(request, context) { // NOT IMPL }, // When the data transfer ends onStopRequest: function(request, context, status) { let alertsService = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService); let progressListener = alertsService.QueryInterface(Ci.nsIAlertsProgressListener); if (progressListener) progressListener.onCancel(UPDATE_NOTIFICATION_NAME); let aus = Cc["@mozilla.org/updates/update-service;1"].getService(Ci.nsIApplicationUpdateService); aus.removeDownloadListener(this); }, // ------------------------------ // nsIProgressEventSink interface // ------------------------------ // When new data has been downloaded onProgress: function(request, context, progress, maxProgress) { this._updateDownloadProgress(progress, maxProgress); }, // When we have new status text onStatus: function(request, context, status, statusText) { // NOT IMPL }, // ------------------------------- // AddonListener // ------------------------------- getAddonListener: function(aUpdate, aUpdatePrompt) { return { _installing: [], _uninstalling: [], onInstalling: function(aAddon, aNeedsRestart) { let index = this._installing.indexOf(aAddon.id); if (index > -1) this._installing.splice(index, 1); if (this._installing.length == 0) { aUpdatePrompt._showDownloadedNotification(aUpdate); AddonManager.removeAddonListener(this); } }, onUninstalling: function(aAddon, aNeedsRestart) { let pending = []; let index = this._uninstalling.indexOf(aAddon.id); if (index > -1) { pending.push(aAddon.id); this._uninstalling.splice(index, 1); } if (this._uninstalling.length == 0) aUpdatePrompt._reinstallLocales(aUpdate, this, pending); } } } }; const NSGetFactory = XPCOMUtils.generateNSGetFactory([UpdatePrompt]);