Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / ui / android / java / src / org / chromium / ui / base / SelectFileDialog.java
1 // Copyright 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.
4
5 package org.chromium.ui.base;
6
7 import android.annotation.TargetApi;
8 import android.app.Activity;
9 import android.content.ClipData;
10 import android.content.ContentResolver;
11 import android.content.Intent;
12 import android.net.Uri;
13 import android.os.AsyncTask;
14 import android.os.Build;
15 import android.os.Environment;
16 import android.provider.MediaStore;
17 import android.text.TextUtils;
18
19 import org.chromium.base.CalledByNative;
20 import org.chromium.base.ContentUriUtils;
21 import org.chromium.base.JNINamespace;
22 import org.chromium.ui.R;
23
24 import java.io.File;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.List;
28
29 /**
30  * A dialog that is triggered from a file input field that allows a user to select a file based on
31  * a set of accepted file types. The path of the selected file is passed to the native dialog.
32  */
33 @JNINamespace("ui")
34 class SelectFileDialog implements WindowAndroid.IntentCallback{
35     private static final String IMAGE_TYPE = "image/";
36     private static final String VIDEO_TYPE = "video/";
37     private static final String AUDIO_TYPE = "audio/";
38     private static final String ALL_IMAGE_TYPES = IMAGE_TYPE + "*";
39     private static final String ALL_VIDEO_TYPES = VIDEO_TYPE + "*";
40     private static final String ALL_AUDIO_TYPES = AUDIO_TYPE + "*";
41     private static final String ANY_TYPES = "*/*";
42     private static final String CAPTURE_IMAGE_DIRECTORY = "browser-photos";
43
44     private final long mNativeSelectFileDialog;
45     private List<String> mFileTypes;
46     private boolean mCapture;
47     private Uri mCameraOutputUri;
48
49     private SelectFileDialog(long nativeSelectFileDialog) {
50         mNativeSelectFileDialog = nativeSelectFileDialog;
51     }
52
53     /**
54      * Creates and starts an intent based on the passed fileTypes and capture value.
55      * @param fileTypes MIME types requested (i.e. "image/*")
56      * @param capture The capture value as described in http://www.w3.org/TR/html-media-capture/
57      * @param multiple Whether it should be possible to select multiple files.
58      * @param window The WindowAndroid that can show intents
59      */
60     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
61     @CalledByNative
62     private void selectFile(
63             String[] fileTypes, boolean capture, boolean multiple, WindowAndroid window) {
64         mFileTypes = new ArrayList<String>(Arrays.asList(fileTypes));
65         mCapture = capture;
66
67         Intent chooser = new Intent(Intent.ACTION_CHOOSER);
68         Intent camera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
69         mCameraOutputUri = Uri.fromFile(getFileForImageCapture());
70         camera.putExtra(MediaStore.EXTRA_OUTPUT, mCameraOutputUri);
71         Intent camcorder = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
72         Intent soundRecorder = new Intent(
73                 MediaStore.Audio.Media.RECORD_SOUND_ACTION);
74
75         // Quick check - if the |capture| parameter is set and |fileTypes| has the appropriate MIME
76         // type, we should just launch the appropriate intent. Otherwise build up a chooser based on
77         // the accept type and then display that to the user.
78         if (captureCamera()) {
79             if (window.showIntent(camera, this, R.string.low_memory_error)) return;
80         } else if (captureCamcorder()) {
81             if (window.showIntent(camcorder, this, R.string.low_memory_error)) return;
82         } else if (captureMicrophone()) {
83             if (window.showIntent(soundRecorder, this, R.string.low_memory_error)) return;
84         }
85
86         Intent getContentIntent = new Intent(Intent.ACTION_GET_CONTENT);
87         getContentIntent.addCategory(Intent.CATEGORY_OPENABLE);
88
89         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && multiple)
90             getContentIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
91
92         ArrayList<Intent> extraIntents = new ArrayList<Intent>();
93         if (!noSpecificType()) {
94             // Create a chooser based on the accept type that was specified in the webpage. Note
95             // that if the web page specified multiple accept types, we will have built a generic
96             // chooser above.
97             if (shouldShowImageTypes()) {
98                 extraIntents.add(camera);
99                 getContentIntent.setType(ALL_IMAGE_TYPES);
100             } else if (shouldShowVideoTypes()) {
101                 extraIntents.add(camcorder);
102                 getContentIntent.setType(ALL_VIDEO_TYPES);
103             } else if (shouldShowAudioTypes()) {
104                 extraIntents.add(soundRecorder);
105                 getContentIntent.setType(ALL_AUDIO_TYPES);
106             }
107         }
108
109         if (extraIntents.isEmpty()) {
110             // We couldn't resolve an accept type, so fallback to a generic chooser.
111             getContentIntent.setType(ANY_TYPES);
112             extraIntents.add(camera);
113             extraIntents.add(camcorder);
114             extraIntents.add(soundRecorder);
115         }
116
117         chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS,
118                 extraIntents.toArray(new Intent[] { }));
119
120         chooser.putExtra(Intent.EXTRA_INTENT, getContentIntent);
121
122         if (!window.showIntent(chooser, this, R.string.low_memory_error)) {
123             onFileNotSelected();
124         }
125     }
126
127     /**
128      * Get a file for the image capture in the CAPTURE_IMAGE_DIRECTORY directory.
129      */
130     private File getFileForImageCapture() {
131         File externalDataDir = Environment.getExternalStoragePublicDirectory(
132                 Environment.DIRECTORY_DCIM);
133         File cameraDataDir = new File(externalDataDir.getAbsolutePath() +
134                 File.separator + CAPTURE_IMAGE_DIRECTORY);
135         if (!cameraDataDir.exists() && !cameraDataDir.mkdirs()) {
136             cameraDataDir = externalDataDir;
137         }
138         File photoFile = new File(cameraDataDir.getAbsolutePath() +
139                 File.separator + System.currentTimeMillis() + ".jpg");
140         return photoFile;
141     }
142
143     /**
144      * Callback method to handle the intent results and pass on the path to the native
145      * SelectFileDialog.
146      * @param window The window that has access to the application activity.
147      * @param resultCode The result code whether the intent returned successfully.
148      * @param contentResolver The content resolver used to extract the path of the selected file.
149      * @param results The results of the requested intent.
150      */
151     @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
152     @Override
153     public void onIntentCompleted(WindowAndroid window, int resultCode,
154             ContentResolver contentResolver, Intent results) {
155         if (resultCode != Activity.RESULT_OK) {
156             onFileNotSelected();
157             return;
158         }
159
160         if (results == null) {
161             // If we have a successful return but no data, then assume this is the camera returning
162             // the photo that we requested.
163             nativeOnFileSelected(mNativeSelectFileDialog, mCameraOutputUri.getPath(), "");
164
165             // Broadcast to the media scanner that there's a new photo on the device so it will
166             // show up right away in the gallery (rather than waiting until the next time the media
167             // scanner runs).
168             window.sendBroadcast(new Intent(
169                     Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, mCameraOutputUri));
170             return;
171         }
172
173         // Path for when EXTRA_ALLOW_MULTIPLE Intent extra has been defined. Each of the selected
174         // files will be shared as an entry on the Intent's ClipData. This functionality is only
175         // available in Android JellyBean MR2 and higher.
176         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 &&
177                 results.getData() == null &&
178                 results.getClipData() != null) {
179             ClipData clipData = results.getClipData();
180
181             int itemCount = clipData.getItemCount();
182             if (itemCount == 0) {
183                 onFileNotSelected();
184                 return;
185             }
186
187             Uri[] filePathArray = new Uri[itemCount];
188             for (int i = 0; i < itemCount; ++i) {
189                 filePathArray[i] = clipData.getItemAt(i).getUri();
190             }
191             GetDisplayNameTask task = new GetDisplayNameTask(contentResolver, true);
192             task.execute(filePathArray);
193             return;
194         }
195
196         if (ContentResolver.SCHEME_FILE.equals(results.getData().getScheme())) {
197             nativeOnFileSelected(mNativeSelectFileDialog,
198                     results.getData().getSchemeSpecificPart(), "");
199             return;
200         }
201
202         if (ContentResolver.SCHEME_CONTENT.equals(results.getScheme())) {
203             GetDisplayNameTask task = new GetDisplayNameTask(contentResolver, false);
204             task.execute(results.getData());
205             return;
206         }
207
208         onFileNotSelected();
209         window.showError(R.string.opening_file_error);
210     }
211
212     private void onFileNotSelected() {
213         nativeOnFileNotSelected(mNativeSelectFileDialog);
214     }
215
216     private boolean noSpecificType() {
217         // We use a single Intent to decide the type of the file chooser we display to the user,
218         // which means we can only give it a single type. If there are multiple accept types
219         // specified, we will fallback to a generic chooser (unless a capture parameter has been
220         // specified, in which case we'll try to satisfy that first.
221         return mFileTypes.size() != 1 || mFileTypes.contains(ANY_TYPES);
222     }
223
224     private boolean shouldShowTypes(String allTypes, String specificType) {
225         if (noSpecificType() || mFileTypes.contains(allTypes)) return true;
226         return acceptSpecificType(specificType);
227     }
228
229     private boolean shouldShowImageTypes() {
230         return shouldShowTypes(ALL_IMAGE_TYPES, IMAGE_TYPE);
231     }
232
233     private boolean shouldShowVideoTypes() {
234         return shouldShowTypes(ALL_VIDEO_TYPES, VIDEO_TYPE);
235     }
236
237     private boolean shouldShowAudioTypes() {
238         return shouldShowTypes(ALL_AUDIO_TYPES, AUDIO_TYPE);
239     }
240
241     private boolean acceptsSpecificType(String type) {
242         return mFileTypes.size() == 1 && TextUtils.equals(mFileTypes.get(0), type);
243     }
244
245     private boolean captureCamera() {
246         return mCapture && acceptsSpecificType(ALL_IMAGE_TYPES);
247     }
248
249     private boolean captureCamcorder() {
250         return mCapture && acceptsSpecificType(ALL_VIDEO_TYPES);
251     }
252
253     private boolean captureMicrophone() {
254         return mCapture && acceptsSpecificType(ALL_AUDIO_TYPES);
255     }
256
257     private boolean acceptSpecificType(String accept) {
258         for (String type : mFileTypes) {
259             if (type.startsWith(accept)) {
260                 return true;
261             }
262         }
263         return false;
264     }
265
266     private class GetDisplayNameTask extends AsyncTask<Uri, Void, String[]> {
267         String[] mFilePaths;
268         final ContentResolver mContentResolver;
269         final boolean mIsMultiple;
270
271         public GetDisplayNameTask(ContentResolver contentResolver, boolean isMultiple) {
272             mContentResolver = contentResolver;
273             mIsMultiple = isMultiple;
274         }
275
276         @Override
277         protected String[] doInBackground(Uri...uris) {
278             mFilePaths = new String[uris.length];
279             String[] displayNames = new String[uris.length];
280             for (int i = 0; i < uris.length; i++) {
281                 mFilePaths[i] = uris[i].toString();
282                 displayNames[i] = ContentUriUtils.getDisplayName(
283                         uris[i], mContentResolver, MediaStore.MediaColumns.DISPLAY_NAME);
284             }
285             return displayNames;
286         }
287
288         @Override
289         protected void onPostExecute(String[] result) {
290             if (mIsMultiple) {
291                 nativeOnMultipleFilesSelected(mNativeSelectFileDialog, mFilePaths, result);
292             } else {
293                 nativeOnFileSelected(mNativeSelectFileDialog, mFilePaths[0], result[0]);
294             }
295         }
296     }
297
298     @CalledByNative
299     private static SelectFileDialog create(long nativeSelectFileDialog) {
300         return new SelectFileDialog(nativeSelectFileDialog);
301     }
302
303     private native void nativeOnFileSelected(long nativeSelectFileDialogImpl,
304             String filePath, String displayName);
305     private native void nativeOnMultipleFilesSelected(long nativeSelectFileDialogImpl,
306             String[] filePathArray, String[] displayNameArray);
307     private native void nativeOnFileNotSelected(long nativeSelectFileDialogImpl);
308 }