Skip to content

Commit 6ac7600

Browse files
committed
fix: openFilePicker bug
1 parent cb69a5d commit 6ac7600

File tree

5 files changed

+285
-12
lines changed

5 files changed

+285
-12
lines changed

README.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -406,15 +406,24 @@ SendIntentAndroid.openFileChooser(
406406

407407
## Example / Open File Picker
408408

409-
Opens Android own file selector to get the selected file and callback path from Uri
409+
Please add these lines to your AndroidManifest.xml file before using next example:
410+
411+
```xml
412+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
413+
```
414+
415+
416+
Opens Android own file selector to get the selected file and callback path from Uri.
417+
Allow multiple selection and don't forget check permission before using.
410418

411419
```javascript
412420
SendIntentAndroid.openFilePicker(
413421
{
414-
type: "file_mimetype", //default is "*/*"
415-
title: "selector title", //default is "Choose File"
422+
type: "file_mimetype", //optional, default is "*/*"
423+
title: "selector title", //optional, default is " "
424+
multiple: true, //optional, default is false
416425
},
417-
filePath => {}
426+
stringArr => {} //return filePaths string and need parse to array (minimum length = 1)
418427
);
419428
```
420429

android/src/main/java/com/burnweb/rnsendintent/RNSendIntentModule.java

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import java.util.List;
3434
import java.util.Map;
3535

36+
import com.burnweb.rnsendintent.utils.RealPathUtil;
3637
import com.facebook.react.bridge.ActivityEventListener;
3738
import com.facebook.react.bridge.BaseActivityEventListener;
3839
import com.facebook.react.bridge.Callback;
@@ -59,7 +60,7 @@
5960

6061
public class RNSendIntentModule extends ReactContextBaseJavaModule {
6162

62-
private static final int FILE_SELECT_CODE = 20190903;
63+
private static final int FILE_SELECT_CODE = 0;
6364
private static final String TAG = RNSendIntentModule.class.getSimpleName();
6465

6566
private static final String TEXT_PLAIN = "text/plain";
@@ -778,13 +779,16 @@ public void openAllEmailApp() {
778779

779780
@ReactMethod
780781
public void openFilePicker(ReadableMap options,Callback callback) {
782+
//Needs permission "android.permission.READ_EXTERNAL_STORAGE"
781783
mCallback = callback;
782784
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
783-
intent.setType(options.getString("type"));
785+
786+
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, options.hasKey("multiple") && options.getBoolean("multiple"));
787+
intent.setType(options.hasKey("type") ? options.getString("type") : "*/*");
784788
intent.addCategory(Intent.CATEGORY_OPENABLE);
785789
try {
786790
Activity currentActivity = getCurrentActivity();
787-
currentActivity.startActivityForResult(Intent.createChooser(intent, options.getString("title")),FILE_SELECT_CODE);
791+
currentActivity.startActivityForResult(Intent.createChooser(intent, options.hasKey("title") ? options.getString("title") : ""),FILE_SELECT_CODE);
788792
} catch (android.content.ActivityNotFoundException ex) {
789793

790794
}
@@ -837,8 +841,24 @@ public void showIgnoreBatteryOptimizationsSettings() {
837841
@Override
838842
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
839843
if (requestCode == FILE_SELECT_CODE && data!=null) {
840-
Uri uri = data.getData();
841-
mCallback.invoke(uri.getPath());
844+
if(data.getData() != null){ // Single file picker
845+
846+
String res = RealPathUtil.getRealPath(reactContext, data.getData());
847+
res = res != null ? "\""+res+"\"" : null;
848+
mCallback.invoke("["+res+"]"); // min length = 1
849+
850+
} else if(data.getClipData() != null){ // Multiple files picker
851+
852+
int len = data.getClipData().getItemCount();
853+
String[] result = new String[len];
854+
for(int i = 0; i < len; i++) {
855+
Uri uri = data.getClipData().getItemAt(i).getUri();
856+
String res = RealPathUtil.getRealPath(reactContext, uri);
857+
result[i] = res != null ? "\""+res+"\"" : null;
858+
}
859+
860+
mCallback.invoke(Arrays.toString(result));
861+
}
842862
}
843863
}
844864
};
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
package com.burnweb.rnsendintent.utils;
2+
3+
import android.annotation.SuppressLint;
4+
import android.content.ContentUris;
5+
import android.content.Context;
6+
import android.database.Cursor;
7+
import android.net.Uri;
8+
import android.os.Build;
9+
import android.os.Environment;
10+
import android.provider.DocumentsContract;
11+
import android.provider.MediaStore;
12+
13+
import androidx.loader.content.CursorLoader;
14+
15+
import java.io.File;
16+
17+
public class RealPathUtil {
18+
19+
public static String getRealPath(Context context, Uri fileUri) {
20+
String realPath;
21+
// SDK < API11
22+
if (Build.VERSION.SDK_INT < 11) {
23+
realPath = getRealPathFromURI_BelowAPI11(context, fileUri);
24+
}
25+
// SDK >= 11 && SDK < 19
26+
else if (Build.VERSION.SDK_INT < 19) {
27+
realPath = getRealPathFromURI_API11to18(context, fileUri);
28+
}
29+
// SDK > 19 (Android 4.4) and up
30+
else {
31+
realPath = getRealPathFromURI_API19(context, fileUri);
32+
}
33+
return realPath;
34+
}
35+
36+
37+
@SuppressLint("NewApi")
38+
public static String getRealPathFromURI_API11to18(Context context, Uri contentUri) {
39+
String[] proj = {MediaStore.Images.Media.DATA};
40+
String result = null;
41+
42+
CursorLoader cursorLoader = new CursorLoader(context, contentUri, proj, null, null, null);
43+
Cursor cursor = cursorLoader.loadInBackground();
44+
45+
if (cursor != null) {
46+
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
47+
cursor.moveToFirst();
48+
result = cursor.getString(column_index);
49+
cursor.close();
50+
}
51+
return result;
52+
}
53+
54+
public static String getRealPathFromURI_BelowAPI11(Context context, Uri contentUri) {
55+
String[] proj = {MediaStore.Images.Media.DATA};
56+
Cursor cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
57+
int column_index = 0;
58+
String result = "";
59+
if (cursor != null) {
60+
column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
61+
cursor.moveToFirst();
62+
result = cursor.getString(column_index);
63+
cursor.close();
64+
return result;
65+
}
66+
return result;
67+
}
68+
69+
@SuppressLint("NewApi")
70+
public static String getRealPathFromURI_API19(final Context context, final Uri uri) {
71+
72+
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
73+
74+
// DocumentProvider
75+
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
76+
// ExternalStorageProvider
77+
if (isExternalStorageDocument(uri)) {
78+
final String docId = DocumentsContract.getDocumentId(uri);
79+
final String[] split = docId.split(":");
80+
final String type = split[0];
81+
82+
// This is for checking Main Memory
83+
if ("primary".equalsIgnoreCase(type)) {
84+
if (split.length > 1) {
85+
return Environment.getExternalStorageDirectory() + "/" + split[1];
86+
} else {
87+
return Environment.getExternalStorageDirectory() + "/";
88+
}
89+
// This is for checking SD Card
90+
} else {
91+
// TODO handle non-primary volumes
92+
String filePath = "";
93+
//getExternalMediaDirs() added in API 21
94+
File[] external = context.getExternalMediaDirs();
95+
if (external.length > 1) {
96+
filePath = external[1].getAbsolutePath();
97+
filePath = filePath.substring(0, filePath.indexOf("Android")) + split[1];
98+
}
99+
return filePath;
100+
//return "storage" + "/" + docId.replace(":", "/");
101+
}
102+
103+
}
104+
// DownloadsProvider
105+
else if (isDownloadsDocument(uri)) {
106+
String fileName = getFilePath(context, uri);
107+
if (fileName != null) {
108+
return Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName;
109+
}
110+
111+
final String id = DocumentsContract.getDocumentId(uri);
112+
if (id.startsWith("raw:")) {
113+
return id.replaceFirst("raw:", "");
114+
}
115+
final Uri contentUri = ContentUris.withAppendedId(
116+
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
117+
return getDataColumn(context, contentUri, null, null);
118+
}
119+
// MediaProvider
120+
else if (isMediaDocument(uri)) {
121+
final String docId = DocumentsContract.getDocumentId(uri);
122+
final String[] split = docId.split(":");
123+
final String type = split[0];
124+
125+
Uri contentUri = null;
126+
if ("image".equals(type)) {
127+
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
128+
} else if ("video".equals(type)) {
129+
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
130+
} else if ("audio".equals(type)) {
131+
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
132+
}
133+
134+
final String selection = "_id=?";
135+
final String[] selectionArgs = new String[]{
136+
split[1]
137+
};
138+
139+
return getDataColumn(context, contentUri, selection, selectionArgs);
140+
}
141+
}
142+
// MediaStore (and general)
143+
else if ("content".equalsIgnoreCase(uri.getScheme())) {
144+
145+
// Return the remote address
146+
if (isGooglePhotosUri(uri))
147+
return uri.getLastPathSegment();
148+
149+
return getDataColumn(context, uri, null, null);
150+
}
151+
// File
152+
else if ("file".equalsIgnoreCase(uri.getScheme())) {
153+
return uri.getPath();
154+
}
155+
156+
return null;
157+
}
158+
159+
public static String getDataColumn(Context context, Uri uri, String selection,
160+
String[] selectionArgs) {
161+
162+
Cursor cursor = null;
163+
final String column = "_data";
164+
final String[] projection = {
165+
column
166+
};
167+
168+
try {
169+
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
170+
null);
171+
if (cursor != null && cursor.moveToFirst()) {
172+
final int index = cursor.getColumnIndexOrThrow(column);
173+
174+
return cursor.getString(index);
175+
}
176+
} finally {
177+
if (cursor != null)
178+
cursor.close();
179+
}
180+
return null;
181+
}
182+
183+
184+
public static String getFilePath(Context context, Uri uri) {
185+
186+
Cursor cursor = null;
187+
final String[] projection = {
188+
MediaStore.MediaColumns.DISPLAY_NAME
189+
};
190+
191+
try {
192+
cursor = context.getContentResolver().query(uri, projection, null, null,
193+
null);
194+
if (cursor != null && cursor.moveToFirst()) {
195+
final int index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME);
196+
return cursor.getString(index);
197+
}
198+
} finally {
199+
if (cursor != null)
200+
cursor.close();
201+
}
202+
return null;
203+
}
204+
205+
/**
206+
* @param uri The Uri to check.
207+
* @return Whether the Uri authority is ExternalStorageProvider.
208+
*/
209+
public static boolean isExternalStorageDocument(Uri uri) {
210+
return "com.android.externalstorage.documents".equals(uri.getAuthority());
211+
}
212+
213+
/**
214+
* @param uri The Uri to check.
215+
* @return Whether the Uri authority is DownloadsProvider.
216+
*/
217+
public static boolean isDownloadsDocument(Uri uri) {
218+
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
219+
}
220+
221+
/**
222+
* @param uri The Uri to check.
223+
* @return Whether the Uri authority is MediaProvider.
224+
*/
225+
public static boolean isMediaDocument(Uri uri) {
226+
return "com.android.providers.media.documents".equals(uri.getAuthority());
227+
}
228+
229+
/**
230+
* @param uri The Uri to check.
231+
* @return Whether the Uri authority is Google Photos.
232+
*/
233+
public static boolean isGooglePhotosUri(Uri uri) {
234+
return "com.google.android.apps.photos.content".equals(uri.getAuthority());
235+
}
236+
237+
}

