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

This commit is contained in:
Mounir Lamouri 2012-03-18 17:15:56 +01:00
parent aff853b60c
commit 9cfc298e5a
13 changed files with 361 additions and 210 deletions

View File

@ -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) {

View File

@ -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<String> mFilePickerResult = new SynchronousQueue<String>();
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<PromptService.PromptListItem> items, ArrayList<Intent> aIntents) {
PackageManager pm = mAppContext.getPackageManager();
final List<ResolveInfo> lri =
pm.queryIntentActivityOptions(GeckoApp.mAppContext.getComponentName(), null, intent, 0);
if (lri == null) {
return 0;
}
for (int i=0; i<lri.size(); i++) {
final ResolveInfo ri = lri.get(i);
Intent rintent = new Intent(intent);
rintent.setComponent(new ComponentName(
ri.activityInfo.applicationInfo.packageName,
ri.activityInfo.name));
PromptService.PromptListItem item = new PromptService.PromptListItem(ri.loadLabel(pm).toString());
item.icon = ri.loadIcon(pm);
items.add(item);
aIntents.add(rintent);
}
return lri.size();
}
private int AddFilePickingActivities(ArrayList<PromptService.PromptListItem> aItems, String aType, ArrayList<Intent> 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<Intent> aIntents) {
ArrayList<PromptService.PromptListItem> items = new ArrayList<PromptService.PromptListItem>();
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<String> mFilePickerResult = new SynchronousQueue<String>();
public String showFilePicker(String aMimeType) {
ArrayList<Intent> intents = new ArrayList<Intent>();
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) {

View File

@ -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<String> sPromptQueue = null;
public static void registerGeckoEventListener(String event, GeckoEventListener listener) {
if (mEventListeners == null)
mEventListeners = new HashMap<String, ArrayList<GeckoEventListener>>();
@ -1687,8 +1689,6 @@ public class GeckoAppShell
String type = geckoObject.getString("type");
if (type.equals("Prompt:Show")) {
if (sPromptQueue == null)
sPromptQueue = new SynchronousQueue<String>();
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);
}

View File

@ -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<String> mPromptQueue = new SynchronousQueue<String>();
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<PromptListItem> {
@ -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;

View File

@ -119,3 +119,8 @@
<!ENTITY abouthome_no_top_sites "You do not have any top sites yet. Tap the Title Bar to start browsing.">
<!ENTITY abouthome_about_sync "Set up Firefox Sync to access bookmarks, history and tabs from your other devices">
<!ENTITY abouthome_sync_bold_name "Firefox Sync">
<!ENTITY filepicker_title "Choose File">
<!ENTITY filepicker_audio_title "Choose or record a sound">
<!ENTITY filepicker_image_title "Choose or take a picture">
<!ENTITY filepicker_video_title "Choose or record a video">

View File

@ -123,4 +123,9 @@
<string name="abouthome_no_top_sites">&abouthome_no_top_sites;</string>
<string name="abouthome_about_sync">&abouthome_about_sync;</string>
<string name="abouthome_sync_bold_name">&abouthome_sync_bold_name;</string>
<string name="filepicker_title">&filepicker_title;</string>
<string name="filepicker_audio_title">&filepicker_audio_title;</string>
<string name="filepicker_image_title">&filepicker_image_title;</string>
<string name="filepicker_video_title">&filepicker_video_title;</string>
</resources>

View File

@ -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 <me@kylehuey.com>
* Fabrice Desré <fabrice@mozilla.com>
*
* 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);

View File

@ -71,7 +71,6 @@ EXTRA_COMPONENTS = \
FormAutoComplete.js \
LoginManagerPrompter.js \
BlocklistPrompt.js \
CapturePicker.js \
$(NULL)
ifdef MOZ_SAFE_BROWSING

View File

@ -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}

View File

@ -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<jstring>(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<jstring>(env->CallStaticObjectMethod(
mGeckoAppShellClass,
jShowFilePicker, jstrFilers));
jShowFilePickerForMimeType, jstrFilers));
aFilePath.Assign(nsJNIString(jstr));
}

View File

@ -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;

View File

@ -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)

View File

@ -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