зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1200445 - Expose android native apps trough the navigator.mozApps api r=snorp,ferjm
This commit is contained in:
Родитель
c1e44ec580
Коммит
1a84049c64
|
@ -0,0 +1,123 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
const { interfaces: Ci, utils: Cu } = Components;
|
||||||
|
|
||||||
|
this.EXPORTED_SYMBOLS = ["AndroidUtils"];
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/AppsUtils.jsm");
|
||||||
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "Messaging",
|
||||||
|
"resource://gre/modules/Messaging.jsm");
|
||||||
|
|
||||||
|
let appsRegistry = null;
|
||||||
|
|
||||||
|
function debug() {
|
||||||
|
//dump("-*- AndroidUtils " + Array.slice(arguments) + "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper functions to manage Android native apps. We keep them in the
|
||||||
|
// registry with a `kind` equals to "android-native" and we also store
|
||||||
|
// the package name and class name in the registry.
|
||||||
|
// Communication with the android side happens through json messages.
|
||||||
|
|
||||||
|
this.AndroidUtils = {
|
||||||
|
init: function(aRegistry) {
|
||||||
|
appsRegistry = aRegistry;
|
||||||
|
Services.obs.addObserver(this, "Android:Apps:Installed", false);
|
||||||
|
Services.obs.addObserver(this, "Android:Apps:Uninstalled", false);
|
||||||
|
},
|
||||||
|
|
||||||
|
uninit: function() {
|
||||||
|
Services.obs.removeObserver(this, "Android:Apps:Installed");
|
||||||
|
Services.obs.removeObserver(this, "Android:Apps:Uninstalled");
|
||||||
|
},
|
||||||
|
|
||||||
|
getOriginAndManifestURL: function(aPackageName) {
|
||||||
|
let origin = "android://" + aPackageName.toLowerCase();
|
||||||
|
let manifestURL = origin + "/manifest.webapp";
|
||||||
|
return [origin, manifestURL];
|
||||||
|
},
|
||||||
|
|
||||||
|
getPackageAndClassFromManifestURL: function(aManifestURL) {
|
||||||
|
debug("getPackageAndClassFromManifestURL " + aManifestURL);
|
||||||
|
let app = appsRegistry.getAppByManifestURL(aManifestURL);
|
||||||
|
if (!app) {
|
||||||
|
debug("No app for " + aManifestURL);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return [app.android_packagename, app.android_classname];
|
||||||
|
},
|
||||||
|
|
||||||
|
buildAndroidAppData: function(aApp) {
|
||||||
|
// Use the package and class name to get a unique origin.
|
||||||
|
// We put the version with the normal case as part of the manifest url.
|
||||||
|
let [origin, manifestURL] =
|
||||||
|
this.getOriginAndManifestURL(aApp.packagename);
|
||||||
|
// TODO: Bug 1204557 to improve the icons support.
|
||||||
|
let manifest = {
|
||||||
|
name: aApp.name,
|
||||||
|
icons: { "96": aApp.icon }
|
||||||
|
}
|
||||||
|
debug("Origin is " + origin);
|
||||||
|
let appData = {
|
||||||
|
app: {
|
||||||
|
installOrigin: origin,
|
||||||
|
origin: origin,
|
||||||
|
manifest: manifest,
|
||||||
|
manifestURL: manifestURL,
|
||||||
|
manifestHash: AppsUtils.computeHash(JSON.stringify(manifest)),
|
||||||
|
appStatus: Ci.nsIPrincipal.APP_STATUS_INSTALLED,
|
||||||
|
removable: aApp.removable,
|
||||||
|
android_packagename: aApp.packagename,
|
||||||
|
android_classname: aApp.classname
|
||||||
|
},
|
||||||
|
isBrowser: false,
|
||||||
|
isPackage: false
|
||||||
|
};
|
||||||
|
|
||||||
|
return appData;
|
||||||
|
},
|
||||||
|
|
||||||
|
installAndroidApps: function() {
|
||||||
|
return Messaging.sendRequestForResult({ type: "Apps:GetList" }).then(
|
||||||
|
aApps => {
|
||||||
|
debug("Got " + aApps.apps.length + " android apps.");
|
||||||
|
let promises = [];
|
||||||
|
aApps.apps.forEach(app => {
|
||||||
|
debug("App is " + app.name + " removable? " + app.removable);
|
||||||
|
let p = new Promise((aResolveInstall, aRejectInstall) => {
|
||||||
|
let appData = this.buildAndroidAppData(app);
|
||||||
|
appsRegistry.confirmInstall(appData, null, aResolveInstall);
|
||||||
|
});
|
||||||
|
promises.push(p);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for all apps to be installed.
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
|
).then(appsRegistry._saveApps.bind(appsRegistry));
|
||||||
|
},
|
||||||
|
|
||||||
|
observe: function(aSubject, aTopic, aData) {
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = JSON.parse(aData);
|
||||||
|
} catch(e) {
|
||||||
|
debug(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aTopic == "Android:Apps:Installed") {
|
||||||
|
let appData = this.buildAndroidAppData(data);
|
||||||
|
appsRegistry.confirmInstall(appData);
|
||||||
|
} else if (aTopic == "Android:Apps:Uninstalled") {
|
||||||
|
let [origin, manifestURL] =
|
||||||
|
this.getOriginAndManifestURL(data.packagename);
|
||||||
|
appsRegistry.uninstall(manifestURL);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
|
@ -130,6 +130,10 @@ function _setAppProperties(aObj, aApp) {
|
||||||
aObj.kind = aApp.kind;
|
aObj.kind = aApp.kind;
|
||||||
aObj.enabled = aApp.enabled !== undefined ? aApp.enabled : true;
|
aObj.enabled = aApp.enabled !== undefined ? aApp.enabled : true;
|
||||||
aObj.sideloaded = aApp.sideloaded;
|
aObj.sideloaded = aApp.sideloaded;
|
||||||
|
#ifdef MOZ_B2GDROID
|
||||||
|
aObj.android_packagename = aApp.android_packagename;
|
||||||
|
aObj.android_classname = aApp.android_classname;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
this.AppsUtils = {
|
this.AppsUtils = {
|
||||||
|
|
|
@ -91,6 +91,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "ImportExport",
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
|
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
|
||||||
"resource://gre/modules/AppConstants.jsm");
|
"resource://gre/modules/AppConstants.jsm");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "Messaging",
|
||||||
|
"resource://gre/modules/Messaging.jsm");
|
||||||
|
|
||||||
#ifdef MOZ_WIDGET_GONK
|
#ifdef MOZ_WIDGET_GONK
|
||||||
XPCOMUtils.defineLazyGetter(this, "libcutils", function() {
|
XPCOMUtils.defineLazyGetter(this, "libcutils", function() {
|
||||||
Cu.import("resource://gre/modules/systemlibs.js");
|
Cu.import("resource://gre/modules/systemlibs.js");
|
||||||
|
@ -195,6 +198,7 @@ this.DOMApplicationRegistry = {
|
||||||
get kPackaged() "packaged",
|
get kPackaged() "packaged",
|
||||||
get kHosted() "hosted",
|
get kHosted() "hosted",
|
||||||
get kHostedAppcache() "hosted-appcache",
|
get kHostedAppcache() "hosted-appcache",
|
||||||
|
get kAndroid() "android-native",
|
||||||
|
|
||||||
// Path to the webapps.json file where we store the registry data.
|
// Path to the webapps.json file where we store the registry data.
|
||||||
appsFile: null,
|
appsFile: null,
|
||||||
|
@ -259,6 +263,11 @@ this.DOMApplicationRegistry = {
|
||||||
this.getFullAppByManifestURL.bind(this));
|
this.getFullAppByManifestURL.bind(this));
|
||||||
|
|
||||||
MessageBroadcaster.init(this.getAppByManifestURL);
|
MessageBroadcaster.init(this.getAppByManifestURL);
|
||||||
|
|
||||||
|
if (AppConstants.MOZ_B2GDROID) {
|
||||||
|
Cu.import("resource://gre/modules/AndroidUtils.jsm");
|
||||||
|
AndroidUtils.init(this);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// loads the current registry, that could be empty on first run.
|
// loads the current registry, that could be empty on first run.
|
||||||
|
@ -464,7 +473,9 @@ this.DOMApplicationRegistry = {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
appKind: function(aApp, aManifest) {
|
appKind: function(aApp, aManifest) {
|
||||||
if (aApp.origin.startsWith("app://")) {
|
if (aApp.origin.startsWith("android://")) {
|
||||||
|
return this.kAndroid;
|
||||||
|
} if (aApp.origin.startsWith("app://")) {
|
||||||
return this.kPackaged;
|
return this.kPackaged;
|
||||||
} else {
|
} else {
|
||||||
// Hosted apps, can be appcached or not.
|
// Hosted apps, can be appcached or not.
|
||||||
|
@ -522,7 +533,10 @@ this.DOMApplicationRegistry = {
|
||||||
|
|
||||||
// Installs a 3rd party app.
|
// Installs a 3rd party app.
|
||||||
installPreinstalledApp: function installPreinstalledApp(aId) {
|
installPreinstalledApp: function installPreinstalledApp(aId) {
|
||||||
#ifdef MOZ_WIDGET_GONK
|
if (!AppConstants.MOZ_B2GDROID && AppConstants.platform !== "gonk") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// In some cases, the app might be already installed under a different ID but
|
// In some cases, the app might be already installed under a different ID but
|
||||||
// with the same manifestURL. In that case, the only content of the webapp will
|
// with the same manifestURL. In that case, the only content of the webapp will
|
||||||
// be the id of the old version, which is the one we'll keep.
|
// be the id of the old version, which is the one we'll keep.
|
||||||
|
@ -625,7 +639,6 @@ this.DOMApplicationRegistry = {
|
||||||
zipReader.close();
|
zipReader.close();
|
||||||
}
|
}
|
||||||
return isPreinstalled;
|
return isPreinstalled;
|
||||||
#endif
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// For hosted apps, uninstall an app served from http:// if we have
|
// For hosted apps, uninstall an app served from http:// if we have
|
||||||
|
@ -792,6 +805,10 @@ this.DOMApplicationRegistry = {
|
||||||
yield this.installSystemApps();
|
yield this.installSystemApps();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (AppConstants.MOZ_B2GDROID) {
|
||||||
|
yield AndroidUtils.installAndroidApps();
|
||||||
|
}
|
||||||
|
|
||||||
// At first run, install preloaded apps and set up their permissions.
|
// At first run, install preloaded apps and set up their permissions.
|
||||||
for (let id in this.webapps) {
|
for (let id in this.webapps) {
|
||||||
let isPreinstalled = this.installPreinstalledApp(id);
|
let isPreinstalled = this.installPreinstalledApp(id);
|
||||||
|
@ -804,7 +821,7 @@ this.DOMApplicationRegistry = {
|
||||||
}
|
}
|
||||||
// Need to update the persisted list of apps since
|
// Need to update the persisted list of apps since
|
||||||
// installPreinstalledApp() removes the ones failing to install.
|
// installPreinstalledApp() removes the ones failing to install.
|
||||||
this._saveApps();
|
yield this._saveApps();
|
||||||
|
|
||||||
Services.prefs.setBoolPref("dom.apps.reset-permissions", true);
|
Services.prefs.setBoolPref("dom.apps.reset-permissions", true);
|
||||||
}
|
}
|
||||||
|
@ -1195,6 +1212,9 @@ this.DOMApplicationRegistry = {
|
||||||
Services.obs.removeObserver(this, "xpcom-shutdown");
|
Services.obs.removeObserver(this, "xpcom-shutdown");
|
||||||
cpmm = null;
|
cpmm = null;
|
||||||
ppmm = null;
|
ppmm = null;
|
||||||
|
if (AppConstants.MOZ_B2GDROID) {
|
||||||
|
AndroidUtils.uninit();
|
||||||
|
}
|
||||||
} else if (aTopic == "memory-pressure") {
|
} else if (aTopic == "memory-pressure") {
|
||||||
// Clear the manifest cache on memory pressure.
|
// Clear the manifest cache on memory pressure.
|
||||||
this._manifestCache = {};
|
this._manifestCache = {};
|
||||||
|
@ -1304,22 +1324,22 @@ this.DOMApplicationRegistry = {
|
||||||
this.registryReady.then( () => {
|
this.registryReady.then( () => {
|
||||||
switch (aMessage.name) {
|
switch (aMessage.name) {
|
||||||
case "Webapps:Install": {
|
case "Webapps:Install": {
|
||||||
#ifdef MOZ_WIDGET_ANDROID
|
if (AppConstants.platform == "android" && !AppConstants.MOZ_B2GDROID) {
|
||||||
Services.obs.notifyObservers(mm, "webapps-runtime-install", JSON.stringify(msg));
|
Services.obs.notifyObservers(mm, "webapps-runtime-install", JSON.stringify(msg));
|
||||||
#else
|
} else {
|
||||||
this.doInstall(msg, mm);
|
this.doInstall(msg, mm);
|
||||||
#endif
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "Webapps:GetSelf":
|
case "Webapps:GetSelf":
|
||||||
this.getSelf(msg, mm);
|
this.getSelf(msg, mm);
|
||||||
break;
|
break;
|
||||||
case "Webapps:Uninstall":
|
case "Webapps:Uninstall":
|
||||||
#ifdef MOZ_WIDGET_ANDROID
|
if (AppConstants.platform == "android" && !AppConstants.MOZ_B2GDROID) {
|
||||||
Services.obs.notifyObservers(mm, "webapps-runtime-uninstall", JSON.stringify(msg));
|
Services.obs.notifyObservers(mm, "webapps-runtime-uninstall", JSON.stringify(msg));
|
||||||
#else
|
} else {
|
||||||
this.doUninstall(msg, mm);
|
this.doUninstall(msg, mm);
|
||||||
#endif
|
}
|
||||||
break;
|
break;
|
||||||
case "Webapps:Launch":
|
case "Webapps:Launch":
|
||||||
this.doLaunch(msg, mm);
|
this.doLaunch(msg, mm);
|
||||||
|
@ -1337,11 +1357,11 @@ this.DOMApplicationRegistry = {
|
||||||
this.getNotInstalled(msg, mm);
|
this.getNotInstalled(msg, mm);
|
||||||
break;
|
break;
|
||||||
case "Webapps:InstallPackage": {
|
case "Webapps:InstallPackage": {
|
||||||
#ifdef MOZ_WIDGET_ANDROID
|
if (AppConstants.platform == "android" && !AppConstants.MOZ_B2GDROID) {
|
||||||
Services.obs.notifyObservers(mm, "webapps-runtime-install-package", JSON.stringify(msg));
|
Services.obs.notifyObservers(mm, "webapps-runtime-install-package", JSON.stringify(msg));
|
||||||
#else
|
} else {
|
||||||
this.doInstallPackage(msg, mm);
|
this.doInstallPackage(msg, mm);
|
||||||
#endif
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "Webapps:Download":
|
case "Webapps:Download":
|
||||||
|
@ -1602,6 +1622,19 @@ this.DOMApplicationRegistry = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delegate native android apps launch.
|
||||||
|
if (this.kAndroid == app.kind) {
|
||||||
|
debug("Launching android app " + app.origin);
|
||||||
|
let [packageName, className] =
|
||||||
|
AndroidUtils.getPackageAndClassFromManifestURL(aManifestURL);
|
||||||
|
debug(" " + packageName + " " + className);
|
||||||
|
Messaging.sendRequest({ type: "Apps:Launch",
|
||||||
|
packagename: packageName,
|
||||||
|
classname: className });
|
||||||
|
aOnSuccess();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// We have to clone the app object as nsIDOMApplication objects are
|
// We have to clone the app object as nsIDOMApplication objects are
|
||||||
// stringified as an empty object. (see bug 830376)
|
// stringified as an empty object. (see bug 830376)
|
||||||
let appClone = AppsUtils.cloneAppObject(app);
|
let appClone = AppsUtils.cloneAppObject(app);
|
||||||
|
@ -2046,8 +2079,9 @@ this.DOMApplicationRegistry = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the app is packaged and its manifestURL has an app:// scheme,
|
// If the app is packaged and its manifestURL has an app:// scheme,
|
||||||
// then we can't have an update.
|
// or if it's a native Android app then we can't have an update.
|
||||||
if (app.kind == this.kPackaged && app.manifestURL.startsWith("app://")) {
|
if (app.kind == this.kAndroid ||
|
||||||
|
(app.kind == this.kPackaged && app.manifestURL.startsWith("app://"))) {
|
||||||
sendError("NOT_UPDATABLE");
|
sendError("NOT_UPDATABLE");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -2779,8 +2813,10 @@ this.DOMApplicationRegistry = {
|
||||||
_setupApp: function(aData, aId) {
|
_setupApp: function(aData, aId) {
|
||||||
let app = aData.app;
|
let app = aData.app;
|
||||||
|
|
||||||
// app can be uninstalled
|
// app can be uninstalled by default.
|
||||||
|
if (app.removable === undefined) {
|
||||||
app.removable = true;
|
app.removable = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (aData.isPackage) {
|
if (aData.isPackage) {
|
||||||
// Override the origin with the correct id.
|
// Override the origin with the correct id.
|
||||||
|
@ -2813,7 +2849,8 @@ this.DOMApplicationRegistry = {
|
||||||
appObject.downloading = true;
|
appObject.downloading = true;
|
||||||
appObject.downloadSize = aLocaleManifest.size;
|
appObject.downloadSize = aLocaleManifest.size;
|
||||||
appObject.readyToApplyDownload = false;
|
appObject.readyToApplyDownload = false;
|
||||||
} else if (appObject.kind == this.kHosted) {
|
} else if (appObject.kind == this.kHosted ||
|
||||||
|
appObject.kind == this.kAndroid) {
|
||||||
appObject.installState = "installed";
|
appObject.installState = "installed";
|
||||||
appObject.downloadAvailable = false;
|
appObject.downloadAvailable = false;
|
||||||
appObject.downloading = false;
|
appObject.downloading = false;
|
||||||
|
@ -2869,8 +2906,6 @@ this.DOMApplicationRegistry = {
|
||||||
|
|
||||||
app.appStatus = AppsUtils.getAppManifestStatus(aManifest);
|
app.appStatus = AppsUtils.getAppManifestStatus(aManifest);
|
||||||
|
|
||||||
app.removable = true;
|
|
||||||
|
|
||||||
// Reuse the app ID if the scheme is "app".
|
// Reuse the app ID if the scheme is "app".
|
||||||
let uri = Services.io.newURI(app.origin, null, null);
|
let uri = Services.io.newURI(app.origin, null, null);
|
||||||
if (uri.scheme == "app") {
|
if (uri.scheme == "app") {
|
||||||
|
@ -2960,6 +2995,7 @@ this.DOMApplicationRegistry = {
|
||||||
let appObject = this._cloneApp(aData, app, manifest, jsonManifest, id, localId);
|
let appObject = this._cloneApp(aData, app, manifest, jsonManifest, id, localId);
|
||||||
|
|
||||||
this.webapps[id] = appObject;
|
this.webapps[id] = appObject;
|
||||||
|
this._manifestCache[id] = jsonManifest;
|
||||||
|
|
||||||
// For package apps, the permissions are not in the mini-manifest, so
|
// For package apps, the permissions are not in the mini-manifest, so
|
||||||
// don't update the permissions yet.
|
// don't update the permissions yet.
|
||||||
|
@ -4064,6 +4100,17 @@ this.DOMApplicationRegistry = {
|
||||||
try {
|
try {
|
||||||
aData.app = yield this._getAppWithManifest(aData.manifestURL);
|
aData.app = yield this._getAppWithManifest(aData.manifestURL);
|
||||||
|
|
||||||
|
if (this.kAndroid == aData.app.kind) {
|
||||||
|
debug("Uninstalling android app " + aData.app.origin);
|
||||||
|
let [packageName, className] =
|
||||||
|
AndroidUtils.getPackageAndClassFromManifestURL(aData.manifestURL);
|
||||||
|
Messaging.sendRequest({ type: "Apps:Uninstall",
|
||||||
|
packagename: packageName,
|
||||||
|
classname: className });
|
||||||
|
// We have to wait for Android's uninstall before sending the
|
||||||
|
// uninstall event, so fake an error here.
|
||||||
|
response = "Webapps:Uninstall:Return:KO";
|
||||||
|
} else {
|
||||||
let prefName = "dom.mozApps.auto_confirm_uninstall";
|
let prefName = "dom.mozApps.auto_confirm_uninstall";
|
||||||
if (Services.prefs.prefHasUserValue(prefName) &&
|
if (Services.prefs.prefHasUserValue(prefName) &&
|
||||||
Services.prefs.getBoolPref(prefName)) {
|
Services.prefs.getBoolPref(prefName)) {
|
||||||
|
@ -4071,6 +4118,7 @@ this.DOMApplicationRegistry = {
|
||||||
} else {
|
} else {
|
||||||
yield this._promptForUninstall(aData);
|
yield this._promptForUninstall(aData);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
aData.error = error;
|
aData.error = error;
|
||||||
response = "Webapps:Uninstall:Return:KO";
|
response = "Webapps:Uninstall:Return:KO";
|
||||||
|
|
|
@ -42,6 +42,11 @@ EXTRA_JS_MODULES += [
|
||||||
'UserCustomizations.jsm',
|
'UserCustomizations.jsm',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if CONFIG['MOZ_B2GDROID']:
|
||||||
|
EXTRA_JS_MODULES += [
|
||||||
|
'AndroidUtils.jsm',
|
||||||
|
]
|
||||||
|
|
||||||
EXTRA_PP_JS_MODULES += [
|
EXTRA_PP_JS_MODULES += [
|
||||||
'AppsUtils.jsm',
|
'AppsUtils.jsm',
|
||||||
'ImportExport.jsm',
|
'ImportExport.jsm',
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
ANDROID_MANIFEST_FILE := src/main/AndroidManifest.xml
|
ANDROID_MANIFEST_FILE := src/main/AndroidManifest.xml
|
||||||
|
|
||||||
JAVAFILES := \
|
JAVAFILES := \
|
||||||
|
src/main/java/org/mozilla/b2gdroid/Apps.java \
|
||||||
src/main/java/org/mozilla/b2gdroid/Launcher.java \
|
src/main/java/org/mozilla/b2gdroid/Launcher.java \
|
||||||
src/main/java/org/mozilla/b2gdroid/ScreenStateObserver.java \
|
src/main/java/org/mozilla/b2gdroid/ScreenStateObserver.java \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
package org.mozilla.b2gdroid;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.pm.ActivityInfo;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.Base64;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
|
||||||
|
import org.mozilla.gecko.EventDispatcher;
|
||||||
|
import org.mozilla.gecko.util.GeckoEventListener;
|
||||||
|
|
||||||
|
import org.mozilla.gecko.GeckoAppShell;
|
||||||
|
import org.mozilla.gecko.GeckoEvent;
|
||||||
|
|
||||||
|
class Apps extends BroadcastReceiver
|
||||||
|
implements GeckoEventListener {
|
||||||
|
private static final String LOGTAG = "B2G:Apps";
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
|
Apps(Context context) {
|
||||||
|
mContext = context;
|
||||||
|
EventDispatcher.getInstance()
|
||||||
|
.registerGeckoThreadListener(this,
|
||||||
|
"Apps:GetList",
|
||||||
|
"Apps:Launch",
|
||||||
|
"Apps:Uninstall");
|
||||||
|
|
||||||
|
// Observe app installation and removal.
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(Intent.ACTION_PACKAGE_ADDED);
|
||||||
|
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
|
||||||
|
filter.addDataScheme("package");
|
||||||
|
mContext.registerReceiver(this, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy() {
|
||||||
|
mContext.unregisterReceiver(this);
|
||||||
|
EventDispatcher.getInstance()
|
||||||
|
.unregisterGeckoThreadListener(this,
|
||||||
|
"Apps:GetList",
|
||||||
|
"Apps:Launch",
|
||||||
|
"Apps:Uninstall");
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject activityInfoToJson(ActivityInfo info, PackageManager pm) {
|
||||||
|
JSONObject obj = new JSONObject();
|
||||||
|
try {
|
||||||
|
obj.put("name", info.loadLabel(pm).toString());
|
||||||
|
obj.put("packagename", info.packageName);
|
||||||
|
obj.put("classname", info.name);
|
||||||
|
|
||||||
|
final ApplicationInfo appInfo = info.applicationInfo;
|
||||||
|
// Pre-installed apps can't be uninstalled.
|
||||||
|
final boolean removable =
|
||||||
|
(appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0;
|
||||||
|
|
||||||
|
obj.put("removable", removable);
|
||||||
|
|
||||||
|
// For now, create a data: url for the icon, since we need additional
|
||||||
|
// android:// protocol support for icons. Once it's there we'll do
|
||||||
|
// something like: obj.put("icon", "android:icon/" + info.packageName);
|
||||||
|
Drawable d = pm.getApplicationIcon(info.packageName);
|
||||||
|
Bitmap bitmap = ((BitmapDrawable)d).getBitmap();
|
||||||
|
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
|
||||||
|
byte[] byteArray = byteArrayOutputStream.toByteArray();
|
||||||
|
String encoded = Base64.encodeToString(byteArray, Base64.DEFAULT);
|
||||||
|
obj.put("icon", "data:image/png;base64," + encoded);
|
||||||
|
} catch(Exception ex) {
|
||||||
|
Log.wtf(LOGTAG, "Error building ActivityInfo JSON", ex);
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handleMessage(String event, JSONObject message) {
|
||||||
|
Log.w(LOGTAG, "Received " + event);
|
||||||
|
|
||||||
|
if ("Apps:GetList".equals(event)) {
|
||||||
|
JSONObject ret = new JSONObject();
|
||||||
|
JSONArray array = new JSONArray();
|
||||||
|
PackageManager pm = mContext.getPackageManager();
|
||||||
|
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
|
||||||
|
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||||
|
final Iterator<ResolveInfo> i = pm.queryIntentActivities(mainIntent, 0).iterator();
|
||||||
|
try {
|
||||||
|
while (i.hasNext()) {
|
||||||
|
ActivityInfo info = i.next().activityInfo;
|
||||||
|
array.put(activityInfoToJson(info, pm));
|
||||||
|
}
|
||||||
|
ret.put("apps", array);
|
||||||
|
} catch(Exception ex) {
|
||||||
|
Log.wtf(LOGTAG, "error, making list of apps", ex);
|
||||||
|
}
|
||||||
|
EventDispatcher.sendResponse(message, ret);
|
||||||
|
} else if ("Apps:Launch".equals(event)) {
|
||||||
|
try {
|
||||||
|
String className = message.getString("classname");
|
||||||
|
String packageName = message.getString("packagename");
|
||||||
|
final Intent intent = new Intent(Intent.ACTION_MAIN, null);
|
||||||
|
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||||
|
intent.setClassName(packageName, className);
|
||||||
|
mContext.startActivity(intent);
|
||||||
|
} catch(Exception ex) {
|
||||||
|
Log.wtf(LOGTAG, "Error launching app", ex);
|
||||||
|
}
|
||||||
|
} else if ("Apps:Uninstall".equals(event)) {
|
||||||
|
try {
|
||||||
|
String packageName = message.getString("packagename");
|
||||||
|
Uri packageUri = Uri.parse("package:" + packageName);
|
||||||
|
final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri);
|
||||||
|
mContext.startActivity(intent);
|
||||||
|
} catch(Exception ex) {
|
||||||
|
Log.wtf(LOGTAG, "Error uninstalling app", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
Log.d(LOGTAG, intent.getAction() + " " + intent.getDataString());
|
||||||
|
|
||||||
|
String packageName = intent.getDataString().substring(8);
|
||||||
|
String action = intent.getAction();
|
||||||
|
if ("android.intent.action.PACKAGE_ADDED".equals(action)) {
|
||||||
|
PackageManager pm = mContext.getPackageManager();
|
||||||
|
Intent launch = pm.getLaunchIntentForPackage(packageName);
|
||||||
|
if (launch == null) {
|
||||||
|
Log.d(LOGTAG, "No launchable intent for " + packageName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ActivityInfo info = launch.resolveActivityInfo(pm, 0);
|
||||||
|
|
||||||
|
JSONObject obj = activityInfoToJson(info, pm);
|
||||||
|
GeckoEvent e = GeckoEvent.createBroadcastEvent("Android:Apps:Installed", obj.toString());
|
||||||
|
GeckoAppShell.sendEventToGecko(e);
|
||||||
|
} else if ("android.intent.action.PACKAGE_REMOVED".equals(action)) {
|
||||||
|
JSONObject obj = new JSONObject();
|
||||||
|
try {
|
||||||
|
obj.put("packagename", packageName);
|
||||||
|
} catch(Exception ex) {
|
||||||
|
Log.wtf(LOGTAG, "Error building PACKAGE_REMOVED JSON", ex);
|
||||||
|
}
|
||||||
|
GeckoEvent e = GeckoEvent.createBroadcastEvent("Android:Apps:Uninstalled", obj.toString());
|
||||||
|
GeckoAppShell.sendEventToGecko(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,9 +4,6 @@
|
||||||
|
|
||||||
package org.mozilla.b2gdroid;
|
package org.mozilla.b2gdroid;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.util.Iterator;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
import android.app.KeyguardManager;
|
import android.app.KeyguardManager;
|
||||||
|
@ -14,24 +11,12 @@ import android.app.KeyguardManager.KeyguardLock;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.pm.ActivityInfo;
|
|
||||||
import android.content.pm.ApplicationInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.ResolveInfo;
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
|
||||||
import android.graphics.drawable.Drawable;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Base64;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONException;
|
|
||||||
|
|
||||||
import org.mozilla.gecko.BaseGeckoInterface;
|
import org.mozilla.gecko.BaseGeckoInterface;
|
||||||
import org.mozilla.gecko.ContactService;
|
import org.mozilla.gecko.ContactService;
|
||||||
|
@ -45,6 +30,7 @@ import org.mozilla.gecko.IntentHelper;
|
||||||
import org.mozilla.gecko.util.GeckoEventListener;
|
import org.mozilla.gecko.util.GeckoEventListener;
|
||||||
|
|
||||||
import org.mozilla.b2gdroid.ScreenStateObserver;
|
import org.mozilla.b2gdroid.ScreenStateObserver;
|
||||||
|
import org.mozilla.b2gdroid.Apps;
|
||||||
|
|
||||||
public class Launcher extends Activity
|
public class Launcher extends Activity
|
||||||
implements GeckoEventListener, ContextGetter {
|
implements GeckoEventListener, ContextGetter {
|
||||||
|
@ -52,6 +38,7 @@ public class Launcher extends Activity
|
||||||
|
|
||||||
private ContactService mContactService;
|
private ContactService mContactService;
|
||||||
private ScreenStateObserver mScreenStateObserver;
|
private ScreenStateObserver mScreenStateObserver;
|
||||||
|
private Apps mApps;
|
||||||
|
|
||||||
/** ContextGetter */
|
/** ContextGetter */
|
||||||
public Context getContext() {
|
public Context getContext() {
|
||||||
|
@ -68,6 +55,7 @@ public class Launcher extends Activity
|
||||||
|
|
||||||
GeckoBatteryManager.getInstance().start(this);
|
GeckoBatteryManager.getInstance().start(this);
|
||||||
mContactService = new ContactService(EventDispatcher.getInstance(), this);
|
mContactService = new ContactService(EventDispatcher.getInstance(), this);
|
||||||
|
mApps = new Apps(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void hideSplashScreen() {
|
private void hideSplashScreen() {
|
||||||
|
@ -123,6 +111,7 @@ public class Launcher extends Activity
|
||||||
"Launcher:Ready");
|
"Launcher:Ready");
|
||||||
|
|
||||||
mContactService.destroy();
|
mContactService.destroy();
|
||||||
|
mApps.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Загрузка…
Ссылка в новой задаче