123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420 |
- package com.reactnativecommunity.webview;
-
- import android.Manifest;
- import android.app.Activity;
- import android.app.DownloadManager;
- import android.content.Context;
- import android.content.Intent;
- import android.content.pm.PackageManager;
- import android.net.Uri;
- import android.os.Build;
- import android.os.Environment;
- import android.os.Parcelable;
- import android.provider.MediaStore;
- import androidx.annotation.RequiresApi;
- import androidx.core.content.ContextCompat;
- import androidx.core.content.FileProvider;
- import android.util.Log;
- import android.webkit.MimeTypeMap;
- import android.webkit.ValueCallback;
- import android.webkit.WebChromeClient;
- import android.widget.Toast;
-
- import com.facebook.react.bridge.ActivityEventListener;
- import com.facebook.react.bridge.Promise;
- import com.facebook.react.bridge.ReactApplicationContext;
- import com.facebook.react.bridge.ReactContextBaseJavaModule;
- import com.facebook.react.bridge.ReactMethod;
- import com.facebook.react.module.annotations.ReactModule;
- import com.facebook.react.modules.core.PermissionAwareActivity;
- 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;
-
- @ReactModule(name = RNCWebViewModule.MODULE_NAME)
- public class RNCWebViewModule extends ReactContextBaseJavaModule implements ActivityEventListener {
- public static final String MODULE_NAME = "RNCWebView";
- private static final int PICKER = 1;
- private static final int PICKER_LEGACY = 3;
- private static final int FILE_DOWNLOAD_PERMISSION_REQUEST = 1;
- final String DEFAULT_MIME_TYPES = "*/*";
- private ValueCallback<Uri> filePathCallbackLegacy;
- private ValueCallback<Uri[]> filePathCallback;
- private Uri outputFileUri;
- private DownloadManager.Request downloadRequest;
- private PermissionListener webviewFileDownloaderPermissionListener = new PermissionListener() {
- @Override
- public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
- switch (requestCode) {
- case FILE_DOWNLOAD_PERMISSION_REQUEST: {
- // If request is cancelled, the result arrays are empty.
- if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- if (downloadRequest != null) {
- downloadFile();
- }
- } else {
- Toast.makeText(getCurrentActivity().getApplicationContext(), "Cannot download files as permission was denied. Please provide permission to write to storage, in order to download files.", Toast.LENGTH_LONG).show();
- }
- return true;
- }
- }
- return false;
- }
- };
-
- public RNCWebViewModule(ReactApplicationContext reactContext) {
- super(reactContext);
- reactContext.addActivityEventListener(this);
- }
-
- @Override
- public String getName() {
- return MODULE_NAME;
- }
-
- @ReactMethod
- public void isFileUploadSupported(final Promise promise) {
- Boolean result = false;
- int current = Build.VERSION.SDK_INT;
- if (current >= Build.VERSION_CODES.LOLLIPOP) {
- result = true;
- }
- if (current >= Build.VERSION_CODES.JELLY_BEAN && current <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
- result = true;
- }
- promise.resolve(result);
- }
-
- public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
-
- if (filePathCallback == null && filePathCallbackLegacy == null) {
- return;
- }
-
- // based off of which button was pressed, we get an activity result and a file
- // the camera activity doesn't properly return the filename* (I think?) so we use
- // this filename instead
- switch (requestCode) {
- case PICKER:
- if (resultCode != RESULT_OK) {
- if (filePathCallback != null) {
- filePathCallback.onReceiveValue(null);
- }
- } else {
- Uri result[] = this.getSelectedFiles(data, resultCode);
- if (result != null) {
- filePathCallback.onReceiveValue(result);
- } else {
- filePathCallback.onReceiveValue(new Uri[]{outputFileUri});
- }
- }
- break;
- case PICKER_LEGACY:
- Uri result = resultCode != Activity.RESULT_OK ? null : data == null ? outputFileUri : data.getData();
- filePathCallbackLegacy.onReceiveValue(result);
- break;
-
- }
- filePathCallback = null;
- filePathCallbackLegacy = null;
- outputFileUri = null;
- }
-
- public void onNewIntent(Intent intent) {
- }
-
- private Uri[] getSelectedFiles(Intent data, int resultCode) {
- if (data == null) {
- return null;
- }
-
- // we have one file selected
- if (data.getData() != null) {
- if (resultCode == RESULT_OK && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- return WebChromeClient.FileChooserParams.parseResult(resultCode, data);
- } else {
- return null;
- }
- }
-
- // we have multiple files selected
- if (data.getClipData() != null) {
- final int numSelectedFiles = data.getClipData().getItemCount();
- Uri[] result = new Uri[numSelectedFiles];
- for (int i = 0; i < numSelectedFiles; i++) {
- result[i] = data.getClipData().getItemAt(i).getUri();
- }
- return result;
- }
- return null;
- }
-
- public void startPhotoPickerIntent(ValueCallback<Uri> filePathCallback, String acceptType) {
- filePathCallbackLegacy = filePathCallback;
-
- Intent fileChooserIntent = getFileChooserIntent(acceptType);
- Intent chooserIntent = Intent.createChooser(fileChooserIntent, "");
-
- ArrayList<Parcelable> extraIntents = new ArrayList<>();
- if (acceptsImages(acceptType)) {
- extraIntents.add(getPhotoIntent());
- }
- if (acceptsVideo(acceptType)) {
- extraIntents.add(getVideoIntent());
- }
- chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Parcelable[]{}));
-
- if (chooserIntent.resolveActivity(getCurrentActivity().getPackageManager()) != null) {
- getCurrentActivity().startActivityForResult(chooserIntent, PICKER_LEGACY);
- } else {
- Log.w("RNCWebViewModule", "there is no Activity to handle this Intent");
- }
- }
-
- @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
- public boolean startPhotoPickerIntent(final ValueCallback<Uri[]> callback, final Intent intent, final String[] acceptTypes, final boolean allowMultiple) {
- 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);
-
- Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
- chooserIntent.putExtra(Intent.EXTRA_INTENT, fileSelectionIntent);
- chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Parcelable[]{}));
-
- if (chooserIntent.resolveActivity(getCurrentActivity().getPackageManager()) != null) {
- getCurrentActivity().startActivityForResult(chooserIntent, PICKER);
- } else {
- Log.w("RNCWebViewModule", "there is no Activity to handle this Intent");
- }
-
- return true;
- }
-
- public void setDownloadRequest(DownloadManager.Request request) {
- this.downloadRequest = request;
- }
-
- public void downloadFile() {
- DownloadManager dm = (DownloadManager) getCurrentActivity().getBaseContext().getSystemService(Context.DOWNLOAD_SERVICE);
- String downloadMessage = "Downloading";
-
- dm.enqueue(this.downloadRequest);
-
- Toast.makeText(getCurrentActivity().getApplicationContext(), downloadMessage, Toast.LENGTH_LONG).show();
- }
-
- public boolean grantFileDownloaderPermissions() {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
- return true;
- }
-
- boolean result = true;
- if (ContextCompat.checkSelfPermission(getCurrentActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
- result = false;
- }
-
- if (!result) {
- PermissionAwareActivity activity = getPermissionAwareActivity();
- activity.requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, FILE_DOWNLOAD_PERMISSION_REQUEST, webviewFileDownloaderPermissionListener);
- }
-
- 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);
- intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
- return intent;
- }
-
- private Intent getVideoIntent() {
- Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
- outputFileUri = getOutputUri(MediaStore.ACTION_VIDEO_CAPTURE);
- intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
- return intent;
- }
-
- private Intent getFileChooserIntent(String acceptTypes) {
- String _acceptTypes = acceptTypes;
- if (acceptTypes.isEmpty()) {
- _acceptTypes = DEFAULT_MIME_TYPES;
- }
- if (acceptTypes.matches("\\.\\w+")) {
- _acceptTypes = getMimeTypeFromExtension(acceptTypes.replace(".", ""));
- }
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType(_acceptTypes);
- return intent;
- }
-
- private Intent getFileChooserIntent(String[] acceptTypes, boolean allowMultiple) {
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("*/*");
- intent.putExtra(Intent.EXTRA_MIME_TYPES, getAcceptedMimeType(acceptTypes));
- intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple);
- return intent;
- }
-
- private Boolean acceptsImages(String types) {
- String mimeType = types;
- if (types.matches("\\.\\w+")) {
- mimeType = getMimeTypeFromExtension(types.replace(".", ""));
- }
- return mimeType.isEmpty() || mimeType.toLowerCase().contains("image");
- }
-
- private Boolean acceptsImages(String[] types) {
- String[] mimeTypes = getAcceptedMimeType(types);
- return isArrayEmpty(mimeTypes) || arrayContainsString(mimeTypes, "image");
- }
-
- private Boolean acceptsVideo(String types) {
- String mimeType = types;
- if (types.matches("\\.\\w+")) {
- mimeType = getMimeTypeFromExtension(types.replace(".", ""));
- }
- return mimeType.isEmpty() || mimeType.toLowerCase().contains("video");
- }
-
- private Boolean acceptsVideo(String[] types) {
- String[] mimeTypes = getAcceptedMimeType(types);
- return isArrayEmpty(mimeTypes) || arrayContainsString(mimeTypes, "video");
- }
-
- private Boolean arrayContainsString(String[] array, String pattern) {
- for (String content : array) {
- if (content.contains(pattern)) {
- return true;
- }
- }
- return false;
- }
-
- private String[] getAcceptedMimeType(String[] types) {
- if (isArrayEmpty(types)) {
- return new String[]{DEFAULT_MIME_TYPES};
- }
- String[] mimeTypes = new String[types.length];
- for (int i = 0; i < types.length; i++) {
- String t = types[i];
- // convert file extensions to mime types
- if (t.matches("\\.\\w+")) {
- String mimeType = getMimeTypeFromExtension(t.replace(".", ""));
- mimeTypes[i] = mimeType;
- } else {
- mimeTypes[i] = t;
- }
- }
- return mimeTypes;
- }
-
- private String getMimeTypeFromExtension(String extension) {
- String type = null;
- if (extension != null) {
- type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
- }
- return type;
- }
-
- private Uri getOutputUri(String intentType) {
- File capturedFile = null;
- try {
- capturedFile = getCapturedFile(intentType);
- } catch (IOException e) {
- Log.e("CREATE FILE", "Error occurred while creating the File", e);
- e.printStackTrace();
- }
-
- // for versions below 6.0 (23) we use the old File creation & permissions model
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
- return Uri.fromFile(capturedFile);
- }
-
- // for versions 6.0+ (23) we use the FileProvider to avoid runtime permissions
- String packageName = getReactApplicationContext().getPackageName();
- return FileProvider.getUriForFile(getReactApplicationContext(), packageName + ".fileprovider", capturedFile);
- }
-
- private File getCapturedFile(String intentType) throws IOException {
- String prefix = "";
- String suffix = "";
- String dir = "";
- String filename = "";
-
- if (intentType.equals(MediaStore.ACTION_IMAGE_CAPTURE)) {
- prefix = "image-";
- suffix = ".jpg";
- dir = Environment.DIRECTORY_PICTURES;
- } else if (intentType.equals(MediaStore.ACTION_VIDEO_CAPTURE)) {
- prefix = "video-";
- suffix = ".mp4";
- dir = Environment.DIRECTORY_MOVIES;
- }
-
- filename = prefix + String.valueOf(System.currentTimeMillis()) + suffix;
-
- // for versions below 6.0 (23) we use the old File creation & permissions model
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
- // only this Directory works on all tested Android versions
- // ctx.getExternalFilesDir(dir) was failing on Android 5.0 (sdk 21)
- File storageDir = Environment.getExternalStoragePublicDirectory(dir);
- return new File(storageDir, filename);
- }
-
- File storageDir = getReactApplicationContext().getExternalFilesDir(null);
- return File.createTempFile(filename, suffix, storageDir);
- }
-
- private Boolean isArrayEmpty(String[] arr) {
- // when our array returned from getAcceptTypes() has no values set from the webview
- // i.e. <input type="file" />, without any "accept" attr
- // will be an array with one empty string element, afaik
- return arr.length == 0 || (arr.length == 1 && arr[0].length() == 0);
- }
-
- private PermissionAwareActivity getPermissionAwareActivity() {
- Activity activity = getCurrentActivity();
- if (activity == null) {
- throw new IllegalStateException("Tried to use permissions API while not attached to an Activity.");
- } else if (!(activity instanceof PermissionAwareActivity)) {
- throw new IllegalStateException("Tried to use permissions API but the host Activity doesn't implement PermissionAwareActivity.");
- }
- return (PermissionAwareActivity) activity;
- }
- }
|