Bug 957070 - move webapp event listeners/handlers from GeckoAppShell to webapp/ class; r=wesj
authorMartyn Haigh <mhaigh@mozilla.com>
Fri, 24 Jan 2014 21:57:13 -0800
changeset 165227 6638c6ade38c11f181690cfb646050dec8012336
parent 165226 f777e85ac4421df46d2b9fc0e75a1b02d925d6b9
child 165228 def86a00c331a7d6f9583e067f238f45d843aeae
push id26082
push userphilringnalda@gmail.com
push dateSun, 26 Jan 2014 03:55:40 +0000
treeherdermozilla-central@ce31e8b8cbbd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerswesj
bugs957070
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 957070 - move webapp event listeners/handlers from GeckoAppShell to webapp/ class; r=wesj
CLOBBER
mobile/android/base/AndroidManifest.xml.in
mobile/android/base/GeckoApp.java
mobile/android/base/GeckoAppShell.java
mobile/android/base/moz.build
mobile/android/base/webapp/Allocator.java
mobile/android/base/webapp/ApkResources.java
mobile/android/base/webapp/Dispatcher.java
mobile/android/base/webapp/EventListener.java
mobile/android/base/webapp/InstallHelper.java
mobile/android/base/webapp/InstallListener.java
mobile/android/base/webapp/UninstallListener.java
mobile/android/base/webapp/WebAppAllocator.java
mobile/android/base/webapp/WebAppDispatcher.java
mobile/android/base/webapp/WebAppImpl.java
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 948583, first part, apparently requires a clobber.  (Ideas for fixing this involve removing jsopcode.tbl, which is a bit too big to do while holding up this patch.)
+Bug 957070 requires a clobber to resolve a reference to a program class member.
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -154,17 +154,17 @@
             <!-- For debugging -->
             <intent-filter>
                 <action android:name="org.mozilla.gecko.DEBUG" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
 
 #ifdef MOZ_ANDROID_SYNTHAPKS
-        <activity android:name="org.mozilla.gecko.webapp.WebAppDispatcher"
+        <activity android:name="org.mozilla.gecko.webapp.Dispatcher"
             android:noHistory="true" >
             <intent-filter>
                 <!-- catch links from synthetic apks -->
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:mimeType="application/webapp" />
             </intent-filter>
         </activity>
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -24,16 +24,17 @@ import org.mozilla.gecko.updater.UpdateS
 import org.mozilla.gecko.util.ActivityResultHandler;
 import org.mozilla.gecko.util.EventDispatcher;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.GeckoEventResponder;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.UiAsyncTask;
 import org.mozilla.gecko.webapp.UninstallListener;
+import org.mozilla.gecko.webapp.EventListener;
 import org.mozilla.gecko.widget.ButtonToast;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.app.Activity;
 import android.app.AlertDialog;
