michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const Cu = Components.utils; michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: michael@0: this.EXPORTED_SYMBOLS = ["OperatorAppsRegistry"]; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/FileUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Webapps.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/osfile.jsm"); michael@0: Cu.import("resource://gre/modules/AppsUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Task.jsm"); michael@0: michael@0: let Path = OS.Path; michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: XPCOMUtils.defineLazyServiceGetter(this, "iccProvider", michael@0: "@mozilla.org/ril/content-helper;1", michael@0: "nsIIccProvider"); michael@0: #endif michael@0: michael@0: function debug(aMsg) { michael@0: //dump("-*-*- OperatorApps.jsm : " + aMsg + "\n"); michael@0: } michael@0: michael@0: // Single Variant source dir will be set in PREF_SINGLE_VARIANT_DIR michael@0: // preference. michael@0: // if PREF_SINGLE_VARIANT_DIR does not exist or has not value, it will use (as michael@0: // single variant source) the value of michael@0: // DIRECTORY_NAME + "/" + SINGLE_VARIANT_SOURCE_DIR value instead. michael@0: // SINGLE_VARIANT_CONF_FILE will be stored on Single Variant Source. michael@0: // Apps will be stored on an app per directory basis, hanging from michael@0: // SINGLE_VARIANT_SOURCE_DIR michael@0: const DIRECTORY_NAME = "webappsDir"; michael@0: const SINGLE_VARIANT_SOURCE_DIR = "svoperapps"; michael@0: const SINGLE_VARIANT_CONF_FILE = "singlevariantconf.json"; michael@0: const PREF_FIRST_RUN_WITH_SIM = "dom.webapps.firstRunWithSIM"; michael@0: const PREF_SINGLE_VARIANT_DIR = "dom.mozApps.single_variant_sourcedir"; michael@0: const METADATA = "metadata.json"; michael@0: const UPDATEMANIFEST = "update.webapp"; michael@0: const MANIFEST = "manifest.webapp"; michael@0: const APPLICATION_ZIP = "application.zip"; michael@0: michael@0: function isFirstRunWithSIM() { michael@0: try { michael@0: if (Services.prefs.prefHasUserValue(PREF_FIRST_RUN_WITH_SIM)) { michael@0: return Services.prefs.getBoolPref(PREF_FIRST_RUN_WITH_SIM); michael@0: } michael@0: } catch(e) { michael@0: debug ("Error getting pref. " + e); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: #ifdef MOZ_B2G_RIL michael@0: let iccListener = { michael@0: notifyStkCommand: function() {}, michael@0: michael@0: notifyStkSessionEnd: function() {}, michael@0: michael@0: notifyCardStateChanged: function() {}, michael@0: michael@0: notifyIccInfoChanged: function() { michael@0: // TODO: Bug 927709 - OperatorApps for multi-sim michael@0: // In Multi-sim, there is more than one client in iccProvider. Each michael@0: // client represents a icc service. To maintain the backward compatibility michael@0: // with single sim, we always use client 0 for now. Adding support for michael@0: // multiple sim will be addressed in bug 927709, if needed. michael@0: let clientId = 0; michael@0: let iccInfo = iccProvider.getIccInfo(clientId); michael@0: if (iccInfo && iccInfo.mcc && iccInfo.mnc) { michael@0: let mcc = iccInfo.mcc; michael@0: let mnc = iccInfo.mnc; michael@0: debug("******* iccListener cardIccInfo MCC-MNC: " + mcc + "-" + mnc); michael@0: iccProvider.unregisterIccMsg(clientId, this); michael@0: OperatorAppsRegistry._installOperatorApps(mcc, mnc); michael@0: michael@0: debug("Broadcast message first-run-with-sim"); michael@0: let messenger = Cc["@mozilla.org/system-message-internal;1"] michael@0: .getService(Ci.nsISystemMessagesInternal); michael@0: messenger.broadcastMessage("first-run-with-sim", { mcc: mcc, michael@0: mnc: mnc }); michael@0: } michael@0: } michael@0: }; michael@0: #endif michael@0: michael@0: this.OperatorAppsRegistry = { michael@0: michael@0: _baseDirectory: null, michael@0: michael@0: init: function() { michael@0: debug("init"); michael@0: #ifdef MOZ_B2G_RIL michael@0: if (isFirstRunWithSIM()) { michael@0: debug("First Run with SIM"); michael@0: Task.spawn(function() { michael@0: try { michael@0: yield this._initializeSourceDir(); michael@0: // TODO: Bug 927709 - OperatorApps for multi-sim michael@0: // In Multi-sim, there is more than one client in iccProvider. Each michael@0: // client represents a icc service. To maintain the backward michael@0: // compatibility with single sim, we always use client 0 for now. michael@0: // Adding support for multiple sim will be addressed in bug 927709, if michael@0: // needed. michael@0: let clientId = 0; michael@0: let iccInfo = iccProvider.getIccInfo(clientId); michael@0: let mcc = 0; michael@0: let mnc = 0; michael@0: if (iccInfo && iccInfo.mcc) { michael@0: mcc = iccInfo.mcc; michael@0: } michael@0: if (iccInfo && iccInfo.mnc) { michael@0: mnc = iccInfo.mnc; michael@0: } michael@0: if (mcc && mnc) { michael@0: this._installOperatorApps(mcc, mnc); michael@0: } else { michael@0: iccProvider.registerIccMsg(clientId, iccListener); michael@0: } michael@0: } catch (e) { michael@0: debug("Error Initializing OperatorApps. " + e); michael@0: } michael@0: }.bind(this)); michael@0: } else { michael@0: debug("No First Run with SIM"); michael@0: } michael@0: #endif michael@0: }, michael@0: michael@0: _copyDirectory: function(aOrg, aDst) { michael@0: debug("copying " + aOrg + " to " + aDst); michael@0: return aDst && Task.spawn(function() { michael@0: try { michael@0: let orgDir = Cc["@mozilla.org/file/local;1"] michael@0: .createInstance(Ci.nsIFile); michael@0: orgDir.initWithPath(aOrg); michael@0: if (!orgDir.exists() || !orgDir.isDirectory()) { michael@0: debug(aOrg + " does not exist or is not a directory"); michael@0: return; michael@0: } michael@0: michael@0: let dstDir = Cc["@mozilla.org/file/local;1"] michael@0: .createInstance(Ci.nsIFile); michael@0: dstDir.initWithPath(aDst); michael@0: if (!dstDir.exists()) { michael@0: dstDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); michael@0: } michael@0: michael@0: let entries = orgDir.directoryEntries; michael@0: while (entries.hasMoreElements()) { michael@0: let entry = entries.getNext().QueryInterface(Ci.nsIFile); michael@0: michael@0: if (!entry.isDirectory()) { michael@0: // Remove the file, because copyTo doesn't overwrite files. michael@0: let dstFile = dstDir.clone(); michael@0: dstFile.append(entry.leafName); michael@0: if(dstFile.exists()) { michael@0: dstFile.remove(false); michael@0: } michael@0: michael@0: entry.copyTo(dstDir, entry.leafName); michael@0: } else { michael@0: yield this._copyDirectory(entry.path, michael@0: Path.join(aDst, entry.leafName)); michael@0: } michael@0: } michael@0: } catch (e) { michael@0: debug("Error copying " + aOrg + " to " + aDst + ". " + e); michael@0: } michael@0: }.bind(this)); michael@0: }, michael@0: michael@0: _initializeSourceDir: function() { michael@0: return Task.spawn(function() { michael@0: let svFinalDirName; michael@0: try { michael@0: svFinalDirName = Services.prefs.getCharPref(PREF_SINGLE_VARIANT_DIR); michael@0: } catch(e) { michael@0: debug ("Error getting pref. " + e); michael@0: this.appsDir = FileUtils.getFile(DIRECTORY_NAME, michael@0: [SINGLE_VARIANT_SOURCE_DIR]).path; michael@0: return; michael@0: } michael@0: // If SINGLE_VARIANT_CONF_FILE is in PREF_SINGLE_VARIANT_DIR return michael@0: // PREF_SINGLE_VARIANT_DIR as sourceDir, else go to michael@0: // DIRECTORY_NAME + SINGLE_VARIANT_SOURCE_DIR and move all apps (and michael@0: // configuration file) to PREF_SINGLE_VARIANT_DIR and return michael@0: // PREF_SINGLE_VARIANT_DIR as sourceDir. michael@0: let svFinalDir = Cc["@mozilla.org/file/local;1"] michael@0: .createInstance(Ci.nsIFile); michael@0: svFinalDir.initWithPath(svFinalDirName); michael@0: if (!svFinalDir.exists()) { michael@0: svFinalDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); michael@0: } michael@0: michael@0: let svIndex = svFinalDir.clone(); michael@0: svIndex.append(SINGLE_VARIANT_CONF_FILE); michael@0: if (!svIndex.exists()) { michael@0: let svSourceDir = FileUtils.getFile(DIRECTORY_NAME, michael@0: [SINGLE_VARIANT_SOURCE_DIR]); michael@0: michael@0: yield this._copyDirectory(svSourceDir.path, svFinalDirName); michael@0: michael@0: debug("removing directory:" + svSourceDir.path); michael@0: try { michael@0: svSourceDir.remove(true); michael@0: } catch(ex) { } michael@0: } michael@0: michael@0: this.appsDir = svFinalDirName; michael@0: }.bind(this)); michael@0: }, michael@0: michael@0: set appsDir(aDir) { michael@0: debug("appsDir SET: " + aDir); michael@0: if (aDir) { michael@0: this._baseDirectory = Cc["@mozilla.org/file/local;1"] michael@0: .createInstance(Ci.nsILocalFile); michael@0: this._baseDirectory.initWithPath(aDir); michael@0: } else { michael@0: this._baseDirectory = null; michael@0: } michael@0: }, michael@0: michael@0: get appsDir() { michael@0: return this._baseDirectory; michael@0: }, michael@0: michael@0: eraseVariantAppsNotInList: function(aIdsApp) { michael@0: if (!aIdsApp || !Array.isArray(aIdsApp)) { michael@0: aIdsApp = [ ]; michael@0: } michael@0: michael@0: let svDir; michael@0: try { michael@0: svDir = this.appsDir.clone(); michael@0: } catch (e) { michael@0: debug("eraseVariantAppsNotInList --> Error getting Dir "+ michael@0: svDir.path + ". " + e); michael@0: return; michael@0: } michael@0: michael@0: if (!svDir || !svDir.exists()) { michael@0: return; michael@0: } michael@0: michael@0: let entries = svDir.directoryEntries; michael@0: while (entries.hasMoreElements()) { michael@0: let entry = entries.getNext().QueryInterface(Ci.nsIFile); michael@0: if (entry.isDirectory() && aIdsApp.indexOf(entry.leafName) < 0) { michael@0: try{ michael@0: entry.remove(true); michael@0: } catch(e) { michael@0: debug("Error removing [" + entry.path + "]." + e); michael@0: } michael@0: } michael@0: } michael@0: }, michael@0: michael@0: _launchInstall: function(isPackage, aId, aMetadata, aManifest) { michael@0: if (!aManifest) { michael@0: debug("Error: The application " + aId + " does not have a manifest"); michael@0: return; michael@0: } michael@0: michael@0: let appData = { michael@0: app: { michael@0: installOrigin: aMetadata.installOrigin, michael@0: origin: aMetadata.origin, michael@0: manifestURL: aMetadata.manifestURL, michael@0: manifestHash: AppsUtils.computeHash(JSON.stringify(aManifest)) michael@0: }, michael@0: appId: undefined, michael@0: isBrowser: false, michael@0: isPackage: isPackage, michael@0: forceSuccessAck: true michael@0: }; michael@0: michael@0: if (isPackage) { michael@0: debug("aId:" + aId + ". Installing as packaged app."); michael@0: let installPack = this.appsDir.clone(); michael@0: installPack.append(aId); michael@0: installPack.append(APPLICATION_ZIP); michael@0: michael@0: if (!installPack.exists()) { michael@0: debug("SV " + installPack.path + " file do not exists for app " + aId); michael@0: return; michael@0: } michael@0: michael@0: appData.app.localInstallPath = installPack.path; michael@0: appData.app.updateManifest = aManifest; michael@0: DOMApplicationRegistry.confirmInstall(appData); michael@0: } else { michael@0: debug("aId:" + aId + ". Installing as hosted app."); michael@0: appData.app.manifest = aManifest; michael@0: DOMApplicationRegistry.confirmInstall(appData); michael@0: } michael@0: }, michael@0: michael@0: _installOperatorApps: function(aMcc, aMnc) { michael@0: Task.spawn(function() { michael@0: debug("Install operator apps ---> mcc:"+ aMcc + ", mnc:" + aMnc); michael@0: if (!isFirstRunWithSIM()) { michael@0: debug("Operator apps already installed."); michael@0: return; michael@0: } michael@0: michael@0: let aIdsApp = yield this._getSingleVariantApps(aMcc, aMnc); michael@0: debug("installOperatorApps --> aIdsApp:" + JSON.stringify(aIdsApp)); michael@0: for (let i = 0; i < aIdsApp.length; i++) { michael@0: let aId = aIdsApp[i]; michael@0: let aMetadata = yield AppsUtils.loadJSONAsync( michael@0: Path.join(this.appsDir.path, aId, METADATA)); michael@0: if (!aMetadata) { michael@0: debug("Error reading metadata file"); michael@0: return; michael@0: } michael@0: michael@0: debug("metadata:" + JSON.stringify(aMetadata)); michael@0: let isPackage = true; michael@0: let manifest; michael@0: let manifests = [UPDATEMANIFEST, MANIFEST]; michael@0: for (let j = 0; j < manifests.length; j++) { michael@0: manifest = yield AppsUtils.loadJSONAsync( michael@0: Path.join(this.appsDir.path, aId, manifests[j])); michael@0: michael@0: if (!manifest) { michael@0: isPackage = false; michael@0: } else { michael@0: break; michael@0: } michael@0: } michael@0: if (manifest) { michael@0: this._launchInstall(isPackage, aId, aMetadata, manifest); michael@0: } else { michael@0: debug ("Error. Neither " + UPDATEMANIFEST + " file nor " + MANIFEST + michael@0: " file for " + aId + " app."); michael@0: } michael@0: } michael@0: this.eraseVariantAppsNotInList(aIdsApp); michael@0: Services.prefs.setBoolPref(PREF_FIRST_RUN_WITH_SIM, false); michael@0: Services.prefs.savePrefFile(null); michael@0: }.bind(this)).then(null, function(aError) { michael@0: debug("Error: " + aError); michael@0: }); michael@0: }, michael@0: michael@0: _getSingleVariantApps: function(aMcc, aMnc) { michael@0: michael@0: function normalizeCode(aCode) { michael@0: let ncode = "" + aCode; michael@0: while (ncode.length < 3) { michael@0: ncode = "0" + ncode; michael@0: } michael@0: return ncode; michael@0: } michael@0: michael@0: return Task.spawn(function*() { michael@0: let key = normalizeCode(aMcc) + "-" + normalizeCode(aMnc); michael@0: let file = Path.join(this.appsDir.path, SINGLE_VARIANT_CONF_FILE); michael@0: let aData = yield AppsUtils.loadJSONAsync(file); michael@0: michael@0: if (!aData || !(key in aData)) { michael@0: return []; michael@0: } michael@0: michael@0: return aData[key]; michael@0: }.bind(this)); michael@0: } michael@0: }; michael@0: michael@0: OperatorAppsRegistry.init();