Bug 970506 - Break up FilePicker and ActivityHandlerHelper. r=lucasr
authorWes Johnston <wjohnston@mozilla.com>
Fri, 14 Feb 2014 12:02:05 -0800
changeset 169383 20ae4f402369ddb2c85c59ce2e44946a6fecccca
parent 169382 cc2833ffc0c5c9517018157913a34eb4931f4031
child 169384 11fae31f5b14e427a34537145cd2a86e22964d53
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewerslucasr
bugs970506
milestone30.0a1
Bug 970506 - Break up FilePicker and ActivityHandlerHelper. r=lucasr
mobile/android/base/ActivityHandlerHelper.java
mobile/android/base/FilePicker.java
mobile/android/base/FilePickerResultHandler.java
mobile/android/base/GeckoApp.java
mobile/android/base/GeckoAppShell.java
mobile/android/base/GeckoApplication.java
mobile/android/base/moz.build
mobile/android/base/webapp/EventListener.java
--- a/mobile/android/base/ActivityHandlerHelper.java
+++ b/mobile/android/base/ActivityHandlerHelper.java
@@ -1,243 +1,38 @@
 /* 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;
 
-import org.mozilla.gecko.prompts.Prompt;
-import org.mozilla.gecko.prompts.PromptService;
 import org.mozilla.gecko.util.ActivityResultHandler;
 import org.mozilla.gecko.util.ActivityResultHandlerMap;
-import org.mozilla.gecko.util.ThreadUtils;
-import org.mozilla.gecko.util.GeckoEventListener;
-
-import org.json.JSONException;
-import org.json.JSONObject;
 
 import android.app.Activity;
-import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.Parcelable;
-import android.provider.MediaStore;
-import android.util.Log;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.TimeUnit;
-
-public class ActivityHandlerHelper implements GeckoEventListener {
-    private static final String LOGTAG = "GeckoActivityHandlerHelper";
-
-
-    private final ActivityResultHandlerMap mActivityResultHandlerMap;
-    public interface ResultHandler {
-        public void gotFile(String filename);
-    }
 
-    @SuppressWarnings("serial")
-    public ActivityHandlerHelper() {
-        mActivityResultHandlerMap = new ActivityResultHandlerMap();
-        GeckoAppShell.getEventDispatcher().registerEventListener("FilePicker:Show", this);
-    }
-
-    @Override
-    public void handleMessage(String event, final JSONObject message) {
-        if (event.equals("FilePicker:Show")) {
-            String mimeType = "*/*";
-            String mode = message.optString("mode");
+public class ActivityHandlerHelper {
+    private static final String LOGTAG = "GeckoActivityHandlerHelper";
+    private static final ActivityResultHandlerMap mActivityResultHandlerMap = new ActivityResultHandlerMap();
 
-            if ("mimeType".equals(mode))
-                mimeType = message.optString("mimeType");
-            else if ("extension".equals(mode))
-                mimeType = GeckoAppShell.getMimeTypeFromExtensions(message.optString("extensions"));
-
-            showFilePickerAsync(GeckoAppShell.getGeckoInterface().getActivity(), mimeType, new ResultHandler() {
-                public void gotFile(String filename) {
-                    try {
-                        message.put("file", filename);
-                    } catch (JSONException ex) {
-                        Log.i(LOGTAG, "Can't add filename to message " + filename);
-                    }
-                    GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(
-                        "FilePicker:Result", message.toString()));
-                }
-            });
-        }
-    }
-
-    public int makeRequestCode(ActivityResultHandler aHandler) {
+    private static int makeRequestCode(ActivityResultHandler aHandler) {
         return mActivityResultHandlerMap.put(aHandler);
     }
 
-    public void startIntentForActivity (Activity activity, Intent intent, ActivityResultHandler activityResultHandler) {
+    public static void startIntent(Intent intent, ActivityResultHandler activityResultHandler) {
+        startIntentForActivity(GeckoAppShell.getGeckoInterface().getActivity(), intent, activityResultHandler);
+    }
+
+    public static void startIntentForActivity(Activity activity, Intent intent, ActivityResultHandler activityResultHandler) {
         activity.startActivityForResult(intent, mActivityResultHandlerMap.put(activityResultHandler));
     }
 
-    private void addActivities(Context context, Intent intent, HashMap<String, Intent> intents, HashMap<String, Intent> filters) {
-        PackageManager pm = context.getPackageManager();
-        List<ResolveInfo> lri = pm.queryIntentActivityOptions(GeckoAppShell.getGeckoInterface().getActivity().getComponentName(), null, intent, 0);
-        for (ResolveInfo ri : lri) {
-            ComponentName cn = new ComponentName(ri.activityInfo.applicationInfo.packageName, ri.activityInfo.name);
-            if (filters != null && !filters.containsKey(cn.toString())) {
-                Intent rintent = new Intent(intent);
-                rintent.setComponent(cn);
-                intents.put(cn.toString(), rintent);
-            }
-        }
-    }
 
-    private Intent getIntent(Context context, String mimeType) {
-        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
-        intent.setType(mimeType);
-        intent.addCategory(Intent.CATEGORY_OPENABLE);
-        return intent;
-    }
-
-    private List<Intent> getIntentsForFilePicker(final Context context,
-                                                       final String mimeType,
-                                                       final FilePickerResultHandler fileHandler) {
-        // The base intent to use for the file picker. Even if this is an implicit intent, Android will
-        // still show a list of Activitiees that match this action/type.
-        Intent baseIntent;
-        // A HashMap of Activities the base intent will show in the chooser. This is used
-        // to filter activities from other intents so that we don't show duplicates.
-        HashMap<String, Intent> baseIntents = new HashMap<String, Intent>();
-        // A list of other activities to shwo in the picker (and the intents to launch them).
-        HashMap<String, Intent> intents = new HashMap<String, Intent> ();
-
-        if ("audio/*".equals(mimeType)) {
-            // For audio the only intent is the mimetype
-            baseIntent = getIntent(context, mimeType);
-            addActivities(context, baseIntent, baseIntents, null);
-        } else if ("image/*".equals(mimeType)) {
-            // For images the base is a capture intent
-            baseIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
-            baseIntent.putExtra(MediaStore.EXTRA_OUTPUT,
-                            Uri.fromFile(new File(Environment.getExternalStorageDirectory(),
-                                                  fileHandler.generateImageName())));
-            addActivities(context, baseIntent, baseIntents, null);
-
-            // We also add the mimetype intent
-            addActivities(context, getIntent(context, mimeType), intents, baseIntents);
-        } else if ("video/*".equals(mimeType)) {
-            // For videos the base is a capture intent
-            baseIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
-            addActivities(context, baseIntent, baseIntents, null);
-
-            // We also add the mimetype intent
-            addActivities(context, getIntent(context, mimeType), intents, baseIntents);
-        } else {
-            // If we don't have a known mimetype, we just search for */*
-            baseIntent = getIntent(context, "*/*");
-            addActivities(context, baseIntent, baseIntents, null);
-
-            // But we also add the video and audio capture intents
-            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
-            intent.putExtra(MediaStore.EXTRA_OUTPUT,
-                            Uri.fromFile(new File(Environment.getExternalStorageDirectory(),
-                                                  fileHandler.generateImageName())));
-            addActivities(context, intent, intents, baseIntents);
-
-            intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
-            addActivities(context, intent, intents, baseIntents);
-        }
-
-        // If we didn't find any activities, we fall back to the */* mimetype intent
-        if (baseIntents.size() == 0 && intents.size() == 0) {
-            intents.clear();
-
-            baseIntent = getIntent(context, "*/*");
-            addActivities(context, baseIntent, baseIntents, null);
-        }
-
-        ArrayList<Intent> vals = new ArrayList<Intent>(intents.values());
-        vals.add(0, baseIntent);
-        return vals;
-    }
-
-    private String getFilePickerTitle(Context context, String aMimeType) {
-        if (aMimeType.equals("audio/*")) {
-            return context.getString(R.string.filepicker_audio_title);
-        } else if (aMimeType.equals("image/*")) {
-            return context.getString(R.string.filepicker_image_title);
-        } else if (aMimeType.equals("video/*")) {
-            return context.getString(R.string.filepicker_video_title);
-        } else {
-            return context.getString(R.string.filepicker_title);
-        }
-    }
-
-    private interface IntentHandler {
-        public void gotIntent(Intent intent);
-    }
-
-    /* Gets an intent that can open a particular mimetype. Will show a prompt with a list
-     * of Activities that can handle the mietype. Asynchronously calls the handler when
-     * one of the intents is selected. If the caller passes in null for the handler, will still
-     * prompt for the activity, but will throw away the result.
-     */
-    private void getFilePickerIntentAsync(final Context context,
-                                          final String mimeType,
-                                          final FilePickerResultHandler fileHandler,
-                                          final IntentHandler handler) {
-        List<Intent> intents = getIntentsForFilePicker(context, mimeType, fileHandler);
-
-        if (intents.size() == 0) {
-            Log.i(LOGTAG, "no activities for the file picker!");
-            handler.gotIntent(null);
-            return;
-        }
-
-        Intent base = intents.remove(0);
-        if (intents.size() == 0) {
-            handler.gotIntent(base);
-            return;
-        }
-
-        Intent chooser = Intent.createChooser(base, getFilePickerTitle(context, mimeType));
-        chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents.toArray(new Parcelable[]{}));
-        handler.gotIntent(chooser);
-    }
-
-    /* Allows the user to pick an activity to load files from using a list prompt. Then opens the activity and
-     * sends the file returned to the passed in handler. If a null handler is passed in, will still
-     * pick and launch the file picker, but will throw away the result.
-     */
-    public void showFilePickerAsync(final Activity parentActivity, String aMimeType, final ResultHandler handler) {
-        final FilePickerResultHandler fileHandler = new FilePickerResultHandler(handler);
-        getFilePickerIntentAsync(parentActivity, aMimeType, fileHandler, new IntentHandler() {
-            @Override
-            public void gotIntent(Intent intent) {
-                if (handler == null) {
-                    return;
-                }
-
-                if (intent == null) {
-                    handler.gotFile("");
-                    return;
-                }
-
-                parentActivity.startActivityForResult(intent, mActivityResultHandlerMap.put(fileHandler));
-            }
-        });
-    }
-
-    boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
+    public static boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
         ActivityResultHandler handler = mActivityResultHandlerMap.getAndRemove(requestCode);
         if (handler != null) {
             handler.onActivityResult(resultCode, data);
             return true;
         }
         return false;
     }
 }
