bug 741284 - add async file request method r=kats
authorBrad Lassey <blassey@mozilla.com>
Thu, 05 Apr 2012 01:50:06 -0400
changeset 94376 bdaa03c8f24475287fc531eb8145074b0997c6af
parent 94375 442ae4ba8a9508c710b143d3b5a72ff2bcbb589d
child 94377 b51259905d852073bfbbe9227efeeb82501fb3ef
push id886
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 19:57:52 +0000
treeherdermozilla-beta@bbd8d5efd6d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs741284
milestone14.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 741284 - add async file request method r=kats
embedding/android/GeckoAppShell.java
mobile/android/base/GeckoApp.java
mobile/android/base/GeckoAppShell.java
mozglue/android/APKOpen.cpp
widget/android/AndroidBridge.cpp
widget/android/AndroidBridge.h
widget/android/AndroidJNI.cpp
--- a/embedding/android/GeckoAppShell.java
+++ b/embedding/android/GeckoAppShell.java
@@ -1862,9 +1862,15 @@ public class GeckoAppShell
 
     public static void lockScreenOrientation(int aOrientation) {
         GeckoScreenOrientationListener.getInstance().lockScreenOrientation(aOrientation);
     }
 
     public static void unlockScreenOrientation() {
         GeckoScreenOrientationListener.getInstance().unlockScreenOrientation();
     }
+
+    static native void notifyFilePickerResult(String filePath, long id);
+
+    /* Stubbed out because this is called from AndroidBridge for Native Fennec */
+    public static void showFilePickerAsync(String aMimeType, long id) {
+    }
 }
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -160,20 +160,21 @@ abstract public class GeckoApp
     }
 
     static Vector<ExtraMenuItem> sExtraMenuItems = new Vector<ExtraMenuItem>();
 
     public enum LaunchState {Launching, WaitForDebugger,
                              Launched, GeckoRunning, GeckoExiting};
     private static LaunchState sLaunchState = LaunchState.Launching;
 
-    private static final int FILE_PICKER_REQUEST = 1;
-    private static final int AWESOMEBAR_REQUEST = 2;
-    private static final int CAMERA_IMAGE_CAPTURE_REQUEST = 3;
-    private static final int CAMERA_VIDEO_CAPTURE_REQUEST = 4;
+    private ActivityResultHandlerMap mActivityResultHandlerMap = new ActivityResultHandlerMap();
+    private FilePickerResultHandlerSync mFilePickerResultHandlerSync = new FilePickerResultHandlerSync();
+    private AwesomebarResultHandler mAwesomebarResultHandler = new AwesomebarResultHandler();
+    private CameraImageResultHandler mCameraImageResultHandler = new CameraImageResultHandler();
+    private CameraVideoResultHandler mCameraVideoResultHandler = new CameraVideoResultHandler();
 
     public static boolean checkLaunchState(LaunchState checkState) {
         synchronized(sLaunchState) {
             return sLaunchState == checkState;
         }
     }
 
     static void setLaunchState(LaunchState setState) {
@@ -2435,29 +2436,39 @@ abstract public class GeckoApp
         }
 
         return intents.get(itemId);
     }
 
     private String mImageFilePath = "";
     private SynchronousQueue<String> mFilePickerResult = new SynchronousQueue<String>();
 
