7391ac5ba349667b937c9d7b9d1eb03b7f579ecf
[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.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;
15
16 import org.chromium.base.CalledByNative;
17 import org.chromium.base.JNINamespace;
18 import org.chromium.ui.R;
19
20 import java.io.File;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.List;
24
25 /**
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.
28  */
29 @JNINamespace("ui")
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";
39
40     private final long mNativeSelectFileDialog;
41     private List<String> mFileTypes;
42     private boolean mCapture;
43     private Uri mCameraOutputUri;
44
45     private SelectFileDialog(long nativeSelectFileDialog) {
46         mNativeSelectFileDialog = nativeSelectFileDialog;
47     }
48
49     /**
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
54      */
55     @CalledByNative
56     private void selectFile(String[] fileTypes, boolean capture, WindowAndroid window) {
57         mFileTypes = new ArrayList<String>(Arrays.asList(fileTypes));
58         mCapture = capture;
59
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);
67
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;
77         }
78
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
85             // chooser above.
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);
95             }
96         }
97
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);
104         }
105
106         chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS,
107                 extraIntents.toArray(new Intent[] { }));
108
109         chooser.putExtra(Intent.EXTRA_INTENT, getContentIntent);
110
111         if (!window.showIntent(chooser, this, R.string.low_memory_error)) {
112             onFileNotSelected();
113         }
114     }
115
116     /**
117      * Get a file for the image capture in the CAPTURE_IMAGE_DIRECTORY directory.
118      */
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;
126         }
127         File photoFile = new File(cameraDataDir.getAbsolutePath() +
128                 File.separator + System.currentTimeMillis() + ".jpg");
129         return photoFile;
130     }
131
132     /**
133      * @return the display name of the @code uri if present in the database
134      *  or an empty string otherwise.
135      */
136     private String resolveFileName(Uri uri, ContentResolver contentResolver) {
137         if (contentResolver == null || uri == null) return "";
138         Cursor cursor = null;
139         try {
140             cursor = contentResolver.query(uri, null, null, null, null);
141
142             if (cursor != null && cursor.getCount() >= 1) {
143                 cursor.moveToFirst();
144                 int index = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
145                 if (index > -1) return cursor.getString(index);
146             }
147         } catch (NullPointerException e) {
148             // Some android models don't handle the provider call correctly.
149             // see crbug.com/345393
150             return "";
151         } finally {
152             if (cursor != null) cursor.close();
153         }
154         return "";
155     }
156
157     /**
158      * Callback method to handle the intent results and pass on the path to the native
159      * SelectFileDialog.
160      * @param window The window that has access to the application activity.
161      * @param resultCode The result code whether the intent returned successfully.
162      * @param contentResolver The content resolver used to extract the path of the selected file.
163      * @param results The results of the requested intent.
164      */
165     @Override
166     public void onIntentCompleted(WindowAndroid window, int resultCode,
167             ContentResolver contentResolver, Intent results) {
168         if (resultCode != Activity.RESULT_OK) {
169             onFileNotSelected();
170             return;
171         }
172
173         if (results == null) {
174             // If we have a successful return but no data, then assume this is the camera returning
175             // the photo that we requested.
176             nativeOnFileSelected(mNativeSelectFileDialog, mCameraOutputUri.getPath(), "");
177
178             // Broadcast to the media scanner that there's a new photo on the device so it will
179             // show up right away in the gallery (rather than waiting until the next time the media
180             // scanner runs).
181             window.sendBroadcast(new Intent(
182                     Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, mCameraOutputUri));
183             return;
184         }
185
186         if (ContentResolver.SCHEME_FILE.equals(results.getData().getScheme())) {
187             nativeOnFileSelected(mNativeSelectFileDialog,
188                     results.getData().getSchemeSpecificPart(), "");
189             return;
190         }
191
192         if (ContentResolver.SCHEME_CONTENT.equals(results.getScheme())) {
193             nativeOnFileSelected(mNativeSelectFileDialog,
194                                  results.getData().toString(),
195                                  resolveFileName(results.getData(),
196                                                  contentResolver));
197             return;
198         }
199
200         onFileNotSelected();
201         window.showError(R.string.opening_file_error);
202     }
203
204     private void onFileNotSelected() {
205         nativeOnFileNotSelected(mNativeSelectFileDialog);
206     }
207
208     private boolean noSpecificType() {
209         // We use a single Intent to decide the type of the file chooser we display to the user,
210         // which means we can only give it a single type. If there are multiple accept types
211         // specified, we will fallback to a generic chooser (unless a capture parameter has been
212         // specified, in which case we'll try to satisfy that first.
213         return mFileTypes.size() != 1 || mFileTypes.contains(ANY_TYPES);
214     }
215
216     private boolean shouldShowTypes(String allTypes, String specificType) {
217         if (noSpecificType() || mFileTypes.contains(allTypes)) return true;
218         return acceptSpecificType(specificType);
219     }
220
221     private boolean shouldShowImageTypes() {
222         return shouldShowTypes(ALL_IMAGE_TYPES, IMAGE_TYPE);
223     }
224
225     private boolean shouldShowVideoTypes() {
226         return shouldShowTypes(ALL_VIDEO_TYPES, VIDEO_TYPE);
227     }
228
229     private boolean shouldShowAudioTypes() {
230         return shouldShowTypes(ALL_AUDIO_TYPES, AUDIO_TYPE);
231     }
232
233     private boolean acceptsSpecificType(String type) {
234         return mFileTypes.size() == 1 && TextUtils.equals(mFileTypes.get(0), type);
235     }
236
237     private boolean captureCamera() {
238         return mCapture && acceptsSpecificType(ALL_IMAGE_TYPES);
239     }
240
241     private boolean captureCamcorder() {
242         return mCapture && acceptsSpecificType(ALL_VIDEO_TYPES);
243     }
244
245     private boolean captureMicrophone() {
246         return mCapture && acceptsSpecificType(ALL_AUDIO_TYPES);
247     }
248
249     private boolean acceptSpecificType(String accept) {
250         for (String type : mFileTypes) {
251             if (type.startsWith(accept)) {
252                 return true;
253             }
254         }
255         return false;
256     }
257
258     @CalledByNative
259     private static SelectFileDialog create(long nativeSelectFileDialog) {
260         return new SelectFileDialog(nativeSelectFileDialog);
261     }
262
263     private native void nativeOnFileSelected(long nativeSelectFileDialogImpl,
264             String filePath, String displayName);
265     private native void nativeOnFileNotSelected(long nativeSelectFileDialogImpl);
266 }