fix(android): improve content uri handling (#9874)

closes: #9871
This commit is contained in:
Osei Fortune
2022-04-15 00:36:30 -04:00
committed by GitHub
parent c2503e3693
commit a272296d87
5 changed files with 284 additions and 27 deletions

View File

@ -607,6 +607,7 @@ export class FileSystemAccess implements IFileSystemAccess {
}
export class FileSystemAccess29 extends FileSystemAccess {
__skip = true;
getLastModified(path: string): Date {
if (isContentUri(path)) {
return new Date(getOrSetHelper(path).getLastModified() * 1000);

View File

@ -126,6 +126,26 @@ export class FileSystemEntity {
return;
}
const localError = function (error) {
if (onError) {
onError(error);
}
return null;
};
const fileAccess = getFileAccess();
// call rename for FileSystemAccess29
if ((<any>fileAccess).__skip) {
fileAccess.rename(this.path, newName, localError);
const fileInfo = getFileAccess().getFile(this.path, null);
if (fileInfo) {
this._name = fileInfo.name;
this._extension = fileInfo.extension;
}
return;
}
const parentFolder = this.parent;
if (!parentFolder) {
if (onError) {
@ -135,18 +155,9 @@ export class FileSystemEntity {
return;
}
const fileAccess = getFileAccess();
const path = parentFolder.path;
const newPath = fileAccess.joinPath(path, newName);
const localError = function (error) {
if (onError) {
onError(error);
}
return null;
};
fileAccess.rename(this.path, newPath, localError);
this._path = newPath;
this._name = newName;

View File

@ -83,6 +83,12 @@ dependencies {
outLogger.withStyle(Style.SuccessHeader).println "\t + using android X library androidx.appcompat:appcompat:$androidXAppCompatVersion"
}
def androidXDocumentFileVersion = "1.0.1"
if (project.hasProperty("androidXDocumentFile")) {
androidXDocumentFileVersion = androidXDocumentFile
outLogger.withStyle(Style.SuccessHeader).println "\t + using android X library androidx.documentfile:documentfile:$androidXDocumentFileVersion"
}
implementation fileTree(include: ['*.jar'], dir: 'libs')
@ -93,6 +99,7 @@ dependencies {
implementation "androidx.transition:transition:$androidXTransitionVersion"
implementation "androidx.exifinterface:exifinterface:$androidXExifInterfaceVersion"
implementation "androidx.appcompat:appcompat:$androidXAppCompatVersion"
implementation "androidx.documentfile:documentfile:$androidXDocumentFileVersion"
}
task cleanBuildDir (type: Delete) {

View File

@ -5,15 +5,20 @@ import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.webkit.MimeTypeMap;
import androidx.annotation.Nullable;
import androidx.documentfile.provider.DocumentFile;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
@ -42,6 +47,51 @@ public class FileHelper {
this.uri = uri;
}
private static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri
.getAuthority());
}
private static @Nullable
Cursor getCursor(Context context, Uri uri) {
Cursor cursor = null;
try {
if (Build.VERSION.SDK_INT >= 19) {
if (DocumentsContract.isDocumentUri(context, uri)) {
String docId = DocumentsContract.getDocumentId(uri);
String[] split = docId.split(":");
String type = split[0];
Uri contentUri;
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;
} else {
contentUri = MediaStore.Files.getContentUri("external");
}
String selection = "_id=?";
String[] selectionArgs = {split[1]};
cursor = context.getContentResolver().query(
contentUri, null, selection, selectionArgs, null, null
);
}
}
if (cursor == null) {
cursor = context.getContentResolver().query(uri, null, null, null, null);
}
} catch (Exception e) {
cursor = null;
}
return cursor;
}
public static boolean exists(Context context, String string) {
try {
return exists(context, Uri.parse(string));
@ -51,9 +101,17 @@ public class FileHelper {
}
public static boolean exists(Context context, Uri uri) {
Cursor cursor = context.getContentResolver()
.query(uri, null, null, null, null);
if (Build.VERSION.SDK_INT >= 19 && isExternalStorageDocument(uri)) {
File file = getFile(context, uri);
if (file != null) {
return file.exists();
}
return false;
}
Cursor cursor = getCursor(context, uri);
if (cursor == null) {
return false;
}
boolean exists = cursor.moveToFirst();
cursor.close();
return exists;
@ -62,17 +120,77 @@ public class FileHelper {
public static @Nullable
FileHelper fromString(Context context, String string) {
try {
return fromUri(context, Uri.parse(string));
} catch (Exception ignored) {
return fromUri(context, Uri.parse(Uri.decode(string)), false);
} catch (Exception e) {
return null;
}
}
@SuppressWarnings("deprecation")
private static @Nullable
File getFile(Context context, Uri uri) {
if (Build.VERSION.SDK_INT >= 19) {
String docId = DocumentsContract.getDocumentId(uri);
String[] split = docId.split(":");
String type = split[0];
String path = split[1];
if ("primary".equals(type)) {
String[] parts = Uri.decode(uri.toString()).split(":" + path + "/");
String file = Environment.getExternalStorageDirectory() + "/" + path + "/" + parts[1];
return new File(file);
} else {
File[] cacheDirs = context.getExternalCacheDirs();
String storageDir = null;
for (File cacheDir : cacheDirs) {
final String cachePath = cacheDir.getPath();
int index = cachePath.indexOf(type);
if (index >= 0) {
storageDir = cachePath.substring(0, index + type.length());
}
}
if (storageDir != null) {
return new File(storageDir + "/" + path);
}
}
}
return null;
}
public static @Nullable
FileHelper fromUri(Context context, Uri uri) {
Cursor cursor = context.getContentResolver()
.query(uri, null, null, null, null);
return fromUri(context, uri, true);
}
private static @Nullable
FileHelper fromUri(Context context, Uri contentUri, boolean parseUri) {
Uri uri;
if (parseUri) {
uri = Uri.parse(Uri.decode(contentUri.toString()));
} else {
uri = contentUri;
}
if (Build.VERSION.SDK_INT >= 19 && isExternalStorageDocument(uri)) {
File file = getFile(context, uri);
if (file == null) {
return null;
}
FileHelper helper = new FileHelper(uri);
helper.size = file.length();
helper.name = file.getName();
helper.mime = context.getContentResolver().getType(uri);
helper.lastModified = file.lastModified() / 1000;
return helper;
}
Cursor cursor = getCursor(context, uri);
if (cursor == null) {
return null;
}
int sizeIndex = cursor.getColumnIndex(
MediaStore.MediaColumns.SIZE
@ -104,20 +222,41 @@ public class FileHelper {
updateInternal(context, true);
}
private void updateValue(Context context, Uri uri, ContentValues values) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
context.getContentResolver().update(uri, values, null);
} else {
context.getContentResolver().update(uri, values, null, null);
}
}
private void updateInternal(Context context, boolean force) {
if (force) {
// trigger db update
ContentValues values = new ContentValues();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
context.getContentResolver().update(uri, values, null);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (isExternalStorageDocument(uri)) {
return;
}
if (DocumentsContract.isDocumentUri(context, uri)) {
DocumentFile file = DocumentFile.fromSingleUri(context, uri);
if (file != null) {
updateValue(context, file.getUri(), values);
}
} else {
updateValue(context, uri, values);
}
} else {
context.getContentResolver().update(uri, values, null, null);
updateValue(context, uri, values);
}
}
Cursor cursor = context.getContentResolver()
.query(uri, null, null, null, null);
Cursor cursor = getCursor(context, uri);
if (cursor == null) {
return;
}
int sizeIndex = cursor.getColumnIndex(
MediaStore.MediaColumns.SIZE
@ -170,8 +309,35 @@ public class FileHelper {
return lastModified;
}
private InputStream getInputStream(Context context, Uri uri) throws Exception {
if (Build.VERSION.SDK_INT >= 19) {
if (isExternalStorageDocument(uri)) {
File file = getFile(context, uri);
return new FileInputStream(file);
}
if (DocumentsContract.isDocumentUri(context, uri)) {
return context.getContentResolver().openInputStream(DocumentFile.fromSingleUri(context, uri).getUri());
}
}
return context.getContentResolver().openInputStream(uri);
}
private OutputStream getOutputStream(Context context, Uri uri) throws Exception {
if (Build.VERSION.SDK_INT >= 19) {
if (isExternalStorageDocument(uri)) {
File file = getFile(context, uri);
return new FileOutputStream(file);
}
if (DocumentsContract.isDocumentUri(context, uri)) {
return context.getContentResolver().openOutputStream(DocumentFile.fromSingleUri(context, uri).getUri());
}
}
return context.getContentResolver().openOutputStream(uri);
}
private byte[] readSyncInternal(Context context) throws Exception {
InputStream is = context.getContentResolver().openInputStream(uri);
InputStream is = getInputStream(context, uri);
byte[] array = new byte[(int) size];
is.read(array);
is.close();
@ -207,7 +373,7 @@ public class FileHelper {
characterSet = "UTF-8";
}
InputStream is = context.getContentResolver().openInputStream(uri);
InputStream is = getInputStream(context, uri);
InputStreamReader isr = new InputStreamReader(is, characterSet);
BufferedReader reader = new BufferedReader(isr);
char[] buf = new char[is.available()];
@ -239,7 +405,7 @@ public class FileHelper {
}
private void writeSyncInternal(Context context, byte[] content) throws Exception {
OutputStream os = context.getContentResolver().openOutputStream(uri);
OutputStream os = getOutputStream(context, uri);
os.write(content, 0, content.length);
os.flush();
os.close();
@ -268,7 +434,7 @@ public class FileHelper {
}
private void writeTextSyncInternal(Context context, String content, @Nullable String encoding) throws Exception {
OutputStream os = context.getContentResolver().openOutputStream(uri);
OutputStream os = getOutputStream(context, uri);
String characterSet = encoding;
if (characterSet == null) {
characterSet = "UTF-8";
@ -313,7 +479,7 @@ public class FileHelper {
}
private void copyToFileInternal(Context context, File file) throws Exception {
InputStream is = context.getContentResolver().openInputStream(uri);
InputStream is = getInputStream(context, uri);
FileOutputStream os = new FileOutputStream(file);
copyToFileInternal(is, os);
}
@ -344,6 +510,41 @@ public class FileHelper {
public boolean delete(Context context) {
try {
if (Build.VERSION.SDK_INT >= 19) {
if (isExternalStorageDocument(uri)) {
File file = getFile(context, uri);
if (file != null) {
return file.delete();
}
return false;
}
if (DocumentsContract.isDocumentUri(context, uri)) {
String docId = DocumentsContract.getDocumentId(uri);
String[] split = docId.split(":");
String type = split[0];
Uri contentUri;
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;
} else {
contentUri = MediaStore.Files.getContentUri("external");
}
String selection = "_id=?";
String[] selectionArgs = {split[1]};
return context.getContentResolver().delete(
contentUri, selection, selectionArgs
) > 0;
}
}
return context.getContentResolver().delete(uri, null, null) > 0;
} catch (SecurityException e) {
return false;
@ -354,6 +555,44 @@ public class FileHelper {
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DISPLAY_NAME, newName);
if (Build.VERSION.SDK_INT >= 19) {
if (isExternalStorageDocument(uri)) {
File file = getFile(context, uri);
if (file != null) {
file.renameTo(new File(file.getParentFile(), newName));
return;
}
return;
}
if (DocumentsContract.isDocumentUri(context, uri)) {
String docId = DocumentsContract.getDocumentId(uri);
String[] split = docId.split(":");
String type = split[0];
Uri contentUri;
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;
} else {
contentUri = MediaStore.Files.getContentUri("external");
}
String selection = "_id=?";
String[] selectionArgs = {split[1]};
context.getContentResolver().update(
contentUri, values, selection, selectionArgs
);
return;
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
context.getContentResolver().update(uri, values, null);
} else {
@ -373,7 +612,6 @@ public class FileHelper {
}
}
public void rename(Context context, String newName, Callback callback) {
executor.execute(() -> {
try {