Skip to content

Commit b773ae4

Browse files
KenCorbettJrKenCorbettTruepicerisu
authored
feat: add camera intent with file input capture (#1609)
* Allow image file input to be from camera * Reverting some irrellevant formatting changes * Removing the openFileChooser functions as they are no longer necessary * Update templates/project/res/xml/opener_paths.xml * Code review feedback * Adding license to provider paths xml file * Adding a comment describing the proper use of the core cordova file provider * Adding the ability to query the android.media.action.IMAGE_CAPTURE intent action * Only including a cache path * Applying code review feedback --------- Co-authored-by: Ken Corbett <[email protected]> Co-authored-by: エリス <[email protected]>
1 parent ebf0b10 commit b773ae4

File tree

3 files changed

+125
-24
lines changed

3 files changed

+125
-24
lines changed

framework/src/org/apache/cordova/engine/SystemWebChromeClient.java

+86-24
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,22 @@ Licensed to the Apache Software Foundation (ASF) under one
1818
*/
1919
package org.apache.cordova.engine;
2020

21+
import java.io.IOException;
22+
import java.io.File;
2123
import java.util.Arrays;
22-
import android.annotation.TargetApi;
24+
import java.util.ArrayList;
25+
import java.util.List;
2326
import android.app.Activity;
27+
import android.content.ClipData;
2428
import android.content.Context;
2529
import android.content.ActivityNotFoundException;
2630
import android.content.Intent;
31+
import android.content.pm.PackageManager;
2732
import android.net.Uri;
28-
import android.os.Build;
33+
import android.provider.MediaStore;
2934
import android.view.Gravity;
3035
import android.view.View;
3136
import android.view.ViewGroup.LayoutParams;
32-
import android.webkit.ConsoleMessage;
3337
import android.webkit.GeolocationPermissions.Callback;
3438
import android.webkit.JsPromptResult;
3539
import android.webkit.JsResult;
@@ -41,6 +45,7 @@ Licensed to the Apache Software Foundation (ASF) under one
4145
import android.widget.LinearLayout;
4246
import android.widget.ProgressBar;
4347
import android.widget.RelativeLayout;
48+
import androidx.core.content.FileProvider;
4449

4550
import org.apache.cordova.CordovaDialogsHelper;
4651
import org.apache.cordova.CordovaPlugin;
@@ -212,53 +217,110 @@ public View getVideoLoadingProgressView() {
212217
}
213218

214219
@Override
215-
public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> filePathsCallback, final WebChromeClient.FileChooserParams fileChooserParams) {
220+
public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> filePathsCallback,
221+
final WebChromeClient.FileChooserParams fileChooserParams) {
222+
Intent fileIntent = fileChooserParams.createIntent();
223+
216224
// Check if multiple-select is specified
217225
Boolean selectMultiple = false;
218226
if (fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE) {
219227
selectMultiple = true;
220228
}
221-
Intent intent = fileChooserParams.createIntent();
222-
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, selectMultiple);
223-
229+
fileIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, selectMultiple);
230+
224231
// Uses Intent.EXTRA_MIME_TYPES to pass multiple mime types.
225232
String[] acceptTypes = fileChooserParams.getAcceptTypes();
226233
if (acceptTypes.length > 1) {
227-
intent.setType("*/*"); // Accept all, filter mime types by Intent.EXTRA_MIME_TYPES.
228-
intent.putExtra(Intent.EXTRA_MIME_TYPES, acceptTypes);
234+
fileIntent.setType("*/*"); // Accept all, filter mime types by Intent.EXTRA_MIME_TYPES.
235+
fileIntent.putExtra(Intent.EXTRA_MIME_TYPES, acceptTypes);
236+
}
237+
238+
// Image from camera intent
239+
Uri tempUri = null;
240+
Intent captureIntent = null;
241+
if (fileChooserParams.isCaptureEnabled()) {
242+
captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
243+
Context context = parentEngine.getView().getContext();
244+
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
245+
&& captureIntent.resolveActivity(context.getPackageManager()) != null) {
246+
try {
247+
File tempFile = createTempFile(context);
248+
LOG.d(LOG_TAG, "Temporary photo capture file: " + tempFile);
249+
tempUri = createUriForFile(context, tempFile);
250+
LOG.d(LOG_TAG, "Temporary photo capture URI: " + tempUri);
251+
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, tempUri);
252+
} catch (IOException e) {
253+
LOG.e(LOG_TAG, "Unable to create temporary file for photo capture", e);
254+
captureIntent = null;
255+
}
256+
} else {
257+
LOG.w(LOG_TAG, "Device does not support photo capture");
258+
captureIntent = null;
259+
}
260+
}
261+
final Uri captureUri = tempUri;
262+
263+
// Chooser intent
264+
Intent chooserIntent = Intent.createChooser(fileIntent, null);
265+
if (captureIntent != null) {
266+
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { captureIntent });
229267
}
268+
230269
try {
270+
LOG.i(LOG_TAG, "Starting intent for file chooser");
231271
parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
232272
@Override
233273
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
274+
// Handle result
234275
Uri[] result = null;
235-
if (resultCode == Activity.RESULT_OK && intent != null) {
236-
if (intent.getClipData() != null) {
237-
// handle multiple-selected files
238-
final int numSelectedFiles = intent.getClipData().getItemCount();
239-
result = new Uri[numSelectedFiles];
240-
for (int i = 0; i < numSelectedFiles; i++) {
241-
result[i] = intent.getClipData().getItemAt(i).getUri();
242-
LOG.d(LOG_TAG, "Receive file chooser URL: " + result[i]);
276+
if (resultCode == Activity.RESULT_OK) {
277+
List<Uri> uris = new ArrayList<Uri>();
278+
279+
if (intent != null && intent.getData() != null) { // single file
280+
LOG.v(LOG_TAG, "Adding file (single): " + intent.getData());
281+
uris.add(intent.getData());
282+
} else if (captureUri != null) { // camera
283+
LOG.v(LOG_TAG, "Adding camera capture: " + captureUri);
284+
uris.add(captureUri);
285+
} else if (intent != null && intent.getClipData() != null) { // multiple files
286+
ClipData clipData = intent.getClipData();
287+
int count = clipData.getItemCount();
288+
for (int i = 0; i < count; i++) {
289+
Uri uri = clipData.getItemAt(i).getUri();
290+
LOG.v(LOG_TAG, "Adding file (multiple): " + uri);
291+
if (uri != null) {
292+
uris.add(uri);
293+
}
243294
}
244295
}
245-
else if (intent.getData() != null) {
246-
// handle single-selected file
247-
result = WebChromeClient.FileChooserParams.parseResult(resultCode, intent);
248-
LOG.d(LOG_TAG, "Receive file chooser URL: " + result);
296+
297+
if (!uris.isEmpty()) {
298+
LOG.d(LOG_TAG, "Receive file chooser URL: " + uris.toString());
299+
result = uris.toArray(new Uri[uris.size()]);
249300
}
250301
}
251302
filePathsCallback.onReceiveValue(result);
252303
}
253-
}, intent, FILECHOOSER_RESULTCODE);
304+
}, chooserIntent, FILECHOOSER_RESULTCODE);
254305
} catch (ActivityNotFoundException e) {
255-
LOG.w("No activity found to handle file chooser intent.", e);
306+
LOG.w(LOG_TAG, "No activity found to handle file chooser intent.", e);
256307
filePathsCallback.onReceiveValue(null);
257308
}
258309
return true;
259310
}
260311

