blob: e3eda33a4fd6d17dcbf7c66c290a4d75d2c12927 [file] [log] [blame]
Michael Kolb8233fac2010-10-26 16:08:53 -07001/*
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
Bijan Amirzada41242f22014-03-21 12:12:18 -070017package com.android.browser;
Michael Kolb8233fac2010-10-26 16:08:53 -070018
19import android.app.Activity;
Ben Murdoche4c0cae2011-02-18 11:25:38 +000020import android.content.ActivityNotFoundException;
Vivek Sekharb54614f2014-05-01 19:03:37 -070021import android.content.ContentResolver;
Michael Kolb8233fac2010-10-26 16:08:53 -070022import android.content.Intent;
kaiyiz4b59d262013-07-30 10:41:38 +080023import android.database.Cursor;
Michael Kolb8233fac2010-10-26 16:08:53 -070024import android.net.Uri;
25import android.os.Environment;
26import android.provider.MediaStore;
27import android.webkit.ValueCallback;
Vivek Sekharb54614f2014-05-01 19:03:37 -070028import android.util.Log;
Ben Murdoche4c0cae2011-02-18 11:25:38 +000029import android.widget.Toast;
Michael Kolb8233fac2010-10-26 16:08:53 -070030
Bijan Amirzada41242f22014-03-21 12:12:18 -070031import com.android.browser.R;
32import com.android.browser.reflect.ReflectHelper;
Bijan Amirzada9b1e9882014-02-26 17:15:46 -080033
Michael Kolb8233fac2010-10-26 16:08:53 -070034import java.io.File;
35import java.util.Vector;
36
37/**
38 * Handle the file upload callbacks from WebView here
39 */
40public class UploadHandler {
41
Vivek Sekharb54614f2014-05-01 19:03:37 -070042 private static final String TAG = "UploadHandler";
Michael Kolb8233fac2010-10-26 16:08:53 -070043 /*
44 * The Object used to inform the WebView of the file to upload.
45 */
46 private ValueCallback<Uri> mUploadMessage;
Vivek Sekharb54614f2014-05-01 19:03:37 -070047 private ValueCallback<String[]> mUploadFilePaths;
Michael Kolb8233fac2010-10-26 16:08:53 -070048 private String mCameraFilePath;
49
Ben Murdoch51f6a2f2011-02-21 12:27:07 +000050 private boolean mHandled;
51 private boolean mCaughtActivityNotFoundException;
52
Michael Kolb8233fac2010-10-26 16:08:53 -070053 private Controller mController;
54
55 public UploadHandler(Controller controller) {
56 mController = controller;
57 }
58
59 String getFilePath() {
60 return mCameraFilePath;
61 }
62
Ben Murdoch51f6a2f2011-02-21 12:27:07 +000063 boolean handled() {
64 return mHandled;
65 }
66
Michael Kolb8233fac2010-10-26 16:08:53 -070067 void onResult(int resultCode, Intent intent) {
Ben Murdoch51f6a2f2011-02-21 12:27:07 +000068
69 if (resultCode == Activity.RESULT_CANCELED && mCaughtActivityNotFoundException) {
70 // Couldn't resolve an activity, we are going to try again so skip
71 // this result.
72 mCaughtActivityNotFoundException = false;
73 return;
74 }
75
Michael Kolb8233fac2010-10-26 16:08:53 -070076 Uri result = intent == null || resultCode != Activity.RESULT_OK ? null
77 : intent.getData();
78
79 // As we ask the camera to save the result of the user taking
80 // a picture, the camera application does not return anything other
81 // than RESULT_OK. So we need to check whether the file we expected
82 // was written to disk in the in the case that we
83 // did not get an intent returned but did get a RESULT_OK. If it was,
84 // we assume that this result has came back from the camera.
85 if (result == null && intent == null && resultCode == Activity.RESULT_OK) {
86 File cameraFile = new File(mCameraFilePath);
87 if (cameraFile.exists()) {
88 result = Uri.fromFile(cameraFile);
89 // Broadcast to the media scanner that we have a new photo
90 // so it will be added into the gallery for the user.
91 mController.getActivity().sendBroadcast(
92 new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result));
93 }
94 }
95
Vivek Sekharb54614f2014-05-01 19:03:37 -070096 // try to get local file path from uri
97 boolean hasGoodFilePath = false;
98 String filePath = null;
99 if (result != null) {
100 String scheme = result.getScheme();
101 if ("file".equals(scheme)) {
102 filePath = result.getPath();
103 hasGoodFilePath = filePath != null && !filePath.isEmpty();
104 } else if ("content".equals(scheme)) {
105 ContentResolver cr = mController.getActivity().getContentResolver();
106 String[] projection = {"_data"};
107 Cursor c = cr.query(result, projection, null, null, null);
108 try {
109 if (c != null && c.moveToFirst()) {
110 filePath = c.getString(0);
111 hasGoodFilePath = filePath != null && !filePath.isEmpty();
112 }
113 } finally {
114 if (c != null) {
115 c.close();
116 }
117 }
118 }
119 }
120
121 // Add for carrier feature - prevent uploading DRM type files.
Bijan Amirzadae75909d2014-05-06 14:18:54 -0700122 boolean drmUploadEnabled = mController.getContext().getResources().getBoolean(
123 R.bool.drm_uploads);
Vivek Sekharb54614f2014-05-01 19:03:37 -0700124 boolean isDRMFileType = false;
Bijan Amirzadae75909d2014-05-06 14:18:54 -0700125 if (drmUploadEnabled && filePath != null
Vivek Sekharb54614f2014-05-01 19:03:37 -0700126 && (filePath.endsWith(".fl") || filePath.endsWith(".dm")
127 || filePath.endsWith(".dcf") || filePath.endsWith(".dr")
128 || filePath.endsWith(".dd"))) {
129 isDRMFileType = true;
130 Toast.makeText(mController.getContext(), R.string.drm_file_unsupported,
131 Toast.LENGTH_LONG).show();
132 }
133
134 if (mUploadMessage != null) {
135 if (!isDRMFileType) {
136 mUploadMessage.onReceiveValue(result);
137 } else {
138 mUploadMessage.onReceiveValue(null);
139 }
140 }
141
142 if (mUploadFilePaths != null) {
143 if (hasGoodFilePath && !isDRMFileType) {
144 Log.d(TAG, "upload file path:" + filePath);
145 mUploadFilePaths.onReceiveValue(new String[]{filePath});
146 } else {
147 mUploadFilePaths.onReceiveValue(null);
148 }
kaiyiz4b59d262013-07-30 10:41:38 +0800149 }
150
Ben Murdoch51f6a2f2011-02-21 12:27:07 +0000151 mHandled = true;
152 mCaughtActivityNotFoundException = false;
Michael Kolb8233fac2010-10-26 16:08:53 -0700153 }
154
Ben Murdoch8cad4132012-01-11 10:56:43 +0000155 void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
Michael Kolb8233fac2010-10-26 16:08:53 -0700156
157 final String imageMimeType = "image/*";
158 final String videoMimeType = "video/*";
159 final String audioMimeType = "audio/*";
160 final String mediaSourceKey = "capture";
161 final String mediaSourceValueCamera = "camera";
162 final String mediaSourceValueFileSystem = "filesystem";
163 final String mediaSourceValueCamcorder = "camcorder";
164 final String mediaSourceValueMicrophone = "microphone";
165
Ben Murdoche4c0cae2011-02-18 11:25:38 +0000166 // According to the spec, media source can be 'filesystem' or 'camera' or 'camcorder'
Ben Murdoch8cad4132012-01-11 10:56:43 +0000167 // or 'microphone' and the default value should be 'filesystem'.
168 String mediaSource = mediaSourceValueFileSystem;
Michael Kolb8233fac2010-10-26 16:08:53 -0700169
Michael Kolb8233fac2010-10-26 16:08:53 -0700170 if (mUploadMessage != null) {
171 // Already a file picker operation in progress.
172 return;
173 }
174
175 mUploadMessage = uploadMsg;
176
177 // Parse the accept type.
178 String params[] = acceptType.split(";");
179 String mimeType = params[0];
180
Ben Murdoch8cad4132012-01-11 10:56:43 +0000181 if (capture.length() > 0) {
182 mediaSource = capture;
183 }
184
185 if (capture.equals(mediaSourceValueFileSystem)) {
186 // To maintain backwards compatibility with the previous implementation
187 // of the media capture API, if the value of the 'capture' attribute is
188 // "filesystem", we should examine the accept-type for a MIME type that
189 // may specify a different capture value.
190 for (String p : params) {
191 String[] keyValue = p.split("=");
192 if (keyValue.length == 2) {
193 // Process key=value parameters.
194 if (mediaSourceKey.equals(keyValue[0])) {
195 mediaSource = keyValue[1];
196 }
Michael Kolb8233fac2010-10-26 16:08:53 -0700197 }
198 }
199 }
200
Ben Murdoche4c0cae2011-02-18 11:25:38 +0000201 //Ensure it is not still set from a previous upload.
202 mCameraFilePath = null;
203
204 if (mimeType.equals(imageMimeType)) {
205 if (mediaSource.equals(mediaSourceValueCamera)) {
206 // Specified 'image/*' and requested the camera, so go ahead and launch the
207 // camera directly.
208 startActivity(createCameraIntent());
209 return;
Ben Murdoche4c0cae2011-02-18 11:25:38 +0000210 } else {
Ben Murdoch8cad4132012-01-11 10:56:43 +0000211 // Specified just 'image/*', capture=filesystem, or an invalid capture parameter.
212 // In all these cases we show a traditional picker filetered on accept type
213 // so launch an intent for both the Camera and image/* OPENABLE.
Ben Murdoche4c0cae2011-02-18 11:25:38 +0000214 Intent chooser = createChooserIntent(createCameraIntent());
215 chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(imageMimeType));
216 startActivity(chooser);
217 return;
218 }
219 } else if (mimeType.equals(videoMimeType)) {
220 if (mediaSource.equals(mediaSourceValueCamcorder)) {
221 // Specified 'video/*' and requested the camcorder, so go ahead and launch the
222 // camcorder directly.
223 startActivity(createCamcorderIntent());
224 return;
Ben Murdoch8cad4132012-01-11 10:56:43 +0000225 } else {
226 // Specified just 'video/*', capture=filesystem or an invalid capture parameter.
227 // In all these cases we show an intent for the traditional file picker, filtered
228 // on accept type so launch an intent for both camcorder and video/* OPENABLE.
Ben Murdoche4c0cae2011-02-18 11:25:38 +0000229 Intent chooser = createChooserIntent(createCamcorderIntent());
230 chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(videoMimeType));
231 startActivity(chooser);
232 return;
233 }
234 } else if (mimeType.equals(audioMimeType)) {
235 if (mediaSource.equals(mediaSourceValueMicrophone)) {
236 // Specified 'audio/*' and requested microphone, so go ahead and launch the sound
237 // recorder.
238 startActivity(createSoundRecorderIntent());
239 return;
Ben Murdoche4c0cae2011-02-18 11:25:38 +0000240 } else {
Ben Murdoch8cad4132012-01-11 10:56:43 +0000241 // Specified just 'audio/*', capture=filesystem of an invalid capture parameter.
242 // In all these cases so go ahead and launch an intent for both the sound
Ben Murdoche4c0cae2011-02-18 11:25:38 +0000243 // recorder and audio/* OPENABLE.
244 Intent chooser = createChooserIntent(createSoundRecorderIntent());
245 chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(audioMimeType));
246 startActivity(chooser);
247 return;
248 }
249 }
250
251 // No special handling based on the accept type was necessary, so trigger the default
252 // file upload chooser.
253 startActivity(createDefaultOpenableIntent());
254 }
255
Vivek Sekharb54614f2014-05-01 19:03:37 -0700256 void showFileChooser(ValueCallback<String[]> uploadFilePaths, String acceptTypes,
257 boolean capture) {
258
259 final String imageMimeType = "image/*";
260 final String videoMimeType = "video/*";
261 final String audioMimeType = "audio/*";
262
263 if (mUploadFilePaths != null) {
264 // Already a file picker operation in progress.
265 return;
266 }
267
268 mUploadFilePaths = uploadFilePaths;
269
270 // Parse the accept type.
271 String params[] = acceptTypes.split(";");
272 String mimeType = params[0];
273
274 // Ensure it is not still set from a previous upload.
275 mCameraFilePath = null;
276
277 if (mimeType.equals(imageMimeType)) {
278 if (capture) {
279 // Specified 'image/*' and capture=true, so go ahead and launch the
280 // camera directly.
281 startActivity(createCameraIntent());
282 return;
283 } else {
284 // Specified just 'image/*', capture=false, or no capture value.
285 // In all these cases we show a traditional picker filetered on accept type
286 // so launch an intent for both the Camera and image/* OPENABLE.
287 Intent chooser = createChooserIntent(createCameraIntent());
288 chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(imageMimeType));
289 startActivity(chooser);
290 return;
291 }
292 } else if (mimeType.equals(videoMimeType)) {
293 if (capture) {
294 // Specified 'video/*' and capture=true, so go ahead and launch the
295 // camcorder directly.
296 startActivity(createCamcorderIntent());
297 return;
298 } else {
299 // Specified just 'video/*', capture=false, or no capture value.
300 // In all these cases we show an intent for the traditional file picker, filtered
301 // on accept type so launch an intent for both camcorder and video/* OPENABLE.
302 Intent chooser = createChooserIntent(createCamcorderIntent());
303 chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(videoMimeType));
304 startActivity(chooser);
305 return;
306 }
307 } else if (mimeType.equals(audioMimeType)) {
308 if (capture) {
309 // Specified 'audio/*' and capture=true, so go ahead and launch the sound
310 // recorder.
311 startActivity(createSoundRecorderIntent());
312 return;
313 } else {
314 // Specified just 'audio/*', capture=false, or no capture value.
315 // In all these cases so go ahead and launch an intent for both the sound
316 // recorder and audio/* OPENABLE.
317 Intent chooser = createChooserIntent(createSoundRecorderIntent());
318 chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(audioMimeType));
319 startActivity(chooser);
320 return;
321 }
322 }
323
324 // No special handling based on the accept type was necessary, so trigger the default
325 // file upload chooser.
326 startActivity(createDefaultOpenableIntent());
327 }
328
329
Ben Murdoche4c0cae2011-02-18 11:25:38 +0000330 private void startActivity(Intent intent) {
331 try {
332 mController.getActivity().startActivityForResult(intent, Controller.FILE_SELECTED);
333 } catch (ActivityNotFoundException e) {
334 // No installed app was able to handle the intent that
335 // we sent, so fallback to the default file upload control.
336 try {
Ben Murdoch51f6a2f2011-02-21 12:27:07 +0000337 mCaughtActivityNotFoundException = true;
Ben Murdoche4c0cae2011-02-18 11:25:38 +0000338 mController.getActivity().startActivityForResult(createDefaultOpenableIntent(),
339 Controller.FILE_SELECTED);
340 } catch (ActivityNotFoundException e2) {
341 // Nothing can return us a file, so file upload is effectively disabled.
342 Toast.makeText(mController.getActivity(), R.string.uploads_disabled,
343 Toast.LENGTH_LONG).show();
344 }
345 }
346 }
347
348 private Intent createDefaultOpenableIntent() {
349 // Create and return a chooser with the default OPENABLE
350 // actions including the camera, camcorder and sound
351 // recorder where available.
Michael Kolb8233fac2010-10-26 16:08:53 -0700352 Intent i = new Intent(Intent.ACTION_GET_CONTENT);
353 i.addCategory(Intent.CATEGORY_OPENABLE);
Ben Murdoche4c0cae2011-02-18 11:25:38 +0000354 i.setType("*/*");
Michael Kolb8233fac2010-10-26 16:08:53 -0700355
Ben Murdoche4c0cae2011-02-18 11:25:38 +0000356 Intent chooser = createChooserIntent(createCameraIntent(), createCamcorderIntent(),
357 createSoundRecorderIntent());
358 chooser.putExtra(Intent.EXTRA_INTENT, i);
359 return chooser;
360 }
361
362 private Intent createChooserIntent(Intent... intents) {
363 Intent chooser = new Intent(Intent.ACTION_CHOOSER);
364 chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents);
365 chooser.putExtra(Intent.EXTRA_TITLE,
366 mController.getActivity().getResources()
367 .getString(R.string.choose_upload));
368 return chooser;
369 }
370
371 private Intent createOpenableIntent(String type) {
372 Intent i = new Intent(Intent.ACTION_GET_CONTENT);
373 i.addCategory(Intent.CATEGORY_OPENABLE);
374 i.setType(type);
375 return i;
376 }
377
378 private Intent createCameraIntent() {
Michael Kolb8233fac2010-10-26 16:08:53 -0700379 Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
380 File externalDataDir = Environment.getExternalStoragePublicDirectory(
381 Environment.DIRECTORY_DCIM);
382 File cameraDataDir = new File(externalDataDir.getAbsolutePath() +
383 File.separator + "browser-photos");
384 cameraDataDir.mkdirs();
385 mCameraFilePath = cameraDataDir.getAbsolutePath() + File.separator +
386 System.currentTimeMillis() + ".jpg";
387 cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(mCameraFilePath)));
Ben Murdoche4c0cae2011-02-18 11:25:38 +0000388 return cameraIntent;
Michael Kolb8233fac2010-10-26 16:08:53 -0700389 }
390
Ben Murdoche4c0cae2011-02-18 11:25:38 +0000391 private Intent createCamcorderIntent() {
392 return new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
393 }
394
395 private Intent createSoundRecorderIntent() {
396 return new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
Michael Kolb8233fac2010-10-26 16:08:53 -0700397 }
398
399}