From 4093682e08a7a047de39046e8e48d9a78b2ad0f2 Mon Sep 17 00:00:00 2001 From: Daniel Vicory Date: Tue, 18 Feb 2020 18:40:30 -0800 Subject: [PATCH] fix(Android): Don't show camera options for a file upload when they can not be used (#1210) * Don't show camera options for a file upload that would result in nothing happening for the user. On Android, if the application declares the camera permission, then even intents that use the camera require permission to be granted. This is a problem for apps that combine an in-app camera with a WebView that has file uploading and the user has not given permission for the camera. Note, this will not request permission for camera. This will simply prevent showing the camera options that would be a no-op action for users. It does this by checking if the camera permission is declared, and if so, checks that the user has granted permission. More information: https://blog.egorand.me/taking-photos-not-so-simply-how-i-got-bitten-by-action_image_capture/ * Add example and documentation about camera option availability in file uploads for Android. --- .../webview/RNCWebViewModule.java | 30 ++++++-- docs/Guide.md | 10 ++- example/App.tsx | 15 ++++ .../android/app/src/main/AndroidManifest.xml | 1 + example/examples/Uploads.tsx | 69 +++++++++++++++++++ 5 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 example/examples/Uploads.tsx diff --git a/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewModule.java b/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewModule.java index b730535..f342b1c 100644 --- a/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewModule.java +++ b/android/src/main/java/com/reactnativecommunity/webview/RNCWebViewModule.java @@ -32,6 +32,7 @@ import com.facebook.react.modules.core.PermissionListener; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import static android.app.Activity.RESULT_OK; @@ -180,11 +181,13 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti filePathCallback = callback; ArrayList extraIntents = new ArrayList<>(); - if (acceptsImages(acceptTypes)) { - extraIntents.add(getPhotoIntent()); - } - if (acceptsVideo(acceptTypes)) { - extraIntents.add(getVideoIntent()); + if (! needsCameraPermission()) { + if (acceptsImages(acceptTypes)) { + extraIntents.add(getPhotoIntent()); + } + if (acceptsVideo(acceptTypes)) { + extraIntents.add(getVideoIntent()); + } } Intent fileSelectionIntent = getFileChooserIntent(acceptTypes, allowMultiple); @@ -233,6 +236,23 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti return result; } + protected boolean needsCameraPermission() { + boolean needed = false; + + PackageManager packageManager = getCurrentActivity().getPackageManager(); + try { + String[] requestedPermissions = packageManager.getPackageInfo(getReactApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS).requestedPermissions; + if (Arrays.asList(requestedPermissions).contains(Manifest.permission.CAMERA) + && ContextCompat.checkSelfPermission(getCurrentActivity(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { + needed = true; + } + } catch (PackageManager.NameNotFoundException e) { + needed = true; + } + + return needed; + } + private Intent getPhotoIntent() { Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); outputFileUri = getOutputUri(MediaStore.ACTION_IMAGE_CAPTURE); diff --git a/docs/Guide.md b/docs/Guide.md index ac016fe..f6c633b 100644 --- a/docs/Guide.md +++ b/docs/Guide.md @@ -191,6 +191,12 @@ Add permission in AndroidManifest.xml: ``` +###### Camera option availability in uploading for Android + +If the file input indicates that images or video is desired with [`accept`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#accept), then the WebView will attempt to provide options to the user to use their camera to take a picture or video. + +Normally, apps that do not have permission to use the camera can prompt the user to use an external app so that the requesting app has no need for permission. However, Android has made a special exception for this around the camera to reduce confusion for users. If an app *can* request the camera permission because it has been declared, and the user has not granted the permission, it may not fire an intent that would use the camera (`MediaStore.ACTION_IMAGE_CAPTURE` or `MediaStore.ACTION_VIDEO_CAPTURE`). In this scenario, it is up to the developer to request camera permission before a file upload directly using the camera is necessary. + ##### Check for File Upload support, with `static isFileUploadSupported()` File Upload using `` is not supported for Android 4.4 KitKat (see [details](https://github.com/delight-im/Android-AdvancedWebView/issues/4#issuecomment-70372146)): @@ -301,7 +307,7 @@ _Under the hood_ #### The `injectedJavaScriptBeforeContentLoaded` prop -This is a script that runs **before** the web page loads for the first time. It only runs once, even if the page is reloaded or navigated away. This is useful if you want to inject anything into the window, localstorage, or document prior to the web code executing. +This is a script that runs **before** the web page loads for the first time. It only runs once, even if the page is reloaded or navigated away. This is useful if you want to inject anything into the window, localstorage, or document prior to the web code executing. ```jsx import React, { Component } from 'react'; @@ -329,7 +335,7 @@ export default class App extends Component { } ``` -This runs the JavaScript in the `runFirst` string before the page is loaded. In this case, the value of `window.isNativeApp` will be set to true before the web code executes. +This runs the JavaScript in the `runFirst` string before the page is loaded. In this case, the value of `window.isNativeApp` will be set to true before the web code executes. #### The `injectJavaScript` method diff --git a/example/App.tsx b/example/App.tsx index 3e81c8f..a5c9f93 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -7,11 +7,13 @@ import { View, Keyboard, Button, + Platform, } from 'react-native'; import Alerts from './examples/Alerts'; import Scrolling from './examples/Scrolling'; import Background from './examples/Background'; +import Uploads from './examples/Uploads'; const TESTS = { Alerts: { @@ -38,6 +40,14 @@ const TESTS = { return ; }, }, + Uploads: { + title: 'Uploads', + testId: 'uploads', + description: 'Upload test', + render() { + return ; + }, + }, }; type Props = {}; @@ -91,6 +101,11 @@ export default class App extends Component { title="Background" onPress={() => this._changeTest('Background')} /> + {Platform.OS === 'android' &&