зеркало из https://github.com/mozilla/gecko-dev.git
Bug 893800 - Install single variant apps the first time a SIM is detected. r=fabrice
This commit is contained in:
Родитель
440511ff28
Коммит
2171964928
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -10,7 +10,9 @@ const Ci = Components.interfaces;
|
|||
const Cr = Components.results;
|
||||
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/WebappOSUtils.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
|
@ -492,6 +494,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"]
|
||||
|
|
|
@ -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();
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2296,6 +2308,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];
|
||||
|
||||
|
@ -2387,10 +2407,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);
|
||||
}
|
||||
|
@ -2586,8 +2613,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.
|
||||
|
@ -2639,8 +2669,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";
|
||||
|
|
|
@ -34,6 +34,7 @@ EXTRA_JS_MODULES += [
|
|||
|
||||
EXTRA_PP_JS_MODULES += [
|
||||
'AppsUtils.jsm',
|
||||
'OperatorApps.jsm',
|
||||
'Webapps.jsm',
|
||||
]
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче