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.Intent;
22import android.net.Uri;
23import android.os.Environment;
24import android.provider.MediaStore;
25import android.webkit.ValueCallback;
26import android.widget.Toast;
27
28import java.io.File;
29import java.util.Vector;
30
31/**
32 * Handle the file upload callbacks from WebView here
33 */
34public 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