This is an automated email from the ASF dual-hosted git repository. erisu pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/cordova-android.git
The following commit(s) were added to refs/heads/master by this push: new b773ae48 feat: add camera intent with file input capture (#1609) b773ae48 is described below commit b773ae48f44a8610366c81bda509d821a6d949ef Author: Ken Corbett <kenneth.corb...@gmail.com> AuthorDate: Thu May 9 00:30:49 2024 -0400 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 <k...@truepic.com> Co-authored-by: エリス <er...@users.noreply.github.com> --- .../cordova/engine/SystemWebChromeClient.java | 110 ++++++++++++++++----- templates/project/AndroidManifest.xml | 9 ++ .../res/xml/cdv_core_file_provider_paths.xml | 30 ++++++ 3 files changed, 125 insertions(+), 24 deletions(-) diff --git a/framework/src/org/apache/cordova/engine/SystemWebChromeClient.java b/framework/src/org/apache/cordova/engine/SystemWebChromeClient.java index cad098e4..da3ed931 100755 --- a/framework/src/org/apache/cordova/engine/SystemWebChromeClient.java +++ b/framework/src/org/apache/cordova/engine/SystemWebChromeClient.java @@ -18,18 +18,22 @@ */ package org.apache.cordova.engine; +import java.io.IOException; +import java.io.File; import java.util.Arrays; -import android.annotation.TargetApi; +import java.util.ArrayList; +import java.util.List; import android.app.Activity; +import android.content.ClipData; import android.content.Context; import android.content.ActivityNotFoundException; import android.content.Intent; +import android.content.pm.PackageManager; import android.net.Uri; -import android.os.Build; +import android.provider.MediaStore; import android.view.Gravity; import android.view.View; import android.view.ViewGroup.LayoutParams; -import android.webkit.ConsoleMessage; import android.webkit.GeolocationPermissions.Callback; import android.webkit.JsPromptResult; import android.webkit.JsResult; @@ -41,6 +45,7 @@ import android.webkit.PermissionRequest; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.RelativeLayout; +import androidx.core.content.FileProvider; import org.apache.cordova.CordovaDialogsHelper; import org.apache.cordova.CordovaPlugin; @@ -212,53 +217,110 @@ public class SystemWebChromeClient extends WebChromeClient { } @Override - public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> filePathsCallback, final WebChromeClient.FileChooserParams fileChooserParams) { + public boolean onShowFileChooser(WebView webView, final ValueCallback<Uri[]> filePathsCallback, + final WebChromeClient.FileChooserParams fileChooserParams) { + Intent fileIntent = fileChooserParams.createIntent(); + // Check if multiple-select is specified Boolean selectMultiple = false; if (fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE) { selectMultiple = true; } - Intent intent = fileChooserParams.createIntent(); - intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, selectMultiple); - + fileIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, selectMultiple); + // Uses Intent.EXTRA_MIME_TYPES to pass multiple mime types. String[] acceptTypes = fileChooserParams.getAcceptTypes(); if (acceptTypes.length > 1) { - intent.setType("*/*"); // Accept all, filter mime types by Intent.EXTRA_MIME_TYPES. - intent.putExtra(Intent.EXTRA_MIME_TYPES, acceptTypes); + fileIntent.setType("*/*"); // Accept all, filter mime types by Intent.EXTRA_MIME_TYPES. + fileIntent.putExtra(Intent.EXTRA_MIME_TYPES, acceptTypes); + } + + // Image from camera intent + Uri tempUri = null; + Intent captureIntent = null; + if (fileChooserParams.isCaptureEnabled()) { + captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + Context context = parentEngine.getView().getContext(); + if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) + && captureIntent.resolveActivity(context.getPackageManager()) != null) { + try { + File tempFile = createTempFile(context); + LOG.d(LOG_TAG, "Temporary photo capture file: " + tempFile); + tempUri = createUriForFile(context, tempFile); + LOG.d(LOG_TAG, "Temporary photo capture URI: " + tempUri); + captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, tempUri); + } catch (IOException e) { + LOG.e(LOG_TAG, "Unable to create temporary file for photo capture", e); + captureIntent = null; + } + } else { + LOG.w(LOG_TAG, "Device does not support photo capture"); + captureIntent = null; + } + } + final Uri captureUri = tempUri; + + // Chooser intent + Intent chooserIntent = Intent.createChooser(fileIntent, null); + if (captureIntent != null) { + chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] { captureIntent }); } + try { + LOG.i(LOG_TAG, "Starting intent for file chooser"); parentEngine.cordova.startActivityForResult(new CordovaPlugin() { @Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { + // Handle result Uri[] result = null; - if (resultCode == Activity.RESULT_OK && intent != null) { - if (intent.getClipData() != null) { - // handle multiple-selected files - final int numSelectedFiles = intent.getClipData().getItemCount(); - result = new Uri[numSelectedFiles]; - for (int i = 0; i < numSelectedFiles; i++) { - result[i] = intent.getClipData().getItemAt(i).getUri(); - LOG.d(LOG_TAG, "Receive file chooser URL: " + result[i]); + if (resultCode == Activity.RESULT_OK) { + List<Uri> uris = new ArrayList<Uri>(); + + if (intent != null && intent.getData() != null) { // single file + LOG.v(LOG_TAG, "Adding file (single): " + intent.getData()); + uris.add(intent.getData()); + } else if (captureUri != null) { // camera + LOG.v(LOG_TAG, "Adding camera capture: " + captureUri); + uris.add(captureUri); + } else if (intent != null && intent.getClipData() != null) { // multiple files + ClipData clipData = intent.getClipData(); + int count = clipData.getItemCount(); + for (int i = 0; i < count; i++) { + Uri uri = clipData.getItemAt(i).getUri(); + LOG.v(LOG_TAG, "Adding file (multiple): " + uri); + if (uri != null) { + uris.add(uri); + } } } - else if (intent.getData() != null) { - // handle single-selected file - result = WebChromeClient.FileChooserParams.parseResult(resultCode, intent); - LOG.d(LOG_TAG, "Receive file chooser URL: " + result); + + if (!uris.isEmpty()) { + LOG.d(LOG_TAG, "Receive file chooser URL: " + uris.toString()); + result = uris.toArray(new Uri[uris.size()]); } } filePathsCallback.onReceiveValue(result); } - }, intent, FILECHOOSER_RESULTCODE); + }, chooserIntent, FILECHOOSER_RESULTCODE); } catch (ActivityNotFoundException e) { - LOG.w("No activity found to handle file chooser intent.", e); + LOG.w(LOG_TAG, "No activity found to handle file chooser intent.", e); filePathsCallback.onReceiveValue(null); } return true; } - @Override + private File createTempFile(Context context) throws IOException { + // Create an image file name + File tempFile = File.createTempFile("temp", ".jpg", context.getCacheDir()); + return tempFile; + } + + private Uri createUriForFile(Context context, File tempFile) throws IOException { + String appId = context.getPackageName(); + Uri uri = FileProvider.getUriForFile(context, appId + ".cdv.core.file.provider", tempFile); + return uri; + } + public void onPermissionRequest(final PermissionRequest request) { LOG.d(LOG_TAG, "onPermissionRequest: " + Arrays.toString(request.getResources())); request.grant(request.getResources()); diff --git a/templates/project/AndroidManifest.xml b/templates/project/AndroidManifest.xml index 06b7fd6c..e7bc7a45 100644 --- a/templates/project/AndroidManifest.xml +++ b/templates/project/AndroidManifest.xml @@ -46,5 +46,14 @@ <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> + <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.cdv.core.file.provider" android:exported="false" android:grantUriPermissions="true"> + <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/cdv_core_file_provider_paths" /> + </provider> </application> + + <queries> + <intent> + <action android:name="android.media.action.IMAGE_CAPTURE" /> + </intent> + </queries> </manifest> diff --git a/templates/project/res/xml/cdv_core_file_provider_paths.xml b/templates/project/res/xml/cdv_core_file_provider_paths.xml new file mode 100644 index 00000000..fc1bd315 --- /dev/null +++ b/templates/project/res/xml/cdv_core_file_provider_paths.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +--> +<!-- + Note: This File provider should only be used by the Cordova core + itself and should not be used for responding to Intents because + it will expose the app's private data folders. + + For more information about FileProviders see: + https://developer.android.com/reference/androidx/core/content/FileProvider +--> +<paths xmlns:android="http://schemas.android.com/apk/res/android"> + <cache-path name="cache" path="." /> +</paths> --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@cordova.apache.org For additional commands, e-mail: commits-h...@cordova.apache.org