index.d.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ declare namespace SendIntentAndroid {
4343
type: string
4444
}
4545

46+
interface FilePickerOptions {
47+
type?: string
48+
title?: string
49+
multiple?: boolean
50+
}
51+
4652
const sendText: (config: TextIntentConfig) => void
4753
const sendPhoneCall: (phoneNumber: string, phoneAppOnly?: boolean) => void
4854
const sendPhoneDial: (phoneNumber: string, phoneAppOnly?: boolean) => void
@@ -68,10 +74,11 @@ declare namespace SendIntentAndroid {
6874
const openChromeIntent: (dataUri: string) => Promise<boolean>
6975
const openDownloadManager: () => void
7076
const openFileChooser: (options: FileChooserOptions, title: string) => void
77+
const openFilePicker: (options: FilePickerOptions, callback: void) => void
7178
const openEmailApp: () => void
7279
const openAllEmailApp: () => void
7380
const TEXT_PLAIN: unique symbol
7481
const TEXT_HTML: unique symbol
7582
}
7683

77-
export = SendIntentAndroid
84+
export = SendIntentAndroid

index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ var SendIntentAndroid = {
105105
openFileChooser(options, title) {
106106
return RNSendIntentAndroid.openFileChooser(options, title);
107107
},
108-
openFilePicker({ type = "*/*", title = "Choose File" }, callback) {
109-
return RNSendIntentAndroid.openFilePicker({ type, title }, callback);
108+
openFilePicker(options, callback) {
109+
return RNSendIntentAndroid.openFilePicker(options, callback);
110110
},
111111
openEmailApp() {
112112
RNSendIntentAndroid.openEmailApp();

0 commit comments

Comments
 (0)