mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 00:01:50 +00:00
409 lines
13 KiB
JavaScript
409 lines
13 KiB
JavaScript
/* 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 Cu = Components.utils;
|
|
const Cc = Components.classes;
|
|
const Ci = Components.interfaces;
|
|
|
|
this.EXPORTED_SYMBOLS = ["OperatorAppsRegistry"];
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
Cu.import("resource://gre/modules/FileUtils.jsm");
|
|
Cu.import("resource://gre/modules/Webapps.jsm");
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
Cu.import("resource://gre/modules/osfile.jsm");
|
|
Cu.import("resource://gre/modules/AppsUtils.jsm");
|
|
Cu.import("resource://gre/modules/Task.jsm");
|
|
|
|
var Path = OS.Path;
|
|
|
|
#ifdef MOZ_B2G_RIL
|
|
XPCOMUtils.defineLazyServiceGetter(this, "gIccService",
|
|
"@mozilla.org/icc/iccservice;1",
|
|
"nsIIccService");
|
|
#endif
|
|
|
|
function debug(aMsg) {
|
|
//dump("-*-*- OperatorApps.jsm : " + aMsg + "\n");
|
|
}
|
|
|
|
// Single Variant source dir will be set in PREF_SINGLE_VARIANT_DIR
|
|
// preference.
|
|
// if PREF_SINGLE_VARIANT_DIR does not exist or has not value, it will use (as
|
|
// single variant source) the value of
|
|
// DIRECTORY_NAME + "/" + SINGLE_VARIANT_SOURCE_DIR value instead.
|
|
// SINGLE_VARIANT_CONF_FILE will be stored on Single Variant Source.
|
|
// Apps will be stored on an app per directory basis, hanging from
|
|
// SINGLE_VARIANT_SOURCE_DIR
|
|
const DIRECTORY_NAME = "webappsDir";
|
|
const SINGLE_VARIANT_SOURCE_DIR = "svoperapps";
|
|
const SINGLE_VARIANT_CONF_FILE = "singlevariantconf.json";
|
|
const PREF_FIRST_RUN_WITH_SIM = "dom.webapps.firstRunWithSIM";
|
|
const PREF_SINGLE_VARIANT_DIR = "dom.mozApps.single_variant_sourcedir";
|
|
const METADATA = "metadata.json";
|
|
const UPDATEMANIFEST = "update.webapp";
|
|
const MANIFEST = "manifest.webapp";
|
|
const APPLICATION_ZIP = "application.zip";
|
|
|
|
function isFirstRunWithSIM() {
|
|
try {
|
|
if (Services.prefs.prefHasUserValue(PREF_FIRST_RUN_WITH_SIM)) {
|
|
return Services.prefs.getBoolPref(PREF_FIRST_RUN_WITH_SIM);
|
|
}
|
|
} catch(e) {
|
|
debug ("Error getting pref. " + e);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#ifdef MOZ_B2G_RIL
|
|
var iccListener = {
|
|
notifyStkCommand: function() {},
|
|
|
|
notifyStkSessionEnd: function() {},
|
|
|
|
notifyCardStateChanged: function() {},
|
|
|
|
notifyIccInfoChanged: function() {
|
|
// TODO: Bug 927709 - OperatorApps for multi-sim
|
|
// In Multi-sim, there is more than one client in IccService. Each
|
|
// client represents a icc handle. To maintain the backward compatibility
|
|
// with single sim, we always use client 0 for now. Adding support for
|
|
// multiple sim will be addressed in bug 927709, if needed.
|
|
let clientId = 0;
|
|
let icc = gIccService.getIccByServiceId(clientId);
|
|
let iccInfo = icc && icc.iccInfo;
|
|
if (iccInfo && iccInfo.mcc && iccInfo.mnc) {
|
|
let mcc = iccInfo.mcc;
|
|
let mnc = iccInfo.mnc;
|
|
debug("******* iccListener cardIccInfo MCC-MNC: " + mcc + "-" + mnc);
|
|
icc.unregisterListener(this);
|
|
OperatorAppsRegistry._installOperatorApps(mcc, mnc);
|
|
|
|
debug("Broadcast message first-run-with-sim");
|
|
let messenger = Cc["@mozilla.org/system-message-internal;1"]
|
|
.getService(Ci.nsISystemMessagesInternal);
|
|
messenger.broadcastMessage("first-run-with-sim", { mcc: mcc,
|
|
mnc: mnc });
|
|
}
|
|
}
|
|
};
|
|
#endif
|
|
|
|
this.OperatorAppsRegistry = {
|
|
|
|
_baseDirectory: null,
|
|
|
|
init: function() {
|
|
debug("init");
|
|
#ifdef MOZ_B2G_RIL
|
|
if (isFirstRunWithSIM()) {
|
|
debug("First Run with SIM");
|
|
Task.spawn(function() {
|
|
try {
|
|
yield this._initializeSourceDir();
|
|
// TODO: Bug 927709 - OperatorApps for multi-sim
|
|
// In Multi-sim, there is more than one client in IccService. Each
|
|
// client represents a icc handle. To maintain the backward
|
|
// compatibility with single sim, we always use client 0 for now.
|
|
// Adding support for multiple sim will be addressed in bug 927709, if
|
|
// needed.
|
|
let clientId = 0;
|
|
let icc = gIccService.getIccByServiceId(clientId);
|
|
let iccInfo = icc && icc.iccInfo;
|
|
let mcc = 0;
|
|
let mnc = 0;
|
|
if (iccInfo && iccInfo.mcc) {
|
|
mcc = iccInfo.mcc;
|
|
}
|
|
if (iccInfo && iccInfo.mnc) {
|
|
mnc = iccInfo.mnc;
|
|
}
|
|
if (mcc && mnc) {
|
|
this._installOperatorApps(mcc, mnc);
|
|
let messenger = Cc["@mozilla.org/system-message-internal;1"]
|
|
.getService(Ci.nsISystemMessagesInternal);
|
|
messenger.broadcastMessage("first-run-with-sim", { mcc: mcc,
|
|
mnc: mnc });
|
|
|
|
} else {
|
|
icc.registerListener(iccListener);
|
|
}
|
|
} catch (e) {
|
|
debug("Error Initializing OperatorApps. " + e);
|
|
}
|
|
}.bind(this));
|
|
} else {
|
|
debug("No First Run with SIM");
|
|
}
|
|
#endif
|
|
},
|
|
|
|
_copyDirectory: function(aOrg, aDst) {
|
|
debug("copying " + aOrg + " to " + aDst);
|
|
return aDst && Task.spawn(function() {
|
|
try {
|
|
let orgDir = Cc["@mozilla.org/file/local;1"]
|
|
.createInstance(Ci.nsIFile);
|
|
orgDir.initWithPath(aOrg);
|
|
if (!orgDir.exists() || !orgDir.isDirectory()) {
|
|
debug(aOrg + " does not exist or is not a directory");
|
|
return;
|
|
}
|
|
|
|
let dstDir = Cc["@mozilla.org/file/local;1"]
|
|
.createInstance(Ci.nsIFile);
|
|
dstDir.initWithPath(aDst);
|
|
if (!dstDir.exists()) {
|
|
dstDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
|
|
}
|
|
|
|
let entries = orgDir.directoryEntries;
|
|
while (entries.hasMoreElements()) {
|
|
let entry = entries.getNext().QueryInterface(Ci.nsIFile);
|
|
|
|
if (!entry.isDirectory()) {
|
|
// Remove the file, because copyTo doesn't overwrite files.
|
|
let dstFile = dstDir.clone();
|
|
dstFile.append(entry.leafName);
|
|
if(dstFile.exists()) {
|
|
dstFile.remove(false);
|
|
}
|
|
|
|
entry.copyTo(dstDir, entry.leafName);
|
|
} else {
|
|
yield this._copyDirectory(entry.path,
|
|
Path.join(aDst, entry.leafName));
|
|
}
|
|
}
|
|
} catch (e) {
|
|
debug("Error copying " + aOrg + " to " + aDst + ". " + e);
|
|
}
|
|
}.bind(this));
|
|
},
|
|
|
|
_initializeSourceDir: function() {
|
|
return Task.spawn(function() {
|
|
let svFinalDirName;
|
|
try {
|
|
svFinalDirName = Services.prefs.getCharPref(PREF_SINGLE_VARIANT_DIR);
|
|
} catch(e) {
|
|
debug ("Error getting pref. " + e);
|
|
this.appsDir = FileUtils.getFile(DIRECTORY_NAME,
|
|
[SINGLE_VARIANT_SOURCE_DIR]).path;
|
|
return;
|
|
}
|
|
// If SINGLE_VARIANT_CONF_FILE is in PREF_SINGLE_VARIANT_DIR return
|
|
// PREF_SINGLE_VARIANT_DIR as sourceDir, else go to
|
|
// DIRECTORY_NAME + SINGLE_VARIANT_SOURCE_DIR and move all apps (and
|
|
// configuration file) to PREF_SINGLE_VARIANT_DIR and return
|
|
// PREF_SINGLE_VARIANT_DIR as sourceDir.
|
|
let svFinalDir = Cc["@mozilla.org/file/local;1"]
|
|
.createInstance(Ci.nsIFile);
|
|
svFinalDir.initWithPath(svFinalDirName);
|
|
if (!svFinalDir.exists()) {
|
|
svFinalDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
|
|
}
|
|
|
|
let svIndex = svFinalDir.clone();
|
|
svIndex.append(SINGLE_VARIANT_CONF_FILE);
|
|
if (!svIndex.exists()) {
|
|
let svSourceDir = FileUtils.getFile(DIRECTORY_NAME,
|
|
[SINGLE_VARIANT_SOURCE_DIR]);
|
|
|
|
yield this._copyDirectory(svSourceDir.path, svFinalDirName);
|
|
|
|
debug("removing directory:" + svSourceDir.path);
|
|
try {
|
|
svSourceDir.remove(true);
|
|
} catch(ex) { }
|
|
}
|
|
|
|
this.appsDir = svFinalDirName;
|
|
}.bind(this));
|
|
},
|
|
|
|
set appsDir(aDir) {
|
|
debug("appsDir SET: " + aDir);
|
|
if (aDir) {
|
|
this._baseDirectory = Cc["@mozilla.org/file/local;1"]
|
|
.createInstance(Ci.nsILocalFile);
|
|
this._baseDirectory.initWithPath(aDir);
|
|
} else {
|
|
this._baseDirectory = null;
|
|
}
|
|
},
|
|
|
|
get appsDir() {
|
|
return this._baseDirectory;
|
|
},
|
|
|
|
eraseVariantAppsNotInList: function(aIdsApp) {
|
|
if (!aIdsApp) {
|
|
aIdsApp = [ ];
|
|
}
|
|
|
|
let svDir;
|
|
try {
|
|
svDir = this.appsDir.clone();
|
|
} catch (e) {
|
|
debug("eraseVariantAppsNotInList --> Error getting Dir "+
|
|
svDir.path + ". " + e);
|
|
return;
|
|
}
|
|
|
|
if (!svDir || !svDir.exists()) {
|
|
return;
|
|
}
|
|
|
|
let entries = svDir.directoryEntries;
|
|
while (entries.hasMoreElements()) {
|
|
let entry = entries.getNext().QueryInterface(Ci.nsIFile);
|
|
if (entry.isDirectory() && aIdsApp.indexOf(entry.leafName) < 0) {
|
|
try{
|
|
entry.remove(true);
|
|
} catch(e) {
|
|
debug("Error removing [" + entry.path + "]." + e);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
_launchInstall: function(isPackage, aId, aMetadata, aManifest) {
|
|
if (!aManifest) {
|
|
debug("Error: The application " + aId + " does not have a manifest");
|
|
return;
|
|
}
|
|
|
|
let appData = {
|
|
app: {
|
|
installOrigin: aMetadata.installOrigin,
|
|
origin: aMetadata.origin,
|
|
manifestURL: aMetadata.manifestURL,
|
|
manifestHash: AppsUtils.computeHash(JSON.stringify(aManifest))
|
|
},
|
|
appId: undefined,
|
|
isBrowser: false,
|
|
isPackage: isPackage,
|
|
forceSuccessAck: true
|
|
};
|
|
|
|
if (isPackage) {
|
|
debug("aId:" + aId + ". Installing as packaged app.");
|
|
let installPack = this.appsDir.clone();
|
|
installPack.append(aId);
|
|
installPack.append(APPLICATION_ZIP);
|
|
|
|
if (!installPack.exists()) {
|
|
debug("SV " + installPack.path + " file do not exists for app " + aId);
|
|
return;
|
|
}
|
|
|
|
appData.app.localInstallPath = installPack.path;
|
|
appData.app.updateManifest = aManifest;
|
|
DOMApplicationRegistry.confirmInstall(appData);
|
|
} else {
|
|
debug("aId:" + aId + ". Installing as hosted app.");
|
|
appData.app.manifest = aManifest;
|
|
DOMApplicationRegistry.confirmInstall(appData);
|
|
}
|
|
},
|
|
|
|
_installOperatorApps: function(aMcc, aMnc) {
|
|
function normalizeCode(aCode) {
|
|
let ncode = "" + aCode;
|
|
while (ncode.length < 3) {
|
|
ncode = "0" + ncode;
|
|
}
|
|
return ncode;
|
|
}
|
|
|
|
Task.spawn(function() {
|
|
debug("Install operator apps ---> mcc:"+ aMcc + ", mnc:" + aMnc);
|
|
if (!isFirstRunWithSIM()) {
|
|
debug("Operator apps already installed.");
|
|
return;
|
|
}
|
|
|
|
let key = normalizeCode(aMcc) + "-" + normalizeCode(aMnc);
|
|
let aIdsApp = yield this._getSingleVariantDatas();
|
|
|
|
// aIdsApp will be undefined if the singleVariant config file not exist
|
|
// or will have the following format:
|
|
// {"mmc1-mnc1": [ap11,...,ap1N],..., "mmcM-mncM": [apM1,...,apMN]}
|
|
// Behavior:
|
|
// * If the configuration file does not exist, it's equivalent to
|
|
// passing []
|
|
// * If the configuration file has data and the phone boots with a SIM
|
|
// that isn't on the configuration file then we must have the same
|
|
// behavior as if the phone had booted without a SIM inserted
|
|
// (that is, don't do anything)
|
|
// * If the phone boots with a configured SIM (mcc-mnc exists on
|
|
// configuration file) then recover the app list to install
|
|
if (!aIdsApp) {
|
|
debug("No " + SINGLE_VARIANT_CONF_FILE + " in " + this.appsDir.path);
|
|
aIdsApp = [];
|
|
} else if (aIdsApp[key] === undefined) {
|
|
debug("First Run with SIM not configured");
|
|
return;
|
|
} else {
|
|
debug("First Run with configured SIM ");
|
|
aIdsApp = aIdsApp[key];
|
|
if (!Array.isArray(aIdsApp)) {
|
|
aIdsApp = [aIdsApp];
|
|
}
|
|
}
|
|
|
|
debug("installOperatorApps --> aIdsApp:" + JSON.stringify(aIdsApp));
|
|
for (let i = 0; i < aIdsApp.length; i++) {
|
|
let aId = aIdsApp[i];
|
|
let aMetadata = yield AppsUtils.loadJSONAsync(
|
|
Path.join(this.appsDir.path, aId, METADATA));
|
|
if (!aMetadata) {
|
|
debug("Error reading metadata file");
|
|
return;
|
|
}
|
|
|
|
debug("metadata:" + JSON.stringify(aMetadata));
|
|
let isPackage = true;
|
|
let manifest;
|
|
let manifests = [UPDATEMANIFEST, MANIFEST];
|
|
for (let j = 0; j < manifests.length; j++) {
|
|
manifest = yield AppsUtils.loadJSONAsync(
|
|
Path.join(this.appsDir.path, aId, manifests[j]));
|
|
|
|
if (!manifest) {
|
|
isPackage = false;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (manifest) {
|
|
this._launchInstall(isPackage, aId, aMetadata, manifest);
|
|
} else {
|
|
debug ("Error. Neither " + UPDATEMANIFEST + " file nor " + MANIFEST +
|
|
" file for " + aId + " app.");
|
|
}
|
|
}
|
|
this.eraseVariantAppsNotInList(aIdsApp);
|
|
Services.prefs.setBoolPref(PREF_FIRST_RUN_WITH_SIM, false);
|
|
Services.prefs.savePrefFile(null);
|
|
}.bind(this)).then(null, function(aError) {
|
|
debug("Error: " + aError);
|
|
});
|
|
},
|
|
|
|
_getSingleVariantDatas: function() {
|
|
return Task.spawn(function*() {
|
|
let file = Path.join(this.appsDir.path, SINGLE_VARIANT_CONF_FILE);
|
|
let aData = yield AppsUtils.loadJSONAsync(file);
|
|
return aData;
|
|
}.bind(this));
|
|
}
|
|
};
|
|
|
|
OperatorAppsRegistry.init();
|