Açıklama Yok

RNCWebViewModule.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. package com.reactnativecommunity.webview;
  2. import android.Manifest;
  3. import android.app.Activity;
  4. import android.app.DownloadManager;
  5. import android.content.Context;
  6. import android.content.Intent;
  7. import android.content.pm.PackageManager;
  8. import android.net.Uri;
  9. import android.os.Build;
  10. import android.os.Environment;
  11. import android.os.Parcelable;
  12. import android.provider.MediaStore;
  13. import android.support.annotation.RequiresApi;
  14. import android.support.v4.content.ContextCompat;
  15. import android.support.v4.content.FileProvider;
  16. import android.util.Log;
  17. import android.webkit.ValueCallback;
  18. import android.webkit.WebChromeClient;
  19. import android.widget.Toast;
  20. import com.facebook.react.bridge.ActivityEventListener;
  21. import com.facebook.react.bridge.Promise;
  22. import com.facebook.react.bridge.ReactApplicationContext;
  23. import com.facebook.react.bridge.ReactContextBaseJavaModule;
  24. import com.facebook.react.bridge.ReactMethod;
  25. import com.facebook.react.modules.core.PermissionAwareActivity;
  26. import com.facebook.react.modules.core.PermissionListener;
  27. import java.io.File;
  28. import java.io.IOException;
  29. import java.util.ArrayList;
  30. import static android.app.Activity.RESULT_OK;
  31. public class RNCWebViewModule extends ReactContextBaseJavaModule implements ActivityEventListener {
  32. private final ReactApplicationContext reactContext;
  33. private RNCWebViewPackage aPackage;
  34. private static final int PICKER = 1;
  35. private static final int PICKER_LEGACY = 3;
  36. private ValueCallback<Uri> filePathCallbackLegacy;
  37. private ValueCallback<Uri[]> filePathCallback;
  38. private Uri outputFileUri;
  39. private DownloadManager.Request downloadRequest;
  40. private static final int FILE_DOWNLOAD_PERMISSION_REQUEST = 1;
  41. final String DEFAULT_MIME_TYPES = "*/*";
  42. public RNCWebViewModule(ReactApplicationContext reactContext) {
  43. super(reactContext);
  44. this.reactContext = reactContext;
  45. reactContext.addActivityEventListener(this);
  46. }
  47. @Override
  48. public String getName() {
  49. return "RNCWebView";
  50. }
  51. @ReactMethod
  52. public void isFileUploadSupported(final Promise promise) {
  53. Boolean result = false;
  54. int current = Build.VERSION.SDK_INT;
  55. if (current >= Build.VERSION_CODES.LOLLIPOP) {
  56. result = true;
  57. }
  58. if (current >= Build.VERSION_CODES.JELLY_BEAN && current <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
  59. result = true;
  60. }
  61. promise.resolve(result);
  62. }
  63. public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
  64. if (filePathCallback == null && filePathCallbackLegacy == null) {
  65. return;
  66. }
  67. // based off of which button was pressed, we get an activity result and a file
  68. // the camera activity doesn't properly return the filename* (I think?) so we use
  69. // this filename instead
  70. switch (requestCode) {
  71. case PICKER:
  72. if (resultCode != RESULT_OK) {
  73. if (filePathCallback != null) {
  74. filePathCallback.onReceiveValue(null);
  75. }
  76. } else {
  77. Uri result[] = this.getSelectedFiles(data, resultCode);
  78. if (result != null) {
  79. filePathCallback.onReceiveValue(result);
  80. } else {
  81. filePathCallback.onReceiveValue(new Uri[] { outputFileUri });
  82. }
  83. }
  84. break;
  85. case PICKER_LEGACY:
  86. Uri result = resultCode != Activity.RESULT_OK ? null : data == null ? outputFileUri : data.getData();
  87. filePathCallbackLegacy.onReceiveValue(result);
  88. break;
  89. }
  90. filePathCallback = null;
  91. filePathCallbackLegacy= null;
  92. outputFileUri = null;
  93. }
  94. public void onNewIntent(Intent intent) {
  95. }
  96. private Uri[] getSelectedFiles(Intent data, int resultCode) {
  97. if (data == null) {
  98. return null;
  99. }
  100. // we have one file selected
  101. if (data.getData() != null) {
  102. if (resultCode == RESULT_OK && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  103. return WebChromeClient.FileChooserParams.parseResult(resultCode, data);
  104. } else {
  105. return null;
  106. }
  107. }
  108. // we have multiple files selected
  109. if (data.getClipData() != null) {
  110. final int numSelectedFiles = data.getClipData().getItemCount();
  111. Uri[] result = new Uri[numSelectedFiles];
  112. for (int i = 0; i < numSelectedFiles; i++) {
  113. result[i] = data.getClipData().getItemAt(i).getUri();
  114. }
  115. return result;
  116. }
  117. return null;
  118. }
  119. public void startPhotoPickerIntent(ValueCallback<Uri> filePathCallback, String acceptType) {
  120. filePathCallbackLegacy = filePathCallback;
  121. Intent fileChooserIntent = getFileChooserIntent(acceptType);
  122. Intent chooserIntent = Intent.createChooser(fileChooserIntent, "");
  123. ArrayList<Parcelable> extraIntents = new ArrayList<>();
  124. if (acceptsImages(acceptType)) {
  125. extraIntents.add(getPhotoIntent());
  126. }
  127. if (acceptsVideo(acceptType)) {
  128. extraIntents.add(getVideoIntent());
  129. }
  130. chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Parcelable[]{}));
  131. if (chooserIntent.resolveActivity(getCurrentActivity().getPackageManager()) != null) {
  132. getCurrentActivity().startActivityForResult(chooserIntent, PICKER_LEGACY);
  133. } else {
  134. Log.w("RNCWebViewModule", "there is no Activity to handle this Intent");
  135. }
  136. }
  137. @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
  138. public boolean startPhotoPickerIntent(final ValueCallback<Uri[]> callback, final Intent intent, final String[] acceptTypes, final boolean allowMultiple) {
  139. filePathCallback = callback;
  140. ArrayList<Parcelable> extraIntents = new ArrayList<>();
  141. if (acceptsImages(acceptTypes)) {
  142. extraIntents.add(getPhotoIntent());
  143. }
  144. if (acceptsVideo(acceptTypes)) {
  145. extraIntents.add(getVideoIntent());
  146. }
  147. Intent fileSelectionIntent = getFileChooserIntent(acceptTypes, allowMultiple);
  148. Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
  149. chooserIntent.putExtra(Intent.EXTRA_INTENT, fileSelectionIntent);
  150. chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Parcelable[]{}));
  151. if (chooserIntent.resolveActivity(getCurrentActivity().getPackageManager()) != null) {
  152. getCurrentActivity().startActivityForResult(chooserIntent, PICKER);
  153. } else {
  154. Log.w("RNCWebViewModule", "there is no Activity to handle this Intent");
  155. }
  156. return true;
  157. }
  158. public void setDownloadRequest(DownloadManager.Request request) {
  159. this.downloadRequest = request;
  160. }
  161. public void downloadFile() {
  162. DownloadManager dm = (DownloadManager) getCurrentActivity().getBaseContext().getSystemService(Context.DOWNLOAD_SERVICE);
  163. String downloadMessage = "Downloading";
  164. dm.enqueue(this.downloadRequest);
  165. Toast.makeText(getCurrentActivity().getApplicationContext(), downloadMessage, Toast.LENGTH_LONG).show();
  166. }
  167. public boolean grantFileDownloaderPermissions() {
  168. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
  169. return true;
  170. }
  171. boolean result = true;
  172. if (ContextCompat.checkSelfPermission(getCurrentActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
  173. result = false;
  174. }
  175. if (!result) {
  176. PermissionAwareActivity activity = getPermissionAwareActivity();
  177. activity.requestPermissions(new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE }, FILE_DOWNLOAD_PERMISSION_REQUEST, webviewFileDownloaderPermissionListener);
  178. }
  179. return result;
  180. }
  181. public RNCWebViewPackage getPackage() {
  182. return this.aPackage;
  183. }
  184. public void setPackage(RNCWebViewPackage aPackage) {
  185. this.aPackage = aPackage;
  186. }
  187. private Intent getPhotoIntent() {
  188. Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
  189. outputFileUri = getOutputUri(MediaStore.ACTION_IMAGE_CAPTURE);
  190. intent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
  191. return intent;
  192. }
  193. private Intent getVideoIntent() {
  194. Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
  195. // @todo from experience, for Videos we get the data onActivityResult
  196. // so there's no need to store the Uri
  197. Uri outputVideoUri = getOutputUri(MediaStore.ACTION_VIDEO_CAPTURE);
  198. intent.putExtra(MediaStore.EXTRA_OUTPUT, outputVideoUri);
  199. return intent;
  200. }
  201. private Intent getFileChooserIntent(String acceptTypes) {
  202. String _acceptTypes = acceptTypes;
  203. if (acceptTypes.isEmpty()) {
  204. _acceptTypes = DEFAULT_MIME_TYPES;
  205. }
  206. Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
  207. intent.addCategory(Intent.CATEGORY_OPENABLE);
  208. intent.setType(_acceptTypes);
  209. return intent;
  210. }
  211. private Intent getFileChooserIntent(String[] acceptTypes, boolean allowMultiple) {
  212. Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
  213. intent.addCategory(Intent.CATEGORY_OPENABLE);
  214. intent.setType("*/*");
  215. intent.putExtra(Intent.EXTRA_MIME_TYPES, getAcceptedMimeType(acceptTypes));
  216. intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple);
  217. return intent;
  218. }
  219. private Boolean acceptsImages(String types) {
  220. return types.isEmpty() || types.toLowerCase().contains("image");
  221. }
  222. private Boolean acceptsImages(String[] types) {
  223. return isArrayEmpty(types) || arrayContainsString(types, "image");
  224. }
  225. private Boolean acceptsVideo(String types) {
  226. return types.isEmpty() || types.toLowerCase().contains("video");
  227. }
  228. private Boolean acceptsVideo(String[] types) {
  229. return isArrayEmpty(types) || arrayContainsString(types, "video");
  230. }
  231. private Boolean arrayContainsString(String[] array, String pattern){
  232. for(String content : array){
  233. if(content.contains(pattern)){
  234. return true;
  235. }
  236. }
  237. return false;
  238. }
  239. private String[] getAcceptedMimeType(String[] types) {
  240. if (isArrayEmpty(types)) {
  241. return new String[]{DEFAULT_MIME_TYPES};
  242. }
  243. return types;
  244. }
  245. private Uri getOutputUri(String intentType) {
  246. File capturedFile = null;
  247. try {
  248. capturedFile = getCapturedFile(intentType);
  249. } catch (IOException e) {
  250. Log.e("CREATE FILE", "Error occurred while creating the File", e);
  251. e.printStackTrace();
  252. }
  253. // for versions below 6.0 (23) we use the old File creation & permissions model
  254. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
  255. return Uri.fromFile(capturedFile);
  256. }
  257. // for versions 6.0+ (23) we use the FileProvider to avoid runtime permissions
  258. String packageName = getReactApplicationContext().getPackageName();
  259. return FileProvider.getUriForFile(getReactApplicationContext(), packageName+".fileprovider", capturedFile);
  260. }
  261. private File getCapturedFile(String intentType) throws IOException {
  262. String prefix = "";
  263. String suffix = "";
  264. String dir = "";
  265. String filename = "";
  266. if (intentType.equals(MediaStore.ACTION_IMAGE_CAPTURE)) {
  267. prefix = "image-";
  268. suffix = ".jpg";
  269. dir = Environment.DIRECTORY_PICTURES;
  270. } else if (intentType.equals(MediaStore.ACTION_VIDEO_CAPTURE)) {
  271. prefix = "video-";
  272. suffix = ".mp4";
  273. dir = Environment.DIRECTORY_MOVIES;
  274. }
  275. filename = prefix + String.valueOf(System.currentTimeMillis()) + suffix;
  276. // for versions below 6.0 (23) we use the old File creation & permissions model
  277. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
  278. // only this Directory works on all tested Android versions
  279. // ctx.getExternalFilesDir(dir) was failing on Android 5.0 (sdk 21)
  280. File storageDir = Environment.getExternalStoragePublicDirectory(dir);
  281. return new File(storageDir, filename);
  282. }
  283. File storageDir = getReactApplicationContext().getExternalFilesDir(null);
  284. return File.createTempFile(filename, suffix, storageDir);
  285. }
  286. private Boolean isArrayEmpty(String[] arr) {
  287. // when our array returned from getAcceptTypes() has no values set from the webview
  288. // i.e. <input type="file" />, without any "accept" attr
  289. // will be an array with one empty string element, afaik
  290. return arr.length == 0 || (arr.length == 1 && arr[0].length() == 0);
  291. }
  292. private PermissionAwareActivity getPermissionAwareActivity() {
  293. Activity activity = getCurrentActivity();
  294. if (activity == null) {
  295. throw new IllegalStateException("Tried to use permissions API while not attached to an Activity.");
  296. } else if (!(activity instanceof PermissionAwareActivity)) {
  297. throw new IllegalStateException("Tried to use permissions API but the host Activity doesn't implement PermissionAwareActivity.");
  298. }
  299. return (PermissionAwareActivity) activity;
  300. }
  301. private PermissionListener webviewFileDownloaderPermissionListener = new PermissionListener() {
  302. @Override
  303. public boolean onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
  304. switch (requestCode) {
  305. case FILE_DOWNLOAD_PERMISSION_REQUEST: {
  306. // If request is cancelled, the result arrays are empty.
  307. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
  308. if (downloadRequest != null) {
  309. downloadFile();
  310. }
  311. } else {
  312. 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();
  313. }
  314. return true;
  315. }
  316. }
  317. return false;
  318. }
  319. };
  320. }