@@ -628,44 +629,23 @@ public abstract class GeckoApp
             } else if (event.equals("Accessibility:Ready")) {
                 GeckoAccessibility.updateAccessibilitySettings(this);
             } else if (event.equals("Shortcut:Remove")) {
                 final String url = message.getString("url");
                 final String origin = message.getString("origin");
                 final String title = message.getString("title");
                 final String type = message.getString("shortcutType");
                 GeckoAppShell.removeShortcut(title, url, origin, type);
-            } else if (AppConstants.MOZ_ANDROID_SYNTHAPKS && event.equals("WebApps:InstallApk")) {
-                GeckoAppShell.installApk(this, message.getString("filePath"), message.getString("data"));
             } else if (!AppConstants.MOZ_ANDROID_SYNTHAPKS && event.equals("WebApps:PreInstall")) {
                 String name = message.getString("name");
                 String manifestURL = message.getString("manifestURL");
                 String origin = message.getString("origin");
+
                 // preInstallWebapp will return a File object pointing to the profile directory of the webapp
-                mCurrentResponse = GeckoAppShell.preInstallWebApp(name, manifestURL, origin).toString();
-            } else if (event.equals("WebApps:PostInstall")) {
-                if (AppConstants.MOZ_ANDROID_SYNTHAPKS) {
-                    GeckoAppShell.postInstallWebApp(message.getString("apkPackageName"), message.getString("origin"));
-                } else {
-                    String name = message.getString("name");
-                    String manifestURL = message.getString("manifestURL");
-                    String iconURL = message.getString("iconURL");
-                    String originalOrigin = message.getString("originalOrigin");
-                    String origin = message.getString("origin");
-                    GeckoAppShell.postInstallWebApp(name, manifestURL, origin, iconURL, originalOrigin);
-                }
-            } else if (event.equals("WebApps:Open")) {
-                String manifestURL = message.getString("manifestURL");
-                String origin = message.getString("origin");
-                Intent intent = GeckoAppShell.getWebAppIntent(manifestURL, origin, "", null);
-                if (intent == null)
-                    return;
-                startActivity(intent);
-            } else if (!AppConstants.MOZ_ANDROID_SYNTHAPKS && event.equals("WebApps:Uninstall")) {
-                GeckoAppShell.uninstallWebApp(message.getString("origin"));
+                mCurrentResponse = EventListener.preInstallWebApp(name, manifestURL, origin).toString();
             } else if (event.equals("Share:Text")) {
                 String text = message.getString("text");
                 GeckoAppShell.openUriExternal(text, "text/plain", "", "", Intent.ACTION_SEND, "");
             } else if (event.equals("Share:Image")) {
                 String src = message.getString("url");
                 String type = message.getString("mime");
                 GeckoAppShell.shareImage(src, type);
             } else if (event.equals("Image:SetAs")) {
@@ -1559,35 +1539,32 @@ public abstract class GeckoApp
         registerEventListener("ToggleChrome:Show");
         registerEventListener("ToggleChrome:Focus");
         registerEventListener("Permissions:Data");
         registerEventListener("Session:StatePurged");
         registerEventListener("Bookmark:Insert");
         registerEventListener("Accessibility:Event");
         registerEventListener("Accessibility:Ready");
         registerEventListener("Shortcut:Remove");
-        // TODO Consider moving webapp install-related things into InstallHelper.
-        registerEventListener("WebApps:InstallApk");
-        registerEventListener("WebApps:PreInstall");
-        registerEventListener("WebApps:PostInstall");
-        registerEventListener("WebApps:Open");
-        registerEventListener("WebApps:Uninstall");
         registerEventListener("Share:Text");
         registerEventListener("Share:Image");
         registerEventListener("Image:SetAs");
         registerEventListener("Sanitize:ClearHistory");
         registerEventListener("Update:Check");
         registerEventListener("Update:Download");
         registerEventListener("Update:Install");
         registerEventListener("PrivateBrowsing:Data");
         registerEventListener("Contact:Add");
         registerEventListener("Intent:Open");
         registerEventListener("Intent:GetHandlers");
         registerEventListener("Locale:Set");
         registerEventListener("SystemUI:Visibility");
+        registerEventListener("WebApps:PreInstall");
+
+        EventListener.registerEvents();
 
         if (SmsManager.getInstance() != null) {
           SmsManager.getInstance().start();
         }
 
         mContactService = new ContactService(GeckoAppShell.getEventDispatcher(), this);
 
         mPromptService = new PromptService(this);
@@ -2090,34 +2067,32 @@ public abstract class GeckoApp
         unregisterEventListener("ToggleChrome:Show");
         unregisterEventListener("ToggleChrome:Focus");
         unregisterEventListener("Permissions:Data");
         unregisterEventListener("Session:StatePurged");
         unregisterEventListener("Bookmark:Insert");
         unregisterEventListener("Accessibility:Event");
         unregisterEventListener("Accessibility:Ready");
         unregisterEventListener("Shortcut:Remove");
-        unregisterEventListener("WebApps:InstallApk");
-        unregisterEventListener("WebApps:PreInstall");
-        unregisterEventListener("WebApps:PostInstall");
-        unregisterEventListener("WebApps:Open");
-        unregisterEventListener("WebApps:Uninstall");
         unregisterEventListener("Share:Text");
         unregisterEventListener("Share:Image");
         unregisterEventListener("Image:SetAs");
         unregisterEventListener("Sanitize:ClearHistory");
         unregisterEventListener("Update:Check");
         unregisterEventListener("Update:Download");
         unregisterEventListener("Update:Install");
         unregisterEventListener("PrivateBrowsing:Data");
         unregisterEventListener("Contact:Add");
         unregisterEventListener("Intent:Open");
         unregisterEventListener("Intent:GetHandlers");
         unregisterEventListener("Locale:Set");
         unregisterEventListener("SystemUI:Visibility");
+        unregisterEventListener("WebApps:PreInstall");
+
+        EventListener.unregisterEvents();
 
         deleteTempFiles();
 
         if (mLayerView != null)
             mLayerView.destroy();
         if (mDoorHangerPopup != null)
             mDoorHangerPopup.destroy();
         if (mFormAssistPopup != null)
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -5,28 +5,29 @@
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.gfx.GeckoLayerClient;
 import org.mozilla.gecko.gfx.LayerView;
 import org.mozilla.gecko.gfx.PanZoomController;
-import org.mozilla.gecko.mozglue.JNITarget;
+import org.mozilla.gecko.mozglue.GeckoLoader;
 import org.mozilla.gecko.mozglue.generatorannotations.OptionalGeneratedParameter;
 import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI;
+import org.mozilla.gecko.mozglue.JNITarget;
+import org.mozilla.gecko.mozglue.RobocopTarget;
 import org.mozilla.gecko.prompts.PromptService;
-import org.mozilla.gecko.mozglue.GeckoLoader;
-import org.mozilla.gecko.mozglue.RobocopTarget;
 import org.mozilla.gecko.util.ActivityResultHandler;
 import org.mozilla.gecko.util.EventDispatcher;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.ProxySelector;
 import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.webapp.Allocator;
 import org.mozilla.gecko.webapp.InstallListener;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.PendingIntent;
@@ -713,44 +714,21 @@ public class GeckoAppShell
         System.exit(0);
     }
 
     @WrapElementForJNI
     static void scheduleRestart() {
         gRestartScheduled = true;
     }
 
