diff --git a/android/app/src/main/java/com/mattermost/share/RealPathUtil.java b/android/app/src/main/java/com/mattermost/share/RealPathUtil.java index 60678e541..7244ce5cd 100644 --- a/android/app/src/main/java/com/mattermost/share/RealPathUtil.java +++ b/android/app/src/main/java/com/mattermost/share/RealPathUtil.java @@ -3,11 +3,9 @@ package com.mattermost.share; import android.content.Context; import android.database.Cursor; import android.net.Uri; -import android.os.Build; import android.provider.DocumentsContract; import android.provider.MediaStore; import android.provider.OpenableColumns; -import android.content.ContentUris; import android.content.ContentResolver; import android.os.Environment; import android.webkit.MimeTypeMap; @@ -15,18 +13,18 @@ import android.util.Log; import android.text.TextUtils; import android.os.ParcelFileDescriptor; + import java.io.*; import java.nio.channels.FileChannel; +import java.util.Objects; // Class based on the steveevers DocumentHelper https://gist.github.com/steveevers/a5af24c226f44bb8fdc3 public class RealPathUtil { public static String getRealPathFromURI(final Context context, final Uri uri) { - final boolean isKitKatOrNewer = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; - // DocumentProvider - if (isKitKatOrNewer && DocumentsContract.isDocumentUri(context, uri)) { + if (DocumentsContract.isDocumentUri(context, uri)) { // ExternalStorageProvider if (isExternalStorageDocument(uri)) { final String docId = DocumentsContract.getDocumentId(uri); @@ -111,6 +109,7 @@ public class RealPathUtil { int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); returnCursor.moveToFirst(); fileName = sanitizeFilename(returnCursor.getString(nameIndex)); + returnCursor.close(); } catch (Exception e) { // just continue to get the filename with the last segment of the path @@ -118,7 +117,7 @@ public class RealPathUtil { try { if (TextUtils.isEmpty(fileName)) { - fileName = sanitizeFilename(uri.getLastPathSegment().toString().trim()); + fileName = sanitizeFilename(uri.getLastPathSegment().trim()); } @@ -127,7 +126,6 @@ public class RealPathUtil { cacheDir.mkdirs(); } - String mimeType = getMimeType(uri.getPath()); tmpFile = new File(cacheDir, fileName); tmpFile.createNewFile(); @@ -234,7 +232,7 @@ public class RealPathUtil { private static void deleteRecursive(File fileOrDirectory) { if (fileOrDirectory.isDirectory()) - for (File child : fileOrDirectory.listFiles()) + for (File child : Objects.requireNonNull(fileOrDirectory.listFiles())) deleteRecursive(child); fileOrDirectory.delete(); diff --git a/patches/react-native-image-picker+2.3.4.patch b/patches/react-native-image-picker+2.3.4.patch index 683f7a8af..5817952ca 100644 --- a/patches/react-native-image-picker+2.3.4.patch +++ b/patches/react-native-image-picker+2.3.4.patch @@ -1,5 +1,5 @@ diff --git a/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/ImagePickerModule.java b/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/ImagePickerModule.java -index 48fb5c1..3872d91 100644 +index 48fb5c1..5ca1e20 100644 --- a/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/ImagePickerModule.java +++ b/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/ImagePickerModule.java @@ -3,6 +3,7 @@ package com.imagepicker; @@ -10,15 +10,7 @@ index 48fb5c1..3872d91 100644 import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -@@ -49,6 +50,7 @@ import java.io.InputStream; - import java.io.OutputStream; - import java.lang.ref.WeakReference; - import java.util.List; -+import java.util.ArrayList; - - import com.facebook.react.modules.core.PermissionListener; - import com.facebook.react.modules.core.PermissionAwareActivity; -@@ -69,6 +71,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule +@@ -69,6 +70,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule public static final int REQUEST_LAUNCH_IMAGE_LIBRARY = 13002; public static final int REQUEST_LAUNCH_VIDEO_LIBRARY = 13003; public static final int REQUEST_LAUNCH_VIDEO_CAPTURE = 13004; @@ -26,11 +18,14 @@ index 48fb5c1..3872d91 100644 public static final int REQUEST_PERMISSIONS_FOR_CAMERA = 14001; public static final int REQUEST_PERMISSIONS_FOR_LIBRARY = 14002; -@@ -266,26 +269,24 @@ public class ImagePickerModule extends ReactContextBaseJavaModule +@@ -265,27 +267,22 @@ public class ImagePickerModule extends ReactContextBaseJavaModule + { cameraIntent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, videoDurationLimit); } - } -+ else if (pickBoth) { +- } +- else +- { ++ } else if (pickBoth) { + Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + this.setImageCaptureUri(takePictureIntent); + Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); @@ -41,9 +36,7 @@ index 48fb5c1..3872d91 100644 + cameraIntent.putExtra(Intent.EXTRA_TITLE, "Choose an action"); + cameraIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray); + requestCode = REQUEST_LAUNCH_MIXED_CAPTURE; -+ } - else - { ++ } else { requestCode = REQUEST_LAUNCH_IMAGE_CAPTURE; cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); @@ -66,23 +59,24 @@ index 48fb5c1..3872d91 100644 } if (cameraIntent.resolveActivity(reactContext.getPackageManager()) == null) -@@ -350,16 +351,19 @@ public class ImagePickerModule extends ReactContextBaseJavaModule +@@ -349,17 +346,18 @@ public class ImagePickerModule extends ReactContextBaseJavaModule + requestCode = REQUEST_LAUNCH_VIDEO_LIBRARY; libraryIntent = new Intent(Intent.ACTION_PICK); libraryIntent.setType("video/*"); - } -+ else if (pickBoth) { +- } +- else +- { ++ } else if (pickBoth) { + libraryIntent = new Intent(Intent.ACTION_GET_CONTENT); + libraryIntent.addCategory(Intent.CATEGORY_OPENABLE); + libraryIntent.setType("image/*"); + libraryIntent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"image/*", "video/*"}); + requestCode = REQUEST_LAUNCH_MIXED_CAPTURE; -+ } - else - { ++ } else { requestCode = REQUEST_LAUNCH_IMAGE_LIBRARY; libraryIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); -- + - if (pickBoth) - { - libraryIntent.setType("image/* video/*"); @@ -91,7 +85,7 @@ index 48fb5c1..3872d91 100644 } if (libraryIntent.resolveActivity(reactContext.getPackageManager()) == null) -@@ -385,75 +389,47 @@ public class ImagePickerModule extends ReactContextBaseJavaModule +@@ -385,75 +383,42 @@ public class ImagePickerModule extends ReactContextBaseJavaModule } } @@ -113,29 +107,10 @@ index 48fb5c1..3872d91 100644 - callback = null; - return; - } -+ protected String getMimeType(Activity activity, Uri uri) { -+ String mimeType = null; -+ if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { -+ ContentResolver cr = activity.getApplicationContext().getContentResolver(); -+ mimeType = cr.getType(uri); -+ } else { -+ String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri -+ .toString()); -+ mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension( -+ fileExtension.toLowerCase()); -+ } -+ return mimeType; -+ } - +- - Uri uri = null; - switch (requestCode) -+ protected void extractImageFromResult(Activity activity, Uri uri, int requestCode) { -+ String realPath = getRealPathFromURI(uri); -+ String mime = getMimeType(activity, uri); -+ final boolean isUrl = !TextUtils.isEmpty(realPath) && -+ Patterns.WEB_URL.matcher(realPath).matches(); -+ if (realPath == null || isUrl) - { +- { - case REQUEST_LAUNCH_IMAGE_CAPTURE: - uri = cameraCaptureURI; - break; @@ -172,20 +147,35 @@ index 48fb5c1..3872d91 100644 - responseHelper.invokeResponse(callback); - callback = null; - return; -- ++ protected String getMimeType(Activity activity, Uri uri) { ++ String mimeType = null; ++ if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { ++ ContentResolver cr = activity.getApplicationContext().getContentResolver(); ++ mimeType = cr.getType(uri); ++ } else { ++ String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString()); ++ mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension( ++ fileExtension.toLowerCase()); ++ } ++ return mimeType; ++ } + - case REQUEST_LAUNCH_VIDEO_CAPTURE: - final String path = getRealPathFromURI(data.getData()); - responseHelper.putString("uri", data.getData().toString()); - responseHelper.putString("path", path); - fileScan(reactContext, path); -+ try -+ { ++ protected void extractImageFromResult(Activity activity, Uri uri, int requestCode) { ++ String realPath = getRealPathFromURI(uri); ++ String mime = getMimeType(activity, uri); ++ final boolean isUrl = !TextUtils.isEmpty(realPath) && ++ Patterns.WEB_URL.matcher(realPath).matches(); ++ if (isUrl) { ++ try { + File file = createFileFromURI(uri); + realPath = file.getAbsolutePath(); + uri = Uri.fromFile(file); -+ } -+ catch (Exception e) -+ { ++ } catch (Exception e) { + // image not in cache + responseHelper.putString("error", "Could not read photo"); + responseHelper.putString("uri", uri.toString()); @@ -201,7 +191,7 @@ index 48fb5c1..3872d91 100644 final ReadExifResult result = readExifInterface(responseHelper, imageConfig); if (result.error != null) -@@ -461,6 +437,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule +@@ -461,6 +426,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule removeUselessFiles(requestCode, imageConfig); responseHelper.invokeError(callback, result.error.getMessage()); callback = null; @@ -209,7 +199,7 @@ index 48fb5c1..3872d91 100644 return; } -@@ -472,7 +449,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule +@@ -472,7 +438,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule updatedResultResponse(uri, imageConfig.original.getAbsolutePath()); // don't create a new file if contraint are respected @@ -218,7 +208,7 @@ index 48fb5c1..3872d91 100644 { responseHelper.putInt("width", initialWidth); responseHelper.putInt("height", initialHeight); -@@ -481,6 +458,14 @@ public class ImagePickerModule extends ReactContextBaseJavaModule +@@ -481,6 +447,13 @@ public class ImagePickerModule extends ReactContextBaseJavaModule else { imageConfig = getResizedImage(reactContext, this.options, imageConfig, initialWidth, initialHeight, requestCode); @@ -229,11 +219,10 @@ index 48fb5c1..3872d91 100644 + this.options = null; + return; + } -+ if (imageConfig.resized == null) { removeUselessFiles(requestCode, imageConfig); -@@ -523,6 +508,64 @@ public class ImagePickerModule extends ReactContextBaseJavaModule +@@ -523,6 +496,61 @@ public class ImagePickerModule extends ReactContextBaseJavaModule this.options = null; } @@ -269,15 +258,12 @@ index 48fb5c1..3872d91 100644 + case REQUEST_LAUNCH_IMAGE_CAPTURE: + extractImageFromResult(activity, cameraCaptureURI, requestCode); + break; -+ + case REQUEST_LAUNCH_IMAGE_LIBRARY: + extractImageFromResult(activity, data.getData(), requestCode); + break; -+ + case REQUEST_LAUNCH_VIDEO_LIBRARY: + extractVideoFromResult(data.getData()); + break; -+ + case REQUEST_LAUNCH_MIXED_CAPTURE: + case REQUEST_LAUNCH_VIDEO_CAPTURE: + if (data == null || data.getData() == null) { @@ -298,7 +284,7 @@ index 48fb5c1..3872d91 100644 public void invokeCustomButton(@NonNull final String action) { responseHelper.invokeCustomButton(this.callback, action); -@@ -551,7 +594,8 @@ public class ImagePickerModule extends ReactContextBaseJavaModule +@@ -551,7 +579,8 @@ public class ImagePickerModule extends ReactContextBaseJavaModule { return callback == null || (cameraCaptureURI == null && requestCode == REQUEST_LAUNCH_IMAGE_CAPTURE) || (requestCode != REQUEST_LAUNCH_IMAGE_CAPTURE && requestCode != REQUEST_LAUNCH_IMAGE_LIBRARY @@ -308,7 +294,7 @@ index 48fb5c1..3872d91 100644 } private void updatedResultResponse(@Nullable final Uri uri, -@@ -571,22 +615,23 @@ public class ImagePickerModule extends ReactContextBaseJavaModule +@@ -571,22 +600,24 @@ public class ImagePickerModule extends ReactContextBaseJavaModule @NonNull final Callback callback, @NonNull final int requestCode) { @@ -318,21 +304,21 @@ index 48fb5c1..3872d91 100644 - .checkSelfPermission(activity, Manifest.permission.CAMERA); - - boolean permissionsGranted = false; -- + + int selfCheckResult = 0; switch (requestCode) { case REQUEST_PERMISSIONS_FOR_LIBRARY: - permissionsGranted = writePermission == PackageManager.PERMISSION_GRANTED; + selfCheckResult = ActivityCompat -+ .checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); ++ .checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); break; case REQUEST_PERMISSIONS_FOR_CAMERA: - permissionsGranted = cameraPermission == PackageManager.PERMISSION_GRANTED && writePermission == PackageManager.PERMISSION_GRANTED; + selfCheckResult = ActivityCompat -+ .checkSelfPermission(activity, Manifest.permission.CAMERA); ++ .checkSelfPermission(activity, Manifest.permission.CAMERA); + if (selfCheckResult == PackageManager.PERMISSION_GRANTED) { + selfCheckResult = ActivityCompat -+ .checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); ++ .checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); + } break; } @@ -341,42 +327,52 @@ index 48fb5c1..3872d91 100644 if (!permissionsGranted) { final Boolean dontAskAgain = ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) && ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.CAMERA); -@@ -787,4 +832,22 @@ public class ImagePickerModule extends ReactContextBaseJavaModule +@@ -787,4 +818,21 @@ public class ImagePickerModule extends ReactContextBaseJavaModule videoDurationLimit = options.getInt("durationLimit"); } } + + private void setImageCaptureUri(Intent cameraIntent) { + final File original = createNewFile(reactContext, this.options, false); -+ imageConfig = imageConfig.withOriginalFile(original); ++ imageConfig = imageConfig.withOriginalFile(original); + -+ if (imageConfig.original != null) { -+ cameraCaptureURI = RealPathUtil.compatUriFromFile(reactContext, imageConfig.original); -+ }else { -+ responseHelper.invokeError(callback, "Couldn't get file path for photo"); -+ return; -+ } -+ if (cameraCaptureURI == null) -+ { -+ responseHelper.invokeError(callback, "Couldn't get file path for photo"); -+ return; -+ } -+ cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraCaptureURI); ++ if (imageConfig.original != null) { ++ cameraCaptureURI = RealPathUtil.compatUriFromFile(reactContext, imageConfig.original); ++ } else { ++ responseHelper.invokeError(callback, "Couldn't get file path for photo"); ++ return; ++ } ++ if (cameraCaptureURI == null) { ++ responseHelper.invokeError(callback, "Couldn't get file path for photo"); ++ return; ++ } ++ cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraCaptureURI); + } } diff --git a/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/utils/RealPathUtil.java b/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/utils/RealPathUtil.java -index cc90dce..c1cc74d 100644 +index cc90dce..72ddc92 100644 --- a/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/utils/RealPathUtil.java +++ b/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/utils/RealPathUtil.java -@@ -7,16 +7,22 @@ import android.net.Uri; - import android.os.Build; +@@ -1,201 +1,270 @@ + package com.imagepicker.utils; + +-import android.annotation.SuppressLint; + import android.content.Context; + import android.database.Cursor; + import android.net.Uri; +-import android.os.Build; import android.provider.DocumentsContract; import android.provider.MediaStore; +-import android.content.ContentUris; +import android.provider.OpenableColumns; - import android.content.ContentUris; ++import android.content.ContentResolver; import android.os.Environment; -+import android.os.ParcelFileDescriptor; +import android.webkit.MimeTypeMap; ++import android.util.Log; ++import android.text.TextUtils; ++ ++import android.os.ParcelFileDescriptor; ++ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.content.FileProvider; @@ -384,93 +380,432 @@ index cc90dce..c1cc74d 100644 -import java.io.File; +import java.io.*; +import java.nio.channels.FileChannel; ++import java.util.Objects; ++ ++// Class based on the steveevers DocumentHelper https://gist.github.com/steveevers/a5af24c226f44bb8fdc3 public class RealPathUtil { - -+ public static final String CACHE_DIR_NAME = "mmShare"; ++ public static final String CACHE_DIR_NAME = "mmShare"; + - public static @Nullable Uri compatUriFromFile(@NonNull final Context context, - @NonNull final File file) { - Uri result = null; -@@ -58,12 +64,7 @@ public class RealPathUtil { - } - // DownloadsProvider - else if (isDownloadsDocument(uri)) { ++ public static @Nullable ++ Uri compatUriFromFile(@NonNull final Context context, ++ @NonNull final File file) { ++ Uri result = null; ++ final String packageName = context.getApplicationContext().getPackageName(); ++ final String authority = packageName + ".provider"; ++ try { ++ result = FileProvider.getUriForFile(context, authority, file); ++ } ++ catch(IllegalArgumentException e) { ++ e.printStackTrace(); ++ } ++ return result; ++ } ++ ++ public static String getRealPathFromURI(final Context context, final Uri uri) { ++ ++ // DocumentProvider ++ if (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]; ++ } ++ } else if (isDownloadsDocument(uri)) { ++ // DownloadsProvider ++ ++ final String id = DocumentsContract.getDocumentId(uri); ++ if (!TextUtils.isEmpty(id)) { ++ if (id.startsWith("raw:")) { ++ return id.replaceFirst("raw:", ""); ++ } ++ try { ++ return getPathFromSavingTempFile(context, uri); ++ } catch (NumberFormatException e) { ++ Log.e("ReactNative", "DownloadsProvider unexpected uri " + uri.toString()); ++ return null; ++ } ++ } ++ } else if (isMediaDocument(uri)) { ++ // MediaProvider ++ ++ 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] ++ }; ++ ++ if (contentUri != null) { ++ return getDataColumn(context, contentUri, selection, selectionArgs); ++ } else { ++ return getPathFromSavingTempFile(context, uri); ++ } ++ } ++ } ++ ++ if ("content".equalsIgnoreCase(uri.getScheme())) { ++ // MediaStore (and general) ++ ++ if (isGooglePhotosUri(uri)) { ++ return uri.getLastPathSegment(); ++ } ++ ++ // Try save to tmp file, and return tmp file path ++ return getPathFromSavingTempFile(context, uri); ++ } else if ("file".equalsIgnoreCase(uri.getScheme())) { ++ return uri.getPath(); ++ } ++ ++ return null; ++ } ++ ++ public static String getPathFromSavingTempFile(Context context, final Uri uri) { ++ File tmpFile; ++ String fileName = null; ++ ++ if (uri == null || uri.isRelative()) { ++ return null; ++ } ++ ++ // Try and get the filename from the Uri ++ try { ++ Cursor returnCursor = ++ context.getContentResolver().query(uri, null, null, null, null); ++ int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); ++ returnCursor.moveToFirst(); ++ fileName = sanitizeFilename(returnCursor.getString(nameIndex)); ++ returnCursor.close(); ++ ++ } catch (Exception e) { ++ // just continue to get the filename with the last segment of the path ++ } ++ ++ try { ++ if (TextUtils.isEmpty(fileName)) { ++ fileName = sanitizeFilename(uri.getLastPathSegment().trim()); ++ } ++ ++ ++ File cacheDir = new File(context.getCacheDir(), CACHE_DIR_NAME); ++ if (!cacheDir.exists()) { ++ cacheDir.mkdirs(); ++ } ++ ++ tmpFile = new File(cacheDir, fileName); ++ tmpFile.createNewFile(); ++ ++ ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r"); ++ ++ FileChannel src = new FileInputStream(pfd.getFileDescriptor()).getChannel(); ++ FileChannel dst = new FileOutputStream(tmpFile).getChannel(); ++ dst.transferFrom(src, 0, src.size()); ++ src.close(); ++ dst.close(); ++ } catch (IOException ex) { ++ return null; ++ } ++ return tmpFile.getAbsolutePath(); ++ } ++ ++ public static String getDataColumn(Context context, Uri uri, String selection, ++ String[] selectionArgs) { ++ ++ Cursor cursor = 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); ++ return cursor.getString(index); ++ } ++ } finally { ++ if (cursor != null) ++ cursor.close(); ++ } ++ return null; ++ } ++ ++ ++ public static boolean isExternalStorageDocument(Uri uri) { ++ return "com.android.externalstorage.documents".equals(uri.getAuthority()); ++ } ++ ++ public static boolean isDownloadsDocument(Uri uri) { ++ return "com.android.providers.downloads.documents".equals(uri.getAuthority()); ++ } ++ ++ public static boolean isMediaDocument(Uri uri) { ++ return "com.android.providers.media.documents".equals(uri.getAuthority()); ++ } ++ ++ public static boolean isGooglePhotosUri(Uri uri) { ++ return "com.google.android.apps.photos.content".equals(uri.getAuthority()); ++ } ++ ++ public static String getExtension(String uri) { ++ if (uri == null) { ++ return null; ++ } ++ ++ int dot = uri.lastIndexOf("."); ++ if (dot >= 0) { ++ return uri.substring(dot); ++ } else { ++ // No extension. ++ return ""; ++ } ++ } ++ ++ public static String getMimeType(File file) { ++ ++ String extension = getExtension(file.getName()); ++ ++ if (extension.length() > 0) ++ return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.substring(1)); ++ ++ return "application/octet-stream"; ++ } ++ ++ public static String getMimeType(String filePath) { ++ File file = new File(filePath); ++ return getMimeType(file); ++ } ++ ++ public static String getMimeTypeFromUri(final Context context, final Uri uri) { ++ try { ++ ContentResolver cR = context.getContentResolver(); ++ return cR.getType(uri); ++ } catch (Exception e) { ++ return "application/octet-stream"; ++ } ++ } ++ ++ public static void deleteTempFiles(final File dir) { ++ try { ++ if (dir.isDirectory()) { ++ deleteRecursive(dir); ++ } ++ } catch (Exception e) { ++ // do nothing ++ } ++ } ++ ++ private static void deleteRecursive(File fileOrDirectory) { ++ if (fileOrDirectory.isDirectory()) ++ for (File child : Objects.requireNonNull(fileOrDirectory.listFiles())) ++ deleteRecursive(child); ++ ++ fileOrDirectory.delete(); ++ } ++ ++ private static String sanitizeFilename(String filename) { ++ if (filename == null) { ++ return null; ++ } + +- public static @Nullable Uri compatUriFromFile(@NonNull final Context context, +- @NonNull final File file) { +- Uri result = null; +- if (Build.VERSION.SDK_INT < 21) { +- result = Uri.fromFile(file); +- } +- else { +- final String packageName = context.getApplicationContext().getPackageName(); +- final String authority = new StringBuilder(packageName).append(".provider").toString(); +- try { +- result = FileProvider.getUriForFile(context, authority, file); +- } +- catch(IllegalArgumentException e) { +- e.printStackTrace(); +- } +- } +- return result; +- } +- +- @SuppressLint("NewApi") +- public static @Nullable String getRealPathFromURI(@NonNull final Context context, +- @NonNull 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)) { - - final String id = DocumentsContract.getDocumentId(uri); - final Uri contentUri = ContentUris.withAppendedId( - Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); - - return getDataColumn(context, contentUri, null, null); -+ return getPathFromSavingTempFile(context, uri); - } - // MediaProvider - else if (isMediaDocument(uri)) { -@@ -89,7 +90,7 @@ public class RealPathUtil { - } - } - // MediaStore (and general) +- } +- // 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); +- } +- } +- // MediaStore (and general) - else if ("content".equalsIgnoreCase(uri.getScheme())) { -+ if ("content".equalsIgnoreCase(uri.getScheme())) { - - // Return the remote address - if (isGooglePhotosUri(uri)) -@@ -98,7 +99,7 @@ public class RealPathUtil { - if (isFileProviderUri(context, uri)) - return getFileProviderPath(context, uri); - +- +- // Return the remote address +- if (isGooglePhotosUri(uri)) +- return uri.getLastPathSegment(); +- +- if (isFileProviderUri(context, uri)) +- return getFileProviderPath(context, uri); +- - return getDataColumn(context, uri, null, null); -+ return getPathFromSavingTempFile(context, uri); - } - // File - else if ("file".equalsIgnoreCase(uri.getScheme())) { -@@ -108,6 +109,49 @@ public class RealPathUtil { - return null; - } - -+ -+ public static String getPathFromSavingTempFile(Context context, final Uri uri) { -+ File tmpFile; -+ String fileName = null; -+ -+ // Try and get the filename from the Uri -+ try { -+ Cursor returnCursor = -+ context.getContentResolver().query(uri, null, null, null, null); -+ int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); -+ returnCursor.moveToFirst(); -+ fileName = returnCursor.getString(nameIndex); -+ } catch (Exception e) { -+ // just continue to get the filename with the last segment of the path -+ } -+ -+ try { -+ if (fileName == null) { -+ fileName = uri.getLastPathSegment().toString().trim(); -+ } -+ -+ -+ File cacheDir = new File(context.getCacheDir(), CACHE_DIR_NAME); -+ if (!cacheDir.exists()) { -+ cacheDir.mkdirs(); -+ } -+ -+ tmpFile = new File(cacheDir, fileName); -+ tmpFile.createNewFile(); -+ -+ ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r"); -+ -+ FileChannel src = new FileInputStream(pfd.getFileDescriptor()).getChannel(); -+ FileChannel dst = new FileOutputStream(tmpFile).getChannel(); -+ dst.transferFrom(src, 0, src.size()); -+ src.close(); -+ dst.close(); -+ } catch (IOException ex) { -+ return null; -+ } -+ return tmpFile.getAbsolutePath(); -+ } -+ - /** - * Get the value of the data column for this Uri. This is useful for - * MediaStore Uris, and other file-based ContentProviders. +- } +- // File +- else if ("file".equalsIgnoreCase(uri.getScheme())) { +- return uri.getPath(); +- } +- +- 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; +- 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); +- return cursor.getString(index); +- } +- } finally { +- if (cursor != null) +- cursor.close(); +- } +- return null; +- } +- +- +- /** +- * @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(@NonNull final Uri uri) { +- return "com.google.android.apps.photos.content".equals(uri.getAuthority()); +- } +- +- /** +- * @param context The Application context +- * @param uri The Uri is checked by functions +- * @return Whether the Uri authority is FileProvider +- */ +- public static boolean isFileProviderUri(@NonNull final Context context, +- @NonNull final Uri uri) { +- final String packageName = context.getPackageName(); +- final String authority = new StringBuilder(packageName).append(".provider").toString(); +- return authority.equals(uri.getAuthority()); +- } +- +- /** +- * @param context The Application context +- * @param uri The Uri is checked by functions +- * @return File path or null if file is missing +- */ +- public static @Nullable String getFileProviderPath(@NonNull final Context context, +- @NonNull final Uri uri) +- { +- final File appDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES); +- final File file = new File(appDir, uri.getLastPathSegment()); +- return file.exists() ? file.toString(): null; +- } ++ File f = new File(filename); ++ return f.getName(); ++ } + }