diff --git a/embedding/android/GeckoAppShell.java b/embedding/android/GeckoAppShell.java index 5af12aa5d874..a954ad8edf44 100644 --- a/embedding/android/GeckoAppShell.java +++ b/embedding/android/GeckoAppShell.java @@ -1867,4 +1867,10 @@ public class GeckoAppShell 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) { + } } diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index 743bd09152b2..1dfb96eff155 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -165,10 +165,11 @@ abstract public class GeckoApp 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) { @@ -2440,6 +2441,16 @@ abstract public class GeckoApp private String mImageFilePath = ""; private SynchronousQueue mFilePickerResult = new SynchronousQueue(); + 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); @@ -2448,11 +2459,11 @@ abstract public class GeckoApp } 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 ""; @@ -2491,7 +2502,7 @@ abstract public class GeckoApp } } } - startActivityForResult(intent, AWESOMEBAR_REQUEST); + startActivityForResult(intent, mActivityResultHandlerMap.put(mAwesomebarResultHandler)); return true; } @@ -2563,66 +2574,96 @@ abstract public class GeckoApp static int kCaptureIndex = 0; + public interface ActivityResultHandler { + public void onActivityResult(int resultCode, Intent data); + } + + class ActivityResultHandlerMap { + private Map mMap = new HashMap(); + 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(); - } - } - 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)); + ActivityResultHandler handler = mActivityResultHandlerMap.getAndRemove(requestCode); + if (handler != null) + handler.onActivityResult(resultCode, data); + else + super.onActivityResult(requestCode, resultCode, data); + } - 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); - } - } + 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 { - mFilePickerResult.put(filePickerResult); + 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(); + 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(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)); @@ -2631,12 +2672,15 @@ abstract public class GeckoApp 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); @@ -2645,13 +2689,15 @@ abstract public class GeckoApp } 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(), @@ -2665,8 +2711,7 @@ abstract public class GeckoApp Log.i(LOGTAG, "error returning file picker result", e); } - break; - } + } } // If searchEngine is provided, url will be used as the search query. diff --git a/mobile/android/base/GeckoAppShell.java b/mobile/android/base/GeckoAppShell.java index 1b7ed0f3cab4..702990729273 100644 --- a/mobile/android/base/GeckoAppShell.java +++ b/mobile/android/base/GeckoAppShell.java @@ -2050,4 +2050,24 @@ public class GeckoAppShell 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); + } } diff --git a/mozglue/android/APKOpen.cpp b/mozglue/android/APKOpen.cpp index cbaba36ef4cf..793b0f74e23f 100644 --- a/mozglue/android/APKOpen.cpp +++ b/mozglue/android/APKOpen.cpp @@ -349,6 +349,7 @@ 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; @@ -764,6 +765,7 @@ loadGeckoLibs(const char *apkName) GETFUNC(notifyListCreated); GETFUNC(notifyGotNextMessage); GETFUNC(notifyReadingMessageListFailed); + GETFUNC(notifyFilePickerResult); #undef GETFUNC sStartupTimeline = (uint64_t *)__wrap_dlsym(xul_handle, "_ZN7mozilla15StartupTimeline16sStartupTimelineE"); gettimeofday(&t1, 0); diff --git a/widget/android/AndroidBridge.cpp b/widget/android/AndroidBridge.cpp index c3bb9bfbcbaf..cda316c62b04 100644 --- a/widget/android/AndroidBridge.cpp +++ b/widget/android/AndroidBridge.cpp @@ -73,6 +73,8 @@ using namespace mozilla; +NS_IMPL_THREADSAFE_ISUPPORTS0(nsFilePickerCallback) + AndroidBridge *AndroidBridge::sBridge = 0; AndroidBridge * @@ -134,6 +136,7 @@ AndroidBridge::Init(JNIEnv *jEnv, 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"); @@ -771,6 +774,20 @@ AndroidBridge::ShowFilePickerForMimeType(nsAString& aFilePath, const nsAString& 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) { diff --git a/widget/android/AndroidBridge.h b/widget/android/AndroidBridge.h index bb227068c7b7..9b6fcca7bb8e 100644 --- a/widget/android/AndroidBridge.h +++ b/widget/android/AndroidBridge.h @@ -111,6 +111,15 @@ typedef struct AndroidSystemColors { nscolor panelColorBackground; } AndroidSystemColors; +class nsFilePickerCallback : nsISupports { +public: + NS_DECL_ISUPPORTS + virtual void handleResult(nsAString& filePath) = 0; + nsFilePickerCallback() {} +protected: + virtual ~nsFilePickerCallback() {} +}; + class AndroidBridge { public: @@ -242,6 +251,7 @@ public: 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); @@ -482,6 +492,7 @@ protected: jmethodID jShowAlertNotification; jmethodID jShowFilePickerForExtensions; jmethodID jShowFilePickerForMimeType; + jmethodID jShowFilePickerAsync; jmethodID jAlertsProgressListener_OnProgress; jmethodID jAlertsProgressListener_OnCancel; jmethodID jGetDpi; diff --git a/widget/android/AndroidJNI.cpp b/widget/android/AndroidJNI.cpp index 0eb1d8920933..214fcc2e36bc 100644 --- a/widget/android/AndroidJNI.cpp +++ b/widget/android/AndroidJNI.cpp @@ -102,6 +102,7 @@ extern "C" { 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); @@ -909,4 +910,29 @@ 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 runnable = + new NotifyFilePickerResultRunnable(path, (long)callback); + NS_DispatchToMainThread(runnable); +} + #endif