From 3f5c4187e2bbbc25ed7ce5fdf57ab595c00c8d62 Mon Sep 17 00:00:00 2001 From: Mounir Lamouri Date: Sun, 18 Mar 2012 17:15:56 +0100 Subject: [PATCH] Bug 730289 - Filepicker on Android should allow picking or capturing media instead of having a specific button for capture. r=dougt,wesj ui-r=madhava --- embedding/android/GeckoAppShell.java | 8 +- mobile/android/base/GeckoApp.java | 219 +++++++++++++++--- mobile/android/base/GeckoAppShell.java | 16 +- mobile/android/base/PromptService.java | 91 +++++++- .../base/locales/en-US/android_strings.dtd | 5 + mobile/android/base/strings.xml.in | 5 + mobile/android/components/CapturePicker.js | 140 ----------- mobile/android/components/Makefile.in | 1 - .../components/MobileComponents.manifest | 4 - widget/android/AndroidBridge.cpp | 31 ++- widget/android/AndroidBridge.h | 6 +- widget/android/nsFilePicker.cpp | 41 +++- widget/android/nsFilePicker.h | 4 +- 13 files changed, 361 insertions(+), 210 deletions(-) delete mode 100644 mobile/android/components/CapturePicker.js diff --git a/embedding/android/GeckoAppShell.java b/embedding/android/GeckoAppShell.java index b083b0d643a3..8d26d32858ac 100644 --- a/embedding/android/GeckoAppShell.java +++ b/embedding/android/GeckoAppShell.java @@ -1105,9 +1105,13 @@ public class GeckoAppShell GeckoApp.mAppContext.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER); } - public static String showFilePicker(String aFilters) { + public static String showFilePickerForExtensions(String aExtensions) { return GeckoApp.mAppContext. - showFilePicker(getMimeTypeFromExtensions(aFilters)); + showFilePicker(getMimeTypeFromExtensions(aExtensions)); + } + + public static String showFilePickerForMimeType(String aMimeType) { + return GeckoApp.mAppContext.showFilePicker(aMimeType); } public static void performHapticFeedback(boolean aIsLongPress) { diff --git a/mobile/android/base/GeckoApp.java b/mobile/android/base/GeckoApp.java index 4483ad29a71a..ef6464091fd5 100644 --- a/mobile/android/base/GeckoApp.java +++ b/mobile/android/base/GeckoApp.java @@ -71,6 +71,7 @@ import org.json.*; import android.os.*; import android.app.*; import android.text.*; +import android.text.format.Time; import android.view.*; import android.view.inputmethod.*; import android.view.ViewGroup.LayoutParams; @@ -176,7 +177,8 @@ abstract public class GeckoApp private static final int FILE_PICKER_REQUEST = 1; private static final int AWESOMEBAR_REQUEST = 2; - private static final int CAMERA_CAPTURE_REQUEST = 3; + private static final int CAMERA_IMAGE_CAPTURE_REQUEST = 3; + private static final int CAMERA_VIDEO_CAPTURE_REQUEST = 4; public static boolean checkLaunchState(LaunchState checkState) { synchronized(sLaunchState) { @@ -942,9 +944,6 @@ abstract public class GeckoApp final String uri = message.getString("uri"); final String title = message.getString("title"); handleLoadError(tabId, uri, title); - } else if (event.equals("onCameraCapture")) { - //GeckoApp.mAppContext.doCameraCapture(message.getString("path")); - doCameraCapture(); } else if (event.equals("Doorhanger:Add")) { handleDoorHanger(message); } else if (event.equals("Doorhanger:Remove")) { @@ -2342,24 +2341,168 @@ abstract public class GeckoApp Log.i(LOGTAG, "Profile migration took " + timeDiff + " ms"); } - private SynchronousQueue mFilePickerResult = new SynchronousQueue(); - public String showFilePicker(String aMimeType) { + /** + * The FilePickerPromptRunnable has to be called to show an intent-like + * context menu UI using the PromptService. + */ + private class FilePickerPromptRunnable implements Runnable { + public FilePickerPromptRunnable(String aTitle, PromptService.PromptListItem[] aItems) { + super(); + mTitle = aTitle; + mItems = aItems; + } + + public void run() { + GeckoAppShell.getPromptService().Show(mTitle, "", null, mItems, false); + } + + private String mTitle; + private PromptService.PromptListItem[] mItems; + } + + private int addIntentActivitiesToList(Intent intent, ArrayList items, ArrayList aIntents) { + PackageManager pm = mAppContext.getPackageManager(); + final List lri = + pm.queryIntentActivityOptions(GeckoApp.mAppContext.getComponentName(), null, intent, 0); + + if (lri == null) { + return 0; + } + + for (int i=0; i aItems, String aType, ArrayList aIntents) { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType(aType); intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType(aMimeType); - GeckoApp.this. - startActivityForResult( - Intent.createChooser(intent, getString(R.string.choose_file)), - FILE_PICKER_REQUEST); + + return addIntentActivitiesToList(intent, aItems, aIntents); + } + + static private String generateImageName() { + Time now = new Time(); + now.setToNow(); + return now.format("%Y-%m-%d %H.%M.%S") + ".jpg"; + } + + private PromptService.PromptListItem[] getItemsAndIntentsForFilePicker(String aMimeType, ArrayList aIntents) { + ArrayList items = new ArrayList(); + + if (aMimeType.equals("audio/*")) { + if (AddFilePickingActivities(items, "audio/*", aIntents) <= 0) { + AddFilePickingActivities(items, "*/*", aIntents); + } + } else if (aMimeType.equals("image/*")) { + mImageFilePath = generateImageName(); + Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE); + intent.putExtra(MediaStore.EXTRA_OUTPUT, + Uri.fromFile(new File(Environment.getExternalStorageDirectory(), + mImageFilePath))); + addIntentActivitiesToList(intent, items, aIntents); + + if (AddFilePickingActivities(items, "image/*", aIntents) <= 0) { + AddFilePickingActivities(items, "*/*", aIntents); + } + } else if (aMimeType.equals("video/*")) { + Intent intent = new Intent(android.provider.MediaStore.ACTION_VIDEO_CAPTURE); + addIntentActivitiesToList(intent, items, aIntents); + + if (AddFilePickingActivities(items, "video/*", aIntents) <= 0) { + AddFilePickingActivities(items, "*/*", aIntents); + } + } else { + mImageFilePath = generateImageName(); + Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE); + intent.putExtra(MediaStore.EXTRA_OUTPUT, + Uri.fromFile(new File(Environment.getExternalStorageDirectory(), + mImageFilePath))); + addIntentActivitiesToList(intent, items, aIntents); + + intent = new Intent(android.provider.MediaStore.ACTION_VIDEO_CAPTURE); + addIntentActivitiesToList(intent, items, aIntents); + + AddFilePickingActivities(items, "*/*", aIntents); + } + + return items.toArray(new PromptService.PromptListItem[] {}); + } + + private String getFilePickerTitle(String aMimeType) { + if (aMimeType.equals("audio/*")) { + return getString(R.string.filepicker_audio_title); + } else if (aMimeType.equals("image/*")) { + return getString(R.string.filepicker_image_title); + } else if (aMimeType.equals("video/*")) { + return getString(R.string.filepicker_video_title); + } else { + return getString(R.string.filepicker_title); + } + } + + private String mImageFilePath = ""; + private SynchronousQueue mFilePickerResult = new SynchronousQueue(); + + public String showFilePicker(String aMimeType) { + ArrayList intents = new ArrayList(); + PromptService.PromptListItem[] items = getItemsAndIntentsForFilePicker(aMimeType, intents); + + GeckoAppShell.getHandler().post(new FilePickerPromptRunnable(getFilePickerTitle(aMimeType), items)); + + String promptServiceResult = ""; + try { + promptServiceResult = PromptService.waitForReturn(); + } catch (InterruptedException e) { + Log.e(LOGTAG, "showing prompt failed: ", e); + return ""; + } + + int itemId = -1; + try { + itemId = new JSONObject(promptServiceResult).getInt("button"); + + if (itemId == -1) { + return ""; + } + } catch (org.json.JSONException e) { + Log.e(LOGTAG, "result from promptservice was invalid: ", e); + return ""; + } + + Intent intent = intents.get(itemId); + + if (intent.getAction().equals(android.provider.MediaStore.ACTION_IMAGE_CAPTURE)) { + startActivityForResult(intent, CAMERA_IMAGE_CAPTURE_REQUEST); + } else if (intent.getAction().equals(android.provider.MediaStore.ACTION_VIDEO_CAPTURE)) { + startActivityForResult(intent, CAMERA_VIDEO_CAPTURE_REQUEST); + } else if (intent.getAction().equals(Intent.ACTION_GET_CONTENT)) { + startActivityForResult(intent, FILE_PICKER_REQUEST); + } else { + Log.e(LOGTAG, "We should not get an intent with another action!"); + return ""; + } + String filePickerResult = ""; try { while (null == (filePickerResult = mFilePickerResult.poll(1, TimeUnit.MILLISECONDS))) { - Log.i(LOGTAG, "processing events from showFilePicker "); GeckoAppShell.processNextNativeEvent(); } } catch (InterruptedException e) { - Log.i(LOGTAG, "showing file picker ", e); + Log.e(LOGTAG, "showing file picker failed: ", e); } return filePickerResult; @@ -2527,27 +2670,43 @@ abstract public class GeckoApp loadRequest(url, type, searchEngine, userEntered); } break; - case CAMERA_CAPTURE_REQUEST: - Log.i(LOGTAG, "Returning from CAMERA_CAPTURE_REQUEST: " + resultCode); - File file = new File(Environment.getExternalStorageDirectory(), "cameraCapture-" + Integer.toString(kCaptureIndex) + ".jpg"); - kCaptureIndex++; - GeckoEvent e = GeckoEvent.createBroadcastEvent("cameraCaptureDone", resultCode == Activity.RESULT_OK ? - "{\"ok\": true, \"path\": \"" + file.getPath() + "\" }" : - "{\"ok\": false, \"path\": \"" + file.getPath() + "\" }"); - GeckoAppShell.sendEventToGecko(e); + case CAMERA_IMAGE_CAPTURE_REQUEST: + try { + if (resultCode != Activity.RESULT_OK) { + mFilePickerResult.put(""); + break; + } + + 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: + try { + if (data == null || resultCode != Activity.RESULT_OK) { + mFilePickerResult.put(""); + break; + } + + 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; } } - public void doCameraCapture() { - File file = new File(Environment.getExternalStorageDirectory(), "cameraCapture-" + Integer.toString(kCaptureIndex) + ".jpg"); - - Intent intent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE); - intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file)); - - startActivityForResult(intent, CAMERA_CAPTURE_REQUEST); - } - // 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) { diff --git a/mobile/android/base/GeckoAppShell.java b/mobile/android/base/GeckoAppShell.java index f09964e4da23..ebd509a8dd7e 100644 --- a/mobile/android/base/GeckoAppShell.java +++ b/mobile/android/base/GeckoAppShell.java @@ -1099,9 +1099,13 @@ public class GeckoAppShell GeckoApp.mAppContext.setFullScreen(fullscreen); } - public static String showFilePicker(String aFilters) { + public static String showFilePickerForExtensions(String aExtensions) { return GeckoApp.mAppContext. - showFilePicker(getMimeTypeFromExtensions(aFilters)); + showFilePicker(getMimeTypeFromExtensions(aExtensions)); + } + + public static String showFilePickerForMimeType(String aMimeType) { + return GeckoApp.mAppContext.showFilePicker(aMimeType); } public static void performHapticFeedback(boolean aIsLongPress) { @@ -1626,8 +1630,6 @@ public class GeckoAppShell } } - static SynchronousQueue sPromptQueue = null; - public static void registerGeckoEventListener(String event, GeckoEventListener listener) { if (mEventListeners == null) mEventListeners = new HashMap>(); @@ -1687,8 +1689,6 @@ public class GeckoAppShell String type = geckoObject.getString("type"); if (type.equals("Prompt:Show")) { - if (sPromptQueue == null) - sPromptQueue = new SynchronousQueue(); getHandler().post(new Runnable() { public void run() { getPromptService().processMessage(geckoObject); @@ -1697,9 +1697,7 @@ public class GeckoAppShell String promptServiceResult = ""; try { - while (null == (promptServiceResult = sPromptQueue.poll(1, TimeUnit.MILLISECONDS))) { - processNextNativeEvent(); - } + promptServiceResult = PromptService.waitForReturn(); } catch (InterruptedException e) { Log.i(LOGTAG, "showing prompt ", e); } diff --git a/mobile/android/base/PromptService.java b/mobile/android/base/PromptService.java index 15d63604faae..d328d6ab4984 100644 --- a/mobile/android/base/PromptService.java +++ b/mobile/android/base/PromptService.java @@ -39,6 +39,8 @@ package org.mozilla.gecko; import android.util.Log; import java.lang.String; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.TimeUnit; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; @@ -46,7 +48,10 @@ import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnMultiChoiceClickListener; import android.content.res.Resources; +import android.graphics.Bitmap; import android.graphics.Color; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.text.InputType; import android.text.method.PasswordTransformationMethod; import android.util.TypedValue; @@ -74,15 +79,40 @@ public class PromptService implements OnClickListener, OnCancelListener, OnItemC private PromptInput[] mInputs; private AlertDialog mDialog = null; private static LayoutInflater mInflater; - private final static int PADDING_SIZE = 32; // in dip units - private static int mPaddingSize = 0; // calculated from PADDING_SIZE. In pixel units + + private final static int GROUP_PADDING_SIZE = 32; // in dip units + private static int mGroupPaddingSize = 0; // calculated from GROUP_PADDING_SIZE. In pixel units + + private final static int LEFT_RIGHT_TEXT_WITH_ICON_PADDING = 10; // in dip units + private static int mLeftRightTextWithIconPadding = 0; // calculated from LEFT_RIGHT_TEXT_WITH_ICON_PADDING. + + private final static int TOP_BOTTOM_TEXT_WITH_ICON_PADDING = 8; // in dip units + private static int mTopBottomTextWithIconPadding = 0; // calculated from TOP_BOTTOM_TEXT_WITH_ICON_PADDING. + + private final static int ICON_TEXT_PADDING = 10; // in dip units + private static int mIconTextPadding = 0; // calculated from ICON_TEXT_PADDING. + + private final static int ICON_SIZE = 72; // in dip units + private static int mIconSize = 0; // calculated from ICON_SIZE. PromptService() { mInflater = LayoutInflater.from(GeckoApp.mAppContext); Resources res = GeckoApp.mAppContext.getResources(); - mPaddingSize = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - PADDING_SIZE, - res.getDisplayMetrics()); + mGroupPaddingSize = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + GROUP_PADDING_SIZE, + res.getDisplayMetrics()); + mLeftRightTextWithIconPadding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + LEFT_RIGHT_TEXT_WITH_ICON_PADDING, + res.getDisplayMetrics()); + mTopBottomTextWithIconPadding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + TOP_BOTTOM_TEXT_WITH_ICON_PADDING, + res.getDisplayMetrics()); + mIconTextPadding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + ICON_TEXT_PADDING, + res.getDisplayMetrics()); + mIconSize = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + ICON_SIZE, + res.getDisplayMetrics()); } private class PromptButton { @@ -184,8 +214,8 @@ public class PromptService implements OnClickListener, OnCancelListener, OnItemC builder.setMessage(aText); } - int length = mInputs.length; - if (aMenuList.length > 0) { + int length = mInputs == null ? 0 : mInputs.length; + if (aMenuList != null && aMenuList.length > 0) { int resourceId = android.R.layout.select_dialog_item; if (mSelected != null && mSelected.length > 0) { if (aMultipleSelection) { @@ -230,7 +260,7 @@ public class PromptService implements OnClickListener, OnCancelListener, OnItemC builder.setView((View)linearLayout); } - length = aButtons.length; + length = aButtons == null ? 0 : aButtons.length; if (length > 0) { builder.setPositiveButton(aButtons[0].label, this); } @@ -299,12 +329,24 @@ public class PromptService implements OnClickListener, OnCancelListener, OnItemC finishDialog(ret.toString()); } + static SynchronousQueue mPromptQueue = new SynchronousQueue(); + + static public String waitForReturn() throws InterruptedException { + String value; + + while (null == (value = mPromptQueue.poll(1, TimeUnit.MILLISECONDS))) { + GeckoAppShell.processNextNativeEvent(); + } + + return value; + } + public void finishDialog(String aReturn) { mInputs = null; mDialog = null; mSelected = null; try { - GeckoAppShell.sPromptQueue.put(aReturn); + mPromptQueue.put(aReturn); } catch(Exception ex) { } } @@ -396,12 +438,16 @@ public class PromptService implements OnClickListener, OnCancelListener, OnItemC return list; } - private class PromptListItem { + static public class PromptListItem { public String label = ""; public boolean isGroup = false; public boolean inGroup = false; public boolean disabled = false; public int id = 0; + + // This member can't be accessible from JS, see bug 733749. + public Drawable icon = null; + PromptListItem(JSONObject aObject) { try { label = aObject.getString("label"); } catch(Exception ex) { } try { isGroup = aObject.getBoolean("isGroup"); } catch(Exception ex) { } @@ -409,6 +455,10 @@ public class PromptService implements OnClickListener, OnCancelListener, OnItemC try { disabled = aObject.getBoolean("disabled"); } catch(Exception ex) { } try { id = aObject.getInt("id"); } catch(Exception ex) { } } + + public PromptListItem(String aLabel) { + label = aLabel; + } } public class PromptListAdapter extends ArrayAdapter { @@ -455,15 +505,34 @@ public class PromptService implements OnClickListener, OnCancelListener, OnItemC } if (item.inGroup) { - ct.setPadding(mPaddingSize, 0, 0, 0); + ct.setPadding(mGroupPaddingSize, 0, 0, 0); } } } catch (Exception ex) { } } + TextView t1 = (TextView) row.findViewById(android.R.id.text1); if (t1 != null) { t1.setText(item.label); + if (item.icon != null) { + Resources res = GeckoApp.mAppContext.getResources(); + + // Set padding inside the item. + t1.setPadding(item. inGroup ? mLeftRightTextWithIconPadding + mGroupPaddingSize : mLeftRightTextWithIconPadding, + mTopBottomTextWithIconPadding, + mLeftRightTextWithIconPadding, mTopBottomTextWithIconPadding); + + // Set the padding between the icon and the text. + t1.setCompoundDrawablePadding(mIconTextPadding); + + // We want the icon to be of a specific size. Some do not + // follow this rule so we have to resize them. + Bitmap bitmap = ((BitmapDrawable) item.icon).getBitmap(); + Drawable d = new BitmapDrawable(Bitmap.createScaledBitmap(bitmap, mIconSize, mIconSize, true)); + + t1.setCompoundDrawablesWithIntrinsicBounds(d, null, null, null); + } } return row; diff --git a/mobile/android/base/locales/en-US/android_strings.dtd b/mobile/android/base/locales/en-US/android_strings.dtd index 679fff82ff1d..f4fcc34b6ffb 100644 --- a/mobile/android/base/locales/en-US/android_strings.dtd +++ b/mobile/android/base/locales/en-US/android_strings.dtd @@ -119,3 +119,8 @@ + + + + + diff --git a/mobile/android/base/strings.xml.in b/mobile/android/base/strings.xml.in index bff5c32535e3..59436f06da6a 100644 --- a/mobile/android/base/strings.xml.in +++ b/mobile/android/base/strings.xml.in @@ -123,4 +123,9 @@ &abouthome_no_top_sites; &abouthome_about_sync; &abouthome_sync_bold_name; + + &filepicker_title; + &filepicker_audio_title; + &filepicker_image_title; + &filepicker_video_title; diff --git a/mobile/android/components/CapturePicker.js b/mobile/android/components/CapturePicker.js deleted file mode 100644 index 78a283348121..000000000000 --- a/mobile/android/components/CapturePicker.js +++ /dev/null @@ -1,140 +0,0 @@ -/* -*- Mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil; -*- */ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is CapturePicker.js - * - * The Initial Developer of the Original Code is Mozilla Foundation. - * Portions created by the Initial Developer are Copyright (C) 2010 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Kyle Huey - * Fabrice Desré - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cr = Components.results; -const Cu = Components.utils; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); - -function CapturePicker() { - -} - -CapturePicker.prototype = { - _file: null, - _mode: -1, - _result: -1, - _shown: false, - _title: "", - _type: "", - _window: null, - _done: null, - - // - // nsICapturePicker - // - init: function(aWindow, aTitle, aMode) { - this._window = aWindow; - this._title = aTitle; - this._mode = aMode; - }, - - show: function() { - if (this._shown) - throw Cr.NS_ERROR_UNEXPECTED; - - this._shown = true; - this._file = null; - this._done = false; - - Services.obs.addObserver(this, "cameraCaptureDone", false); - - let msg = { gecko: { type: "onCameraCapture" } }; - Cc["@mozilla.org/android/bridge;1"].getService(Ci.nsIAndroidBridge).handleGeckoMessage(JSON.stringify(msg)); - - // we need to turn all the async messaging into a blocking call - while (!this._done) - Services.tm.currentThread.processNextEvent(true); - - if (this._res.ok) { - this._file = this._res.path; - // delete the file when exiting - let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); - file.initWithPath(this._res.path); - Cc["@mozilla.org/uriloader/external-helper-app-service;1"].getService(Ci.nsPIExternalAppLauncher).deleteTemporaryFileOnExit(file); - } - - return (this._res.ok ? Ci.nsICapturePicker.RETURN_OK : Ci.nsICapturePicker.RETURN_CANCEL); - }, - - observe: function(aObject, aTopic, aData) { - Services.obs.removeObserver(this, "cameraCaptureDone"); - this._done = true; - this._res = JSON.parse(aData); - }, - - modeMayBeAvailable: function(aMode) { - if (aMode != Ci.nsICapturePicker.MODE_STILL) - return false; - return true; - }, - - get file() { - if (this._file) { - let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); - file.initWithPath(this._file); - let utils = this._window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils); - return utils.wrapDOMFile(file); - } else { - throw Cr.NS_ERROR_FAILURE; - } - }, - - get type() { - return this._type; - }, - - set type(aNewType) { - if (this._shown) - throw Cr.NS_ERROR_UNEXPECTED; - else - this._type = aNewType; - }, - - // QI - QueryInterface: XPCOMUtils.generateQI([Ci.nsICapturePicker, Ci.nsIObserver]), - - // XPCOMUtils factory - classID: Components.ID("{cb5a47f0-b58c-4fc3-b61a-358ee95f8238}"), -}; - -var components = [ CapturePicker ]; -const NSGetFactory = XPCOMUtils.generateNSGetFactory(components); diff --git a/mobile/android/components/Makefile.in b/mobile/android/components/Makefile.in index f2664fc19d61..4bfe831734ea 100644 --- a/mobile/android/components/Makefile.in +++ b/mobile/android/components/Makefile.in @@ -71,7 +71,6 @@ EXTRA_COMPONENTS = \ FormAutoComplete.js \ LoginManagerPrompter.js \ BlocklistPrompt.js \ - CapturePicker.js \ $(NULL) ifdef MOZ_SAFE_BROWSING diff --git a/mobile/android/components/MobileComponents.manifest b/mobile/android/components/MobileComponents.manifest index 2372e449aa90..81173e7ddc70 100644 --- a/mobile/android/components/MobileComponents.manifest +++ b/mobile/android/components/MobileComponents.manifest @@ -102,7 +102,3 @@ component {88b3eb21-d072-4e3b-886d-f89d8c49fe59} UpdatePrompt.js contract @mozilla.org/updates/update-prompt;1 {88b3eb21-d072-4e3b-886d-f89d8c49fe59} #endif -# CapturePicker.js -component {cb5a47f0-b58c-4fc3-b61a-358ee95f8238} CapturePicker.js -contract @mozilla.org/capturepicker;1 {cb5a47f0-b58c-4fc3-b61a-358ee95f8238} - diff --git a/widget/android/AndroidBridge.cpp b/widget/android/AndroidBridge.cpp index 9e895fa1ff6c..8b46ec3f2405 100644 --- a/widget/android/AndroidBridge.cpp +++ b/widget/android/AndroidBridge.cpp @@ -130,7 +130,8 @@ AndroidBridge::Init(JNIEnv *jEnv, 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"); - jShowFilePicker = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showFilePicker", "(Ljava/lang/String;)Ljava/lang/String;"); + jShowFilePickerForExtensions = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showFilePickerForExtensions", "(Ljava/lang/String;)Ljava/lang/String;"); + jShowFilePickerForMimeType = (jmethodID) jEnv->GetStaticMethodID(jGeckoAppShellClass, "showFilePickerForMimeType", "(Ljava/lang/String;)Ljava/lang/String;"); 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"); @@ -737,20 +738,38 @@ AndroidBridge::GetDPI() } void -AndroidBridge::ShowFilePicker(nsAString& aFilePath, nsAString& aFilters) +AndroidBridge::ShowFilePickerForExtensions(nsAString& aFilePath, const nsAString& aExtensions) { - ALOG_BRIDGE("AndroidBridge::ShowFilePicker"); + ALOG_BRIDGE("AndroidBridge::ShowFilePickerForExtensions"); JNIEnv *env = GetJNIEnv(); if (!env) return; AutoLocalJNIFrame jniFrame(env); - jstring jstrFilers = env->NewString(nsPromiseFlatString(aFilters).get(), - aFilters.Length()); + jstring jstrFilers = env->NewString(nsPromiseFlatString(aExtensions).get(), + aExtensions.Length()); + jstring jstr = static_cast(env->CallStaticObjectMethod( + mGeckoAppShellClass, + jShowFilePickerForExtensions, jstrFilers)); + aFilePath.Assign(nsJNIString(jstr)); +} + +void +AndroidBridge::ShowFilePickerForMimeType(nsAString& aFilePath, const nsAString& aMimeType) +{ + ALOG_BRIDGE("AndroidBridge::ShowFilePickerForMimeType"); + + JNIEnv *env = GetJNIEnv(); + if (!env) + return; + + AutoLocalJNIFrame jniFrame(env); + jstring jstrFilers = env->NewString(nsPromiseFlatString(aMimeType).get(), + aMimeType.Length()); jstring jstr = static_cast(env->CallStaticObjectMethod( mGeckoAppShellClass, - jShowFilePicker, jstrFilers)); + jShowFilePickerForMimeType, jstrFilers)); aFilePath.Assign(nsJNIString(jstr)); } diff --git a/widget/android/AndroidBridge.h b/widget/android/AndroidBridge.h index 36a0ec1e6d20..73014fbfdd2c 100644 --- a/widget/android/AndroidBridge.h +++ b/widget/android/AndroidBridge.h @@ -240,7 +240,8 @@ public: int GetDPI(); - void ShowFilePicker(nsAString& aFilePath, nsAString& aFilters); + void ShowFilePickerForExtensions(nsAString& aFilePath, const nsAString& aExtensions); + void ShowFilePickerForMimeType(nsAString& aFilePath, const nsAString& aMimeType); void PerformHapticFeedback(bool aIsLongPress); @@ -469,7 +470,8 @@ protected: jmethodID jGetClipboardText; jmethodID jSetClipboardText; jmethodID jShowAlertNotification; - jmethodID jShowFilePicker; + jmethodID jShowFilePickerForExtensions; + jmethodID jShowFilePickerForMimeType; jmethodID jAlertsProgressListener_OnProgress; jmethodID jAlertsProgressListener_OnCancel; jmethodID jGetDpi; diff --git a/widget/android/nsFilePicker.cpp b/widget/android/nsFilePicker.cpp index 354178f9e255..92fab89eb6d9 100644 --- a/widget/android/nsFilePicker.cpp +++ b/widget/android/nsFilePicker.cpp @@ -51,12 +51,37 @@ NS_IMETHODIMP nsFilePicker::Init(nsIDOMWindow *parent, const nsAString& title, : NS_ERROR_NOT_IMPLEMENTED; } +NS_IMETHODIMP nsFilePicker::AppendFilters(PRInt32 aFilterMask) +{ + if (aFilterMask == (filterAudio | filterAll)) { + mMimeTypeFilter.AssignLiteral("audio/*"); + return NS_OK; + } + + if (aFilterMask == (filterImages | filterAll)) { + mMimeTypeFilter.AssignLiteral("image/*"); + return NS_OK; + } + + if (aFilterMask == (filterVideo | filterAll)) { + mMimeTypeFilter.AssignLiteral("video/*"); + return NS_OK; + } + + if (aFilterMask & filterAll) { + mMimeTypeFilter.AssignLiteral("*/*"); + return NS_OK; + } + + return nsBaseFilePicker::AppendFilters(aFilterMask); +} + NS_IMETHODIMP nsFilePicker::AppendFilter(const nsAString& /*title*/, const nsAString& filter) { - if (!mFilters.IsEmpty()) - mFilters.AppendLiteral(", "); - mFilters.Append(filter); + if (!mExtensionsFilter.IsEmpty()) + mExtensionsFilter.AppendLiteral(", "); + mExtensionsFilter.Append(filter); return NS_OK; } @@ -126,7 +151,15 @@ NS_IMETHODIMP nsFilePicker::Show(PRInt16 *_retval NS_OUTPARAM) return NS_ERROR_NOT_IMPLEMENTED; nsAutoString filePath; - mozilla::AndroidBridge::Bridge()->ShowFilePicker(filePath, mFilters); + if (mExtensionsFilter.IsEmpty() == mMimeTypeFilter.IsEmpty()) { + // Both filters or none of them are set. We want to show anything we can. + mozilla::AndroidBridge::Bridge()->ShowFilePickerForMimeType(filePath, NS_LITERAL_STRING("*/*")); + } else if (!mExtensionsFilter.IsEmpty()) { + mozilla::AndroidBridge::Bridge()->ShowFilePickerForExtensions(filePath, mExtensionsFilter); + } else { + mozilla::AndroidBridge::Bridge()->ShowFilePickerForMimeType(filePath, mMimeTypeFilter); + } + *_retval = EmptyString().Equals(filePath) ? nsIFilePicker::returnCancel : nsIFilePicker::returnOK; if (*_retval == nsIFilePicker::returnOK) diff --git a/widget/android/nsFilePicker.h b/widget/android/nsFilePicker.h index 13af1c9cddba..d7928723bf6d 100644 --- a/widget/android/nsFilePicker.h +++ b/widget/android/nsFilePicker.h @@ -48,6 +48,7 @@ public: NS_IMETHODIMP Init(nsIDOMWindow *parent, const nsAString& title, PRInt16 mode); + NS_IMETHOD AppendFilters(PRInt32 aFilterMask); NS_IMETHOD AppendFilter(const nsAString & aTitle, const nsAString & aFilter); NS_IMETHOD GetDefaultString(nsAString & aDefaultString); @@ -62,6 +63,7 @@ public: private: void InitNative(nsIWidget*, const nsAString&, PRInt16) {}; nsString mFilePath; - nsString mFilters; + nsString mExtensionsFilter; + nsString mMimeTypeFilter; }; #endif