From d5d3c9190882a60a3b0ed01fcb17ed6f73e53518 Mon Sep 17 00:00:00 2001 From: Tuan Luong Date: Tue, 7 Jan 2020 19:17:35 +0700 Subject: [PATCH 01/10] get real file path --- .../DocumentPickerModule.java | 9 +- .../PathResolver.java | 228 ++++++++++++++++++ 2 files changed, 233 insertions(+), 4 deletions(-) create mode 100644 android/src/main/java/io/github/elyx0/reactnativedocumentpicker/PathResolver.java diff --git a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java index 5985bce1..3bc8737a 100644 --- a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java +++ b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java @@ -177,19 +177,18 @@ public void onShowActivityResult(int resultCode, Intent data, Promise promise) { private WritableMap getMetadata(Uri uri) { WritableMap map = Arguments.createMap(); - map.putString(FIELD_URI, uri.toString()); - ContentResolver contentResolver = getReactApplicationContext().getContentResolver(); map.putString(FIELD_TYPE, contentResolver.getType(uri)); + String fileName = ""; Cursor cursor = contentResolver.query(uri, null, null, null, null, null); - try { if (cursor != null && cursor.moveToFirst()) { int displayNameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); if (!cursor.isNull(displayNameIndex)) { - map.putString(FIELD_NAME, cursor.getString(displayNameIndex)); + fileName = cursor.getString(displayNameIndex); + map.putString(FIELD_NAME, fileName); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { @@ -209,6 +208,8 @@ private WritableMap getMetadata(Uri uri) { cursor.close(); } } + String path = PathResolver.getRealPathFromURI(getReactApplicationContext(), uri); + map.putString(FIELD_URI, path); return map; } diff --git a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/PathResolver.java b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/PathResolver.java new file mode 100644 index 00000000..d2cf803f --- /dev/null +++ b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/PathResolver.java @@ -0,0 +1,228 @@ +package io.github.elyx0.reactnativedocumentpicker; + +import android.annotation.TargetApi; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.text.TextUtils; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; + +/** + * Original file from rn-fetch-blob + */ +public class PathResolver { + @TargetApi(19) + public static String getRealPathFromURI(final Context context, final Uri uri) { + + final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + + // DocumentProvider + if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { + // ExternalStorageProvider + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } + + // TODO handle non-primary volumes + } + // DownloadsProvider + else if (isDownloadsDocument(uri)) { + try { + final String id = DocumentsContract.getDocumentId(uri); + //Starting with Android O, this "id" is not necessarily a long (row number), + //but might also be a "raw:/some/file/path" URL + if (id != null && id.startsWith("raw:/")) { + Uri rawuri = Uri.parse(id); + String path = rawuri.getPath(); + return path; + } + final Uri contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + + return getDataColumn(context, contentUri, null, null); + } + catch (Exception ex) { + //something went wrong, but android should still be able to handle the original uri by returning null here (see readFile(...)) + return null; + } + + } + // MediaProvider + else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[] { + split[1] + }; + + return getDataColumn(context, contentUri, selection, selectionArgs); + } + else if ("content".equalsIgnoreCase(uri.getScheme())) { + + // Return the remote address + if (isGooglePhotosUri(uri)) + return uri.getLastPathSegment(); + + String path = getDataColumn(context, uri, null, null); + if (TextUtils.isEmpty(path)) { + // Try to copy file + return copyFile(context, uri); + } + return path; + } + // Other Providers + else{ + return copyFile(context, uri); + } + } + // MediaStore (and general) + else if ("content".equalsIgnoreCase(uri.getScheme())) { + + // Return the remote address + if (isGooglePhotosUri(uri)) + return uri.getLastPathSegment(); + + return getDataColumn(context, uri, null, null); + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + return null; + } + + static String copyFile(Context context, Uri uri) { + try { + InputStream attachment = context.getContentResolver().openInputStream(uri); + if (attachment != null) { + String filename = getContentName(context.getContentResolver(), uri); + if (filename != null) { + File file = new File(context.getCacheDir(), filename); + FileOutputStream tmp = new FileOutputStream(file); + byte[] buffer = new byte[1024]; + while (attachment.read(buffer) > 0) { + tmp.write(buffer); + } + tmp.close(); + attachment.close(); + return file.getAbsolutePath(); + } + } + } catch (Exception e) { + return null; + } + return null; + } + + private static String getContentName(ContentResolver resolver, Uri uri) { + Cursor cursor = resolver.query(uri, null, null, null, null); + cursor.moveToFirst(); + int nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME); + if (nameIndex >= 0) { + String name = cursor.getString(nameIndex); + cursor.close(); + return name; + } + return null; + } + + /** + * Get the value of the data column for this Uri. This is useful for + * MediaStore Uris, and other file-based ContentProviders. + * + * @param context The context. + * @param uri The Uri to query. + * @param selection (Optional) Filter used in the query. + * @param selectionArgs (Optional) Selection arguments used in the query. + * @return The value of the _data column, which is typically a file path. + */ + public static String getDataColumn(Context context, Uri uri, String selection, + String[] selectionArgs) { + + Cursor cursor = null; + String result = null; + final String column = "_data"; + final String[] projection = { + column + }; + + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, + null); + if (cursor != null && cursor.moveToFirst()) { + final int index = cursor.getColumnIndexOrThrow(column); + result = cursor.getString(index); + } + } + catch (Exception ex) { + ex.printStackTrace(); + return null; + } + finally { + if (cursor != null) + cursor.close(); + } + return result; + } + + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + public static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + public static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + public static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is Google Photos. + */ + public static boolean isGooglePhotosUri(Uri uri) { + return "com.google.android.apps.photos.content".equals(uri.getAuthority()); + } + +} \ No newline at end of file From 04687f1dc69af80e803be92698f01e4e993ac0e6 Mon Sep 17 00:00:00 2001 From: Tuan Luong Date: Tue, 7 Jan 2020 19:19:15 +0700 Subject: [PATCH 02/10] update variable --- .../DocumentPickerModule.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java index 3bc8737a..0ddd9517 100644 --- a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java +++ b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java @@ -177,18 +177,18 @@ public void onShowActivityResult(int resultCode, Intent data, Promise promise) { private WritableMap getMetadata(Uri uri) { WritableMap map = Arguments.createMap(); - ContentResolver contentResolver = getReactApplicationContext().getContentResolver(); + String path = PathResolver.getRealPathFromURI(getReactApplicationContext(), uri); + map.putString(FIELD_URI, path); + ContentResolver contentResolver = getReactApplicationContext().getContentResolver(); map.putString(FIELD_TYPE, contentResolver.getType(uri)); - String fileName = ""; Cursor cursor = contentResolver.query(uri, null, null, null, null, null); try { if (cursor != null && cursor.moveToFirst()) { int displayNameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); if (!cursor.isNull(displayNameIndex)) { - fileName = cursor.getString(displayNameIndex); - map.putString(FIELD_NAME, fileName); + map.putString(FIELD_NAME, cursor.getString(displayNameIndex)); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { @@ -208,8 +208,6 @@ private WritableMap getMetadata(Uri uri) { cursor.close(); } } - String path = PathResolver.getRealPathFromURI(getReactApplicationContext(), uri); - map.putString(FIELD_URI, path); return map; } From 986abfc23a37f109812f3d087f49a5650feb66c9 Mon Sep 17 00:00:00 2001 From: Tuan Luong Date: Thu, 13 Feb 2020 14:31:02 +0700 Subject: [PATCH 03/10] add property to get file path from uri --- README.md | 3 +- .../DocumentPickerModule.java | 135 ++++++++++++------ index.d.ts | 3 +- 3 files changed, 96 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 38453556..953cb60d 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,7 @@ Use `pick` or `pickMultiple` to open a document picker for the user to select fi - On iOS these must be Apple "[Uniform Type Identifiers](https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html)" - If `type` is omitted it will be treated as `*/*` or `public.content`. - Multiple type strings are not supported on Android before KitKat (API level 19), Jellybean will fall back to `*/*` if you provide an array with more than one value. +- **[Android only] `getPath`**: `boolean` which defaults to `false`. If `getPath` is set to true, it will try to get the real path from content uri. If file is from a remote source, it will download and return the cache file. - **[UWP only] `readContent`**: Boolean which defaults to `false`. If `readContent` is set to true the content of the picked file/files will be read and supplied in the result object. - Be aware that this can introduce a huge performance hit in case of big files. (The files are read completely and into the memory and encoded to base64 afterwards to add them to the result object) @@ -117,7 +118,7 @@ Use `pick` or `pickMultiple` to open a document picker for the user to select fi The object a `pick` Promise resolves to or the objects in the array a `pickMultiple` Promise resolves to will contain the following keys. -- **`uri`**: The URI representing the document picked by the user. _On iOS this will be a `file://` URI for a temporary file in your app's container. On Android this will be a `content://` URI for a document provided by a DocumentProvider that must be accessed with a ContentResolver._ +- **`uri`**: The URI representing the document picked by the user. _On iOS this will be a `file://` URI for a temporary file in your app's container. On Android this will be a `content://` URI for a document provided by a DocumentProvider that must be accessed with a ContentResolver. If `getPath` set to true, this value will be the file path from storage_ - **`type`**: The MIME type of the file. _On Android some DocumentProviders may not provide MIME types for their documents. On iOS this MIME type is based on the best MIME type for the file extension according to Apple's internal "Uniform Type Identifiers" database._ - **`name`**: The display name of the file. _This is normally the filename of the file, but Android does not guarantee that this will be a filename from all DocumentProviders._ - **`size`**: The file size of the document. _On Android some DocumentProviders may not provide this information for a document._ diff --git a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java index 0ddd9517..769cc152 100644 --- a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java +++ b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java @@ -4,6 +4,7 @@ import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.ContentResolver; +import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.Uri; @@ -16,8 +17,10 @@ import com.facebook.react.bridge.ActivityEventListener; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.BaseActivityEventListener; +import com.facebook.react.bridge.GuardedResultAsyncTask; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableArray; @@ -25,6 +28,9 @@ import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; +import java.util.ArrayList; +import java.util.List; + /** * @see android documentation */ @@ -42,6 +48,7 @@ public class DocumentPickerModule extends ReactContextBaseJavaModule { private static final String OPTION_TYPE = "type"; private static final String OPTION_MULIPLE = "multiple"; + private static final String OPTION_GET_PATH= "getPath"; private static final String FIELD_URI = "uri"; private static final String FIELD_NAME = "name"; @@ -54,7 +61,6 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode, if (requestCode == READ_REQUEST_CODE) { if (promise != null) { onShowActivityResult(resultCode, data, promise); - promise = null; } } } @@ -70,6 +76,7 @@ private String[] readableArrayToStringArray(ReadableArray readableArray) { } private Promise promise; + private boolean getRealPath; public DocumentPickerModule(ReactApplicationContext reactContext) { super(reactContext); @@ -90,14 +97,14 @@ public String getName() { @ReactMethod public void pick(ReadableMap args, Promise promise) { Activity currentActivity = getCurrentActivity(); + this.promise = promise; + this.getRealPath = args.hasKey(OPTION_GET_PATH) && args.getBoolean(OPTION_GET_PATH); if (currentActivity == null) { - promise.reject(E_ACTIVITY_DOES_NOT_EXIST, "Current activity does not exist"); + sendError(E_ACTIVITY_DOES_NOT_EXIST, "Current activity does not exist"); return; } - this.promise = promise; - try { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.addCategory(Intent.CATEGORY_OPENABLE); @@ -128,18 +135,16 @@ public void pick(ReadableMap args, Promise promise) { currentActivity.startActivityForResult(intent, READ_REQUEST_CODE, Bundle.EMPTY); } catch (ActivityNotFoundException e) { - this.promise.reject(E_UNABLE_TO_OPEN_FILE_TYPE, e.getLocalizedMessage()); - this.promise = null; + sendError(E_UNABLE_TO_OPEN_FILE_TYPE, e.getLocalizedMessage()); } catch (Exception e) { e.printStackTrace(); - this.promise.reject(E_FAILED_TO_SHOW_PICKER, e.getLocalizedMessage()); - this.promise = null; + sendError(E_FAILED_TO_SHOW_PICKER, e.getLocalizedMessage()); } } public void onShowActivityResult(int resultCode, Intent data, Promise promise) { if (resultCode == Activity.RESULT_CANCELED) { - promise.reject(E_DOCUMENT_PICKER_CANCELED, "User canceled document picker"); + sendError(E_DOCUMENT_PICKER_CANCELED, "User canceled document picker"); } else if (resultCode == Activity.RESULT_OK) { Uri uri = null; ClipData clipData = null; @@ -150,65 +155,109 @@ public void onShowActivityResult(int resultCode, Intent data, Promise promise) { } try { - WritableArray results = Arguments.createArray(); - + List uris = new ArrayList<>(); if (uri != null) { - results.pushMap(getMetadata(uri)); + uris.add(uri); } else if (clipData != null && clipData.getItemCount() > 0) { final int length = clipData.getItemCount(); for (int i = 0; i < length; ++i) { ClipData.Item item = clipData.getItemAt(i); - results.pushMap(getMetadata(item.getUri())); + uris.add(item.getUri()); } } else { - promise.reject(E_INVALID_DATA_RETURNED, "Invalid data returned by intent"); + sendError(E_INVALID_DATA_RETURNED, "Invalid data returned by intent"); return; } - promise.resolve(results); + new ProcessDataTask(getReactApplicationContext(), uris, getRealPath, promise).execute(); } catch (Exception e) { - promise.reject(E_UNEXPECTED_EXCEPTION, e.getLocalizedMessage(), e); + sendError(E_UNEXPECTED_EXCEPTION, e.getLocalizedMessage(), e); } } else { - promise.reject(E_UNKNOWN_ACTIVITY_RESULT, "Unknown activity result: " + resultCode); + sendError(E_UNKNOWN_ACTIVITY_RESULT, "Unknown activity result: " + resultCode); } } - private WritableMap getMetadata(Uri uri) { - WritableMap map = Arguments.createMap(); + static class ProcessDataTask extends GuardedResultAsyncTask { - String path = PathResolver.getRealPathFromURI(getReactApplicationContext(), uri); - map.putString(FIELD_URI, path); + private Context context; + private final List uris; + private final boolean getRealPath; + private final Promise promise; - ContentResolver contentResolver = getReactApplicationContext().getContentResolver(); - map.putString(FIELD_TYPE, contentResolver.getType(uri)); + protected ProcessDataTask(ReactContext reactContext, List uris, boolean getRealPath, Promise promise) { + super(reactContext.getExceptionHandler()); + this.context = reactContext.getApplicationContext(); + this.uris = uris; + this.getRealPath = getRealPath; + this.promise = promise; + } - Cursor cursor = contentResolver.query(uri, null, null, null, null, null); - try { - if (cursor != null && cursor.moveToFirst()) { - int displayNameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); - if (!cursor.isNull(displayNameIndex)) { - map.putString(FIELD_NAME, cursor.getString(displayNameIndex)); - } + @Override + protected ReadableArray doInBackgroundGuarded() { + WritableArray results = Arguments.createArray(); + for (Uri uri : uris) { + results.pushMap(getMetadata(uri)); + } + return results; + } + + @Override + protected void onPostExecuteGuarded(ReadableArray readableArray) { + promise.resolve(readableArray); + } + + private WritableMap getMetadata(Uri uri) { + WritableMap map = Arguments.createMap(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - int mimeIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_MIME_TYPE); - if (!cursor.isNull(mimeIndex)) { - map.putString(FIELD_TYPE, cursor.getString(mimeIndex)); + if (getRealPath) { + String path = PathResolver.getRealPathFromURI(context, uri); + map.putString(FIELD_URI, path); + } else { + map.putString(FIELD_URI, uri.toString()); + } + + ContentResolver contentResolver = context.getContentResolver(); + map.putString(FIELD_TYPE, contentResolver.getType(uri)); + + Cursor cursor = contentResolver.query(uri, null, null, null, null, null); + try { + if (cursor != null && cursor.moveToFirst()) { + int displayNameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + if (!cursor.isNull(displayNameIndex)) { + map.putString(FIELD_NAME, cursor.getString(displayNameIndex)); } - } - int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE); - if (!cursor.isNull(sizeIndex)) { - map.putInt(FIELD_SIZE, cursor.getInt(sizeIndex)); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + int mimeIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_MIME_TYPE); + if (!cursor.isNull(mimeIndex)) { + map.putString(FIELD_TYPE, cursor.getString(mimeIndex)); + } + } + + int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE); + if (!cursor.isNull(sizeIndex)) { + map.putInt(FIELD_SIZE, cursor.getInt(sizeIndex)); + } + } + } finally { + if (cursor != null) { + cursor.close(); } } - } finally { - if (cursor != null) { - cursor.close(); - } + + return map; } + } - return map; + private void sendError(String code, String message) { + sendError(code, message, null); + } + + private void sendError(String code, String message, Exception e) { + if (this.promise != null) { + this.promise.reject(code, message, e); + this.promise = null; + } } } diff --git a/index.d.ts b/index.d.ts index a26c447f..aa772416 100644 --- a/index.d.ts +++ b/index.d.ts @@ -42,7 +42,8 @@ declare module 'react-native-document-picker' { windows: Types['extensions'] }; interface DocumentPickerOptions { - type: Array | DocumentType[OS] + type: Array | DocumentType[OS]; + getPath: boolean; } interface DocumentPickerResponse { uri: string; From 8002f652f3d69133d538056628a8aa1cd45ec59d Mon Sep 17 00:00:00 2001 From: Tuan Luong Date: Fri, 21 Feb 2020 16:04:08 +0700 Subject: [PATCH 04/10] fix bug could not get file from download folder --- .../PathResolver.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/PathResolver.java b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/PathResolver.java index d2cf803f..d5c284ed 100644 --- a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/PathResolver.java +++ b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/PathResolver.java @@ -50,10 +50,21 @@ else if (isDownloadsDocument(uri)) { String path = rawuri.getPath(); return path; } - final Uri contentUri = ContentUris.withAppendedId( - Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); - - return getDataColumn(context, contentUri, null, null); + String[] contentUriPrefixesToTry = new String[]{ + "content://downloads/public_downloads", + "content://downloads/my_downloads" + }; + for (String contentUriPrefix : contentUriPrefixesToTry) { + + final Uri contentUri = ContentUris.withAppendedId( + Uri.parse(contentUriPrefix), Long.valueOf(id)); + + String path = getDataColumn(context, contentUri, null, null); + if (path != null) { + return path; + } + } + return copyFile(context, uri); } catch (Exception ex) { //something went wrong, but android should still be able to handle the original uri by returning null here (see readFile(...)) From ba9bd80e020e7efb76831c3b5ca34e0553153c93 Mon Sep 17 00:00:00 2001 From: Tuan Luong Date: Wed, 25 Mar 2020 06:57:10 +0700 Subject: [PATCH 05/10] change getPath to usePath --- README.md | 4 ++-- .../elyx0/reactnativedocumentpicker/DocumentPickerModule.java | 4 ++-- index.d.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 953cb60d..8ae22a37 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ Use `pick` or `pickMultiple` to open a document picker for the user to select fi - On iOS these must be Apple "[Uniform Type Identifiers](https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html)" - If `type` is omitted it will be treated as `*/*` or `public.content`. - Multiple type strings are not supported on Android before KitKat (API level 19), Jellybean will fall back to `*/*` if you provide an array with more than one value. -- **[Android only] `getPath`**: `boolean` which defaults to `false`. If `getPath` is set to true, it will try to get the real path from content uri. If file is from a remote source, it will download and return the cache file. +- **[Android only] `usePath`**: `boolean` which defaults to `false`. If `usePath` is set to true, it will try to get the real path from content uri. If file is from a remote source, it will download and return the cache file. - **[UWP only] `readContent`**: Boolean which defaults to `false`. If `readContent` is set to true the content of the picked file/files will be read and supplied in the result object. - Be aware that this can introduce a huge performance hit in case of big files. (The files are read completely and into the memory and encoded to base64 afterwards to add them to the result object) @@ -118,7 +118,7 @@ Use `pick` or `pickMultiple` to open a document picker for the user to select fi The object a `pick` Promise resolves to or the objects in the array a `pickMultiple` Promise resolves to will contain the following keys. -- **`uri`**: The URI representing the document picked by the user. _On iOS this will be a `file://` URI for a temporary file in your app's container. On Android this will be a `content://` URI for a document provided by a DocumentProvider that must be accessed with a ContentResolver. If `getPath` set to true, this value will be the file path from storage_ +- **`uri`**: The URI representing the document picked by the user. _On iOS this will be a `file://` URI for a temporary file in your app's container. On Android this will be a `content://` URI for a document provided by a DocumentProvider that must be accessed with a ContentResolver. If `usePath` set to true, this value will be the file path from storage_ - **`type`**: The MIME type of the file. _On Android some DocumentProviders may not provide MIME types for their documents. On iOS this MIME type is based on the best MIME type for the file extension according to Apple's internal "Uniform Type Identifiers" database._ - **`name`**: The display name of the file. _This is normally the filename of the file, but Android does not guarantee that this will be a filename from all DocumentProviders._ - **`size`**: The file size of the document. _On Android some DocumentProviders may not provide this information for a document._ diff --git a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java index 769cc152..4a6bcc10 100644 --- a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java +++ b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java @@ -48,7 +48,7 @@ public class DocumentPickerModule extends ReactContextBaseJavaModule { private static final String OPTION_TYPE = "type"; private static final String OPTION_MULIPLE = "multiple"; - private static final String OPTION_GET_PATH= "getPath"; + private static final String OPTION_USE_PATH= "usePath"; private static final String FIELD_URI = "uri"; private static final String FIELD_NAME = "name"; @@ -98,7 +98,7 @@ public String getName() { public void pick(ReadableMap args, Promise promise) { Activity currentActivity = getCurrentActivity(); this.promise = promise; - this.getRealPath = args.hasKey(OPTION_GET_PATH) && args.getBoolean(OPTION_GET_PATH); + this.getRealPath = args.hasKey(OPTION_USE_PATH) && args.getBoolean(OPTION_USE_PATH); if (currentActivity == null) { sendError(E_ACTIVITY_DOES_NOT_EXIST, "Current activity does not exist"); diff --git a/index.d.ts b/index.d.ts index aa772416..41c2b36d 100644 --- a/index.d.ts +++ b/index.d.ts @@ -43,7 +43,7 @@ declare module 'react-native-document-picker' { }; interface DocumentPickerOptions { type: Array | DocumentType[OS]; - getPath: boolean; + usePath: boolean; } interface DocumentPickerResponse { uri: string; From 5c74d9dec13c588caa606d6b19804e410297095e Mon Sep 17 00:00:00 2001 From: Tuan Luong Date: Tue, 9 Jun 2020 12:46:45 +0700 Subject: [PATCH 06/10] Get data from google drive app --- .../PathResolver.java | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/PathResolver.java b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/PathResolver.java index d5c284ed..8b15e30d 100644 --- a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/PathResolver.java +++ b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/PathResolver.java @@ -70,7 +70,7 @@ else if (isDownloadsDocument(uri)) { //something went wrong, but android should still be able to handle the original uri by returning null here (see readFile(...)) return null; } - + } // MediaProvider else if (isMediaDocument(uri)) { @@ -96,16 +96,7 @@ else if (isMediaDocument(uri)) { } else if ("content".equalsIgnoreCase(uri.getScheme())) { - // Return the remote address - if (isGooglePhotosUri(uri)) - return uri.getLastPathSegment(); - - String path = getDataColumn(context, uri, null, null); - if (TextUtils.isEmpty(path)) { - // Try to copy file - return copyFile(context, uri); - } - return path; + return getContentData(context, uri); } // Other Providers else{ @@ -115,11 +106,7 @@ else if ("content".equalsIgnoreCase(uri.getScheme())) { // MediaStore (and general) else if ("content".equalsIgnoreCase(uri.getScheme())) { - // Return the remote address - if (isGooglePhotosUri(uri)) - return uri.getLastPathSegment(); - - return getDataColumn(context, uri, null, null); + return getContentData(context, uri); } // File else if ("file".equalsIgnoreCase(uri.getScheme())) { @@ -129,6 +116,22 @@ else if ("file".equalsIgnoreCase(uri.getScheme())) { return null; } + private static String getContentData(Context context, Uri uri) { + // Return the remote address + if (isGooglePhotosUri(uri)) { + return uri.getLastPathSegment(); + } else if (isGoogleDriveUri(uri)) { + return copyFile(context, uri); + } + + String path = getDataColumn(context, uri, null, null); + if (TextUtils.isEmpty(path)) { + // Try to copy file + return copyFile(context, uri); + } + return path; + } + static String copyFile(Context context, Uri uri) { try { InputStream attachment = context.getContentResolver().openInputStream(uri); @@ -236,4 +239,7 @@ public static boolean isGooglePhotosUri(Uri uri) { return "com.google.android.apps.photos.content".equals(uri.getAuthority()); } -} \ No newline at end of file + public static boolean isGoogleDriveUri(Uri uri) { + return "content://com.google.android.apps.docs.storage.legacy".equals(uri.getAuthority()); + } +} From b7912e658e299ceaa473870e2f3c59dae44e9592 Mon Sep 17 00:00:00 2001 From: Tuan Luong Date: Wed, 17 Jun 2020 16:49:47 +0700 Subject: [PATCH 07/10] implement copyTo --- .../DocumentPickerModule.java | 96 +++++-- .../PathResolver.java | 245 ------------------ 2 files changed, 70 insertions(+), 271 deletions(-) delete mode 100644 android/src/main/java/io/github/elyx0/reactnativedocumentpicker/PathResolver.java diff --git a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java index eafa3d41..9f82533c 100644 --- a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java +++ b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java @@ -28,6 +28,10 @@ import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -48,10 +52,11 @@ public class DocumentPickerModule extends ReactContextBaseJavaModule { private static final String OPTION_TYPE = "type"; private static final String OPTION_MULIPLE = "multiple"; - private static final String OPTION_USE_PATH= "usePath"; + private static final String OPTION_COPYTO = "copyTo"; private static final String FIELD_URI = "uri"; private static final String FIELD_FILE_COPY_URI = "fileCopyUri"; + private static final String FIELD_COPY_ERROR = "copyError"; private static final String FIELD_NAME = "name"; private static final String FIELD_TYPE = "type"; private static final String FIELD_SIZE = "size"; @@ -68,7 +73,7 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode, }; private Promise promise; - private boolean getRealPath; + private String copyTo; public DocumentPickerModule(ReactApplicationContext reactContext) { super(reactContext); @@ -90,7 +95,7 @@ public String getName() { public void pick(ReadableMap args, Promise promise) { Activity currentActivity = getCurrentActivity(); this.promise = promise; - this.getRealPath = args.hasKey(OPTION_USE_PATH) && args.getBoolean(OPTION_USE_PATH); + this.copyTo = args.hasKey(OPTION_COPYTO) ? args.getString(OPTION_COPYTO) : null; if (currentActivity == null) { sendError(E_ACTIVITY_DOES_NOT_EXIST, "Current activity does not exist"); @@ -160,7 +165,7 @@ public void onShowActivityResult(int resultCode, Intent data, Promise promise) { return; } - new ProcessDataTask(getReactApplicationContext(), uris, getRealPath, promise).execute(); + new ProcessDataTask(getReactApplicationContext(), uris, copyTo, promise).execute(); } catch (Exception e) { sendError(E_UNEXPECTED_EXCEPTION, e.getLocalizedMessage(), e); } @@ -169,18 +174,17 @@ public void onShowActivityResult(int resultCode, Intent data, Promise promise) { } } - static class ProcessDataTask extends GuardedResultAsyncTask { - + private static class ProcessDataTask extends GuardedResultAsyncTask { private Context context; private final List uris; - private final boolean getRealPath; + private final String copyTo; private final Promise promise; - protected ProcessDataTask(ReactContext reactContext, List uris, boolean getRealPath, Promise promise) { + protected ProcessDataTask(ReactContext reactContext, List uris, String copyTo, Promise promise) { super(reactContext.getExceptionHandler()); this.context = reactContext.getApplicationContext(); this.uris = uris; - this.getRealPath = getRealPath; + this.copyTo = copyTo; this.promise = promise; } @@ -199,35 +203,24 @@ protected void onPostExecuteGuarded(ReadableArray readableArray) { } private WritableMap getMetadata(Uri uri) { + ContentResolver contentResolver = context.getContentResolver(); WritableMap map = Arguments.createMap(); - - if (getRealPath) { - String path = PathResolver.getRealPathFromURI(context, uri); - map.putString(FIELD_URI, path); - } else { - map.putString(FIELD_URI, uri.toString()); - } - // TODO vonovak - FIELD_FILE_COPY_URI is implemented on iOS only (copyTo) settings - map.putString(FIELD_FILE_COPY_URI, uri.toString()); - - ContentResolver contentResolver = getReactApplicationContext().getContentResolver(); - + map.putString(FIELD_URI, uri.toString()); map.putString(FIELD_TYPE, contentResolver.getType(uri)); - + String fileName = null; try (Cursor cursor = contentResolver.query(uri, null, null, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { int displayNameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); if (!cursor.isNull(displayNameIndex)) { - map.putString(FIELD_NAME, cursor.getString(displayNameIndex)); + fileName = cursor.getString(displayNameIndex); + map.putString(FIELD_NAME, fileName); } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { int mimeIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_MIME_TYPE); if (!cursor.isNull(mimeIndex)) { map.putString(FIELD_TYPE, cursor.getString(mimeIndex)); } } - int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE); if (!cursor.isNull(sizeIndex)) { map.putInt(FIELD_SIZE, cursor.getInt(sizeIndex)); @@ -235,9 +228,59 @@ private WritableMap getMetadata(Uri uri) { } } + if (copyTo != null) { + File dir = context.getCacheDir(); + if (copyTo.equals("documentDirectory")) { + dir = context.getFilesDir(); + } + if (fileName == null) { + fileName = DocumentsContract.getDocumentId(uri); + } + try { + File destFile = new File(dir, fileName); + String path = copyFile(context, uri, destFile); + map.putString(FIELD_FILE_COPY_URI, path); + } catch (IOException e) { + e.printStackTrace(); + map.putString(FIELD_FILE_COPY_URI, uri.toString()); + map.putString(FIELD_COPY_ERROR, e.getMessage()); + } + } else { + map.putString(FIELD_FILE_COPY_URI, uri.toString()); + } return map; } + public static String copyFile(Context context, Uri uri, File destFile) throws IOException { + InputStream in = null; + FileOutputStream out = null; + try { + in = context.getContentResolver().openInputStream(uri); + if (in != null) { + out = new FileOutputStream(destFile); + byte[] buffer = new byte[1024]; + while (in.read(buffer) > 0) { + out.write(buffer); + } + out.close(); + in.close(); + return destFile.getAbsolutePath(); + } else { + throw new NullPointerException("Invalid input stream"); + } + } catch (Exception e) { + try { + if (in != null) { + in.close(); + } + if (out != null) { + out.close(); + } + } catch (IOException ignored) {} + throw e; + } + } + } private void sendError(String code, String message) { @@ -246,8 +289,9 @@ private void sendError(String code, String message) { private void sendError(String code, String message, Exception e) { if (this.promise != null) { - this.promise.reject(code, message, e); + Promise temp = this.promise; this.promise = null; + temp.reject(code, message, e); } } } diff --git a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/PathResolver.java b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/PathResolver.java deleted file mode 100644 index 8b15e30d..00000000 --- a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/PathResolver.java +++ /dev/null @@ -1,245 +0,0 @@ -package io.github.elyx0.reactnativedocumentpicker; - -import android.annotation.TargetApi; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.os.Environment; -import android.provider.DocumentsContract; -import android.provider.MediaStore; -import android.text.TextUtils; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; - -/** - * Original file from rn-fetch-blob - */ -public class PathResolver { - @TargetApi(19) - public static String getRealPathFromURI(final Context context, final Uri uri) { - - final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; - - // DocumentProvider - if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { - // ExternalStorageProvider - if (isExternalStorageDocument(uri)) { - final String docId = DocumentsContract.getDocumentId(uri); - final String[] split = docId.split(":"); - final String type = split[0]; - - if ("primary".equalsIgnoreCase(type)) { - return Environment.getExternalStorageDirectory() + "/" + split[1]; - } - - // TODO handle non-primary volumes - } - // DownloadsProvider - else if (isDownloadsDocument(uri)) { - try { - final String id = DocumentsContract.getDocumentId(uri); - //Starting with Android O, this "id" is not necessarily a long (row number), - //but might also be a "raw:/some/file/path" URL - if (id != null && id.startsWith("raw:/")) { - Uri rawuri = Uri.parse(id); - String path = rawuri.getPath(); - return path; - } - String[] contentUriPrefixesToTry = new String[]{ - "content://downloads/public_downloads", - "content://downloads/my_downloads" - }; - for (String contentUriPrefix : contentUriPrefixesToTry) { - - final Uri contentUri = ContentUris.withAppendedId( - Uri.parse(contentUriPrefix), Long.valueOf(id)); - - String path = getDataColumn(context, contentUri, null, null); - if (path != null) { - return path; - } - } - return copyFile(context, uri); - } - catch (Exception ex) { - //something went wrong, but android should still be able to handle the original uri by returning null here (see readFile(...)) - return null; - } - - } - // MediaProvider - else if (isMediaDocument(uri)) { - final String docId = DocumentsContract.getDocumentId(uri); - final String[] split = docId.split(":"); - final String type = split[0]; - - Uri contentUri = null; - if ("image".equals(type)) { - contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - } else if ("video".equals(type)) { - contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; - } else if ("audio".equals(type)) { - contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; - } - - final String selection = "_id=?"; - final String[] selectionArgs = new String[] { - split[1] - }; - - return getDataColumn(context, contentUri, selection, selectionArgs); - } - else if ("content".equalsIgnoreCase(uri.getScheme())) { - - return getContentData(context, uri); - } - // Other Providers - else{ - return copyFile(context, uri); - } - } - // MediaStore (and general) - else if ("content".equalsIgnoreCase(uri.getScheme())) { - - return getContentData(context, uri); - } - // File - else if ("file".equalsIgnoreCase(uri.getScheme())) { - return uri.getPath(); - } - - return null; - } - - private static String getContentData(Context context, Uri uri) { - // Return the remote address - if (isGooglePhotosUri(uri)) { - return uri.getLastPathSegment(); - } else if (isGoogleDriveUri(uri)) { - return copyFile(context, uri); - } - - String path = getDataColumn(context, uri, null, null); - if (TextUtils.isEmpty(path)) { - // Try to copy file - return copyFile(context, uri); - } - return path; - } - - static String copyFile(Context context, Uri uri) { - try { - InputStream attachment = context.getContentResolver().openInputStream(uri); - if (attachment != null) { - String filename = getContentName(context.getContentResolver(), uri); - if (filename != null) { - File file = new File(context.getCacheDir(), filename); - FileOutputStream tmp = new FileOutputStream(file); - byte[] buffer = new byte[1024]; - while (attachment.read(buffer) > 0) { - tmp.write(buffer); - } - tmp.close(); - attachment.close(); - return file.getAbsolutePath(); - } - } - } catch (Exception e) { - return null; - } - return null; - } - - private static String getContentName(ContentResolver resolver, Uri uri) { - Cursor cursor = resolver.query(uri, null, null, null, null); - cursor.moveToFirst(); - int nameIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME); - if (nameIndex >= 0) { - String name = cursor.getString(nameIndex); - cursor.close(); - return name; - } - return null; - } - - /** - * Get the value of the data column for this Uri. This is useful for - * MediaStore Uris, and other file-based ContentProviders. - * - * @param context The context. - * @param uri The Uri to query. - * @param selection (Optional) Filter used in the query. - * @param selectionArgs (Optional) Selection arguments used in the query. - * @return The value of the _data column, which is typically a file path. - */ - public static String getDataColumn(Context context, Uri uri, String selection, - String[] selectionArgs) { - - Cursor cursor = null; - String result = null; - final String column = "_data"; - final String[] projection = { - column - }; - - try { - cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, - null); - if (cursor != null && cursor.moveToFirst()) { - final int index = cursor.getColumnIndexOrThrow(column); - result = cursor.getString(index); - } - } - catch (Exception ex) { - ex.printStackTrace(); - return null; - } - finally { - if (cursor != null) - cursor.close(); - } - return result; - } - - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is ExternalStorageProvider. - */ - public static boolean isExternalStorageDocument(Uri uri) { - return "com.android.externalstorage.documents".equals(uri.getAuthority()); - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is DownloadsProvider. - */ - public static boolean isDownloadsDocument(Uri uri) { - return "com.android.providers.downloads.documents".equals(uri.getAuthority()); - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is MediaProvider. - */ - public static boolean isMediaDocument(Uri uri) { - return "com.android.providers.media.documents".equals(uri.getAuthority()); - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is Google Photos. - */ - public static boolean isGooglePhotosUri(Uri uri) { - return "com.google.android.apps.photos.content".equals(uri.getAuthority()); - } - - public static boolean isGoogleDriveUri(Uri uri) { - return "content://com.google.android.apps.docs.storage.legacy".equals(uri.getAuthority()); - } -} From 5b7f9b7c36aff11c12cbc1cd630136e01e8ed7bd Mon Sep 17 00:00:00 2001 From: Tuan Luong Date: Wed, 17 Jun 2020 16:53:41 +0700 Subject: [PATCH 08/10] update README --- README.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c88a64c3..881e05ef 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,9 @@ The type or types of documents to allow selection of. May be an array of types a - If `type` is omitted it will be treated as `*/*` or `public.content`. - Multiple type strings are not supported on Android before KitKat (API level 19), Jellybean will fall back to `*/*` if you provide an array with more than one value. -##### [iOS only] `copyTo`:`"cachesDirectory" | "documentDirectory"`: +##### `copyTo`:`"cachesDirectory" | "documentDirectory"`: -If specified, the picked file is copied to `NSCachesDirectory` / `NSDocumentDirectory` directory. The uri of the copy will be available in result's `fileCopyUri`. If copying the file fails (eg. due to lack of space), `fileCopyUri` will be the same as `uri`, and more details about the error will be available in `copyError` field in the result. +If specified, the picked file is copied to `NSCachesDirectory` / `NSDocumentDirectory` (iOS) or `getCacheDir` / `getFilesDir` (Android). The uri of the copy will be available in result's `fileCopyUri`. If copying the file fails (eg. due to lack of space), `fileCopyUri` will be the same as `uri`, and more details about the error will be available in `copyError` field in the result. This should help if you need to work with the file(s) later on, because by default, [the picked documents are temporary files. They remain available only until your application terminates](https://developer.apple.com/documentation/uikit/uidocumentpickerdelegate/2902364-documentpicker). This may impact performance for large files, so keep this in mind if you expect users to pick particularly large files and your app does not need immediate read access. @@ -77,15 +77,11 @@ The object a `pick` Promise resolves to or the objects in the array a `pickMulti ##### `uri`: -The URI representing the document picked by the user. _On iOS this will be a `file://` URI for a temporary file in your app's container. On Android this will be a `content://` URI for a document provided by a DocumentProvider that must be accessed with a ContentResolver. If `usePath` set to true, this value will be the file path from storage_ - -##### [Android only] `usePath`**: - -`boolean` which defaults to `false`. If `usePath` is set to true, it will try to get the real path from content uri. If file is from a remote source, it will download and return the cache file. +The URI representing the document picked by the user. _On iOS this will be a `file://` URI for a temporary file in your app's container. On Android this will be a `content://` URI for a document provided by a DocumentProvider that must be accessed with a ContentResolver._ ##### `fileCopyUri`: -Same as `uri`, but has special meaning on iOS, if `copyTo` option is specified. +Same as `uri`, but has special meaning if `copyTo` option is specified. ##### `type`: From 3075de0e9825cfd75b75a30408761bfca0d4cf3e Mon Sep 17 00:00:00 2001 From: Tuan Luong Date: Wed, 17 Jun 2020 16:57:17 +0700 Subject: [PATCH 09/10] update index.d.ts --- index.d.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index ebcccabe..fdf6f905 100644 --- a/index.d.ts +++ b/index.d.ts @@ -48,7 +48,6 @@ declare module 'react-native-document-picker' { }; interface DocumentPickerOptions { type: Array | DocumentType[OS]; - usePath: boolean; copyTo?: 'cachesDirectory' | 'documentDirectory'; } interface DocumentPickerResponse { From 2e82dbdc920dd235e3f9646673ec8c16f4bb7a61 Mon Sep 17 00:00:00 2001 From: Tuan Luong Date: Thu, 21 Jan 2021 00:19:19 +0700 Subject: [PATCH 10/10] update code --- .../DocumentPickerModule.java | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java index 074074b2..69719712 100644 --- a/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java +++ b/android/src/main/java/io/github/elyx0/reactnativedocumentpicker/DocumentPickerModule.java @@ -32,6 +32,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; @@ -185,14 +186,14 @@ public void onShowActivityResult(int resultCode, Intent data, Promise promise) { } private static class ProcessDataTask extends GuardedResultAsyncTask { - private Context context; + private final WeakReference weakContext; private final List uris; private final String copyTo; private final Promise promise; protected ProcessDataTask(ReactContext reactContext, List uris, String copyTo, Promise promise) { super(reactContext.getExceptionHandler()); - this.context = reactContext.getApplicationContext(); + this.weakContext = new WeakReference<>(reactContext.getApplicationContext()); this.uris = uris; this.copyTo = copyTo; this.promise = promise; @@ -213,16 +214,19 @@ protected void onPostExecuteGuarded(ReadableArray readableArray) { } private WritableMap getMetadata(Uri uri) { + Context context = weakContext.get(); + if (context == null) { + return Arguments.createMap(); + } ContentResolver contentResolver = context.getContentResolver(); WritableMap map = Arguments.createMap(); map.putString(FIELD_URI, uri.toString()); map.putString(FIELD_TYPE, contentResolver.getType(uri)); - String fileName = null; try (Cursor cursor = contentResolver.query(uri, null, null, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { int displayNameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); if (!cursor.isNull(displayNameIndex)) { - fileName = cursor.getString(displayNameIndex); + String fileName = cursor.getString(displayNameIndex); map.putString(FIELD_NAME, fileName); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { @@ -238,13 +242,19 @@ private WritableMap getMetadata(Uri uri) { } } + prepareFileUri(context, map, uri); + return map; + } + + private void prepareFileUri(Context context, WritableMap map, Uri uri) { if (copyTo != null) { File dir = context.getCacheDir(); if (copyTo.equals("documentDirectory")) { dir = context.getFilesDir(); } + String fileName = map.getString(FIELD_NAME); if (fileName == null) { - fileName = DocumentsContract.getDocumentId(uri); + fileName = System.currentTimeMillis() + ""; } try { File destFile = new File(dir, fileName); @@ -258,7 +268,6 @@ private WritableMap getMetadata(Uri uri) { } else { map.putString(FIELD_FILE_COPY_URI, uri.toString()); } - return map; } public static String copyFile(Context context, Uri uri, File destFile) throws IOException { @@ -290,7 +299,6 @@ public static String copyFile(Context context, Uri uri, File destFile) throws IO throw e; } } - } private void sendError(String code, String message) {