copy from mobile/android/base/ActivityHandlerHelper.java
copy to mobile/android/base/FilePicker.java
--- a/mobile/android/base/ActivityHandlerHelper.java
+++ b/mobile/android/base/FilePicker.java
@@ -1,18 +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/. */
 
 package org.mozilla.gecko;
 
-import org.mozilla.gecko.prompts.Prompt;
-import org.mozilla.gecko.prompts.PromptService;
-import org.mozilla.gecko.util.ActivityResultHandler;
-import org.mozilla.gecko.util.ActivityResultHandlerMap;
+import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.app.Activity;
 import android.content.ComponentName;
@@ -23,221 +20,203 @@ import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.os.Environment;
 import android.os.Parcelable;
 import android.provider.MediaStore;
 import android.util.Log;
 
 import java.io.File;
 import java.util.ArrayList;
-import java.util.List;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.TimeUnit;
 
-public class ActivityHandlerHelper implements GeckoEventListener {
-    private static final String LOGTAG = "GeckoActivityHandlerHelper";
+public class FilePicker implements GeckoEventListener {
+    private static final String LOGTAG = "GeckoFilePicker";
+    private static FilePicker sFilePicker;
+    private final Context mContext;
 
-
-    private final ActivityResultHandlerMap mActivityResultHandlerMap;
     public interface ResultHandler {
         public void gotFile(String filename);
     }
 
-    @SuppressWarnings("serial")
-    public ActivityHandlerHelper() {
-        mActivityResultHandlerMap = new ActivityResultHandlerMap();
+    public static void init(Context context) {
+        if (sFilePicker == null) {
+            sFilePicker = new FilePicker(context);
+        }
+    }
+
+    protected FilePicker(Context context) {
+        mContext = context;
         GeckoAppShell.getEventDispatcher().registerEventListener("FilePicker:Show", this);
     }
 
     @Override
     public void handleMessage(String event, final JSONObject message) {
         if (event.equals("FilePicker:Show")) {
             String mimeType = "*/*";
             String mode = message.optString("mode");
 
             if ("mimeType".equals(mode))
                 mimeType = message.optString("mimeType");
             else if ("extension".equals(mode))
                 mimeType = GeckoAppShell.getMimeTypeFromExtensions(message.optString("extensions"));
 
-            showFilePickerAsync(GeckoAppShell.getGeckoInterface().getActivity(), mimeType, new ResultHandler() {
+            showFilePickerAsync(mimeType, new ResultHandler() {
                 public void gotFile(String filename) {
                     try {
                         message.put("file", filename);
                     } catch (JSONException ex) {
                         Log.i(LOGTAG, "Can't add filename to message " + filename);
                     }
                     GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(
                         "FilePicker:Result", message.toString()));
                 }
             });
         }
     }
 
-    public int makeRequestCode(ActivityResultHandler aHandler) {
-        return mActivityResultHandlerMap.put(aHandler);
-    }
-
-    public void startIntentForActivity (Activity activity, Intent intent, ActivityResultHandler activityResultHandler) {
-        activity.startActivityForResult(intent, mActivityResultHandlerMap.put(activityResultHandler));
-    }
-
-    private void addActivities(Context context, Intent intent, HashMap<String, Intent> intents, HashMap<String, Intent> filters) {
-        PackageManager pm = context.getPackageManager();
-        List<ResolveInfo> lri = pm.queryIntentActivityOptions(GeckoAppShell.getGeckoInterface().getActivity().getComponentName(), null, intent, 0);
+    private void addActivities(Intent intent, HashMap<String, Intent> intents, HashMap<String, Intent> filters) {
+        PackageManager pm = mContext.getPackageManager();
+        List<ResolveInfo> lri = pm.queryIntentActivities(intent, 0);
         for (ResolveInfo ri : lri) {
             ComponentName cn = new ComponentName(ri.activityInfo.applicationInfo.packageName, ri.activityInfo.name);
             if (filters != null && !filters.containsKey(cn.toString())) {
                 Intent rintent = new Intent(intent);
                 rintent.setComponent(cn);
                 intents.put(cn.toString(), rintent);
             }
         }
     }
 
-    private Intent getIntent(Context context, String mimeType) {
+    private Intent getIntent(String mimeType) {
         Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
         intent.setType(mimeType);
         intent.addCategory(Intent.CATEGORY_OPENABLE);
         return intent;
     }
 
-    private List<Intent> getIntentsForFilePicker(final Context context,
-                                                       final String mimeType,
+    private List<Intent> getIntentsForFilePicker(final String mimeType,
                                                        final FilePickerResultHandler fileHandler) {
         // The base intent to use for the file picker. Even if this is an implicit intent, Android will
         // still show a list of Activitiees that match this action/type.
         Intent baseIntent;
         // A HashMap of Activities the base intent will show in the chooser. This is used
         // to filter activities from other intents so that we don't show duplicates.
         HashMap<String, Intent> baseIntents = new HashMap<String, Intent>();
         // A list of other activities to shwo in the picker (and the intents to launch them).
         HashMap<String, Intent> intents = new HashMap<String, Intent> ();
 
         if ("audio/*".equals(mimeType)) {
             // For audio the only intent is the mimetype
-            baseIntent = getIntent(context, mimeType);
-            addActivities(context, baseIntent, baseIntents, null);
+            baseIntent = getIntent(mimeType);
+            addActivities(baseIntent, baseIntents, null);
         } else if ("image/*".equals(mimeType)) {
             // For images the base is a capture intent
             baseIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
             baseIntent.putExtra(MediaStore.EXTRA_OUTPUT,
                             Uri.fromFile(new File(Environment.getExternalStorageDirectory(),
                                                   fileHandler.generateImageName())));
-            addActivities(context, baseIntent, baseIntents, null);
+            addActivities(baseIntent, baseIntents, null);
 
             // We also add the mimetype intent
-            addActivities(context, getIntent(context, mimeType), intents, baseIntents);
+            addActivities(getIntent(mimeType), intents, baseIntents);
         } else if ("video/*".equals(mimeType)) {
             // For videos the base is a capture intent
             baseIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
-            addActivities(context, baseIntent, baseIntents, null);
+            addActivities(baseIntent, baseIntents, null);
 
             // We also add the mimetype intent
-            addActivities(context, getIntent(context, mimeType), intents, baseIntents);
+            addActivities(getIntent(mimeType), intents, baseIntents);
         } else {
-            // If we don't have a known mimetype, we just search for */*
-            baseIntent = getIntent(context, "*/*");
-            addActivities(context, baseIntent, baseIntents, null);
+            baseIntent = getIntent("*/*");
+            addActivities(baseIntent, baseIntents, null);
 
-            // But we also add the video and audio capture intents
             Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
             intent.putExtra(MediaStore.EXTRA_OUTPUT,
                             Uri.fromFile(new File(Environment.getExternalStorageDirectory(),
                                                   fileHandler.generateImageName())));
-            addActivities(context, intent, intents, baseIntents);
-
+            addActivities(intent, intents, baseIntents);
             intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
-            addActivities(context, intent, intents, baseIntents);
+            addActivities(intent, intents, baseIntents);
         }
 
         // If we didn't find any activities, we fall back to the */* mimetype intent
         if (baseIntents.size() == 0 && intents.size() == 0) {
             intents.clear();
 
-            baseIntent = getIntent(context, "*/*");
-            addActivities(context, baseIntent, baseIntents, null);
+            baseIntent = getIntent("*/*");
+            addActivities(baseIntent, baseIntents, null);
         }
 
         ArrayList<Intent> vals = new ArrayList<Intent>(intents.values());
         vals.add(0, baseIntent);
         return vals;
     }
 
-    private String getFilePickerTitle(Context context, String aMimeType) {
-        if (aMimeType.equals("audio/*")) {
-            return context.getString(R.string.filepicker_audio_title);
-        } else if (aMimeType.equals("image/*")) {
-            return context.getString(R.string.filepicker_image_title);
-        } else if (aMimeType.equals("video/*")) {
-            return context.getString(R.string.filepicker_video_title);
+    private String getFilePickerTitle(String mimeType) {
+        if (mimeType.equals("audio/*")) {
+            return mContext.getString(R.string.filepicker_audio_title);
+        } else if (mimeType.equals("image/*")) {
+            return mContext.getString(R.string.filepicker_image_title);
+        } else if (mimeType.equals("video/*")) {
+            return mContext.getString(R.string.filepicker_video_title);
         } else {
-            return context.getString(R.string.filepicker_title);
+            return mContext.getString(R.string.filepicker_title);
         }
     }
 
     private interface IntentHandler {
         public void gotIntent(Intent intent);
     }
 
     /* Gets an intent that can open a particular mimetype. Will show a prompt with a list
      * of Activities that can handle the mietype. Asynchronously calls the handler when
      * one of the intents is selected. If the caller passes in null for the handler, will still
      * prompt for the activity, but will throw away the result.
      */
-    private void getFilePickerIntentAsync(final Context context,
-                                          final String mimeType,
+    private void getFilePickerIntentAsync(final String mimeType,
                                           final FilePickerResultHandler fileHandler,
                                           final IntentHandler handler) {
-        List<Intent> intents = getIntentsForFilePicker(context, mimeType, fileHandler);
+        List<Intent> intents = getIntentsForFilePicker(mimeType, fileHandler);
 
         if (intents.size() == 0) {
             Log.i(LOGTAG, "no activities for the file picker!");
             handler.gotIntent(null);
             return;
         }
 
         Intent base = intents.remove(0);
+
         if (intents.size() == 0) {
             handler.gotIntent(base);
             return;
         }
 
-        Intent chooser = Intent.createChooser(base, getFilePickerTitle(context, mimeType));
+        Intent chooser = Intent.createChooser(base, getFilePickerTitle(mimeType));
         chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents.toArray(new Parcelable[]{}));
         handler.gotIntent(chooser);
     }
 
     /* Allows the user to pick an activity to load files from using a list prompt. Then opens the activity and
      * sends the file returned to the passed in handler. If a null handler is passed in, will still
      * pick and launch the file picker, but will throw away the result.
      */
-    public void showFilePickerAsync(final Activity parentActivity, String aMimeType, final ResultHandler handler) {
+    protected void showFilePickerAsync(String mimeType, final ResultHandler handler) {
         final FilePickerResultHandler fileHandler = new FilePickerResultHandler(handler);
-        getFilePickerIntentAsync(parentActivity, aMimeType, fileHandler, new IntentHandler() {
+        getFilePickerIntentAsync(mimeType, fileHandler, new IntentHandler() {
             @Override
             public void gotIntent(Intent intent) {
                 if (handler == null) {
                     return;
                 }
 
                 if (intent == null) {
                     handler.gotFile("");
                     return;
                 }
 
-                parentActivity.startActivityForResult(intent, mActivityResultHandlerMap.put(fileHandler));
+                ActivityHandlerHelper.startIntent(intent, fileHandler);
             }
         });
     }
-
-    boolean handleActivityResult(int requestCode, int resultCode, Intent data) {
-        ActivityResultHandler handler = mActivityResultHandlerMap.getAndRemove(requestCode);
-        if (handler != null) {
-            handler.onActivityResult(resultCode, data);
-            return true;
-        }
-        return false;
-    }
 }
--- a/mobile/android/base/FilePickerResultHandler.java
+++ b/mobile/android/base/FilePickerResultHandler.java
@@ -29,29 +29,29 @@ import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.IOException;
 import java.util.Queue;
 
 class FilePickerResultHandler implements ActivityResultHandler {
     private static final String LOGTAG = "GeckoFilePickerResultHandler";
 
     protected final Queue<String> mFilePickerResult;
-    protected final ActivityHandlerHelper.ResultHandler mHandler;
+    protected final FilePicker.ResultHandler mHandler;
 
     // this code is really hacky and doesn't belong anywhere so I'm putting it here for now
     // until I can come up with a better solution.
     private String mImageName = "";
 
     public FilePickerResultHandler(Queue<String> resultQueue) {
         mFilePickerResult = resultQueue;
         mHandler = null;
     }
 
     /* Use this constructor to asynchronously listen for results */
-    public FilePickerResultHandler(ActivityHandlerHelper.ResultHandler handler) {
+    public FilePickerResultHandler(FilePicker.ResultHandler handler) {
         mFilePickerResult = null;
         mHandler = handler;
     }
 
     private void sendResult(String res) {
         if (mFilePickerResult != null)
             mFilePickerResult.offer(res);
 
@@ -131,20 +131,16 @@ class FilePickerResultHandler implements
                                     null); // sortOrder
         }
 
         @Override
         public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
             if (cursor.moveToFirst()) {
                 String res = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA));
                 sendResult(res);
-            } else {
-                final FragmentActivity fa = (FragmentActivity) GeckoAppShell.getGeckoInterface().getActivity();
-                final LoaderManager lm = fa.getSupportLoaderManager();
-                lm.initLoader(cursor.hashCode(), null, new FileLoaderCallbacks(mUri));
             }
         }
 
         @Override
         public void onLoaderReset(Loader<Cursor> loader) { }
     }
 
     private class FileLoaderCallbacks implements LoaderCallbacks<Cursor> {
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -974,24 +974,24 @@ public abstract class GeckoApp
             }
             if (image != null) {
                 String path = Media.insertImage(getContentResolver(),image, null, null);
                 final Intent intent = new Intent(Intent.ACTION_ATTACH_DATA);
                 intent.addCategory(Intent.CATEGORY_DEFAULT);
                 intent.setData(Uri.parse(path));
 
                 // Removes the image from storage once the chooser activity ends.
-                GeckoAppShell.sActivityHelper.startIntentForActivity(this,
-                                                                    Intent.createChooser(intent, sAppContext.getString(R.string.set_image_chooser_title)),
-                                                                    new ActivityResultHandler() {
-                                                                        @Override
-                                                                        public void onActivityResult (int resultCode, Intent data) {
-                                                                            getContentResolver().delete(intent.getData(), null, null);
-                                                                        }
-                                                                    });
+                ActivityHandlerHelper.startIntentForActivity(this,
+                                                            Intent.createChooser(intent, sAppContext.getString(R.string.set_image_chooser_title)),
+                                                            new ActivityResultHandler() {
+                                                                @Override
+                                                                public void onActivityResult (int resultCode, Intent data) {
+                                                                    getContentResolver().delete(intent.getData(), null, null);
+                                                                }
+                                                            });
             } else {
                 Toast.makeText(sAppContext, R.string.set_image_fail, Toast.LENGTH_SHORT).show();
             }
         } catch(OutOfMemoryError ome) {
             Log.e(LOGTAG, "Out of Memory when converting to byte array", ome);
         } catch(IOException ioe) {
             Log.e(LOGTAG, "I/O Exception while setting wallpaper", ioe);
         } finally {
@@ -2316,17 +2316,17 @@ public abstract class GeckoApp
             return;
         }
 
         moveTaskToBack(true);
     }
 
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        if (!GeckoAppShell.sActivityHelper.handleActivityResult(requestCode, resultCode, data)) {
+        if (!ActivityHandlerHelper.handleActivityResult(requestCode, resultCode, data)) {
             super.onActivityResult(requestCode, resultCode, data);
         }
     }
 
     public AbsoluteLayout getPluginContainer() { return mPluginContainer; }
 
     // Accelerometer.
     @Override
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -189,17 +189,16 @@ public class GeckoAppShell
     private static Sensor gLinearAccelerometerSensor = null;
     private static Sensor gGyroscopeSensor = null;
     private static Sensor gOrientationSensor = null;
     private static Sensor gProximitySensor = null;
     private static Sensor gLightSensor = null;
 
     private static volatile boolean mLocationHighAccuracy;
 
-    public static ActivityHandlerHelper sActivityHelper = new ActivityHandlerHelper();
     static NotificationClient sNotificationClient;
 
     /* The Android-side API: API methods that Android calls */
 
     // Initialization methods
     public static native void nativeInit();
 
     // helper methods
--- a/mobile/android/base/GeckoApplication.java
+++ b/mobile/android/base/GeckoApplication.java
@@ -109,16 +109,17 @@ public class GeckoApplication extends Ap
 
         mInBackground = false;
     }
 
     @Override
     public void onCreate() {
         HardwareUtils.init(getApplicationContext());
         Clipboard.init(getApplicationContext());
+        FilePicker.init(getApplicationContext());
         GeckoLoader.loadMozGlue();
         super.onCreate();
     }
 
     public boolean isApplicationInBackground() {
         return mInBackground;
     }
 
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -129,16 +129,17 @@ gbjar.sources += [
     'favicons/cache/FaviconsForURL.java',
     'favicons/decoders/FaviconDecoder.java',
     'favicons/decoders/ICODecoder.java',
     'favicons/decoders/IconDirectoryEntry.java',
     'favicons/decoders/LoadFaviconResult.java',
     'favicons/Favicons.java',
     'favicons/LoadFaviconTask.java',
     'favicons/OnFaviconLoadedListener.java',
+    'FilePicker.java',
     'FilePickerResultHandler.java',
     'FindInPageBar.java',
     'FormAssistPopup.java',
     'GeckoAccessibility.java',
     'GeckoActivity.java',
     'GeckoActivityStatus.java',
     'GeckoApp.java',
     'GeckoApplication.java',
--- a/mobile/android/base/webapp/EventListener.java
+++ b/mobile/android/base/webapp/EventListener.java
@@ -1,15 +1,16 @@
 /* -*- 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.ActivityHandlerHelper;
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.util.ActivityResultHandler;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.util.GeckoEventListener;
@@ -218,17 +219,17 @@ public class EventListener implements Ge
             // TODO: propagate the error back to the mozApps.install caller.
             return;
         }
 
         Intent intent = new Intent(Intent.ACTION_VIEW);
         intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
 
         // Now call the package installer.
-        GeckoAppShell.sActivityHelper.startIntentForActivity(context, intent, new ActivityResultHandler() {
+        ActivityHandlerHelper.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();