This is an automated email from the ASF dual-hosted git repository.

normanbreau pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/cordova-plugin-media-capture.git


The following commit(s) were added to refs/heads/master by this push:
     new 7dc2f87  Fix(android): save media capture to File Provider (#302)
7dc2f87 is described below

commit 7dc2f87c6dba6835812c514e338337fdabd03b8d
Author: Mallat <29370498+xaviermal...@users.noreply.github.com>
AuthorDate: Thu Dec 19 15:05:46 2024 +0100

    Fix(android): save media capture to File Provider (#302)
    
    * Fix(android): save media capture to File Provider
    - Save image/audio/video to FileProvider instead of MediaStore external 
storage
    
    * Chore(android): save media to plugin's cache folder
    
    ---------
    
    Co-authored-by: xaviermallat <xavier.mal...@inetum.com>
---
 plugin.xml                                         |  14 ++
 src/android/Capture.java                           | 147 +++++++++++----------
 src/android/FileProvider.java                      |  20 +++
 .../res/xml/mediacapture_provider_paths.xml        |  21 +++
 4 files changed, 130 insertions(+), 72 deletions(-)

diff --git a/plugin.xml b/plugin.xml
index b3c6715..2f01cba 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -76,6 +76,18 @@ xmlns:android="http://schemas.android.com/apk/res/android";
             </feature>
         </config-file>
 
+        <config-file target="AndroidManifest.xml" parent="application">
+            <provider
+                android:name="org.apache.cordova.mediacapture.FileProvider"
+                
android:authorities="${applicationId}.cordova.plugin.mediacapture.provider"
+                android:exported="false"
+                android:grantUriPermissions="true" >
+                <meta-data
+                    android:name="android.support.FILE_PROVIDER_PATHS"
+                    android:resource="@xml/mediacapture_provider_paths"/>
+            </provider>
+        </config-file>
+
         <config-file target="AndroidManifest.xml" parent="/*">
             <uses-permission android:name="android.permission.RECORD_AUDIO" />
             <uses-permission 
android:name="android.permission.READ_EXTERNAL_STORAGE" 
android:maxSdkVersion="32" />
@@ -85,6 +97,8 @@ xmlns:android="http://schemas.android.com/apk/res/android";
         <source-file src="src/android/Capture.java" 
target-dir="src/org/apache/cordova/mediacapture" />
         <source-file src="src/android/FileHelper.java" 
target-dir="src/org/apache/cordova/mediacapture" />
         <source-file src="src/android/PendingRequests.java" 
target-dir="src/org/apache/cordova/mediacapture" />
+        <source-file src="src/android/FileProvider.java" 
target-dir="src/org/apache/cordova/mediacapture" />
+        <source-file src="src/android/res/xml/mediacapture_provider_paths.xml" 
target-dir="res/xml" />
 
         <js-module src="www/android/init.js" name="init">
             <runs />
diff --git a/src/android/Capture.java b/src/android/Capture.java
index c915c39..f589615 100644
--- a/src/android/Capture.java
+++ b/src/android/Capture.java
@@ -23,9 +23,11 @@ import java.io.IOException;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Date;
 
 import org.apache.cordova.CallbackContext;
 import org.apache.cordova.CordovaPlugin;
@@ -93,15 +95,11 @@ public class Capture extends CordovaPlugin {
     private final PendingRequests pendingRequests = new PendingRequests();
 
     private int numPics;                            // Number of pictures 
before capture activity
-    private Uri imageUri;
+    private String audioAbsolutePath;
+    private String imageAbsolutePath;
+    private String videoAbsolutePath;
 
-//    public void setContext(Context mCtx)
-//    {
-//        if (CordovaInterface.class.isInstance(mCtx))
-//            cordova = (CordovaInterface) mCtx;
-//        else
-//            LOG.d(LOG_TAG, "ERROR: You must use the CordovaInterface for 
this to work correctly. Please implement it in your activity");
-//    }
+    private String applicationId;
 
     @Override
     protected void pluginInitialize() {
@@ -132,6 +130,8 @@ public class Capture extends CordovaPlugin {
 
     @Override
     public boolean execute(String action, JSONArray args, CallbackContext 
callbackContext) throws JSONException {
+        this.applicationId = cordova.getContext().getPackageName();
+
         if (action.equals("getFormatData")) {
             JSONObject obj = getFormatData(args.getString(0), 
args.getString(1));
             callbackContext.success(obj);
@@ -142,14 +142,11 @@ public class Capture extends CordovaPlugin {
 
         if (action.equals("captureAudio")) {
             this.captureAudio(pendingRequests.createRequest(CAPTURE_AUDIO, 
options, callbackContext));
-        }
-        else if (action.equals("captureImage")) {
+        } else if (action.equals("captureImage")) {
             this.captureImage(pendingRequests.createRequest(CAPTURE_IMAGE, 
options, callbackContext));
-        }
-        else if (action.equals("captureVideo")) {
+        } else if (action.equals("captureVideo")) {
             this.captureVideo(pendingRequests.createRequest(CAPTURE_VIDEO, 
options, callbackContext));
-        }
-        else {
+        } else {
             return false;
         }
 
@@ -175,18 +172,16 @@ public class Capture extends CordovaPlugin {
 
         // If the mimeType isn't set the rest will fail
         // so let's see if we can determine it.
-        if (mimeType == null || mimeType.equals("") || 
"null".equals(mimeType)) {
+        if (mimeType == null || mimeType.isEmpty() || "null".equals(mimeType)) 
{
             mimeType = FileHelper.getMimeType(fileUrl, cordova);
         }
         LOG.d(LOG_TAG, "Mime type = " + mimeType);
 
         if (mimeType.equals(IMAGE_JPEG) || filePath.endsWith(".jpg")) {
             obj = getImageData(fileUrl, obj);
-        }
-        else if (Arrays.asList(AUDIO_TYPES).contains(mimeType)) {
+        } else if (Arrays.asList(AUDIO_TYPES).contains(mimeType)) {
             obj = getAudioVideoData(filePath, obj, false);
-        }
-        else if (mimeType.equals(VIDEO_3GPP) || mimeType.equals(VIDEO_MP4)) {
+        } else if (mimeType.equals(VIDEO_3GPP) || mimeType.equals(VIDEO_MP4)) {
             obj = getAudioVideoData(filePath, obj, true);
         }
         return obj;
@@ -242,7 +237,7 @@ public class Capture extends CordovaPlugin {
             }
         }
 
-        boolean isMissingPermissions = missingPermissions.size() > 0;
+        boolean isMissingPermissions = !missingPermissions.isEmpty();
         if (isMissingPermissions) {
             String[] missing = missingPermissions.toArray(new 
String[missingPermissions.size()]);
             PermissionHelper.requestPermissions(this, req.requestCode, 
missing);
@@ -262,6 +257,14 @@ public class Capture extends CordovaPlugin {
         return isMissingPermissions(req, cameraPermissions);
     }
 
+    private String getTempDirectoryPath() {
+        File cache = new File(cordova.getActivity().getCacheDir(), 
"org.apache.cordova.mediacapture");
+
+        // Create the cache directory if it doesn't exist
+        cache.mkdirs();
+        return cache.getAbsolutePath();
+    }
+
     /**
      * Sets up an intent to capture audio.  Result handled by 
onActivityResult()
      */
@@ -270,6 +273,16 @@ public class Capture extends CordovaPlugin {
 
         try {
             Intent intent = new 
Intent(android.provider.MediaStore.Audio.Media.RECORD_SOUND_ACTION);
+            String timeStamp = new 
SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
+            String fileName = "cdv_media_capture_audio_" + timeStamp + ".m4a";
+            File audio = new File(getTempDirectoryPath(), fileName);
+            Uri audioUri = 
FileProvider.getUriForFile(this.cordova.getActivity(),
+                    this.applicationId + 
".cordova.plugin.mediacapture.provider",
+                    audio);
+            this.audioAbsolutePath = audio.getAbsolutePath();
+            intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, 
audioUri);
+            LOG.d(LOG_TAG, "Recording an audio and saving to: " + 
this.audioAbsolutePath);
+
             this.cordova.startActivityForResult((CordovaPlugin) this, intent, 
req.requestCode);
         } catch (ActivityNotFoundException ex) {
             pendingRequests.resolveWithFailure(req, 
createErrorObject(CAPTURE_NOT_SUPPORTED, "No Activity found to handle Audio 
Capture."));
@@ -287,11 +300,16 @@ public class Capture extends CordovaPlugin {
 
         Intent intent = new 
Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
 
-        ContentResolver contentResolver = 
this.cordova.getActivity().getContentResolver();
-        ContentValues cv = new ContentValues();
-        cv.put(MediaStore.Images.Media.MIME_TYPE, IMAGE_JPEG);
-        imageUri = 
contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cv);
-        LOG.d(LOG_TAG, "Taking a picture and saving to: " + 
imageUri.toString());
+        String timeStamp = new 
SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
+        String fileName = "cdv_media_capture_image_" + timeStamp + ".jpg";
+        File image = new File(getTempDirectoryPath(), fileName);
+
+        Uri imageUri = FileProvider.getUriForFile(this.cordova.getActivity(),
+                this.applicationId + ".cordova.plugin.mediacapture.provider",
+                image);
+        this.imageAbsolutePath = image.getAbsolutePath();
+        intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, imageUri);
+        LOG.d(LOG_TAG, "Taking a picture and saving to: " + 
this.imageAbsolutePath);
 
         intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, imageUri);
 
@@ -305,6 +323,16 @@ public class Capture extends CordovaPlugin {
         if (isMissingCameraPermissions(req)) return;
 
         Intent intent = new 
Intent(android.provider.MediaStore.ACTION_VIDEO_CAPTURE);
+        String timeStamp = new 
SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
+        String fileName = "cdv_media_capture_video_" + timeStamp + ".mp4";
+        File movie = new File(getTempDirectoryPath(), fileName);
+
+        Uri videoUri = FileProvider.getUriForFile(this.cordova.getActivity(),
+                this.applicationId + ".cordova.plugin.mediacapture.provider",
+                movie);
+        this.videoAbsolutePath = movie.getAbsolutePath();
+        intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, videoUri);
+        LOG.d(LOG_TAG, "Recording a video and saving to: " + 
this.videoAbsolutePath);
 
         if(Build.VERSION.SDK_INT > 7){
             intent.putExtra("android.intent.extra.durationLimit", 
req.duration);
@@ -332,13 +360,13 @@ public class Capture extends CordovaPlugin {
                 public void run() {
                     switch(req.action) {
                         case CAPTURE_AUDIO:
-                            onAudioActivityResult(req, intent);
+                            onAudioActivityResult(req);
                             break;
                         case CAPTURE_IMAGE:
                             onImageActivityResult(req);
                             break;
                         case CAPTURE_VIDEO:
-                            onVideoActivityResult(req, intent);
+                            onVideoActivityResult(req);
                             break;
                     }
                 }
@@ -371,18 +399,11 @@ public class Capture extends CordovaPlugin {
     }
 
 
-    public void onAudioActivityResult(Request req, Intent intent) {
-        // Get the uri of the audio clip
-        Uri data = intent.getData();
-        if (data == null) {
-            pendingRequests.resolveWithFailure(req, 
createErrorObject(CAPTURE_NO_MEDIA_FILES, "Error: data is null"));
-            return;
-        }
-
-        // Create a file object from the uri
-        JSONObject mediaFile = createMediaFile(data);
+    public void onAudioActivityResult(Request req) {
+        // create a file object from the audio absolute path
+        JSONObject mediaFile = 
createMediaFileWithAbsolutePath(this.audioAbsolutePath);
         if (mediaFile == null) {
-            pendingRequests.resolveWithFailure(req, 
createErrorObject(CAPTURE_INTERNAL_ERR, "Error: no mediaFile created from " + 
data));
+            pendingRequests.resolveWithFailure(req, 
createErrorObject(CAPTURE_INTERNAL_ERR, "Error: no mediaFile created from " + 
this.audioAbsolutePath));
             return;
         }
 
@@ -398,17 +419,10 @@ public class Capture extends CordovaPlugin {
     }
 
     public void onImageActivityResult(Request req) {
-        // Get the uri of the image
-        Uri data = imageUri;
-        if (data == null) {
-            pendingRequests.resolveWithFailure(req, 
createErrorObject(CAPTURE_NO_MEDIA_FILES, "Error: data is null"));
-            return;
-        }
-
-        // Create a file object from the uri
-        JSONObject mediaFile = createMediaFile(data);
+        // create a file object from the image absolute path
+        JSONObject mediaFile = 
createMediaFileWithAbsolutePath(this.imageAbsolutePath);
         if (mediaFile == null) {
-            pendingRequests.resolveWithFailure(req, 
createErrorObject(CAPTURE_INTERNAL_ERR, "Error: no mediaFile created from " + 
data));
+            pendingRequests.resolveWithFailure(req, 
createErrorObject(CAPTURE_INTERNAL_ERR, "Error: no mediaFile created from " + 
this.imageAbsolutePath));
             return;
         }
 
@@ -425,18 +439,11 @@ public class Capture extends CordovaPlugin {
         }
     }
 
-    public void onVideoActivityResult(Request req, Intent intent) {
-        // Get the uri of the video clip
-        Uri data = intent.getData();
-        if (data == null) {
-            pendingRequests.resolveWithFailure(req, 
createErrorObject(CAPTURE_NO_MEDIA_FILES, "Error: data is null"));
-            return;
-        }
-
-        // Create a file object from the uri
-        JSONObject mediaFile = createMediaFile(data);
+    public void onVideoActivityResult(Request req) {
+        // create a file object from the video absolute path
+        JSONObject mediaFile = 
createMediaFileWithAbsolutePath(this.videoAbsolutePath);
         if (mediaFile == null) {
-            pendingRequests.resolveWithFailure(req, 
createErrorObject(CAPTURE_INTERNAL_ERR, "Error: no mediaFile created from " + 
data));
+            pendingRequests.resolveWithFailure(req, 
createErrorObject(CAPTURE_INTERNAL_ERR, "Error: no mediaFile created from " + 
this.videoAbsolutePath));
             return;
         }
 
@@ -452,18 +459,14 @@ public class Capture extends CordovaPlugin {
     }
 
     /**
-     * Creates a JSONObject that represents a File from the Uri
+     * Creates a JSONObject that represents a File from the absolute path
      *
-     * @param data the Uri of the audio/image/video
+     * @param path the absolute path saved in FileProvider of the 
audio/image/video
      * @return a JSONObject that represents a File
      * @throws IOException
      */
-    private JSONObject createMediaFile(Uri data) {
-        File fp = webView.getResourceApi().mapUriToFile(data);
-        if (fp == null) {
-            return null;
-        }
-
+    private JSONObject createMediaFileWithAbsolutePath(String path) {
+        File fp = new File(path);
         JSONObject obj = new JSONObject();
 
         Class webViewClass = webView.getClass();
@@ -471,16 +474,15 @@ public class Capture extends CordovaPlugin {
         try {
             Method gpm = webViewClass.getMethod("getPluginManager");
             pm = (PluginManager) gpm.invoke(webView);
-        } catch (NoSuchMethodException e) {
-        } catch (IllegalAccessException e) {
-        } catch (InvocationTargetException e) {
+        } catch (NoSuchMethodException | IllegalAccessException | 
InvocationTargetException e) {
+            // Do Nothing
         }
         if (pm == null) {
             try {
                 Field pmf = webViewClass.getField("pluginManager");
                 pm = (PluginManager)pmf.get(webView);
-            } catch (NoSuchFieldException e) {
-            } catch (IllegalAccessException e) {
+            } catch (NoSuchFieldException | IllegalAccessException e) {
+                // Do Nothing
             }
         }
         FileUtils filePlugin = (FileUtils) pm.getPlugin("File");
@@ -497,6 +499,7 @@ public class Capture extends CordovaPlugin {
             // are reported as video/3gpp. I'm doing this hacky check of the 
URI to see if it
             // is stored in the audio or video content store.
             if (fp.getAbsoluteFile().toString().endsWith(".3gp") || 
fp.getAbsoluteFile().toString().endsWith(".3gpp")) {
+                Uri data = Uri.fromFile(fp);
                 if (data.toString().contains("/audio/")) {
                     obj.put("type", AUDIO_3GPP);
                 } else {
diff --git a/src/android/FileProvider.java b/src/android/FileProvider.java
new file mode 100644
index 0000000..0dc2d04
--- /dev/null
+++ b/src/android/FileProvider.java
@@ -0,0 +1,20 @@
+/*
+       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.
+*/
+package org.apache.cordova.mediacapture;
+
+public class FileProvider extends androidx.core.content.FileProvider {
+}
diff --git a/src/android/res/xml/mediacapture_provider_paths.xml 
b/src/android/res/xml/mediacapture_provider_paths.xml
new file mode 100644
index 0000000..4adfd0c
--- /dev/null
+++ b/src/android/res/xml/mediacapture_provider_paths.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<paths xmlns:android="http://schemas.android.com/apk/res/android";>
+    <cache-path name="cache_files" path="org.apache.cordova.mediacapture" />
+</paths>


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@cordova.apache.org
For additional commands, e-mail: commits-h...@cordova.apache.org

Reply via email to