From 78814e0a217de61b0ea428794e14db1ac0eedce7 Mon Sep 17 00:00:00 2001 From: Myk Melez Date: Tue, 10 Jun 2014 15:52:47 -0700 Subject: [PATCH] Bug 1019054 - re-add uninstallation from about:apps context menu; r=mfinkle,marco --- dom/apps/src/Webapps.jsm | 54 ++++++++++--------- mobile/android/base/webapp/EventListener.java | 19 +++++++ mobile/android/chrome/content/aboutApps.js | 26 +++++++++ mobile/android/chrome/content/aboutApps.xhtml | 5 ++ mobile/android/chrome/content/browser.js | 6 +-- .../locales/en-US/chrome/aboutApps.dtd | 1 - mobile/android/modules/WebappManager.jsm | 45 ++++++++++++++-- 7 files changed, 122 insertions(+), 34 deletions(-) diff --git a/dom/apps/src/Webapps.jsm b/dom/apps/src/Webapps.jsm index f1bbef04b9be..f1b544a384c1 100755 --- a/dom/apps/src/Webapps.jsm +++ b/dom/apps/src/Webapps.jsm @@ -306,7 +306,7 @@ this.DOMApplicationRegistry = { }, // Registers all the activities and system messages. - registerAppsHandlers: function(aRunUpdate) { + registerAppsHandlers: Task.async(function*(aRunUpdate) { this.notifyAppsRegistryStart(); let ids = []; for (let id in this.webapps) { @@ -318,40 +318,38 @@ this.DOMApplicationRegistry = { // Read the CSPs and roles. If MOZ_SYS_MSG is defined this is done on // _processManifestForIds so as to not reading the manifests // twice - this._readManifests(ids).then((aResults) => { - aResults.forEach((aResult) => { - if (!aResult.manifest) { - // If we can't load the manifest, we probably have a corrupted - // registry. We delete the app since we can't do anything with it. - delete this.webapps[aResult.id]; - return; - } - let app = this.webapps[aResult.id]; - app.csp = aResult.manifest.csp || ""; - app.role = aResult.manifest.role || ""; - if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) { - app.redirects = this.sanitizeRedirects(aResult.redirects); - } - }); + let results = yield this._readManifests(ids); + results.forEach((aResult) => { + if (!aResult.manifest) { + // If we can't load the manifest, we probably have a corrupted + // registry. We delete the app since we can't do anything with it. + delete this.webapps[aResult.id]; + return; + } + let app = this.webapps[aResult.id]; + app.csp = aResult.manifest.csp || ""; + app.role = aResult.manifest.role || ""; + if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) { + app.redirects = this.sanitizeRedirects(aResult.redirects); + } }); // Nothing else to do but notifying we're ready. this.notifyAppsRegistryReady(); } - }, + }), - updateDataStoreForApp: function(aId) { + updateDataStoreForApp: Task.async(function*(aId) { if (!this.webapps[aId]) { return; } // Create or Update the DataStore for this app - this._readManifests([{ id: aId }]).then((aResult) => { - let app = this.webapps[aId]; - this.updateDataStore(app.localId, app.origin, app.manifestURL, - aResult[0].manifest, app.appStatus); - }); - }, + let results = yield this._readManifests([{ id: aId }]); + let app = this.webapps[aId]; + this.updateDataStore(app.localId, app.origin, app.manifestURL, + results[0].manifest, app.appStatus); + }), updatePermissionsForApp: function(aId, aIsPreinstalled) { if (!this.webapps[aId]) { @@ -609,10 +607,10 @@ this.DOMApplicationRegistry = { // DataStores must be initialized at startup. for (let id in this.webapps) { - this.updateDataStoreForApp(id); + yield this.updateDataStoreForApp(id); } - this.registerAppsHandlers(runUpdate); + yield this.registerAppsHandlers(runUpdate); }.bind(this)).then(null, Cu.reportError); }, @@ -1101,7 +1099,11 @@ this.DOMApplicationRegistry = { this.getSelf(msg, mm); break; case "Webapps:Uninstall": +#ifdef MOZ_WIDGET_ANDROID + Services.obs.notifyObservers(mm, "webapps-runtime-uninstall", JSON.stringify(msg)); +#else this.doUninstall(msg, mm); +#endif break; case "Webapps:Launch": this.doLaunch(msg, mm); diff --git a/mobile/android/base/webapp/EventListener.java b/mobile/android/base/webapp/EventListener.java index 2ffe3927d6ca..9f0319f576ad 100644 --- a/mobile/android/base/webapp/EventListener.java +++ b/mobile/android/base/webapp/EventListener.java @@ -31,6 +31,7 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.net.Uri; +import android.os.Build; import android.util.Log; public class EventListener implements NativeEventListener { @@ -41,6 +42,7 @@ public class EventListener implements NativeEventListener { EventDispatcher.getInstance().registerGeckoThreadListener(this, "Webapps:Preinstall", "Webapps:InstallApk", + "Webapps:UninstallApk", "Webapps:Postinstall", "Webapps:Open", "Webapps:Uninstall", @@ -51,6 +53,7 @@ public class EventListener implements NativeEventListener { EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "Webapps:Preinstall", "Webapps:InstallApk", + "Webapps:UninstallApk", "Webapps:Postinstall", "Webapps:Open", "Webapps:Uninstall", @@ -62,6 +65,8 @@ public class EventListener implements NativeEventListener { try { if (event.equals("Webapps:InstallApk")) { installApk(GeckoAppShell.getGeckoInterface().getActivity(), message, callback); + } else if (event.equals("Webapps:UninstallApk")) { + uninstallApk(GeckoAppShell.getGeckoInterface().getActivity(), message); } else if (event.equals("Webapps:Postinstall")) { postInstallWebapp(message.getString("apkPackageName"), message.getString("origin")); } else if (event.equals("Webapps:Open")) { @@ -193,6 +198,20 @@ public class EventListener implements NativeEventListener { }); } + public static void uninstallApk(final Activity context, NativeJSObject message) { + String packageName = message.getString("apkPackageName"); + Uri packageUri = Uri.parse("package:" + packageName); + + Intent intent; + if (Build.VERSION.SDK_INT < 14) { + intent = new Intent(Intent.ACTION_DELETE, packageUri); + } else { + intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri); + } + + context.startActivity(intent); + } + private static final int DEFAULT_VERSION_CODE = -1; public static JSONObject getApkVersions(Activity context, String[] packageNames) { diff --git a/mobile/android/chrome/content/aboutApps.js b/mobile/android/chrome/content/aboutApps.js index 7113fa68b514..d20d173898d4 100644 --- a/mobile/android/chrome/content/aboutApps.js +++ b/mobile/android/chrome/content/aboutApps.js @@ -47,6 +47,29 @@ function checkForUpdates(aEvent) { WebappManager.checkForUpdates(true); } +let ContextMenus = { + target: null, + + init: function() { + document.addEventListener("contextmenu", this, false); + document.getElementById("uninstallLabel").addEventListener("click", this.uninstall.bind(this), false); + }, + + handleEvent: function(event) { + // store the target of context menu events so that we know which app to act on + this.target = event.target; + while (!this.target.hasAttribute("contextmenu")) { + this.target = this.target.parentNode; + } + }, + + uninstall: function() { + navigator.mozApps.mgmt.uninstall(this.target.app); + + this.target = null; + } +}; + function onLoad(aEvent) { let elmts = document.querySelectorAll("[pref]"); for (let i = 0; i < elmts.length; i++) { @@ -59,6 +82,8 @@ function onLoad(aEvent) { navigator.mozApps.mgmt.onuninstall = onUninstall; updateList(); + ContextMenus.init(); + // XXX - Hack to fix bug 985867 for now document.addEventListener("touchstart", function() { }); } @@ -84,6 +109,7 @@ function addApplication(aApp) { let container = document.createElement("div"); container.className = "app list-item"; + container.setAttribute("contextmenu", "appmenu"); container.setAttribute("id", "app-" + aApp.origin); container.setAttribute("mozApp", aApp.origin); container.setAttribute("title", manifest.name); diff --git a/mobile/android/chrome/content/aboutApps.xhtml b/mobile/android/chrome/content/aboutApps.xhtml index 7c95d8f7c10b..9e457ee1347d 100644 --- a/mobile/android/chrome/content/aboutApps.xhtml +++ b/mobile/android/chrome/content/aboutApps.xhtml @@ -28,6 +28,11 @@ + + + + +
&aboutApps.header;
diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index eb4f7ca83ed9..417f165bfe57 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -328,7 +328,7 @@ var BrowserApp = { Services.obs.addObserver(this, "webapps-runtime-install-package", false); Services.obs.addObserver(this, "webapps-ask-install", false); Services.obs.addObserver(this, "webapps-launch", false); - Services.obs.addObserver(this, "webapps-uninstall", false); + Services.obs.addObserver(this, "webapps-runtime-uninstall", false); Services.obs.addObserver(this, "Webapps:AutoInstall", false); Services.obs.addObserver(this, "Webapps:Load", false); Services.obs.addObserver(this, "Webapps:AutoUninstall", false); @@ -1631,8 +1631,8 @@ var BrowserApp = { break; } - case "webapps-uninstall": { - WebappManager.uninstall(JSON.parse(aData)); + case "webapps-runtime-uninstall": { + WebappManager.uninstall(JSON.parse(aData), aSubject); break; } diff --git a/mobile/android/locales/en-US/chrome/aboutApps.dtd b/mobile/android/locales/en-US/chrome/aboutApps.dtd index 6afabe1d1396..69df67b1c612 100644 --- a/mobile/android/locales/en-US/chrome/aboutApps.dtd +++ b/mobile/android/locales/en-US/chrome/aboutApps.dtd @@ -7,5 +7,4 @@ - diff --git a/mobile/android/modules/WebappManager.jsm b/mobile/android/modules/WebappManager.jsm index c215187ee799..fa068bd57c85 100644 --- a/mobile/android/modules/WebappManager.jsm +++ b/mobile/android/modules/WebappManager.jsm @@ -208,16 +208,53 @@ this.WebappManager = { }); }, - uninstall: function(aData) { + uninstall: Task.async(function*(aData, aMessageManager) { debug("uninstall: " + aData.manifestURL); + yield DOMApplicationRegistry.registryReady; + if (this._testing) { - // We don't have to do anything, as the registry does all the work. + // Go directly to DOM. Do not uninstall APK, do not collect $200. + DOMApplicationRegistry.doUninstall(aData, aMessageManager); return; } - // TODO: uninstall the APK. - }, + let app = DOMApplicationRegistry.getAppByManifestURL(aData.manifestURL); + if (!app) { + throw new Error("app not found in registry"); + } + + // If the APK is installed, then _getAPKVersions will return a version + // for it, so we can use that function to determine its install status. + let apkVersions = yield this._getAPKVersions([ app.apkPackageName ]); + if (app.apkPackageName in apkVersions) { + debug("APK is installed; requesting uninstallation"); + sendMessageToJava({ + type: "Webapps:UninstallApk", + apkPackageName: app.apkPackageName, + }); + + // We don't need to call DOMApplicationRegistry.doUninstall at this point, + // because the APK uninstall listener will call autoUninstall once the APK + // is uninstalled; and if the user cancels the APK uninstallation, then we + // shouldn't remove the app from the registry anyway. + + // But we should tell the requesting document the result of their request. + // TODO: tell the requesting document if uninstallation succeeds or fails + // by storing weak references to the message/manager pair here and then + // using them in autoUninstall if they're still defined when it's called; + // and make EventListener.uninstallApk return an error when APK uninstall + // fails (which it should be able to detect reliably on Android 4+), + // which we observe here and use to notify the requester of failure. + } else { + // The APK isn't installed, but remove the app from the registry anyway, + // to ensure the user can always remove an app from the registry (and thus + // about:apps) even if it's out of sync with installed APKs. + debug("APK not installed; proceeding directly to removal from registry"); + DOMApplicationRegistry.doUninstall(aData, aMessageManager); + } + + }), autoInstall: function(aData) { debug("autoInstall " + aData.manifestURL);