-    // The old implementation of preInstallWebApp.  Not used by MOZ_ANDROID_SYNTHAPKS.
-    public static File preInstallWebApp(String aTitle, String aURI, String aOrigin) {
-        int index = WebAppAllocator.getInstance(getContext()).findAndAllocateIndex(aOrigin, aTitle, (String) null);
-        GeckoProfile profile = GeckoProfile.get(getContext(), "webapp" + index);
-        return profile.getDir();
-    }
-
-    // The old implementation of postInstallWebApp.  Not used by MOZ_ANDROID_SYNTHAPKS.
-    public static void postInstallWebApp(String aTitle, String aURI, String aOrigin, String aIconURL, String aOriginalOrigin) {
-        WebAppAllocator allocator = WebAppAllocator.getInstance(getContext());
-        int index = allocator.getIndexForApp(aOriginalOrigin);
-        assert index != -1 && aIconURL != null;
-        allocator.updateAppAllocation(aOrigin, index, BitmapUtils.getBitmapFromDataURI(aIconURL));
-        createShortcut(aTitle, aURI, aOrigin, aIconURL, "webapp");
-    }
-
-    // The new implementation of postInstallWebApp.  Used by MOZ_ANDROID_SYNTHAPKS.
-    public static void postInstallWebApp(String aPackageName, String aOrigin) {
-        org.mozilla.gecko.webapp.WebAppAllocator allocator = org.mozilla.gecko.webapp.WebAppAllocator.getInstance(getContext());
-        int index = allocator.findOrAllocatePackage(aPackageName);
-        allocator.putOrigin(index, aOrigin);
-    }
-
     public static Intent getWebAppIntent(String aURI, String aOrigin, String aTitle, Bitmap aIcon) {
         Intent intent;
 
         if (AppConstants.MOZ_ANDROID_SYNTHAPKS) {
-            org.mozilla.gecko.webapp.WebAppAllocator slots = org.mozilla.gecko.webapp.WebAppAllocator.getInstance(getContext());
+            Allocator slots = Allocator.getInstance(getContext());
             int index = slots.getIndexForOrigin(aOrigin);
 
             if (index == -1) {
                 return null;
             }
             String packageName = slots.getAppForIndex(index);
             intent = getContext().getPackageManager().getLaunchIntentForPackage(packageName);
             if (aURI != null) {
@@ -868,55 +846,16 @@ public class GeckoAppShell
                     intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, aURI);
 
                 intent.setAction("com.android.launcher.action.UNINSTALL_SHORTCUT");
                 getContext().sendBroadcast(intent);
             }
         });
     }
 
