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

Reply via email to