Backed out 2 changesets (bug 1200445) for test_operator_app_install.xul bustage
authorWes Kocher <wkocher@mozilla.com>
Mon, 14 Sep 2015 12:49:03 -0700
changeset 262282 c8534ce6a2336dcfc335958fba33e5832cce8a50
parent 262281 4166c144e542901cea767877f1ed2216f0814fee
child 262283 de5d084f84b48396820617b18838415ac702830f
push id17477
push userkwierso@gmail.com
push dateMon, 14 Sep 2015 19:49:14 +0000
treeherderb2g-inbound@c8534ce6a233 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1200445
milestone43.0a1
backs outa1f7f70e87bdd63b24a012c687b830572a514d13
fd2a42de520373740a663309d48e10881ed40a5b
Backed out 2 changesets (bug 1200445) for test_operator_app_install.xul bustage Backed out changeset a1f7f70e87bd (bug 1200445) Backed out changeset fd2a42de5203 (bug 1200445)
dom/apps/AndroidUtils.jsm
dom/apps/AppsUtils.jsm
dom/apps/Webapps.jsm
dom/apps/moz.build
mobile/android/b2gdroid/app/Makefile.in
mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Apps.java
mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Launcher.java
deleted file mode 100644
--- a/dom/apps/AndroidUtils.jsm
+++ /dev/null
@@ -1,123 +0,0 @@
-/* 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);
-    }
-  },
-}
--- a/dom/apps/AppsUtils.jsm
+++ b/dom/apps/AppsUtils.jsm
@@ -125,20 +125,16 @@ function _setAppProperties(aObj, aApp) {
   aObj.storeId = aApp.storeId || "";
   aObj.storeVersion = aApp.storeVersion || 0;
   aObj.role = aApp.role || "";
   aObj.redirects = aApp.redirects;
   aObj.widgetPages = aApp.widgetPages || [];
   aObj.kind = aApp.kind;
   aObj.enabled = aApp.enabled !== undefined ? aApp.enabled : true;
   aObj.sideloaded = aApp.sideloaded;
-#ifdef MOZ_B2GDROID
-  aObj.android_packagename = aApp.android_packagename;
-  aObj.android_classname = aApp.android_classname;
-#endif
 }
 
 this.AppsUtils = {
   // Clones a app, without the manifest.
   cloneAppObject: function(aApp) {
     let obj = {};
     _setAppProperties(obj, aApp);
     return obj;
--- a/dom/apps/Webapps.jsm
+++ b/dom/apps/Webapps.jsm
@@ -86,19 +86,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/Langpacks.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ImportExport",
                                   "resource://gre/modules/ImportExport.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                   "resource://gre/modules/AppConstants.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "Messaging",
-                                  "resource://gre/modules/Messaging.jsm");
-
 #ifdef MOZ_WIDGET_GONK
 XPCOMUtils.defineLazyGetter(this, "libcutils", function() {
   Cu.import("resource://gre/modules/systemlibs.js");
   return libcutils;
 });
 #endif
 
 #ifdef MOZ_WIDGET_ANDROID
@@ -193,17 +190,16 @@ XPCOMUtils.defineLazyGetter(this, "permM
 // store even by error.
 const STORE_ID_PENDING_PREFIX = "#unknownID#";
 
 this.DOMApplicationRegistry = {
   // pseudo-constants for the different application kinds.
   get kPackaged()       "packaged",
   get kHosted()         "hosted",
   get kHostedAppcache() "hosted-appcache",
-  get kAndroid()        "android-native",
 
   // Path to the webapps.json file where we store the registry data.
   appsFile: null,
   webapps: { },
   allAppsLaunchable: false,
   _updateHandlers: [ ],
   _pendingUninstalls: {},
   _contentActions: new Map(),
@@ -258,21 +254,16 @@ this.DOMApplicationRegistry = {
 
     this.loadAndUpdateApps();
 
     Langpacks.registerRegistryFunctions(MessageBroadcaster.broadcastMessage.bind(MessageBroadcaster),
                                         this._appIdForManifestURL.bind(this),
                                         this.getFullAppByManifestURL.bind(this));
 
     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.
   loadCurrentRegistry: function() {
     return AppsUtils.loadJSONAsync(this.appsFile).then((aData) => {
       if (!aData) {
         return;
       }
@@ -468,19 +459,17 @@ this.DOMApplicationRegistry = {
     // Create or Update the DataStore for this app
     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);
   }),
 
   appKind: function(aApp, aManifest) {
-    if (aApp.origin.startsWith("android://")) {
-      return this.kAndroid;
-    } if (aApp.origin.startsWith("app://")) {
+    if (aApp.origin.startsWith("app://")) {
       return this.kPackaged;
     } else {
       // Hosted apps, can be appcached or not.
       let kind = this.kHosted;
       if (aManifest.appcache_path) {
         kind = this.kHostedAppcache;
       }
       return kind;
@@ -528,20 +517,17 @@ this.DOMApplicationRegistry = {
           appcache_path: fullAppcachePath
         });
       }
     });
   },
 
   // Installs a 3rd party app.
   installPreinstalledApp: function installPreinstalledApp(aId) {
-    if (!AppConstants.MOZ_B2GDROID && AppConstants.platform !== "gonk") {
-      return false;
-    }
-
+#ifdef MOZ_WIDGET_GONK
     // 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
     // be the id of the old version, which is the one we'll keep.
     let destId  = this.webapps[aId].oldId || aId;
     // We don't need the oldId anymore
     if (destId !== aId) {
       delete this.webapps[aId];
     }
@@ -634,16 +620,17 @@ this.DOMApplicationRegistry = {
       // If we are unable to extract the manifest, cleanup and remove this app.
       debug("Cleaning up: " + e);
       destDir.remove(true);
       delete this.webapps[destId];
     } finally {
       zipReader.close();
     }
     return isPreinstalled;
+#endif
   },
 
   // For hosted apps, uninstall an app served from http:// if we have
   // one installed from the same url with an https:// scheme.
   removeIfHttpsDuplicate: function(aId) {
 #ifdef MOZ_WIDGET_GONK
     let app = this.webapps[aId];
     if (!app || !app.origin.startsWith("http://")) {
@@ -800,33 +787,29 @@ this.DOMApplicationRegistry = {
             debug("Skipping app migration.");
           }
         }
 
         if (AppConstants.MOZ_B2GDROID || AppConstants.MOZ_B2G) {
           yield this.installSystemApps();
         }
 
-        if (AppConstants.MOZ_B2GDROID) {
-          yield AndroidUtils.installAndroidApps();
-        }
-
         // At first run, install preloaded apps and set up their permissions.
         for (let id in this.webapps) {
           let isPreinstalled = this.installPreinstalledApp(id);
           this.removeIfHttpsDuplicate(id);
           if (!this.webapps[id]) {
             continue;
           }
           this.updateOfflineCacheForApp(id);
           this.updatePermissionsForApp(id, isPreinstalled);
         }
         // Need to update the persisted list of apps since
         // installPreinstalledApp() removes the ones failing to install.
-        yield this._saveApps();
+        this._saveApps();
 
         Services.prefs.setBoolPref("dom.apps.reset-permissions", true);
       }
 
       // DataStores must be initialized at startup.
       for (let id in this.webapps) {
         yield this.updateDataStoreForApp(id);
       }
@@ -1207,19 +1190,16 @@ this.DOMApplicationRegistry = {
   observe: function(aSubject, aTopic, aData) {
     if (aTopic == "xpcom-shutdown") {
       this.messages.forEach((function(msgName) {
         ppmm.removeMessageListener(msgName, this);
       }).bind(this));
       Services.obs.removeObserver(this, "xpcom-shutdown");
       cpmm = null;
       ppmm = null;
-      if (AppConstants.MOZ_B2GDROID) {
-        AndroidUtils.uninit();
-      }
     } else if (aTopic == "memory-pressure") {
       // Clear the manifest cache on memory pressure.
       this._manifestCache = {};
     }
   },
 
   formatMessage: function(aData) {
     let msg = aData;
@@ -1319,32 +1299,32 @@ this.DOMApplicationRegistry = {
       return;
     }
 
     // For all the rest (asynchronous), we wait till the registry is ready
     // before processing the message.
     this.registryReady.then( () => {
       switch (aMessage.name) {
         case "Webapps:Install": {
-          if (AppConstants.platform == "android" && !AppConstants.MOZ_B2GDROID) {
-            Services.obs.notifyObservers(mm, "webapps-runtime-install", JSON.stringify(msg));
-          } else {
-            this.doInstall(msg, mm);
-          }
+#ifdef MOZ_WIDGET_ANDROID
+          Services.obs.notifyObservers(mm, "webapps-runtime-install", JSON.stringify(msg));
+#else
+          this.doInstall(msg, mm);
+#endif
           break;
         }
         case "Webapps:GetSelf":
           this.getSelf(msg, mm);
           break;
         case "Webapps:Uninstall":
-          if (AppConstants.platform == "android" && !AppConstants.MOZ_B2GDROID) {
-            Services.obs.notifyObservers(mm, "webapps-runtime-uninstall", JSON.stringify(msg));
-          } else {
-            this.doUninstall(msg, mm);
-          }
+#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);
           break;
         case "Webapps:LocationChange":
           this.onLocationChange(msg.oid);
           break;
         case "Webapps:CheckInstalled":
@@ -1352,21 +1332,21 @@ this.DOMApplicationRegistry = {
           break;
         case "Webapps:GetInstalled":
           this.getInstalled(msg, mm);
           break;
         case "Webapps:GetNotInstalled":
           this.getNotInstalled(msg, mm);
           break;
         case "Webapps:InstallPackage": {
-          if (AppConstants.platform == "android" && !AppConstants.MOZ_B2GDROID) {
-            Services.obs.notifyObservers(mm, "webapps-runtime-install-package", JSON.stringify(msg));
-          } else {
-            this.doInstallPackage(msg, mm);
-          }
+#ifdef MOZ_WIDGET_ANDROID
+          Services.obs.notifyObservers(mm, "webapps-runtime-install-package", JSON.stringify(msg));
+#else
+          this.doInstallPackage(msg, mm);
+#endif
           break;
         }
         case "Webapps:Download":
           this.startDownload(msg.manifestURL);
           break;
         case "Webapps:CancelDownload":
           this.cancelDownload(msg.manifestURL);
           break;
@@ -1617,29 +1597,16 @@ this.DOMApplicationRegistry = {
 
     // Fire an error when trying to launch an app that is not
     // yet fully installed.
     if (app.installState == "pending") {
       aOnFailure("PENDING_APP_NOT_LAUNCHABLE");
       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
     // stringified as an empty object. (see bug 830376)
     let appClone = AppsUtils.cloneAppObject(app);
     appClone.startPoint = aStartPoint;
     appClone.timestamp = aTimeStamp;
     Services.obs.notifyObservers(null, "webapps-launch", JSON.stringify(appClone));
     aOnSuccess();
   },
@@ -2074,19 +2041,18 @@ this.DOMApplicationRegistry = {
 
     // We may be able to remove this when Bug 839071 is fixed.
     if (app.downloading) {
       sendError("APP_IS_DOWNLOADING");
       return;
     }
 
     // If the app is packaged and its manifestURL has an app:// scheme,
-    // or if it's a native Android app then we can't have an update.
-    if (app.kind == this.kAndroid ||
-        (app.kind == this.kPackaged && app.manifestURL.startsWith("app://"))) {
+    // then we can't have an update.
+    if (app.kind == this.kPackaged && app.manifestURL.startsWith("app://")) {
       sendError("NOT_UPDATABLE");
       return;
     }
 
     // For non-removable hosted apps that lives in the core apps dir we
     // only check the appcache because we can't modify the manifest even
     // if it has changed.
     let onlyCheckAppCache = false;
@@ -2808,20 +2774,18 @@ this.DOMApplicationRegistry = {
         this.revertDownloadPackage(id, oldApp, newApp, false, ex);
       }
     }
   }),
 
   _setupApp: function(aData, aId) {
     let app = aData.app;
 
-    // app can be uninstalled by default.
-    if (app.removable === undefined) {
-      app.removable = true;
-    }
+    // app can be uninstalled
+    app.removable = true;
 
     if (aData.isPackage) {
       // Override the origin with the correct id.
       app.origin = "app://" + aId;
     }
 
     app.id = aId;
     app.installTime = Date.now();
@@ -2844,18 +2808,17 @@ this.DOMApplicationRegistry = {
       appObject.downloadSize = 0;
       appObject.readyToApplyDownload = false;
     } else if (appObject.kind == this.kPackaged) {
       appObject.installState = "pending";
       appObject.downloadAvailable = true;
       appObject.downloading = true;
       appObject.downloadSize = aLocaleManifest.size;
       appObject.readyToApplyDownload = false;
-    } else if (appObject.kind == this.kHosted ||
-               appObject.kind == this.kAndroid) {
+    } else if (appObject.kind == this.kHosted) {
       appObject.installState = "installed";
       appObject.downloadAvailable = false;
       appObject.downloading = false;
       appObject.readyToApplyDownload = false;
     } else {
       debug("Unknown app kind: " + appObject.kind);
       throw Error("Unknown app kind: " + appObject.kind);
     }
@@ -2901,16 +2864,18 @@ this.DOMApplicationRegistry = {
 
     app.csp = aManifest.csp || "";
 
     let aLocaleManifest = new ManifestHelper(aManifest, app.origin, app.manifestURL);
     this._saveWidgetsFullPath(aLocaleManifest, app);
 
     app.appStatus = AppsUtils.getAppManifestStatus(aManifest);
 
+    app.removable = true;
+
     // Reuse the app ID if the scheme is "app".
     let uri = Services.io.newURI(app.origin, null, null);
     if (uri.scheme == "app") {
       app.id = uri.host;
     } else {
       app.id = this.makeAppId();
     }
 
@@ -2990,17 +2955,16 @@ this.DOMApplicationRegistry = {
       new ManifestHelper(jsonManifest, app.origin, app.manifestURL);
 
     // Set the application kind.
     app.kind = this.appKind(app, manifest);
 
     let appObject = this._cloneApp(aData, app, manifest, jsonManifest, id, localId);
 
     this.webapps[id] = appObject;
-    this._manifestCache[id] = jsonManifest;
 
     // For package apps, the permissions are not in the mini-manifest, so
     // don't update the permissions yet.
     if (!aData.isPackage) {
       if (supportUseCurrentProfile()) {
         try {
           if (Services.prefs.getBoolPref("dom.apps.developer_mode")) {
             this.webapps[id].appStatus =
@@ -3065,20 +3029,16 @@ this.DOMApplicationRegistry = {
     // corresponding DOMRequest.onsuccess event as soon as the app is properly
     // saved in the registry.
     yield this._saveApps();
 
     aData.isPackage ? appObject.updateManifest = jsonManifest :
                       appObject.manifest = jsonManifest;
     MessageBroadcaster.broadcastMessage("Webapps:AddApp", { id: id, app: appObject });
 
-    // At this point we don't need the manifest on the app object anymore.
-    delete appObject.updateManifest;
-    delete appObject.manifest;
-
     if (!aData.isPackage) {
       this.updateAppHandlers(null, app.manifest, app);
       if (aInstallSuccessCallback) {
         try {
           yield aInstallSuccessCallback(app, app.manifest);
         } catch (e) {
           // Ignore exceptions during the local installation of
           // an app. If it fails, the app will anyway be considered
@@ -4099,34 +4059,22 @@ this.DOMApplicationRegistry = {
     // leaking the whole page associationed with the message manager.
     aMm = Cu.getWeakReference(aMm);
 
     let response = "Webapps:Uninstall:Return:OK";
 
     try {
       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";
+      let prefName = "dom.mozApps.auto_confirm_uninstall";
+      if (Services.prefs.prefHasUserValue(prefName) &&
+          Services.prefs.getBoolPref(prefName)) {
+        yield this._uninstallApp(aData.app);
       } else {
-        let prefName = "dom.mozApps.auto_confirm_uninstall";
-        if (Services.prefs.prefHasUserValue(prefName) &&
-            Services.prefs.getBoolPref(prefName)) {
-          yield this._uninstallApp(aData.app);
-        } else {
-          yield this._promptForUninstall(aData);
-        }
+        yield this._promptForUninstall(aData);
       }
     } catch (error) {
       aData.error = error;
       response = "Webapps:Uninstall:Return:KO";
     }
 
     if ((aMm = aMm.get())) {
       aMm.sendAsyncMessage(response, this.formatMessage(aData));
--- a/dom/apps/moz.build
+++ b/dom/apps/moz.build
@@ -37,21 +37,16 @@ EXTRA_JS_MODULES += [
     'MessageBroadcaster.jsm',
     'OfflineCacheInstaller.jsm',
     'PermissionsInstaller.jsm',
     'PermissionsTable.jsm',
     'StoreTrustAnchor.jsm',
     'UserCustomizations.jsm',
 ]
 
-if CONFIG['MOZ_B2GDROID']:
-    EXTRA_JS_MODULES += [
-        'AndroidUtils.jsm',
-    ]
-
 EXTRA_PP_JS_MODULES += [
     'AppsUtils.jsm',
     'ImportExport.jsm',
     'InterAppCommService.jsm',
     'OperatorApps.jsm',
     'ScriptPreloader.jsm',
     'Webapps.jsm',
 ]
--- a/mobile/android/b2gdroid/app/Makefile.in
+++ b/mobile/android/b2gdroid/app/Makefile.in
@@ -1,16 +1,15 @@
 # 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/.
 
 ANDROID_MANIFEST_FILE := src/main/AndroidManifest.xml
 
 JAVAFILES := \
-  src/main/java/org/mozilla/b2gdroid/Apps.java \
   src/main/java/org/mozilla/b2gdroid/Launcher.java \
   src/main/java/org/mozilla/b2gdroid/ScreenStateObserver.java \
   $(NULL)
 
 # The GeckoView consuming APK depends on the GeckoView JAR files.  There are two
 # issues: first, the GeckoView JAR files need to be built before they are
 # consumed here.  This happens for delicate reasons.  In the (serial) libs tier,
 # base/ is traversed before b2gdroid/app.  Since base/libs builds classes.dex,
deleted file mode 100644
--- a/mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Apps.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/* 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);
-        }
-    }
-}
--- a/mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Launcher.java
+++ b/mobile/android/b2gdroid/app/src/main/java/org/mozilla/b2gdroid/Launcher.java
@@ -1,66 +1,78 @@
 /* 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.app.Activity;
 import android.app.ActivityManager;
 import android.app.KeyguardManager;
 import android.app.KeyguardManager.KeyguardLock;
 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.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.util.Base64;
 import android.util.Log;
 import android.view.View;
+import android.widget.ImageView;
 
 import org.json.JSONObject;
+import org.json.JSONArray;
+import org.json.JSONException;
 
 import org.mozilla.gecko.BaseGeckoInterface;
 import org.mozilla.gecko.ContactService;
 import org.mozilla.gecko.ContextGetter;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoBatteryManager;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoThread;
 import org.mozilla.gecko.IntentHelper;
 import org.mozilla.gecko.util.GeckoEventListener;
 
 import org.mozilla.b2gdroid.ScreenStateObserver;
-import org.mozilla.b2gdroid.Apps;
 
 public class Launcher extends Activity
                       implements GeckoEventListener, ContextGetter {
     private static final String LOGTAG = "B2G";
 
     private ContactService      mContactService;
     private ScreenStateObserver mScreenStateObserver;
-    private Apps                mApps;
 
     /** ContextGetter */
     public Context getContext() {
         return this;
     }
 
     public SharedPreferences getSharedPreferences() {
         return null;
     }
 
     /** Initializes Gecko APIs */
     private void initGecko() {
         GeckoAppShell.setContextGetter(this);
 
         GeckoBatteryManager.getInstance().start(this);
         mContactService = new ContactService(EventDispatcher.getInstance(), this);
-        mApps = new Apps(this);
     }
 
     private void hideSplashScreen() {
         final View splash = findViewById(R.id.splashscreen);
         runOnUiThread(new Runnable() {
             @Override public void run() {
                 splash.setVisibility(View.GONE);
             }
@@ -106,17 +118,16 @@ public class Launcher extends Activity
         IntentHelper.destroy();
         mScreenStateObserver.destroy(this);
         mScreenStateObserver = null;
 
         EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
             "Launcher:Ready");
 
         mContactService.destroy();
-        mApps.destroy();
     }
 
     @Override
     protected void onNewIntent(Intent intent) {
         final String action = intent.getAction();
         Log.w(LOGTAG, "onNewIntent " + action);
         if (Intent.ACTION_VIEW.equals(action)) {
             Log.w(LOGTAG, "Asking gecko to view " + intent.getDataString());