-    public static void uninstallWebApp(final String uniqueURI) {
-        // On uninstall, we need to do a couple of things:
-        //   1. nuke the running app process.
-        //   2. nuke the profile that was assigned to that webapp
-        ThreadUtils.postToBackgroundThread(new Runnable() {
-            @Override
-            public void run() {
-                int index;
-                if (AppConstants.MOZ_ANDROID_SYNTHAPKS) {
-                    index = org.mozilla.gecko.webapp.WebAppAllocator.getInstance(getContext()).releaseIndexForApp(uniqueURI);
-                } else {
-                    index = WebAppAllocator.getInstance(getContext()).releaseIndexForApp(uniqueURI);
-                }
-
-                // if -1, nothing to do; we didn't think it was installed anyway
-                if (index == -1)
-                    return;
-
-                // kill the app if it's running
-                String targetProcessName = getContext().getPackageName();
-                targetProcessName = targetProcessName + ":" + targetProcessName + ".WebApp" + index;
-
-                ActivityManager am = (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE);
-                List<ActivityManager.RunningAppProcessInfo> procs = am.getRunningAppProcesses();
-                if (procs != null) {
-                    for (ActivityManager.RunningAppProcessInfo proc : procs) {
-                        if (proc.processName.equals(targetProcessName)) {
-                            android.os.Process.killProcess(proc.pid);
-                            break;
-                        }
-                    }
-                }
-
-                // then nuke the profile
-                GeckoProfile.removeProfile(getContext(), "webapp" + index);
-            }
-        });
-    }
-
     @JNITarget
     static public int getPreferredIconSize() {
         if (android.os.Build.VERSION.SDK_INT >= 11) {
             ActivityManager am = (ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE);
             return am.getLauncherLargeIconSize();
         } else {
             switch (getDpi()) {
                 case DisplayMetrics.DENSITY_MEDIUM:
@@ -2734,59 +2673,9 @@ public class GeckoAppShell
                 return "PROXY " + proxy.address().toString();
             case SOCKS:
                 return "SOCKS " + proxy.address().toString();
         }
 
         return "DIRECT";
     }
 
-    public static void installApk(final Activity context, String filePath, String data) {
-        // This is the data that mozApps.install sent to Webapps.jsm.
-        JSONObject argsObj = null;
-
-        // We get the manifest url out of javascript here so we can use it as a checksum
-        // in a minute, when a package has been installed.
-        String manifestUrl = null;
-        try {
-            argsObj = new JSONObject(data);
-            manifestUrl = argsObj.getJSONObject("app").getString("manifestURL");
-        } catch (JSONException e) {
-            Log.e(LOGTAG, "can't get manifest URL from JSON data", e);
-            // TODO: propagate the error back to the mozApps.install caller.
-            return;
-        }
-
-        // We will check the manifestUrl from the one in the APK.
-        // Thus, we can have a one-to-one mapping of apk to receiver.
-        final InstallListener receiver = new InstallListener(manifestUrl, argsObj);
-
-        // Listen for packages being installed.
-        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
-        filter.addDataScheme("package");
-        context.registerReceiver(receiver, filter);
-
-        // Now call the package installer.
-        File file = new File(filePath);
-        Intent intent = new Intent(Intent.ACTION_VIEW);
-        intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
-
-        sActivityHelper.startIntentForActivity(context, intent, new ActivityResultHandler() {
-            @Override
-            public void onActivityResult(int resultCode, Intent data) {
-                // The InstallListener will catch the case where the user pressed install.
-                // Now deal with if the user pressed cancel.
-                if (resultCode == Activity.RESULT_CANCELED) {
-                    try {
-                        context.unregisterReceiver(receiver);
-                        receiver.cleanup();
-                    } catch (java.lang.IllegalArgumentException e) {
-                        // IllegalArgumentException happens because resultCode is RESULT_CANCELED
-                        // when the user presses the Done button in the install confirmation dialog,
-                        // even though the install has been successful (and InstallListener already
-                        // unregistered the receiver).
-                        Log.e(LOGTAG, "error unregistering install receiver: ", e);
-                    }
-                }
-            }
-        });
-    }
 }
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -326,22 +326,23 @@ gbjar.sources += [
     'toolbar/ToolbarEditLayout.java',
     'toolbar/ToolbarEditText.java',
     'toolbar/ToolbarProgressView.java',
     'toolbar/ToolbarTitlePrefs.java',
     'TouchEventInterceptor.java',
     'updater/UpdateService.java',
     'updater/UpdateServiceHelper.java',
     'VideoPlayer.java',
+    'webapp/Allocator.java',
     'webapp/ApkResources.java',
+    'webapp/Dispatcher.java',
+    'webapp/EventListener.java',
     'webapp/InstallHelper.java',
     'webapp/InstallListener.java',
     'webapp/UninstallListener.java',
-    'webapp/WebAppAllocator.java',
-    'webapp/WebAppDispatcher.java',
     'webapp/WebAppImpl.java',
     'WebAppAllocator.java',
     'WebAppImpl.java',
     'widget/ActivityChooserModel.java',
     'widget/AllCapsTextView.java',
     'widget/AnimatedHeightLayout.java',
     'widget/ArrowPopup.java',
     'widget/BasicColorPicker.java',
rename from mobile/android/base/webapp/WebAppAllocator.java
rename to mobile/android/base/webapp/Allocator.java
--- a/mobile/android/base/webapp/WebAppAllocator.java
+++ b/mobile/android/base/webapp/Allocator.java
@@ -11,42 +11,42 @@ import org.mozilla.gecko.util.ThreadUtil
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.Editor;
 
 import java.util.ArrayList;
 
 import android.util.Log;
 
-public class WebAppAllocator {
+public class Allocator {
 
     private final String LOGTAG = "GeckoWebAppAllocator";
 
     private static final String PREFIX_ORIGIN = "webapp-origin-";
     private static final String PREFIX_PACKAGE_NAME = "webapp-package-name-";
 
     // The number of WebApp# and WEBAPP# activites/apps/intents
     private final static int MAX_WEB_APPS = 100;
 
-    protected static WebAppAllocator sInstance = null;
-    public static WebAppAllocator getInstance() {
+    protected static Allocator sInstance = null;
+    public static Allocator getInstance() {
         return getInstance(GeckoAppShell.getContext());
     }
 
-    public static synchronized WebAppAllocator getInstance(Context cx) {
+    public static synchronized Allocator getInstance(Context cx) {
         if (sInstance == null) {
-            sInstance = new WebAppAllocator(cx);
+            sInstance = new Allocator(cx);
         }
 
         return sInstance;
     }
 
     SharedPreferences mPrefs;
 
-    protected WebAppAllocator(Context context) {
+    protected Allocator(Context context) {
         mPrefs = context.getSharedPreferences("webapps", Context.MODE_PRIVATE | Context.MODE_MULTI_PROCESS);
     }
 
     private static String appKey(int index) {
         return PREFIX_PACKAGE_NAME + index;
     }
 
     public static String iconKey(int index) {
--- a/mobile/android/base/webapp/ApkResources.java
+++ b/mobile/android/base/webapp/ApkResources.java
@@ -16,17 +16,17 @@ import android.content.pm.PackageManager
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Environment;
 import android.util.Log;
 
 public class ApkResources {
-    private static final String LOGTAG = "GeckoApkResources";
+    private static final String LOGTAG = "GeckoWebAppApkResources";
     private final String mPackageName;
     private final ApplicationInfo mInfo;
     private final Context mContext;
 
     public ApkResources(Context context, String packageName) throws NameNotFoundException {
         mPackageName = packageName;
         mInfo = context.getPackageManager().getApplicationInfo(
                     mPackageName, PackageManager.GET_META_DATA);
rename from mobile/android/base/webapp/WebAppDispatcher.java
rename to mobile/android/base/webapp/Dispatcher.java
--- a/mobile/android/base/webapp/WebAppDispatcher.java
+++ b/mobile/android/base/webapp/Dispatcher.java
@@ -3,30 +3,35 @@
  * 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.gecko.webapp;
 
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
+import android.util.Log;
 
-public class WebAppDispatcher extends Activity {
+public class Dispatcher extends Activity {
     private static final String LOGTAG = "GeckoWebAppDispatcher";
 
     @Override
     protected void onCreate(Bundle bundle) {
         super.onCreate(bundle);
 
-        WebAppAllocator allocator = WebAppAllocator.getInstance(getApplicationContext());
+        Allocator allocator = Allocator.getInstance(getApplicationContext());
 
         if (bundle == null) {
             bundle = getIntent().getExtras();
         }
 
+        if (bundle == null) {
+            Log.e(LOGTAG, "Passed intent data missing.");
+        }
+
         String packageName = bundle.getString("packageName");
 
         int index = allocator.getIndexForApp(packageName);
         boolean isInstalled = index >= 0;
         if (!isInstalled) {
             index = allocator.findOrAllocatePackage(packageName);
         }
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/webapp/EventListener.java
@@ -0,0 +1,217 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * 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.gecko.webapp;
+
+import org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoProfile;
+import org.mozilla.gecko.gfx.BitmapUtils;
+import org.mozilla.gecko.util.ActivityResultHandler;
+import org.mozilla.gecko.util.EventDispatcher;
+import org.mozilla.gecko.util.GeckoEventListener;
+import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.WebAppAllocator;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.util.Log;
+
+import java.io.File;
+import java.util.List;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class EventListener implements GeckoEventListener {
+
+    private static final String LOGTAG = "GeckoWebAppEventListener";
+
+    private EventListener() { }
+
+    private static EventListener mEventListener;
+
+    private static EventListener getEventListener() {
+        if (mEventListener == null) {
+            mEventListener = new EventListener();
+        }
+        return mEventListener;
+    }
+
+    private static void registerEventListener(String event) {
+        GeckoAppShell.getEventDispatcher().registerEventListener(event, EventListener.getEventListener());
+    }
+
+    private static void unregisterEventListener(String event) {
+        GeckoAppShell.getEventDispatcher().unregisterEventListener(event, EventListener.getEventListener());
+    }
+
+    public static void registerEvents() {
+        registerEventListener("WebApps:PreInstall");
+        registerEventListener("WebApps:InstallApk");
+        registerEventListener("WebApps:PostInstall");
+        registerEventListener("WebApps:Open");
+        registerEventListener("WebApps:Uninstall");
+    }
+
+    public static void unregisterEvents() {
+        unregisterEventListener("WebApps:PreInstall");
+        unregisterEventListener("WebApps:InstallApk");
+        unregisterEventListener("WebApps:PostInstall");
+        unregisterEventListener("WebApps:Open");
+        unregisterEventListener("WebApps:Uninstall");
+    }
+
+    @Override
+    public void handleMessage(String event, JSONObject message) {
+        try {
+            if (AppConstants.MOZ_ANDROID_SYNTHAPKS && event.equals("WebApps:InstallApk")) {
+                installApk(GeckoAppShell.getGeckoInterface().getActivity(), message.getString("filePath"), message.getString("data"));
+            } else if (event.equals("WebApps:PostInstall")) {
+                if (AppConstants.MOZ_ANDROID_SYNTHAPKS) {
+                    postInstallWebApp(message.getString("apkPackageName"), message.getString("origin"));
+                } else {
+                    postInstallWebApp(message.getString("name"),
+                                      message.getString("manifestURL"),
+                                      message.getString("origin"),
+                                      message.getString("iconURL"),
+                                      message.getString("originalOrigin"));
+                }
+            } else if (event.equals("WebApps:Open")) {
+                Intent intent = GeckoAppShell.getWebAppIntent(message.getString("manifestURL"),
+                                                              message.getString("origin"),
+                                                              "", null);
+                if (intent == null) {
+                    return;
+                }
+                GeckoAppShell.getGeckoInterface().getActivity().startActivity(intent);
+            } else if (!AppConstants.MOZ_ANDROID_SYNTHAPKS && event.equals("WebApps:Uninstall")) {
+                uninstallWebApp(message.getString("origin"));
+            }
+        } catch (Exception e) {
+            Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
+        }
+    }
+
+    // Not used by MOZ_ANDROID_SYNTHAPKS.
+    public static File preInstallWebApp(String aTitle, String aURI, String aOrigin) {
+        int index = WebAppAllocator.getInstance(GeckoAppShell.getContext()).findAndAllocateIndex(aOrigin, aTitle, (String) null);
+        GeckoProfile profile = GeckoProfile.get(GeckoAppShell.getContext(), "webapp" + index);
+        return profile.getDir();
+    }
+
+    // Not used by MOZ_ANDROID_SYNTHAPKS.
+    public static void postInstallWebApp(String aTitle, String aURI, String aOrigin, String aIconURL, String aOriginalOrigin) {
+        WebAppAllocator allocator = WebAppAllocator.getInstance(GeckoAppShell.getContext());
+        int index = allocator.getIndexForApp(aOriginalOrigin);
+
+        assert aIconURL != null;
+        Bitmap icon = BitmapUtils.getBitmapFromDataURI(aIconURL);
+
+        assert aOrigin != null && index != -1;
+        allocator.updateAppAllocation(aOrigin, index, icon);
+
+        GeckoAppShell.createShortcut(aTitle, aURI, aOrigin, icon, "webapp");
+    }
+
+    // Used by MOZ_ANDROID_SYNTHAPKS.
+    public static void postInstallWebApp(String aPackageName, String aOrigin) {
+        Allocator allocator = Allocator.getInstance(GeckoAppShell.getContext());
+        int index = allocator.findOrAllocatePackage(aPackageName);
+        allocator.putOrigin(index, aOrigin);
+    }
+
+    public static void uninstallWebApp(final String uniqueURI) {
+        // On uninstall, we need to do a couple of things:
+        //   1. nuke the running app process.
+        //   2. nuke the profile that was assigned to that webapp
+        ThreadUtils.postToBackgroundThread(new Runnable() {
+            @Override
+            public void run() {
+                int index;
+                index = Allocator.getInstance(GeckoAppShell.getContext()).releaseIndexForApp(uniqueURI);
+
+                // if -1, nothing to do; we didn't think it was installed anyway
+                if (index == -1)
+                    return;
+
+                // kill the app if it's running
+                String targetProcessName = GeckoAppShell.getContext().getPackageName();
+                targetProcessName = targetProcessName + ":" + targetProcessName + ".WebApp" + index;
+
+                ActivityManager am = (ActivityManager) GeckoAppShell.getContext().getSystemService(Context.ACTIVITY_SERVICE);
+                List<ActivityManager.RunningAppProcessInfo> procs = am.getRunningAppProcesses();
+                if (procs != null) {
+                    for (ActivityManager.RunningAppProcessInfo proc : procs) {
+                        if (proc.processName.equals(targetProcessName)) {
+                            android.os.Process.killProcess(proc.pid);
+                            break;
+                        }
+                    }
+                }
+
+                // then nuke the profile
+                GeckoProfile.removeProfile(GeckoAppShell.getContext(), "webapp" + index);
+            }
+        });
+    }
+
+    public static void installApk(final Activity context, String filePath, String data) {
+        // This is the data that mozApps.install sent to Webapps.jsm.
+        JSONObject argsObj = null;
+
+        // We get the manifest url out of javascript here so we can use it as a checksum
+        // in a minute, when a package has been installed.
+        String manifestUrl = null;
+        try {
+            argsObj = new JSONObject(data);
+            manifestUrl = argsObj.getJSONObject("app").getString("manifestURL");
+        } catch (JSONException e) {
+            Log.e(LOGTAG, "can't get manifest URL from JSON data", e);
+            // TODO: propagate the error back to the mozApps.install caller.
+            return;
+        }
+
+        // We will check the manifestUrl from the one in the APK.
+        // Thus, we can have a one-to-one mapping of apk to receiver.
+        final InstallListener receiver = new InstallListener(manifestUrl, argsObj);
+
+        // Listen for packages being installed.
+        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+        filter.addDataScheme("package");
+        context.registerReceiver(receiver, filter);
+
+        // Now call the package installer.
+        File file = new File(filePath);
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
+
+        GeckoAppShell.sActivityHelper.startIntentForActivity(context, intent, new ActivityResultHandler() {
+            @Override
+            public void onActivityResult(int resultCode, Intent data) {
+                // The InstallListener will catch the case where the user pressed install.
+                // Now deal with if the user pressed cancel.
+                if (resultCode == Activity.RESULT_CANCELED) {
+                    try {
+                        context.unregisterReceiver(receiver);
+                        receiver.cleanup();
+                    } catch (java.lang.IllegalArgumentException e) {
+                        // IllegalArgumentException happens because resultCode is RESULT_CANCELED
+                        // when the user presses the Done button in the install confirmation dialog,
+                        // even though the install has been successful (and InstallListener already
+                        // unregistered the receiver).
+                        Log.e(LOGTAG, "error unregistering install receiver: ", e);
+                    }
+                }
+            }
+        });
+    }
+}
--- a/mobile/android/base/webapp/InstallHelper.java
+++ b/mobile/android/base/webapp/InstallHelper.java
@@ -22,17 +22,17 @@ import org.mozilla.gecko.util.GeckoEvent
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.util.Log;
 
 public class InstallHelper implements GeckoEventListener {
-    private static final String LOGTAG = "GeckoInstallHelper";
+    private static final String LOGTAG = "GeckoWebAppInstallHelper";
     private static final String[] INSTALL_EVENT_NAMES = new String[] {"WebApps:PostInstall"};
     private final Context mContext;
     private final InstallCallback mCallback;
     private final ApkResources mApkResources;
 
     public static interface InstallCallback {
         // on the GeckoThread
         void installCompleted(InstallHelper installHelper, String event, JSONObject message);
@@ -151,17 +151,17 @@ public class InstallHelper implements Ge
     public void registerGeckoListener() {
         for (String eventName : INSTALL_EVENT_NAMES) {
             GeckoAppShell.registerEventListener(eventName, this);
         }
     }
 
     private void calculateColor() {
         ThreadUtils.assertOnBackgroundThread();
-        WebAppAllocator slots = WebAppAllocator.getInstance(mContext);
+        Allocator slots = Allocator.getInstance(mContext);
         int index = slots.getIndexForApp(mApkResources.getPackageName());
         Bitmap bitmap = BitmapUtils.getBitmapFromDrawable(mApkResources.getAppIcon());
         slots.updateColor(index, BitmapUtils.getDominantColor(bitmap));
     }
 
     @Override
     public void handleMessage(String event, JSONObject message) {
         for (String eventName : INSTALL_EVENT_NAMES) {
--- a/mobile/android/base/webapp/InstallListener.java
+++ b/mobile/android/base/webapp/InstallListener.java
@@ -19,17 +19,17 @@ import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.Environment;
 import android.text.TextUtils;
 import android.util.Log;
 
 public class InstallListener extends BroadcastReceiver {
 
-    private static String LOGTAG = "GeckoInstallListener";
+    private static String LOGTAG = "GeckoWebAppInstallListener";
     private JSONObject mData = null;
     private String mManifestUrl;
 
     public InstallListener(String manifestUrl, JSONObject data) {
         mData = data;
         mManifestUrl = manifestUrl;
         assert mManifestUrl != null;
     }
@@ -58,19 +58,20 @@ public class InstallListener extends Bro
         } else if (!isCorrectManifest(manifestUrl)) {
             Log.i(LOGTAG, "Waiting to finish installing " + mManifestUrl + " but this is " +manifestUrl);
             //return;
         }
 
         if (GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning)) {
             InstallHelper installHelper = new InstallHelper(context, apkResources, null);
             try {
-                JSONObject dataObject = mData;
-                dataObject = new JSONObject().put("request", dataObject);
-                WebAppAllocator slots = WebAppAllocator.getInstance(context);
+                JSONObject dataObject = new JSONObject();
+                dataObject.put("request", mData);
+
+                Allocator slots = Allocator.getInstance(context);
                 int i = slots.findOrAllocatePackage(packageName);
                 installHelper.startInstall("webapp" + i, dataObject);
             } catch (JSONException e) {
                 Log.e(LOGTAG, "Couldn't parse data from mozApps.install()", e);
             } catch (IOException e) {
                 Log.e(LOGTAG, "Couldn't install packaged app", e);
             }
         }
--- a/mobile/android/base/webapp/UninstallListener.java
+++ b/mobile/android/base/webapp/UninstallListener.java
@@ -23,28 +23,28 @@ import android.content.pm.PackageManager
 
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.ArrayList;
 
 public class UninstallListener extends BroadcastReceiver {
 
-    private static String LOGTAG = "GeckoUninstallListener";
+    private static String LOGTAG = "GeckoWebAppUninstallListener";
 
     @Override
     public void onReceive(Context context, Intent intent) {
         String packageName = intent.getData().getSchemeSpecificPart();
 
         if (TextUtils.isEmpty(packageName)) {
             Log.i(LOGTAG, "No package name defined in intent");
             return;
         }
 
-        WebAppAllocator allocator = WebAppAllocator.getInstance(context);
+        Allocator allocator = Allocator.getInstance(context);
         ArrayList<String> installedPackages = allocator.getInstalledPackageNames();
 
         if (installedPackages.contains(packageName)) {
             JSONObject message = new JSONObject();
             JSONArray packageNames = new JSONArray();
             try {
                 packageNames.put(packageName);
                 message.put("apkPackageNames", packageNames);
@@ -52,17 +52,17 @@ public class UninstallListener extends B
             } catch (JSONException e) {
                 Log.e(LOGTAG, "JSON EXCEPTION " + e);
             }
         }
     }
 
     public static void initUninstallPackageScan(Context context) {
         // get list of packages we think are installed
-        WebAppAllocator allocator = WebAppAllocator.getInstance(context);
+        Allocator allocator = Allocator.getInstance(context);
         ArrayList<String> fennecPackages = allocator.getInstalledPackageNames();
         ArrayList<String> uninstalledPackages = new ArrayList<String>();
 
         final PackageManager pm = context.getPackageManager();
         //get a list of installed apps on device
         List<ApplicationInfo> packages = pm.getInstalledApplications(PackageManager.GET_META_DATA);
         Set<String> allInstalledPackages = new HashSet<String>();
 
--- a/mobile/android/base/webapp/WebAppImpl.java
+++ b/mobile/android/base/webapp/WebAppImpl.java
@@ -95,17 +95,17 @@ public class WebAppImpl extends GeckoApp
 
         // start Gecko.
         super.onCreate(savedInstance);
 
         mTitlebarText = (TextView)findViewById(R.id.webapp_title);
         mTitlebar = findViewById(R.id.webapp_titlebar);
         mSplashscreen = findViewById(R.id.splashscreen);
 
-        String origin = WebAppAllocator.getInstance(this).getOrigin(getIndex());
+        String origin = Allocator.getInstance(this).getOrigin(getIndex());
         boolean isInstallCompleting = (origin == null);
 
         if (!GeckoThread.checkLaunchState(GeckoThread.LaunchState.GeckoRunning) || !isInstalled || isInstallCompleting) {
             // Show the splash screen if we need to start Gecko, or we need to install this.
             overridePendingTransition(R.anim.grow_fade_in_center, android.R.anim.fade_out);
             showSplash(true);
         } else {
             mSplashscreen.setVisibility(View.GONE);
@@ -149,17 +149,17 @@ public class WebAppImpl extends GeckoApp
     @Override
     protected void loadStartupTab(String uri) {
         // NOP
     }
 
     private void showSplash(boolean isApk) {
 
         // get the favicon dominant color, stored when the app was installed
-        int dominantColor = WebAppAllocator.getInstance().getColor(getIndex());
+        int dominantColor = Allocator.getInstance().getColor(getIndex());
 
         setBackgroundGradient(dominantColor);
 
         ImageView image = (ImageView)findViewById(R.id.splashscreen_icon);
         Drawable d = null;
 
         if (isApk) {
             Uri uri = mApkResources.getAppIconUri();