Bug 893800 - Install single variant apps the first time a SIM is detected. r=fabrice

This commit is contained in:
Carmen Jimenez 2013-07-31 21:34:19 +02:00
parent 34dd9d8511
commit 201ce9870a
7 changed files with 337 additions and 8 deletions

View File

@ -424,6 +424,7 @@ pref("services.push.udp.port", 2442);
#ifdef MOZ_B2G_RIL
pref("dom.mozNetworkStats.enabled", true);
pref("ril.cellbroadcast.disabled", false);
pref("dom.webapps.firstRunWithSIM", false);
#endif
// WebSettings

View File

@ -601,6 +601,10 @@ var shell = {
this.sendEvent(window, 'ContentStart');
#ifdef MOZ_B2G_RIL
Cu.import('resource://gre/modules/OperatorApps.jsm');
#endif
content.addEventListener('load', function shell_homeLoaded() {
content.removeEventListener('load', shell_homeLoaded);
shell.isHomeLoaded = true;

View File

@ -1115,6 +1115,7 @@ xpicleanup@BIN_SUFFIX@
modules/tabview/groups.jsm
modules/tabview/utils.jsm
modules/Webapps.jsm
modules/OperatorApps.jsm
modules/WindowDraggingUtils.jsm
#ifdef XP_WIN
modules/WindowsJumpLists.jsm

View File

@ -12,6 +12,8 @@ const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
return Cc["@mozilla.org/network/util;1"]
@ -489,6 +491,32 @@ this.AppsUtils = {
return true;
},
// Loads a JSON file using OS.file. aFile is a string representing the path
// of the file to be read.
// Returns a Promise resolved with the json payload or rejected with
// OS.File.Error
loadJSONAsync: function(aFile) {
debug("_loadJSONAsync: " + aFile);
return Task.spawn(function() {
let file = yield OS.File.open(aFile, { read: true });
let rawData = yield file.read();
// Read json file into a string
let data;
try {
// Obtain a converter to read from a UTF-8 encoded input stream.
let converter = new TextDecoder();
data = JSON.parse(converter.decode(rawData));
file.close();
} catch (ex) {
debug("Error parsing JSON: " + aFile + ". Error: " + ex);
Cu.reportError("OperatorApps: Could not parse JSON: " +
aFile + " " + ex + "\n" + ex.stack);
throw ex;
}
throw new Task.Result(data);
});
},
// Returns the MD5 hash of a string.
computeHash: function(aString) {
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]

View File

@ -0,0 +1,262 @@
/* 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");
#ifdef MOZ_B2G_RIL
XPCOMUtils.defineLazyServiceGetter(this, "iccProvider",
"@mozilla.org/ril/content-helper;1",
"nsIIccProvider");
XPCOMUtils.defineLazyServiceGetter(this, "mobileConnection",
"@mozilla.org/ril/content-helper;1",
"nsIMobileConnectionProvider");
#endif
function debug(aMsg) {
//dump("-*-*- OperatorApps.jsm : " + aMsg + "\n");
}
const DIRECTORY_NAME = "webappsDir";
// The files will be stored on DIRECTORY_NAME + "/" + SINGLE_VARIANT_SOURCE_DIR
// SINGLE_VARIANT_CONF_FILE will be stored on SINGLE_VARIANT_SOURCE_DIR
// Apps will be stored on a app per directory basis, hanging from
// SINGLE_VARIANT_SOURCE_DIR
const SINGLE_VARIANT_SOURCE_DIR = "svoperapps";
const SINGLE_VARIANT_CONF_FILE = "singlevariantconf.json";
const PREF_FIRST_RUN_WITH_SIM = "dom.webapps.firstRunWithSIM";
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
let iccListener = {
notifyStkCommand: function() {},
notifyStkSessionEnd: function() {},
notifyIccCardLockError: function() {},
notifyCardStateChange: function() {},
notifyIccInfoChanged: function() {
let iccInfo = iccProvider.iccInfo;
if (iccInfo && iccInfo.mcc && iccInfo.mnc) {
debug("******* iccListener cardIccInfo MCC-MNC: " + iccInfo.mcc +
"-" + iccInfo.mnc);
iccProvider.unregisterIccMsg(this);
OperatorAppsRegistry._installOperatorApps(iccInfo.mcc, iccInfo.mnc);
}
}
};
#endif
this.OperatorAppsRegistry = {
_baseDirectory: null,
init: function() {
debug("init");
#ifdef MOZ_B2G_RIL
if (isFirstRunWithSIM()) {
debug("First Run with SIM");
let mcc = 0;
let mnc = 0;
if (mobileConnection.iccInfo && mobileConnection.iccInfo.mcc) {
mcc = mobileConnection.iccInfo.mcc;
}
if (mobileConnection.iccInfo && mobileConnection.iccInfo.mnc) {
mnc = mobileConnection.iccInfo.mnc;
}
if (mcc && mnc) {
this._installOperatorApps(mcc, mnc);
} else {
iccProvider.registerIccMsg(iccListener);
}
} else {
debug("No First Run with SIM");
}
#endif
},
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() {
if (!this._baseDirectory) {
this._baseDirectory = FileUtils.getFile(DIRECTORY_NAME,
[SINGLE_VARIANT_SOURCE_DIR]);
}
return this._baseDirectory;
},
eraseVariantAppsNotInList: function(aIdsApp) {
if (!aIdsApp || !Array.isArray(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
};
if (isPackage) {
debug("aId:" + aId + ". Installing as packaged app.");
let installPack = OS.Path.join(this.appsDir.path, aId, APPLICATION_ZIP);
OS.File.exists(installPack).then(
function(aExists) {
if (!aExists) {
debug("SV " + installPack.path + " file do not exists for app " +
aId);
return;
}
appData.app.localInstallPath = installPack;
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) {
Task.spawn(function() {
debug("Install operator apps ---> mcc:"+ aMcc + ", mnc:" + aMnc);
if (!isFirstRunWithSIM()) {
debug("Operator apps already installed.");
return;
}
let aIdsApp = yield this._getSingleVariantApps(aMcc, aMnc);
debug("installOperatorApps --> aIdsApp:" + JSON.stringify(aIdsApp));
for (let i = 0; i < aIdsApp.length; i++) {
let aId = aIdsApp[i];
let aMetadata = yield AppsUtils.loadJSONAsync(
OS.Path.join(this.appsDir.path, aId, METADATA));
debug("metadata:" + JSON.stringify(aMetadata));
let isPackage = true;
let manifest;
let manifests = [UPDATEMANIFEST, MANIFEST];
for (let j = 0; j < manifests.length; j++) {
try {
manifest = yield AppsUtils.loadJSONAsync(
OS.Path.join(this.appsDir.path, aId, manifests[j]));
break;
} catch (e) {
isPackage = false;
}
}
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);
}.bind(this)).then(null, function(aError) {
debug("Error: " + aError);
});
},
_getSingleVariantApps: function(aMcc, aMnc) {
function normalizeCode(aCode) {
let ncode = "" + aCode;
while (ncode.length < 3) {
ncode = "0" + ncode;
}
return ncode;
}
return Task.spawn(function () {
let key = normalizeCode(aMcc) + "-" + normalizeCode(aMnc);
let file = OS.Path.join(this.appsDir.path, SINGLE_VARIANT_CONF_FILE);
let aData = yield AppsUtils.loadJSONAsync(file);
if (!aData || !(key in aData)) {
return;
}
throw new Task.Result(aData[key]);
}.bind(this));
}
};
OperatorAppsRegistry.init();

View File

@ -2195,6 +2195,12 @@ this.DOMApplicationRegistry = {
}
if (manifest.package_path) {
// If it is a local app then it must been installed from a local file
// instead of web.
let origPath = jsonManifest.package_path;
if (aData.app.localInstallPath) {
jsonManifest.package_path = "file://" + aData.app.localInstallPath;
}
// origin for install apps is meaningless here, since it's app:// and this
// can't be used to resolve package paths.
manifest = new ManifestHelper(jsonManifest, app.manifestURL);
@ -2203,6 +2209,12 @@ this.DOMApplicationRegistry = {
manifest: manifest,
app: appObject,
callback: aInstallSuccessCallback
};
if (aData.app.localInstallPath) {
// if it's a local install, there's no content process so just
// ack the install
this.onInstallSuccessAck(app.manifestURL);
}
}
},
@ -2291,6 +2303,14 @@ this.DOMApplicationRegistry = {
debug("downloadPackage " + JSON.stringify(aApp));
let fullPackagePath = aManifest.fullPackagePath();
// Check if it's a local file install (we've downloaded/sideloaded the
// package already or it did exist on the build).
let isLocalFileInstall =
Services.io.extractScheme(fullPackagePath) === 'file';
let id = this._appIdForManifestURL(aApp.manifestURL);
let app = this.webapps[id];
@ -2382,10 +2402,17 @@ this.DOMApplicationRegistry = {
function download() {
debug("About to download " + aManifest.fullPackagePath());
let requestChannel = NetUtil.newChannel(aManifest.fullPackagePath())
.QueryInterface(Ci.nsIHttpChannel);
requestChannel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
if (app.packageEtag) {
let requestChannel;
if (isLocalFileInstall) {
requestChannel = NetUtil.newChannel(aManifest.fullPackagePath())
.QueryInterface(Ci.nsIFileChannel);
} else {
requestChannel = NetUtil.newChannel(aManifest.fullPackagePath())
.QueryInterface(Ci.nsIHttpChannel);
requestChannel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
}
if (app.packageEtag && !isLocalFileInstall) {
debug("Add If-None-Match header: " + app.packageEtag);
requestChannel.setRequestHeader("If-None-Match", app.packageEtag, false);
}
@ -2581,8 +2608,11 @@ this.DOMApplicationRegistry = {
let signedAppOriginsStr =
Services.prefs.getCharPref(
"dom.mozApps.signed_apps_installable_from");
let isSignedAppOrigin
= signedAppOriginsStr.split(",").indexOf(aApp.installOrigin) > -1;
// If it's a local install and it's signed then we assume
// the app origin is a valid signer.
let isSignedAppOrigin = (isSigned && isLocalFileInstall) ||
signedAppOriginsStr.split(",").
indexOf(aApp.installOrigin) > -1;
if (!isSigned && isSignedAppOrigin) {
// Packaged apps installed from these origins must be signed;
// if not, assume somebody stripped the signature.
@ -2634,8 +2664,10 @@ this.DOMApplicationRegistry = {
throw "INSTALL_FROM_DENIED";
}
let maxStatus = isSigned ? Ci.nsIPrincipal.APP_STATUS_PRIVILEGED
: Ci.nsIPrincipal.APP_STATUS_INSTALLED;
// Local file installs can be privileged even without the signature.
let maxStatus = isSigned || isLocalFileInstall
? Ci.nsIPrincipal.APP_STATUS_PRIVILEGED
: Ci.nsIPrincipal.APP_STATUS_INSTALLED;
if (AppsUtils.getAppManifestStatus(manifest) > maxStatus) {
throw "INVALID_SECURITY_LEVEL";

View File

@ -34,6 +34,7 @@ EXTRA_JS_MODULES += [
EXTRA_PP_JS_MODULES += [
'AppsUtils.jsm',
'OperatorApps.jsm',
'Webapps.jsm',
]