1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.browser; 18 19 import android.app.Activity; 20 import android.content.ActivityNotFoundException; 21 import android.content.Intent; 22 import android.net.Uri; 23 import android.os.Environment; 24 import android.provider.MediaStore; 25 import android.webkit.ValueCallback; 26 import android.widget.Toast; 27 28 import java.io.File; 29 import java.util.Vector; 30 31 /** 32 * Handle the file upload callbacks from WebView here 33 */ 34 public class UploadHandler { 35 36 /* 37 * The Object used to inform the WebView of the file to upload. 38 */ 39 private ValueCallback<Uri> mUploadMessage; 40 private String mCameraFilePath; 41 42 private boolean mHandled; 43 private boolean mCaughtActivityNotFoundException; 44 45 private Controller mController; 46 47 public UploadHandler(Controller controller) { 48 mController = controller; 49 } 50 51 String getFilePath() { 52 return mCameraFilePath; 53 } 54 55 boolean handled() { 56 return mHandled; 57 } 58 59 void onResult(int resultCode, Intent intent) { 60 61 if (resultCode == Activity.RESULT_CANCELED && mCaughtActivityNotFoundException) { 62 // Couldn't resolve an activity, we are going to try again so skip 63 // this result. 64 mCaughtActivityNotFoundException = false; 65 return; 66 } 67 68 Uri result = intent == null || resultCode != Activity.RESULT_OK ? null 69 : intent.getData(); 70 71 // As we ask the camera to save the result of the user taking 72 // a picture, the camera application does not return anything other 73 // than RESULT_OK. So we need to check whether the file we expected 74 // was written to disk in the in the case that we 75 // did not get an intent returned but did get a RESULT_OK. If it was, 76 // we assume that this result has came back from the camera. 77 if (result == null && intent == null && resultCode == Activity.RESULT_OK) { 78 File cameraFile = new File(mCameraFilePath); 79 if (cameraFile.exists()) { 80 result = Uri.fromFile(cameraFile); 81 // Broadcast to the media scanner that we have a new photo 82 // so it will be added into the gallery for the user. 83 mController.getActivity().sendBroadcast( 84 new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result)); 85 } 86 } 87 88 mUploadMessage.onReceiveValue(result); 89 mHandled = true; 90 mCaughtActivityNotFoundException = false; 91 } 92 93 void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) { 94 95 final String imageMimeType = "image/*"; 96 final String videoMimeType = "video/*"; 97 final String audioMimeType = "audio/*"; 98 final String mediaSourceKey = "capture"; 99 final String mediaSourceValueCamera = "camera"; 100 final String mediaSourceValueFileSystem = "filesystem"; 101 final String mediaSourceValueCamcorder = "camcorder"; 102 final String mediaSourceValueMicrophone = "microphone"; 103 104 // According to the spec, media source can be 'filesystem' or 'camera' or 'camcorder' 105 // or 'microphone' and the default value should be 'filesystem'. 106 String mediaSource = mediaSourceValueFileSystem; 107 108 if (mUploadMessage != null) { 109 // Already a file picker operation in progress. 110 return; 111 } 112 113 mUploadMessage = uploadMsg; 114 115 // Parse the accept type. 116 String params[] = acceptType.split(";"); 117 String mimeType = params[0]; 118 119 if (capture.length() > 0) { 120 mediaSource = capture; 121 } 122 123 if (capture.equals(mediaSourceValueFileSystem)) { 124 // To maintain backwards compatibility with the previous implementation 125 // of the media capture API, if the value of the 'capture' attribute is 126 // "filesystem", we should examine the accept-type for a MIME type that 127 // may specify a different capture value. 128 for (String p : params) { 129 String[] keyValue = p.split("="); 130 if (keyValue.length == 2) { 131 // Process key=value parameters. 132 if (mediaSourceKey.equals(keyValue[0])) { 133 mediaSource = keyValue[1]; 134 } 135 } 136 } 137 } 138 139 //Ensure it is not still set from a previous upload. 140 mCameraFilePath = null; 141 142 if (mimeType.equals(imageMimeType)) { 143 if (mediaSource.equals(mediaSourceValueCamera)) { 144 // Specified 'image/*' and requested the camera, so go ahead and launch the 145 // camera directly. 146 startActivity(createCameraIntent()); 147 return; 148 } else { 149 // Specified just 'image/*', capture=filesystem, or an invalid capture parameter. 150 // In all these cases we show a traditional picker filetered on accept type 151 // so launch an intent for both the Camera and image/* OPENABLE. 152 Intent chooser = createChooserIntent(createCameraIntent()); 153 chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(imageMimeType)); 154 startActivity(chooser); 155 return; 156 } 157 } else if (mimeType.equals(videoMimeType)) { 158 if (mediaSource.equals(mediaSourceValueCamcorder)) { 159 // Specified 'video/*' and requested the camcorder, so go ahead and launch the 160 // camcorder directly. 161 startActivity(createCamcorderIntent()); 162 return; 163 } else { 164 // Specified just 'video/*', capture=filesystem or an invalid capture parameter. 165 // In all these cases we show an intent for the traditional file picker, filtered 166 // on accept type so launch an intent for both camcorder and video/* OPENABLE. 167 Intent chooser = createChooserIntent(createCamcorderIntent()); 168 chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(videoMimeType)); 169 startActivity(chooser); 170 return; 171 } 172 } else if (mimeType.equals(audioMimeType)) { 173 if (mediaSource.equals(mediaSourceValueMicrophone)) { 174 // Specified 'audio/*' and requested microphone, so go ahead and launch the sound 175 // recorder. 176 startActivity(createSoundRecorderIntent()); 177 return; 178 } else { 179 // Specified just 'audio/*', capture=filesystem of an invalid capture parameter. 180 // In all these cases so go ahead and launch an intent for both the sound 181 // recorder and audio/* OPENABLE. 182 Intent chooser = createChooserIntent(createSoundRecorderIntent()); 183 chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(audioMimeType)); 184 startActivity(chooser); 185 return; 186 } 187 } 188 189 // No special handling based on the accept type was necessary, so trigger the default 190 // file upload chooser. 191 startActivity(createDefaultOpenableIntent()); 192 } 193 194 private void startActivity(Intent intent) { 195 try { 196 mController.getActivity().startActivityForResult(intent, Controller.FILE_SELECTED); 197 } catch (ActivityNotFoundException e) { 198 // No installed app was able to handle the intent that 199 // we sent, so fallback to the default file upload control. 200 try { 201 mCaughtActivityNotFoundException = true; 202 mController.getActivity().startActivityForResult(createDefaultOpenableIntent(), 203 Controller.FILE_SELECTED); 204 } catch (ActivityNotFoundException e2) { 205 // Nothing can return us a file, so file upload is effectively disabled. 206 Toast.makeText(mController.getActivity(), R.string.uploads_disabled, 207 Toast.LENGTH_LONG).show(); 208 } 209 } 210 } 211 212 private Intent createDefaultOpenableIntent() { 213 // Create and return a chooser with the default OPENABLE 214 // actions including the camera, camcorder and sound 215 // recorder where available. 216 Intent i = new Intent(Intent.ACTION_GET_CONTENT); 217 i.addCategory(Intent.CATEGORY_OPENABLE); 218 i.setType("*/*"); 219 220 Intent chooser = createChooserIntent(createCameraIntent(), createCamcorderIntent(), 221 createSoundRecorderIntent()); 222 chooser.putExtra(Intent.EXTRA_INTENT, i); 223 return chooser; 224 } 225 226 private Intent createChooserIntent(Intent... intents) { 227 Intent chooser = new Intent(Intent.ACTION_CHOOSER); 228 chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents); 229 chooser.putExtra(Intent.EXTRA_TITLE, 230 mController.getActivity().getResources() 231 .getString(R.string.choose_upload)); 232 return chooser; 233 } 234 235 private Intent createOpenableIntent(String type) { 236 Intent i = new Intent(Intent.ACTION_GET_CONTENT); 237 i.addCategory(Intent.CATEGORY_OPENABLE); 238 i.setType(type); 239 return i; 240 } 241 242 private Intent createCameraIntent() { 243 Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 244 File externalDataDir = Environment.getExternalStoragePublicDirectory( 245 Environment.DIRECTORY_DCIM); 246 File cameraDataDir = new File(externalDataDir.getAbsolutePath() + 247 File.separator + "browser-photos"); 248 cameraDataDir.mkdirs(); 249 mCameraFilePath = cameraDataDir.getAbsolutePath() + File.separator + 250 System.currentTimeMillis() + ".jpg"; 251 cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(mCameraFilePath))); 252 return cameraIntent; 253 } 254 255 private Intent createCamcorderIntent() { 256 return new Intent(MediaStore.ACTION_VIDEO_CAPTURE); 257 } 258 259 private Intent createSoundRecorderIntent() { 260 return new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION); 261 } 262 263 } 264