+    public boolean showFilePicker(String aMimeType, ActivityResultHandler handler) {
+        Intent intent = getFilePickerIntent(aMimeType);
+
+        if (intent == null) {
+            return false;
+        }
+        startActivityForResult(intent, mActivityResultHandlerMap.put(handler));
+        return true;
+    }
+
     public String showFilePicker(String aMimeType) {
         Intent intent = getFilePickerIntent(aMimeType);
 
         if (intent == null) {
             return "";
         }
 
         if (intent.getAction().equals(android.provider.MediaStore.ACTION_IMAGE_CAPTURE)) {
-            startActivityForResult(intent, CAMERA_IMAGE_CAPTURE_REQUEST);
+            startActivityForResult(intent, mActivityResultHandlerMap.put(mCameraImageResultHandler));
         } else if (intent.getAction().equals(android.provider.MediaStore.ACTION_VIDEO_CAPTURE)) {
-            startActivityForResult(intent, CAMERA_VIDEO_CAPTURE_REQUEST);
+            startActivityForResult(intent, mActivityResultHandlerMap.put(mCameraVideoResultHandler));
         } else if (intent.getAction().equals(Intent.ACTION_GET_CONTENT)) {
-            startActivityForResult(intent, FILE_PICKER_REQUEST);
+            startActivityForResult(intent, mActivityResultHandlerMap.put(mFilePickerResultHandlerSync));
         } else {
             Log.e(LOGTAG, "We should not get an intent with another action!");
             return "";
         }
 
         String filePickerResult = "";
 
         try {
@@ -2486,17 +2497,17 @@ abstract public class GeckoApp
             Tab tab = Tabs.getInstance().getSelectedTab();
             if (tab != null) {
                 String url = tab.getURL();
                 if (url != null) {
                     intent.putExtra(AwesomeBar.CURRENT_URL_KEY, url);
                 }
             }
         }
-        startActivityForResult(intent, AWESOMEBAR_REQUEST);
+        startActivityForResult(intent, mActivityResultHandlerMap.put(mAwesomebarResultHandler));
         return true;
     }
 
     public boolean doReload() {
         Log.i(LOGTAG, "Reload requested");
         Tab tab = Tabs.getInstance().getSelectedTab();
         if (tab == null)
             return false;
@@ -2558,120 +2569,154 @@ abstract public class GeckoApp
             return;
         }
 
         moveTaskToBack(true);
     }
 
     static int kCaptureIndex = 0;
 
