mirror of
https://github.com/apache/cordova-android.git
synced 2025-03-16 08:21:04 +08:00
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 <ken@truepic.com> Co-authored-by: エリス <erisu@users.noreply.github.com>
This commit is contained in:
parent
ebf0b105a3
commit
b773ae48f4
@ -18,18 +18,22 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.cordova.engine;
|
package org.apache.cordova.engine;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.File;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import android.annotation.TargetApi;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.ClipData;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.provider.MediaStore;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup.LayoutParams;
|
import android.view.ViewGroup.LayoutParams;
|
||||||
import android.webkit.ConsoleMessage;
|
|
||||||
import android.webkit.GeolocationPermissions.Callback;
|
import android.webkit.GeolocationPermissions.Callback;
|
||||||
import android.webkit.JsPromptResult;
|
import android.webkit.JsPromptResult;
|
||||||
import android.webkit.JsResult;
|
import android.webkit.JsResult;
|
||||||
@ -41,6 +45,7 @@ import android.webkit.PermissionRequest;
|
|||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
|
import androidx.core.content.FileProvider;
|
||||||
|
|
||||||
import org.apache.cordova.CordovaDialogsHelper;
|
import org.apache.cordova.CordovaDialogsHelper;
|
||||||
import org.apache.cordova.CordovaPlugin;
|
import org.apache.cordova.CordovaPlugin;
|
||||||
@ -212,53 +217,110 @@ public class SystemWebChromeClient extends WebChromeClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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
|
// Check if multiple-select is specified
|
||||||
Boolean selectMultiple = false;
|
Boolean selectMultiple = false;
|
||||||
if (fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE) {
|
if (fileChooserParams.getMode() == WebChromeClient.FileChooserParams.MODE_OPEN_MULTIPLE) {
|
||||||
selectMultiple = true;
|
selectMultiple = true;
|
||||||
}
|
}
|
||||||
Intent intent = fileChooserParams.createIntent();
|
fileIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, selectMultiple);
|
||||||
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, selectMultiple);
|
|
||||||
|
|
||||||
// Uses Intent.EXTRA_MIME_TYPES to pass multiple mime types.
|
// Uses Intent.EXTRA_MIME_TYPES to pass multiple mime types.
|
||||||
String[] acceptTypes = fileChooserParams.getAcceptTypes();
|
String[] acceptTypes = fileChooserParams.getAcceptTypes();
|
||||||
if (acceptTypes.length > 1) {
|
if (acceptTypes.length > 1) {
|
||||||
intent.setType("*/*"); // Accept all, filter mime types by Intent.EXTRA_MIME_TYPES.
|
fileIntent.setType("*/*"); // Accept all, filter mime types by Intent.EXTRA_MIME_TYPES.
|
||||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, acceptTypes);
|
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 {
|
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() {
|
parentEngine.cordova.startActivityForResult(new CordovaPlugin() {
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||||
|
// Handle result
|
||||||
Uri[] result = null;
|
Uri[] result = null;
|
||||||
if (resultCode == Activity.RESULT_OK && intent != null) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
if (intent.getClipData() != null) {
|
List<Uri> uris = new ArrayList<Uri>();
|
||||||
// handle multiple-selected files
|
|
||||||
final int numSelectedFiles = intent.getClipData().getItemCount();
|
if (intent != null && intent.getData() != null) { // single file
|
||||||
result = new Uri[numSelectedFiles];
|
LOG.v(LOG_TAG, "Adding file (single): " + intent.getData());
|
||||||
for (int i = 0; i < numSelectedFiles; i++) {
|
uris.add(intent.getData());
|
||||||
result[i] = intent.getClipData().getItemAt(i).getUri();
|
} else if (captureUri != null) { // camera
|
||||||
LOG.d(LOG_TAG, "Receive file chooser URL: " + result[i]);
|
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);
|
if (!uris.isEmpty()) {
|
||||||
LOG.d(LOG_TAG, "Receive file chooser URL: " + result);
|
LOG.d(LOG_TAG, "Receive file chooser URL: " + uris.toString());
|
||||||
|
result = uris.toArray(new Uri[uris.size()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
filePathsCallback.onReceiveValue(result);
|
filePathsCallback.onReceiveValue(result);
|
||||||
}
|
}
|
||||||
}, intent, FILECHOOSER_RESULTCODE);
|
}, chooserIntent, FILECHOOSER_RESULTCODE);
|
||||||
} catch (ActivityNotFoundException e) {
|
} 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);
|
filePathsCallback.onReceiveValue(null);
|
||||||
}
|
}
|
||||||
return true;
|
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) {
|
public void onPermissionRequest(final PermissionRequest request) {
|
||||||
LOG.d(LOG_TAG, "onPermissionRequest: " + Arrays.toString(request.getResources()));
|
LOG.d(LOG_TAG, "onPermissionRequest: " + Arrays.toString(request.getResources()));
|
||||||
request.grant(request.getResources());
|
request.grant(request.getResources());
|
||||||
|
@ -46,5 +46,14 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</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>
|
</application>
|
||||||
|
|
||||||
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.media.action.IMAGE_CAPTURE" />
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
30
templates/project/res/xml/cdv_core_file_provider_paths.xml
Normal file
30
templates/project/res/xml/cdv_core_file_provider_paths.xml
Normal file
@ -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>
|
Loading…
x
Reference in New Issue
Block a user