1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.chromium.ui;
7 import android.app.Activity;
8 import android.content.ContentResolver;
9 import android.content.Intent;
10 import android.database.Cursor;
11 import android.net.Uri;
12 import android.os.Environment;
13 import android.provider.MediaStore;
14 import android.text.TextUtils;
17 import java.util.ArrayList;
18 import java.util.Arrays;
19 import java.util.List;
21 import org.chromium.base.CalledByNative;
22 import org.chromium.base.JNINamespace;
23 import org.chromium.ui.WindowAndroid;
26 * A dialog that is triggered from a file input field that allows a user to select a file based on
27 * a set of accepted file types. The path of the selected file is passed to the native dialog.
30 class SelectFileDialog implements WindowAndroid.IntentCallback{
31 private static final String IMAGE_TYPE = "image/";
32 private static final String VIDEO_TYPE = "video/";
33 private static final String AUDIO_TYPE = "audio/";
34 private static final String ALL_IMAGE_TYPES = IMAGE_TYPE + "*";
35 private static final String ALL_VIDEO_TYPES = VIDEO_TYPE + "*";
36 private static final String ALL_AUDIO_TYPES = AUDIO_TYPE + "*";
37 private static final String ANY_TYPES = "*/*";
38 private static final String CAPTURE_IMAGE_DIRECTORY = "browser-photos";
40 private final int mNativeSelectFileDialog;
41 private List<String> mFileTypes;
42 private boolean mCapture;
43 private Uri mCameraOutputUri;
45 private SelectFileDialog(int nativeSelectFileDialog) {
46 mNativeSelectFileDialog = nativeSelectFileDialog;
50 * Creates and starts an intent based on the passed fileTypes and capture value.
51 * @param fileTypes MIME types requested (i.e. "image/*")
52 * @param capture The capture value as described in http://www.w3.org/TR/html-media-capture/
53 * @param window The WindowAndroid that can show intents
56 private void selectFile(String[] fileTypes, boolean capture, WindowAndroid window) {
57 mFileTypes = new ArrayList<String>(Arrays.asList(fileTypes));
60 Intent chooser = new Intent(Intent.ACTION_CHOOSER);
61 Intent camera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
62 mCameraOutputUri = Uri.fromFile(getFileForImageCapture());
63 camera.putExtra(MediaStore.EXTRA_OUTPUT, mCameraOutputUri);
64 Intent camcorder = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
65 Intent soundRecorder = new Intent(
66 MediaStore.Audio.Media.RECORD_SOUND_ACTION);
68 // Quick check - if the |capture| parameter is set and |fileTypes| has the appropriate MIME
69 // type, we should just launch the appropriate intent. Otherwise build up a chooser based on
70 // the accept type and then display that to the user.
71 if (captureCamera()) {
72 if (window.showIntent(camera, this, R.string.low_memory_error)) return;
73 } else if (captureCamcorder()) {
74 if (window.showIntent(camcorder, this, R.string.low_memory_error)) return;
75 } else if (captureMicrophone()) {
76 if (window.showIntent(soundRecorder, this, R.string.low_memory_error)) return;
79 Intent getContentIntent = new Intent(Intent.ACTION_GET_CONTENT);
80 getContentIntent.addCategory(Intent.CATEGORY_OPENABLE);
81 ArrayList<Intent> extraIntents = new ArrayList<Intent>();
82 if (!noSpecificType()) {
83 // Create a chooser based on the accept type that was specified in the webpage. Note
84 // that if the web page specified multiple accept types, we will have built a generic
86 if (shouldShowImageTypes()) {
87 extraIntents.add(camera);
88 getContentIntent.setType(ALL_IMAGE_TYPES);
89 } else if (shouldShowVideoTypes()) {
90 extraIntents.add(camcorder);
91 getContentIntent.setType(ALL_VIDEO_TYPES);
92 } else if (shouldShowAudioTypes()) {
93 extraIntents.add(soundRecorder);
94 getContentIntent.setType(ALL_AUDIO_TYPES);
98 if (extraIntents.isEmpty()) {
99 // We couldn't resolve an accept type, so fallback to a generic chooser.
100 getContentIntent.setType(ANY_TYPES);
101 extraIntents.add(camera);
102 extraIntents.add(camcorder);
103 extraIntents.add(soundRecorder);
106 chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS,
107 extraIntents.toArray(new Intent[] { }));
109 chooser.putExtra(Intent.EXTRA_INTENT, getContentIntent);
111 if (!window.showIntent(chooser, this, R.string.low_memory_error)) {
117 * Get a file for the image capture in the CAPTURE_IMAGE_DIRECTORY directory.
119 private File getFileForImageCapture() {
120 File externalDataDir = Environment.getExternalStoragePublicDirectory(
121 Environment.DIRECTORY_DCIM);
122 File cameraDataDir = new File(externalDataDir.getAbsolutePath() +
123 File.separator + CAPTURE_IMAGE_DIRECTORY);
124 if (!cameraDataDir.exists() && !cameraDataDir.mkdirs()) {
125 cameraDataDir = externalDataDir;
127 File photoFile = new File(cameraDataDir.getAbsolutePath() +
128 File.separator + System.currentTimeMillis() + ".jpg");
133 * Callback method to handle the intent results and pass on the path to the native
135 * @param window The window that has access to the application activity.
136 * @param resultCode The result code whether the intent returned successfully.
137 * @param contentResolver The content resolver used to extract the path of the selected file.
138 * @param results The results of the requested intent.
141 public void onIntentCompleted(WindowAndroid window, int resultCode,
142 ContentResolver contentResolver, Intent results) {
143 if (resultCode != Activity.RESULT_OK) {
147 boolean success = false;
148 if (results == null) {
149 // If we have a successful return but no data, then assume this is the camera returning
150 // the photo that we requested.
151 nativeOnFileSelected(mNativeSelectFileDialog, mCameraOutputUri.getPath());
154 // Broadcast to the media scanner that there's a new photo on the device so it will
155 // show up right away in the gallery (rather than waiting until the next time the media
157 window.sendBroadcast(new Intent(
158 Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, mCameraOutputUri));
160 // We get back a content:// URI from the system if the user picked a file from the
161 // gallery. The ContentView has functionality that will convert that content:// URI to
162 // a file path on disk that Chromium understands.
163 Cursor c = contentResolver.query(results.getData(),
164 new String[] { MediaStore.MediaColumns.DATA }, null, null, null);
166 if (c.getCount() == 1) {
168 String path = c.getString(0);
170 // Not all providers support the MediaStore.DATA column. For example,
171 // Gallery3D (com.android.gallery3d.provider) does not support it for
172 // Picasa Web Album images.
173 nativeOnFileSelected(mNativeSelectFileDialog, path);
182 window.showError(R.string.opening_file_error);
186 private void onFileNotSelected() {
187 nativeOnFileNotSelected(mNativeSelectFileDialog);
190 private boolean noSpecificType() {
191 // We use a single Intent to decide the type of the file chooser we display to the user,
192 // which means we can only give it a single type. If there are multiple accept types
193 // specified, we will fallback to a generic chooser (unless a capture parameter has been
194 // specified, in which case we'll try to satisfy that first.
195 return mFileTypes.size() != 1 || mFileTypes.contains(ANY_TYPES);
198 private boolean shouldShowTypes(String allTypes, String specificType) {
199 if (noSpecificType() || mFileTypes.contains(allTypes)) return true;
200 return acceptSpecificType(specificType);
203 private boolean shouldShowImageTypes() {
204 return shouldShowTypes(ALL_IMAGE_TYPES,IMAGE_TYPE);
207 private boolean shouldShowVideoTypes() {
208 return shouldShowTypes(ALL_VIDEO_TYPES, VIDEO_TYPE);
211 private boolean shouldShowAudioTypes() {
212 return shouldShowTypes(ALL_AUDIO_TYPES, AUDIO_TYPE);
215 private boolean acceptsSpecificType(String type) {
216 return mFileTypes.size() == 1 && TextUtils.equals(mFileTypes.get(0), type);
219 private boolean captureCamera() {
220 return mCapture && acceptsSpecificType(ALL_IMAGE_TYPES);
223 private boolean captureCamcorder() {
224 return mCapture && acceptsSpecificType(ALL_VIDEO_TYPES);
227 private boolean captureMicrophone() {
228 return mCapture && acceptsSpecificType(ALL_AUDIO_TYPES);
231 private boolean acceptSpecificType(String accept) {
232 for (String type : mFileTypes) {
233 if (type.startsWith(accept)) {
241 private static SelectFileDialog create(int nativeSelectFileDialog) {
242 return new SelectFileDialog(nativeSelectFileDialog);
245 private native void nativeOnFileSelected(int nativeSelectFileDialogImpl,
247 private native void nativeOnFileNotSelected(int nativeSelectFileDialogImpl);