+    public interface ActivityResultHandler {
+        public void onActivityResult(int resultCode, Intent data);
+    }
+
+    class ActivityResultHandlerMap {
+        private Map<Integer, ActivityResultHandler> mMap = new HashMap<Integer, ActivityResultHandler>();
+        private int mCounter = 0;
+
+        synchronized int put(ActivityResultHandler handler) {
+            mMap.put(mCounter, handler);
+            return mCounter++;
+        }
+
+        synchronized ActivityResultHandler getAndRemove(int i) {
+            return mMap.remove(i);
+        }
+    }
+
     @Override
     protected void onActivityResult(int requestCode, int resultCode,
                                     Intent data) {
-        super.onActivityResult(requestCode, resultCode, data);
-        switch (requestCode) {
-        case FILE_PICKER_REQUEST:
-            String filePickerResult = "";
-            if (data != null && resultCode == RESULT_OK) {
-                try {
-                    ContentResolver cr = getContentResolver();
-                    Uri uri = data.getData();
-                    Cursor cursor = GeckoApp.mAppContext.getContentResolver().query(
-                        uri, 
-                        new String[] { OpenableColumns.DISPLAY_NAME },
-                        null, 
-                        null, 
-                        null);
-                    String name = null;
-                    if (cursor != null) {
-                        try {
-                            if (cursor.moveToNext()) {
-                                name = cursor.getString(0);
-                            }
-                        } finally {
-                            cursor.close();
+        ActivityResultHandler handler = mActivityResultHandlerMap.getAndRemove(requestCode);
+        if (handler != null)
+            handler.onActivityResult(resultCode, data);
+        else
+            super.onActivityResult(requestCode, resultCode, data);
+    }
+
+    static abstract class FilePickerResultHandler implements ActivityResultHandler {
+        String handleActivityResult(int resultCode, Intent data) {
+            if (data == null && resultCode != RESULT_OK)
+                return null;
+            Uri uri = data.getData();
+            if ("file".equals(uri.getScheme()))
+                return uri.getPath();
+            try {
+                ContentResolver cr = GeckoApp.mAppContext.getContentResolver();
+                Cursor cursor = cr.query(uri, new String[] { OpenableColumns.DISPLAY_NAME },
+                                         null, null, null);
+                String name = null;
+                if (cursor != null) {
+                    try {
+                        if (cursor.moveToNext()) {
+                            name = cursor.getString(0);
                         }
+                    } finally {
+                        cursor.close();
                     }
-                    String fileName = "tmp_";
-                    String fileExt = null;
-                    int period;
-                    if (name == null || (period = name.lastIndexOf('.')) == -1) {
-                        String mimeType = cr.getType(uri);
-                        fileExt = "." + GeckoAppShell.getExtensionFromMimeType(mimeType);
-                    } else {
-                        fileExt = name.substring(period);
-                        fileName = name.substring(0, period);
-                    }
-                    File file = File.createTempFile(fileName, fileExt, GeckoAppShell.getGREDir(GeckoApp.mAppContext));
-
-                    FileOutputStream fos = new FileOutputStream(file);
-                    InputStream is = cr.openInputStream(uri);
-                    byte[] buf = new byte[4096];
-                    int len = is.read(buf);
-                    while (len != -1) {
-                        fos.write(buf, 0, len);
-                        len = is.read(buf);
-                    }
-                    fos.close();
-                    filePickerResult =  file.getAbsolutePath();
-                }catch (Exception e) {
-                    Log.e(LOGTAG, "showing file picker", e);
+                }
+                String fileName = "tmp_";
+                String fileExt = null;
+                int period;
+                if (name == null || (period = name.lastIndexOf('.')) == -1) {
+                    String mimeType = cr.getType(uri);
+                    fileExt = "." + GeckoAppShell.getExtensionFromMimeType(mimeType);
+                } else {
+                    fileExt = name.substring(period);
+                    fileName = name.substring(0, period);
                 }
+                File file = File.createTempFile(fileName, fileExt, GeckoAppShell.getGREDir(GeckoApp.mAppContext));
+                FileOutputStream fos = new FileOutputStream(file);
+                InputStream is = cr.openInputStream(uri);
+                byte[] buf = new byte[4096];
+                int len = is.read(buf);
+                while (len != -1) {
+                    fos.write(buf, 0, len);
+                    len = is.read(buf);
+                }
+                fos.close();
+                return file.getAbsolutePath();
+            } catch (Exception e) {
+                Log.e(LOGTAG, "showing file picker", e);
             }
+            return null;
+        }
+    }
+
+    class FilePickerResultHandlerSync extends FilePickerResultHandler {
+        public void onActivityResult(int resultCode, Intent data) {
             try {
-                mFilePickerResult.put(filePickerResult);
+                mFilePickerResult.put(handleActivityResult(resultCode, data));
             } catch (InterruptedException e) {
                 Log.i(LOGTAG, "error returning file picker result", e);
             }
-            break;
-        case AWESOMEBAR_REQUEST:
+
+        }
+    }
+
+    class AwesomebarResultHandler implements ActivityResultHandler {
+        public void onActivityResult(int resultCode, Intent data) {
             if (data != null) {
                 String url = data.getStringExtra(AwesomeBar.URL_KEY);
                 AwesomeBar.Type type = AwesomeBar.Type.valueOf(data.getStringExtra(AwesomeBar.TYPE_KEY));
                 String searchEngine = data.getStringExtra(AwesomeBar.SEARCH_KEY);
                 boolean userEntered = data.getBooleanExtra(AwesomeBar.USER_ENTERED_KEY, false);
                 if (url != null && url.length() > 0)
                     loadRequest(url, type, searchEngine, userEntered);
             }
-            break;
-        case CAMERA_IMAGE_CAPTURE_REQUEST:
+        }
+    }
+
+    class CameraImageResultHandler implements ActivityResultHandler {
+        public void onActivityResult(int resultCode, Intent data) {            
             try {
                 if (resultCode != Activity.RESULT_OK) {
                     mFilePickerResult.put("");
-                    break;
+                    return;
                 }
 
                 File file = new File(Environment.getExternalStorageDirectory(), mImageFilePath);
                 mImageFilePath = "";
                 mFilePickerResult.put(file.getAbsolutePath());
             } catch (InterruptedException e) {
                 Log.i(LOGTAG, "error returning file picker result", e);
             }
-
-            break;
-        case CAMERA_VIDEO_CAPTURE_REQUEST:
+        }
+    }
+
+    class CameraVideoResultHandler implements ActivityResultHandler {
+        public void onActivityResult(int resultCode, Intent data) {
             try {
                 if (data == null || resultCode != Activity.RESULT_OK) {
                     mFilePickerResult.put("");
-                    break;
+                    return;
                 }
 
                 Cursor cursor = managedQuery(data.getData(),
                                              new String[] { MediaStore.Video.Media.DATA },
                                              null,
                                              null,
                                              null);
                 cursor.moveToFirst();
                 mFilePickerResult.put(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DATA)));
             } catch (InterruptedException e) {
                 Log.i(LOGTAG, "error returning file picker result", e);
             }
 
-            break;
-       }
+        }
     }
 
     // If searchEngine is provided, url will be used as the search query.
     // Otherwise, the url is loaded.
     private void loadRequest(String url, AwesomeBar.Type type, String searchEngine, boolean userEntered) {
         mBrowserToolbar.setTitle(url);
         Log.d(LOGTAG, type.name());
         JSONObject args = new JSONObject();
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -2045,9 +2045,29 @@ public class GeckoAppShell
 
     public static void lockScreenOrientation(int aOrientation) {
         GeckoScreenOrientationListener.getInstance().lockScreenOrientation(aOrientation);
     }
 
     public static void unlockScreenOrientation() {
         GeckoScreenOrientationListener.getInstance().unlockScreenOrientation();
     }
+
+    static class AsyncResultHandler extends GeckoApp.FilePickerResultHandler {
+        private long mId;
+        AsyncResultHandler(long id) {
+            mId = id;
+        }
+
+        public void onActivityResult(int resultCode, Intent data) {
+            GeckoAppShell.notifyFilePickerResult(handleActivityResult(resultCode, data), mId);
+        }
+        
+    }
+
+    static native void notifyFilePickerResult(String filePath, long id);
+
+    /* Called by JNI from AndroidBridge */
+    public static void showFilePickerAsync(String aMimeType, long id) {
+        if (!GeckoApp.mAppContext.showFilePicker(aMimeType, new AsyncResultHandler(id)))
+            GeckoAppShell.notifyFilePickerResult("", id);
+    }
 }
--- a/mozglue/android/APKOpen.cpp
+++ b/mozglue/android/APKOpen.cpp
@@ -344,16 +344,17 @@ SHELL_WRAPPER3(notifySmsSendFailed, jint
 SHELL_WRAPPER7(notifyGetSms, jint, jstring, jstring, jstring, jlong, jint, jlong)
 SHELL_WRAPPER3(notifyGetSmsFailed, jint, jint, jlong)
 SHELL_WRAPPER3(notifySmsDeleted, jboolean, jint, jlong)
 SHELL_WRAPPER3(notifySmsDeleteFailed, jint, jint, jlong)
 SHELL_WRAPPER2(notifyNoMessageInList, jint, jlong)
 SHELL_WRAPPER8(notifyListCreated, jint, jint, jstring, jstring, jstring, jlong, jint, jlong)
 SHELL_WRAPPER7(notifyGotNextMessage, jint, jstring, jstring, jstring, jlong, jint, jlong)
 SHELL_WRAPPER3(notifyReadingMessageListFailed, jint, jint, jlong)
+SHELL_WRAPPER2(notifyFilePickerResult, jstring, jlong)
 
 static void * xul_handle = NULL;
 static void * sqlite_handle = NULL;
 static void * nss_handle = NULL;
 static void * nspr_handle = NULL;
 static void * plc_handle = NULL;
 static bool simple_linker_initialized = false;
 
@@ -759,16 +760,17 @@ loadGeckoLibs(const char *apkName)
   GETFUNC(notifyGetSms);
   GETFUNC(notifyGetSmsFailed);
   GETFUNC(notifySmsDeleted);
   GETFUNC(notifySmsDeleteFailed);
   GETFUNC(notifyNoMessageInList);
   GETFUNC(notifyListCreated);
   GETFUNC(notifyGotNextMessage);
   GETFUNC(notifyReadingMessageListFailed);
+  GETFUNC(notifyFilePickerResult);
 #undef GETFUNC
   sStartupTimeline = (uint64_t *)__wrap_dlsym(xul_handle, "_ZN7mozilla15StartupTimeline16sStartupTimelineE");
   gettimeofday(&t1, 0);
   struct rusage usage2;
   getrusage(RUSAGE_THREAD, &usage2);
   __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Loaded libs in %ldms total, %ldms user, %ldms system, %ld faults",
                       (t1.tv_sec - t0.tv_sec)*1000 + (t1.tv_usec - t0.tv_usec)/1000, 
                       (usage2.ru_utime.tv_sec - usage1.ru_utime.tv_sec)*1000 + (usage2.ru_utime.tv_usec - usage1.ru_utime.tv_usec)/1000,
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -68,16 +68,18 @@
 #define ALOG_BRIDGE(args...)
 #endif
 
 #define IME_FULLSCREEN_PREF "widget.ime.android.landscape_fullscreen"
 #define IME_FULLSCREEN_THRESHOLD_PREF "widget.ime.android.fullscreen_threshold"
 
 using namespace mozilla;
 
+NS_IMPL_THREADSAFE_ISUPPORTS0(nsFilePickerCallback)
+
 AndroidBridge *AndroidBridge::sBridge = 0;
 
 AndroidBridge *
 AndroidBridge::ConstructBridge(JNIEnv *jEnv,
                                jclass jGeckoAppShellClass)
 {
     /* NSS hack -- bionic doesn't handle recursive unloads correctly,
      * because library finalizer functions are called with the dynamic
@@ -129,16 +131,17 @@ AndroidBridge::Init(JNIEnv *jEnv,
     jGetMimeTypeFromExtensions = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getMimeTypeFromExtensions", "(Ljava/lang/String;)Ljava/lang/String;");
     jGetExtensionFromMimeType = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getExtensionFromMimeType", "(Ljava/lang/String;)Ljava/lang/String;");
     jMoveTaskToBack = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "moveTaskToBack", "()V");
     jGetClipboardText = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getClipboardText", "()Ljava/lang/String;");
     jSetClipboardText = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "setClipboardText", "(Ljava/lang/String;)V");
     jShowAlertNotification = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showAlertNotification", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
     jShowFilePickerForExtensions = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showFilePickerForExtensions", "(Ljava/lang/String;)Ljava/lang/String;");
     jShowFilePickerForMimeType = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showFilePickerForMimeType", "(Ljava/lang/String;)Ljava/lang/String;");
+    jShowFilePickerAsync = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showFilePickerAsync", "(Ljava/lang/String;J)V");
     jAlertsProgressListener_OnProgress = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "alertsProgressListener_OnProgress", "(Ljava/lang/String;JJLjava/lang/String;)V");
     jAlertsProgressListener_OnCancel = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "alertsProgressListener_OnCancel", "(Ljava/lang/String;)V");
     jGetDpi = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "getDpi", "()I");
     jSetFullScreen = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "setFullScreen", "(Z)V");
     jShowInputMethodPicker = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showInputMethodPicker", "()V");
     jSetPreventPanning = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "setPreventPanning", "(Z)V");
     jHideProgressDialog = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "hideProgressDialog", "()V");
     jPerformHapticFeedback = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "performHapticFeedback", "(Z)V");
@@ -767,16 +770,30 @@ AndroidBridge::ShowFilePickerForMimeType
                                             aMimeType.Length());
     jstring jstr =  static_cast<jstring>(env->CallStaticObjectMethod(
                                              mGeckoAppShellClass,
                                              jShowFilePickerForMimeType, jstrFilers));
     aFilePath.Assign(nsJNIString(jstr));
 }
 
 void
+AndroidBridge::ShowFilePickerAsync(const nsAString& aMimeType, nsFilePickerCallback* callback)
+{
+    JNIEnv *env = GetJNIEnv();
+    if (!env)
+        return;
+
+    AutoLocalJNIFrame jniFrame(env); 
+    jstring jMimeType = env->NewString(nsPromiseFlatString(aMimeType).get(),
+                                       aMimeType.Length());
+    callback->AddRef();
+    env->CallStaticVoidMethod(mGeckoAppShellClass, jShowFilePickerAsync, jMimeType, (jlong) callback);
+}
+
+void
 AndroidBridge::SetFullScreen(bool aFullScreen)
 {
     ALOG_BRIDGE("AndroidBridge::SetFullScreen");
 
     JNIEnv *env = GetJNIEnv();
     if (!env)
         return;
 
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -106,16 +106,25 @@ typedef struct AndroidSystemColors {
     nscolor textColorTertiaryInverse;
     nscolor textColorHighlight;
     nscolor colorForeground;
     nscolor colorBackground;
     nscolor panelColorForeground;
     nscolor panelColorBackground;
 } AndroidSystemColors;
 
+class nsFilePickerCallback : nsISupports {
+public:
+    NS_DECL_ISUPPORTS
+    virtual void handleResult(nsAString& filePath) = 0;
+    nsFilePickerCallback() {}
+protected:
+    virtual ~nsFilePickerCallback() {}
+};
+
 class AndroidBridge
 {
 public:
     enum {
         NOTIFY_IME_RESETINPUTSTATE = 0,
         NOTIFY_IME_SETOPENSTATE = 1,
         NOTIFY_IME_CANCELCOMPOSITION = 2,
         NOTIFY_IME_FOCUSCHANGE = 3
@@ -237,16 +246,17 @@ public:
                                            const nsAString& aAlertText);
 
     void AlertsProgressListener_OnCancel(const nsAString& aAlertName);
 
     int GetDPI();
 
     void ShowFilePickerForExtensions(nsAString& aFilePath, const nsAString& aExtensions);
     void ShowFilePickerForMimeType(nsAString& aFilePath, const nsAString& aMimeType);
+    void ShowFilePickerAsync(const nsAString& aMimeType, nsFilePickerCallback* callback);
 
     void PerformHapticFeedback(bool aIsLongPress);
 
     void Vibrate(const nsTArray<PRUint32>& aPattern);
     void CancelVibrate();
 
     void SetFullScreen(bool aFullScreen);
 
@@ -477,16 +487,17 @@ protected:
     jmethodID jGetMimeTypeFromExtensions;
     jmethodID jGetExtensionFromMimeType;
     jmethodID jMoveTaskToBack;
     jmethodID jGetClipboardText;
     jmethodID jSetClipboardText;
     jmethodID jShowAlertNotification;
     jmethodID jShowFilePickerForExtensions;
     jmethodID jShowFilePickerForMimeType;
+    jmethodID jShowFilePickerAsync;
     jmethodID jAlertsProgressListener_OnProgress;
     jmethodID jAlertsProgressListener_OnCancel;
     jmethodID jGetDpi;
     jmethodID jSetFullScreen;
     jmethodID jShowInputMethodPicker;
     jmethodID jSetPreventPanning;
     jmethodID jHideProgressDialog;
     jmethodID jPerformHapticFeedback;
--- a/widget/android/AndroidJNI.cpp
+++ b/widget/android/AndroidJNI.cpp
@@ -97,16 +97,17 @@ extern "C" {
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyGetSms(JNIEnv* jenv, jclass, jint, jstring, jstring, jstring, jlong, jint, jlong);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyGetSmsFailed(JNIEnv* jenv, jclass, jint, jint, jlong);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifySmsDeleted(JNIEnv* jenv, jclass, jboolean, jint, jlong);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifySmsDeleteFailed(JNIEnv* jenv, jclass, jint, jint, jlong);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyNoMessageInList(JNIEnv* jenv, jclass, jint, jlong);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyListCreated(JNIEnv* jenv, jclass, jint, jint, jstring, jstring, jstring, jlong, jint, jlong);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyGotNextMessage(JNIEnv* jenv, jclass, jint, jstring, jstring, jstring, jlong, jint, jlong);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyReadingMessageListFailed(JNIEnv* jenv, jclass, jint, jint, jlong);
+    NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult(JNIEnv* jenv, jclass, jstring fileDir, jlong callback);
 
 #ifdef MOZ_JAVA_COMPOSITOR
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_scheduleComposite(JNIEnv* jenv, jclass);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_schedulePauseComposition(JNIEnv* jenv, jclass);
     NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_scheduleResumeComposition(JNIEnv* jenv, jclass);
 #endif
 
 }
@@ -904,9 +905,34 @@ Java_org_mozilla_gecko_GeckoAppShell_sch
 }
 
 NS_EXPORT void JNICALL
 Java_org_mozilla_gecko_GeckoAppShell_scheduleResumeComposition(JNIEnv*, jclass)
 {
     nsWindow::ScheduleResumeComposition();
 }
 
+NS_EXPORT void JNICALL
+Java_org_mozilla_gecko_GeckoAppShell_notifyFilePickerResult(JNIEnv* jenv, jclass, jstring filePath, jlong callback)
+{
+    class NotifyFilePickerResultRunnable : public nsRunnable {
+    public:
+        NotifyFilePickerResultRunnable(nsString& fileDir, long callback) : 
+            mFileDir(fileDir), mCallback(callback) {}
+
+        NS_IMETHODIMP Run() {
+            nsFilePickerCallback* handler = (nsFilePickerCallback*)mCallback;
+            handler->handleResult(mFileDir);
+            handler->Release();
+            return NS_OK;
+        }
+    private:
+        nsString mFileDir;
+        long mCallback;
+    };
+    nsString path = nsJNIString(filePath, jenv);
+    
+    nsCOMPtr<nsIRunnable> runnable =
+        new NotifyFilePickerResultRunnable(path, (long)callback);
+    NS_DispatchToMainThread(runnable);
+}
+
 #endif