261-
@Override
312+
private File createTempFile(Context context) throws IOException {
313+
// Create an image file name
314+
File tempFile = File.createTempFile("temp", ".jpg", context.getCacheDir());
315+
return tempFile;
316+
}
317+
318+
private Uri createUriForFile(Context context, File tempFile) throws IOException {
319+
String appId = context.getPackageName();
320+
Uri uri = FileProvider.getUriForFile(context, appId + ".cdv.core.file.provider", tempFile);
321+
return uri;
322+
}
323+
262324
public void onPermissionRequest(final PermissionRequest request) {
263325
LOG.d(LOG_TAG, "onPermissionRequest: " + Arrays.toString(request.getResources()));
264326
request.grant(request.getResources());

templates/project/AndroidManifest.xml

+9
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,14 @@
4646
<category android:name="android.intent.category.LAUNCHER" />
4747
</intent-filter>
4848
</activity>
49+
<provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.cdv.core.file.provider" android:exported="false" android:grantUriPermissions="true">
50+
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/cdv_core_file_provider_paths" />
51+
</provider>
4952
</application>
53+
54+
<queries>
55+
<intent>
56+
<action android:name="android.media.action.IMAGE_CAPTURE" />
57+
</intent>
58+
</queries>
5059
</manifest>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
Licensed to the Apache Software Foundation (ASF) under one
4+
or more contributor license agreements. See the NOTICE file
5+
distributed with this work for additional information
6+
regarding copyright ownership. The ASF licenses this file
7+
to you under the Apache License, Version 2.0 (the
8+
"License"); you may not use this file except in compliance
9+
with the License. You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing,
14+
software distributed under the License is distributed on an
15+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
KIND, either express or implied. See the License for the
17+
specific language governing permissions and limitations
18+
under the License.
19+
-->
20+
<!--
21+
Note: This File provider should only be used by the Cordova core
22+
itself and should not be used for responding to Intents because
23+
it will expose the app's private data folders.
24+
25+
For more information about FileProviders see:
26+
https://developer.android.com/reference/androidx/core/content/FileProvider
27+
-->
28+
<paths xmlns:android="http://schemas.android.com/apk/res/android">
29+
<cache-path name="cache" path="." />
30+
</paths>

0 commit comments

Comments
 (0)