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.
This commit is contained in:
parent
5024295633
commit
4093682e08
|
@ -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,12 +181,14 @@ public class RNCWebViewModule extends ReactContextBaseJavaModule implements Acti
|
|||
filePathCallback = callback;
|
||||
|
||||
ArrayList<Parcelable> extraIntents = new ArrayList<>();
|
||||
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);
|
||||
|
|
|
@ -191,6 +191,12 @@ Add permission in AndroidManifest.xml:
|
|||
</manifest>
|
||||
```
|
||||
|
||||
###### 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 `<input type="file" />` is not supported for Android 4.4 KitKat (see [details](https://github.com/delight-im/Android-AdvancedWebView/issues/4#issuecomment-70372146)):
|
||||
|
|
|
@ -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 <Background />;
|
||||
},
|
||||
},
|
||||
Uploads: {
|
||||
title: 'Uploads',
|
||||
testId: 'uploads',
|
||||
description: 'Upload test',
|
||||
render() {
|
||||
return <Uploads />;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
type Props = {};
|
||||
|
@ -91,6 +101,11 @@ export default class App extends Component<Props, State> {
|
|||
title="Background"
|
||||
onPress={() => this._changeTest('Background')}
|
||||
/>
|
||||
{Platform.OS === 'android' && <Button
|
||||
testID="testType_uploads"
|
||||
title="Uploads"
|
||||
onPress={() => this._changeTest('Uploads')}
|
||||
/>}
|
||||
</View>
|
||||
|
||||
{restarting ? null : (
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
package="com.example">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import React, {Component} from 'react';
|
||||
import {Button, Linking, Text, View} from 'react-native';
|
||||
|
||||
import WebView from 'react-native-webview';
|
||||
|
||||
const HTML = `
|
||||
<!DOCTYPE html>\n
|
||||
<html>
|
||||
<head>
|
||||
<title>Uploads</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=320, user-scalable=no">
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font: 62.5% arial, sans-serif;
|
||||
background: #ccc;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
<label for="images-only">Images only file upload</label>
|
||||
<input name="images-only" type="file" accept="image/*">
|
||||
</p>
|
||||
<p>
|
||||
<label for="video-only">Video only file upload</label>
|
||||
<input name="video-only" type="file" accept="video/*">
|
||||
</p>
|
||||
<p>
|
||||
<label for="any-file">Any file upload</label>
|
||||
<input name="any-file" type="file">
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
type Props = {};
|
||||
type State = {};
|
||||
|
||||
export default class Uploads extends Component<Props, State> {
|
||||
state = {};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<View style={{ height: 120 }}>
|
||||
<WebView
|
||||
source={{html: HTML}}
|
||||
automaticallyAdjustContentInsets={false}
|
||||
/>
|
||||
</View>
|
||||
<Text>
|
||||
Android limitation: If the file input should show camera options for the user,
|
||||
and the app has the ability to request the camera permission, then the user must
|
||||
grant permission first in order to see the options. Since this example app does
|
||||
have the permission declared, you must allow it in settings to be able to see
|
||||
camera options. If your app does not have the camera permission declared, then
|
||||
there is no restriction to showing the camera options.
|
||||
</Text>
|
||||
<Button
|
||||
title="Open settings"
|
||||
onPress={() => Linking.openSettings()}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue