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