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
17package com.android.browser;
18
19import android.app.Activity;
20import android.content.ActivityNotFoundException;
21import android.content.ClipData;
22import android.content.Intent;
23import android.net.Uri;
24import android.provider.MediaStore;
25import android.support.v4.content.FileProvider;
26import android.webkit.WebChromeClient.FileChooserParams;
27import android.webkit.ValueCallback;
28import android.widget.Toast;
29
30import java.io.File;
31
32/**
33 * Handle the file upload. This does not support selecting multiple files yet.
34 */
35public class UploadHandler {
36    private final static String IMAGE_MIME_TYPE = "image/*";
37    private final static String VIDEO_MIME_TYPE = "video/*";
38    private final static String AUDIO_MIME_TYPE = "audio/*";
39
40    private final static String FILE_PROVIDER_AUTHORITY = "com.android.browser-classic.file";
41
42    /*
43     * The Object used to inform the WebView of the file to upload.
44     */
45    private ValueCallback<Uri[]> mUploadMessage;
46
47    private boolean mHandled;
48    private Controller mController;
49    private FileChooserParams mParams;
50    private Uri mCapturedMedia;
51
52    public UploadHandler(Controller controller) {
53        mController = controller;
54    }
55
56    boolean handled() {
57        return mHandled;
58    }
59
60    void onResult(int resultCode, Intent intent) {
61        Uri[] uris;
62        // As the media capture is always supported, we can't use
63        // FileChooserParams.parseResult().
64        uris = parseResult(resultCode, intent);
65        mUploadMessage.onReceiveValue(uris);
66        mHandled = true;
67    }
68
69    void openFileChooser(ValueCallback<Uri[]> callback, FileChooserParams fileChooserParams) {
70
71        if (mUploadMessage != null) {
72            // Already a file picker operation in progress.
73            return;
74        }
75
76        mUploadMessage = callback;
77        mParams = fileChooserParams;
78        Intent[] captureIntents = createCaptureIntent();
79        assert(captureIntents != null && captureIntents.length > 0);
80        Intent intent = null;
81        // Go to the media capture directly if capture is specified, this is the
82        // preferred way.
83        if (fileChooserParams.isCaptureEnabled() && captureIntents.length == 1) {
84            intent = captureIntents[0];
85        } else {
86            intent = new Intent(Intent.ACTION_CHOOSER);
87            intent.putExtra(Intent.EXTRA_INITIAL_INTENTS, captureIntents);
88            intent.putExtra(Intent.EXTRA_INTENT, fileChooserParams.createIntent());
89        }
90        startActivity(intent);
91    }
92
93    private Uri[] parseResult(int resultCode, Intent intent) {
94        if (resultCode == Activity.RESULT_CANCELED) {
95            return null;
96        }
97        Uri result = intent == null || resultCode != Activity.RESULT_OK ? null
98                : intent.getData();
99
100        // As we ask the camera to save the result of the user taking
101        // a picture, the camera application does not return anything other
102        // than RESULT_OK. So we need to check whether the file we expected
103        // was written to disk in the in the case that we
104        // did not get an intent returned but did get a RESULT_OK. If it was,
105        // we assume that this result has came back from the camera.
106        if (result == null && intent == null && resultCode == Activity.RESULT_OK
107                && mCapturedMedia != null) {
108            result = mCapturedMedia;
109        }
110
111        Uri[] uris = null;
112        if (result != null) {
113            uris = new Uri[1];
114            uris[0] = result;
115        }
116        return uris;
117    }
118
119    private void startActivity(Intent intent) {
120        try {
121            mController.getActivity().startActivityForResult(intent, Controller.FILE_SELECTED);
122        } catch (ActivityNotFoundException e) {
123            // No installed app was able to handle the intent that
124            // we sent, so file upload is effectively disabled.
125            Toast.makeText(mController.getActivity(), R.string.uploads_disabled,
126                    Toast.LENGTH_LONG).show();
127        }
128    }
129
130    private Intent[] createCaptureIntent() {
131        String mimeType = "*/*";
132        String[] acceptTypes = mParams.getAcceptTypes();
133        if ( acceptTypes != null && acceptTypes.length > 0) {
134            mimeType = acceptTypes[0];
135        }
136        Intent[] intents;
137        if (mimeType.equals(IMAGE_MIME_TYPE)) {
138            intents = new Intent[1];
139            intents[0] = createCameraIntent(createTempFileContentUri(".jpg"));
140        } else if (mimeType.equals(VIDEO_MIME_TYPE)) {
141            intents = new Intent[1];
142            intents[0] = createCamcorderIntent();
143        } else if (mimeType.equals(AUDIO_MIME_TYPE)) {
144            intents = new Intent[1];
145            intents[0] = createSoundRecorderIntent();
146        } else {
147            intents = new Intent[3];
148            intents[0] = createCameraIntent(createTempFileContentUri(".jpg"));
149            intents[1] = createCamcorderIntent();
150            intents[2] = createSoundRecorderIntent();
151        }
152        return intents;
153    }
154
155    private Uri createTempFileContentUri(String suffix) {
156        try {
157            File mediaPath = new File(mController.getActivity().getFilesDir(), "captured_media");
158            if (!mediaPath.exists() && !mediaPath.mkdir()) {
159                throw new RuntimeException("Folder cannot be created.");
160            }
161            File mediaFile = File.createTempFile(
162                    String.valueOf(System.currentTimeMillis()), suffix, mediaPath);
163            return FileProvider.getUriForFile(mController.getActivity(),
164                    FILE_PROVIDER_AUTHORITY, mediaFile);
165        } catch (java.io.IOException e) {
166            throw new RuntimeException(e);
167        }
168    }
169
170    private Intent createCameraIntent(Uri contentUri) {
171        if (contentUri == null) throw new IllegalArgumentException();
172        mCapturedMedia = contentUri;
173        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
174        intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION |
175                  Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
176        intent.putExtra(MediaStore.EXTRA_OUTPUT, mCapturedMedia);
177        intent.setClipData(ClipData.newUri(mController.getActivity().getContentResolver(),
178                FILE_PROVIDER_AUTHORITY, mCapturedMedia));
179        return intent;
180    }
181
182    private Intent createCamcorderIntent() {
183        return new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
184    }
185
186    private Intent createSoundRecorderIntent() {
187        return new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
188    }
189}
190