SelectFileDialog.java revision 558790d6acca3451cf3a6b497803a5f07d0bec58
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. 4 5package org.chromium.ui; 6 7import android.app.Activity; 8import android.content.ContentResolver; 9import android.content.Intent; 10import android.database.Cursor; 11import android.net.Uri; 12import android.os.Environment; 13import android.provider.MediaStore; 14import android.text.TextUtils; 15 16import java.io.File; 17import java.util.ArrayList; 18import java.util.Arrays; 19import java.util.List; 20 21import org.chromium.base.CalledByNative; 22import org.chromium.base.JNINamespace; 23import org.chromium.ui.WindowAndroid; 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") 30class 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 int mNativeSelectFileDialog; 41 private List<String> mFileTypes; 42 private boolean mCapture; 43 private Uri mCameraOutputUri; 44 45 private SelectFileDialog(int 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 String lowMemoryError = window.getContext().getString(R.string.low_memory_error); 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, lowMemoryError)) return; 74 } else if (captureCamcorder()) { 75 if (window.showIntent(camcorder, this, lowMemoryError)) return; 76 } else if (captureMicrophone()) { 77 if (window.showIntent(soundRecorder, this, lowMemoryError)) 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, lowMemoryError)) onFileNotSelected(); 113 } 114 115 /** 116 * Get a file for the image capture in the CAPTURE_IMAGE_DIRECTORY directory. 117 */ 118 private File getFileForImageCapture() { 119 File externalDataDir = Environment.getExternalStoragePublicDirectory( 120 Environment.DIRECTORY_DCIM); 121 File cameraDataDir = new File(externalDataDir.getAbsolutePath() + 122 File.separator + CAPTURE_IMAGE_DIRECTORY); 123 if (!cameraDataDir.exists() && !cameraDataDir.mkdirs()) { 124 cameraDataDir = externalDataDir; 125 } 126 File photoFile = new File(cameraDataDir.getAbsolutePath() + 127 File.separator + System.currentTimeMillis() + ".jpg"); 128 return photoFile; 129 } 130 131 /** 132 * Callback method to handle the intent results and pass on the path to the native 133 * SelectFileDialog. 134 * @param window The window that has access to the application activity. 135 * @param resultCode The result code whether the intent returned successfully. 136 * @param contentResolver The content resolver used to extract the path of the selected file. 137 * @param results The results of the requested intent. 138 */ 139 @Override 140 public void onIntentCompleted(WindowAndroid window, int resultCode, 141 ContentResolver contentResolver, Intent results) { 142 if (resultCode != Activity.RESULT_OK) { 143 onFileNotSelected(); 144 return; 145 } 146 boolean success = false; 147 if (results == null) { 148 // If we have a successful return but no data, then assume this is the camera returning 149 // the photo that we requested. 150 nativeOnFileSelected(mNativeSelectFileDialog, mCameraOutputUri.getPath()); 151 success = true; 152 153 // Broadcast to the media scanner that there's a new photo on the device so it will 154 // show up right away in the gallery (rather than waiting until the next time the media 155 // scanner runs). 156 window.sendBroadcast(new Intent( 157 Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, mCameraOutputUri)); 158 } else { 159 // We get back a content:// URI from the system if the user picked a file from the 160 // gallery. The ContentView has functionality that will convert that content:// URI to 161 // a file path on disk that Chromium understands. 162 Cursor c = contentResolver.query(results.getData(), 163 new String[] { MediaStore.MediaColumns.DATA }, null, null, null); 164 if (c != null) { 165 if (c.getCount() == 1) { 166 c.moveToFirst(); 167 String path = c.getString(0); 168 if (path != null) { 169 // Not all providers support the MediaStore.DATA column. For example, 170 // Gallery3D (com.android.gallery3d.provider) does not support it for 171 // Picasa Web Album images. 172 nativeOnFileSelected(mNativeSelectFileDialog, path); 173 success = true; 174 } 175 } 176 c.close(); 177 } 178 } 179 if (!success) { 180 onFileNotSelected(); 181 window.showError(R.string.opening_file_error); 182 } 183 } 184 185 private void onFileNotSelected() { 186 nativeOnFileNotSelected(mNativeSelectFileDialog); 187 } 188 189 private boolean noSpecificType() { 190 // We use a single Intent to decide the type of the file chooser we display to the user, 191 // which means we can only give it a single type. If there are multiple accept types 192 // specified, we will fallback to a generic chooser (unless a capture parameter has been 193 // specified, in which case we'll try to satisfy that first. 194 return mFileTypes.size() != 1 || mFileTypes.contains(ANY_TYPES); 195 } 196 197 private boolean shouldShowTypes(String allTypes, String specificType) { 198 if (noSpecificType() || mFileTypes.contains(allTypes)) return true; 199 return acceptSpecificType(specificType); 200 } 201 202 private boolean shouldShowImageTypes() { 203 return shouldShowTypes(ALL_IMAGE_TYPES,IMAGE_TYPE); 204 } 205 206 private boolean shouldShowVideoTypes() { 207 return shouldShowTypes(ALL_VIDEO_TYPES, VIDEO_TYPE); 208 } 209 210 private boolean shouldShowAudioTypes() { 211 return shouldShowTypes(ALL_AUDIO_TYPES, AUDIO_TYPE); 212 } 213 214 private boolean acceptsSpecificType(String type) { 215 return mFileTypes.size() == 1 && TextUtils.equals(mFileTypes.get(0), type); 216 } 217 218 private boolean captureCamera() { 219 return mCapture && acceptsSpecificType(ALL_IMAGE_TYPES); 220 } 221 222 private boolean captureCamcorder() { 223 return mCapture && acceptsSpecificType(ALL_VIDEO_TYPES); 224 } 225 226 private boolean captureMicrophone() { 227 return mCapture && acceptsSpecificType(ALL_AUDIO_TYPES); 228 } 229 230 private boolean acceptSpecificType(String accept) { 231 for (String type : mFileTypes) { 232 if (type.startsWith(accept)) { 233 return true; 234 } 235 } 236 return false; 237 } 238 239 @CalledByNative 240 private static SelectFileDialog create(int nativeSelectFileDialog) { 241 return new SelectFileDialog(nativeSelectFileDialog); 242 } 243 244 private native void nativeOnFileSelected(int nativeSelectFileDialogImpl, 245 String filePath); 246 private native void nativeOnFileNotSelected(int nativeSelectFileDialogImpl); 247} 248