feat(android): content uri support for File (#9807)

This commit is contained in:
Osei Fortune
2022-03-04 22:56:04 -04:00
committed by GitHub
parent d2f166b832
commit c68d002c9a
9 changed files with 931 additions and 22 deletions

View File

@ -17,6 +17,7 @@
<Button text="touch-animations" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
<Button text="vector-image" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
<Button text="visibility-vs-hidden" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
<Button text="fs-helper" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
</StackLayout>
</ScrollView>
</StackLayout>

View File

@ -0,0 +1,73 @@
import { Page, EventData, Application, File } from '@nativescript/core';
let page: Page;
export function navigatingTo(args: EventData) {
page = <Page>args.object;
}
export function createRandom(args) {
if (global.isAndroid) {
try {
const activity = Application.android.foregroundActivity as androidx.appcompat.app.AppCompatActivity;
const selection = [android.provider.MediaStore.MediaColumns.DISPLAY_NAME, android.provider.MediaStore.MediaColumns._ID];
// testing with downloads as rename only works with a well know collection downloads/audio/photos/videos API 29+
let cursor = activity.getContentResolver().query(android.provider.MediaStore.Downloads.getContentUri('external'), selection, null, null);
let uri;
while (cursor.moveToNext()) {
const index = cursor.getColumnIndex(selection[0]);
const name = cursor.getString(index);
if (name === 'ns_tmp.txt') {
const idIndex = cursor.getColumnIndex(selection[1]);
const id = cursor.getLong(idIndex);
uri = android.net.Uri.parse(`${android.provider.MediaStore.Downloads.getContentUri('external').toString()}/${id}`);
cursor.close();
break;
}
}
if (!uri) {
const values = new android.content.ContentValues();
values.put(android.provider.MediaStore.MediaColumns.DISPLAY_NAME, 'ns_tmp.txt');
values.put(android.provider.MediaStore.MediaColumns.MIME_TYPE, 'text/plain');
uri = activity.getContentResolver().insert(android.provider.MediaStore.Downloads.getContentUri('external'), values);
}
doWork(uri.toString());
} catch (e) {
console.error(e);
}
}
}
function doWork(path: string) {
try {
const file = File.fromPath(path) as File;
console.log('name: ', file.name);
console.log('path: ', file.path);
console.log('parent: ', file.parent);
console.log('size: ', file.size);
console.log('lastModified: ', file.lastModified);
console.log('extension: ', file.extension);
if (file.size > 0) {
console.log('current text: ', file.readTextSync());
} else {
file.writeTextSync('Hello World');
console.log('after write: ', file.readTextSync());
console.log('after write size: ', file.size);
}
file.renameSync(`ns_temp_${Date.now()}.txt`);
console.log('rename: ', file.name);
console.log('rename lastModified: ', file.lastModified);
file.removeSync();
console.log('deleted ?', !File.exists(path));
} catch (e) {
console.error(e);
}
}

View File

@ -0,0 +1,6 @@
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page">
<StackLayout>
<Button text="Create Random" tap="createRandom" />
</StackLayout>
</Page>

View File

@ -1,6 +1,8 @@
import * as textModule from '../text';
import { getNativeApplication } from '../application';
import type { IFileSystemAccess } from './file-system-access';
let applicationContext: android.content.Context;
function getApplicationContext() {
if (!applicationContext) {
@ -10,7 +12,18 @@ function getApplicationContext() {
return applicationContext;
}
export class FileSystemAccess {
function getOrSetHelper(path: string): org.nativescript.widgets.FileHelper {
return org.nativescript.widgets.FileHelper.fromString(applicationContext, path);
}
function isContentUri(path: string): boolean {
if (typeof path === 'string' && path.startsWith('content:')) {
return true;
}
return false;
}
export class FileSystemAccess implements IFileSystemAccess {
private _pathSeparator = '/';
public getLastModified(path: string): Date {
@ -592,3 +605,313 @@ export class FileSystemAccess {
return result;
}
}
export class FileSystemAccess29 extends FileSystemAccess {
getLastModified(path: string): Date {
if (isContentUri(path)) {
return new Date(getOrSetHelper(path).getLastModified() * 1000);
}
return super.getLastModified(path);
}
getFileSize(path: string): number {
if (isContentUri(path)) {
return getOrSetHelper(path).getSize();
}
return super.getFileSize(path);
}
getParent(path: string, onError?: (error: any) => any): { path: string; name: string } {
if (isContentUri(path)) {
return null;
}
return super.getParent(path, onError);
}
getFile(path: string, onError?: (error: any) => any): { path: string; name: string; extension: string } {
if (isContentUri(path)) {
try {
const file = getOrSetHelper(path);
return {
path,
name: file.getName(),
extension: file.getExtension(),
};
} catch (e) {
if (typeof onError === 'function') {
onError(e);
}
return;
}
}
return super.getFile(path, onError);
}
getFolder(path: string, onError?: (error: any) => any): { path: string; name: string } {
if (isContentUri(path)) {
return null;
}
return super.getFolder(path, onError);
}
getEntities(path: string, onError?: (error: any) => any): { path: string; name: string; extension: string }[] {
if (isContentUri(path)) {
return null;
}
return super.getEntities(path, onError);
}
eachEntity(path: string, onEntity: (entity: { path: string; name: string; extension: string }) => boolean, onError?: (error: any) => any) {
if (isContentUri(path)) {
return null;
}
super.eachEntity(path, onEntity);
}
fileExists(path: string): boolean {
if (isContentUri(path)) {
return org.nativescript.widgets.FileHelper.exists(applicationContext, path);
}
return super.fileExists(path);
}
folderExists(path: string): boolean {
if (isContentUri(path)) {
return null;
}
return super.folderExists(path);
}
deleteFile(path: string, onError?: (error: any) => any) {
if (isContentUri(path)) {
try {
getOrSetHelper(path).delete(applicationContext);
} catch (e) {
onError?.(e);
}
} else {
super.deleteFile(path, onError);
}
}
deleteFolder(path: string, onError?: (error: any) => any) {
if (!isContentUri(path)) {
super.deleteFolder(path, onError);
}
}
emptyFolder(path: string, onError?: (error: any) => any): void {
if (!isContentUri(path)) {
super.emptyFolder(path, onError);
}
}
rename(path: string, newPath: string, onError?: (error: any) => any): void {
if (isContentUri(path)) {
let callback = null;
if (typeof onError === 'function') {
callback = new org.nativescript.widgets.FileHelper.Callback({
onSuccess(result) {},
onError(error) {
onError(error);
},
});
}
getOrSetHelper(path).renameSync(applicationContext, newPath, callback);
} else {
super.rename(path, newPath, onError);
}
}
public renameAsync(path: string, newPath: string): Promise<any> {
return new Promise<void>((resolve, reject) => {
getOrSetHelper(path).renameSync(
applicationContext,
newPath,
new org.nativescript.widgets.FileHelper.Callback({
onSuccess(result) {
resolve();
},
onError(error) {
reject(error);
},
})
);
});
}
getDocumentsFolderPath(): string {
return super.getDocumentsFolderPath();
}
getTempFolderPath(): string {
return super.getDocumentsFolderPath();
}
getLogicalRootPath(): string {
return super.getDocumentsFolderPath();
}
getCurrentAppPath(): string {
return super.getDocumentsFolderPath();
}
public readText = this.readTextSync.bind(this);
readTextAsync(path: string, encoding?: any): Promise<string> {
if (isContentUri(path)) {
return new Promise((resolve, reject) => {
getOrSetHelper(path).readText(
applicationContext,
encoding ?? null,
new org.nativescript.widgets.FileHelper.Callback({
onSuccess(result) {
resolve(result);
},
onError(error) {
reject(error);
},
})
);
});
}
return super.readTextAsync(path, encoding);
}
readTextSync(path: string, onError?: (error: any) => any, encoding?: any): string {
if (isContentUri(path)) {
let callback = null;
if (typeof onError === 'function') {
callback = new org.nativescript.widgets.FileHelper.Callback({
onSuccess(result) {},
onError(error) {
onError(error);
},
});
}
return getOrSetHelper(path).readTextSync(applicationContext, encoding ?? null, callback);
} else {
return super.readTextSync(path, onError, encoding);
}
}
read = this.readSync.bind(this);
readAsync(path: string): Promise<any> {
if (isContentUri(path)) {
return new Promise((resolve, reject) => {
getOrSetHelper(path).read(
applicationContext,
new org.nativescript.widgets.FileHelper.Callback({
onSuccess(result) {
resolve(result);
},
onError(error) {
reject(error);
},
})
);
});
}
return super.readAsync(path);
}
readSync(path: string, onError?: (error: any) => any) {
if (isContentUri(path)) {
let callback = null;
if (typeof onError === 'function') {
callback = new org.nativescript.widgets.FileHelper.Callback({
onSuccess(result) {},
onError(error) {
onError(error);
},
});
}
return getOrSetHelper(path).readSync(applicationContext, callback);
}
return super.readSync(path, onError);
}
writeText = this.writeTextSync.bind(this);
writeTextAsync(path: string, content: string, encoding?: any): Promise<void> {
if (isContentUri(path)) {
return new Promise<void>((resolve, reject) => {
getOrSetHelper(path).writeText(
applicationContext,
content,
encoding ?? null,
new org.nativescript.widgets.FileHelper.Callback({
onSuccess(result) {
resolve();
},
onError(error) {
reject(error);
},
})
);
});
}
return super.writeTextAsync(path, content, encoding);
}
writeTextSync(path: string, content: string, onError?: (error: any) => any, encoding?: any) {
if (isContentUri(path)) {
let callback = null;
if (typeof onError === 'function') {
callback = new org.nativescript.widgets.FileHelper.Callback({
onSuccess(result) {},
onError(error) {
onError(error);
},
});
}
getOrSetHelper(path).writeTextSync(applicationContext, content, encoding ?? null, callback);
} else {
super.writeTextSync(path, content, onError);
}
}
write = this.writeSync.bind(this);
writeAsync(path: string, content: any): Promise<void> {
if (isContentUri(path)) {
return new Promise<void>((resolve, reject) => {
getOrSetHelper(path).write(
applicationContext,
content,
new org.nativescript.widgets.FileHelper.Callback({
onSuccess(result) {
resolve();
},
onError(error) {
reject(error);
},
})
);
});
}
return super.writeAsync(path, content);
}
writeSync(path: string, content: any, onError?: (error: any) => any) {
if (isContentUri(path)) {
let callback = null;
if (typeof onError === 'function') {
callback = new org.nativescript.widgets.FileHelper.Callback({
onSuccess(result) {},
onError(error) {
onError(error);
},
});
}
getOrSetHelper(path).writeSync(applicationContext, content, callback);
} else {
super.writeSync(path, content, onError);
}
}
getFileExtension(path: string): string {
if (isContentUri(path)) {
return getOrSetHelper(path).getExtension();
}
return super.getFileExtension(path);
}
getPathSeparator(): string {
return super.getPathSeparator();
}
normalizePath(path: string): string {
return super.normalizePath(path);
}
joinPath(left: string, right: string): string {
return super.joinPath(left, right);
}
joinPaths(paths: string[]): string {
return super.joinPaths(paths);
}
}

View File

@ -1,7 +1,7 @@
/**
* An utility class used to provide methods to access and work with the file system.
*/
export class FileSystemAccess {
export interface IFileSystemAccess {
/**
* Gets the last modified date of a file with a given path.
* @param path Path to the file.
@ -248,3 +248,75 @@ export class FileSystemAccess {
*/
joinPaths(paths: string[]): string;
}
export class FileSystemAccess implements IFileSystemAccess {
getLastModified(path: string): Date;
getFileSize(path: string): number;
getParent(path: string, onError?: (error: any) => any): { path: string; name: string };
getFile(path: string, onError?: (error: any) => any): { path: string; name: string; extension: string };
getFolder(path: string, onError?: (error: any) => any): { path: string; name: string };
getEntities(path: string, onError?: (error: any) => any): Array<{ path: string; name: string; extension: string }>;
eachEntity(path: string, onEntity: (entity: { path: string; name: string; extension: string }) => boolean, onError?: (error: any) => any);
fileExists(path: string): boolean;
folderExists(path: string): boolean;
deleteFile(path: string, onError?: (error: any) => any);
deleteFolder(path: string, onError?: (error: any) => any);
emptyFolder(path: string, onError?: (error: any) => any): void;
rename(path: string, newPath: string, onError?: (error: any) => any): void;
getDocumentsFolderPath(): string;
getTempFolderPath(): string;
getLogicalRootPath(): string;
getCurrentAppPath(): string;
readText(path: string, onError?: (error: any) => any, encoding?: any): string;
readTextAsync(path: string, encoding?: any): Promise<string>;
readTextSync(path: string, onError?: (error: any) => any, encoding?: any): string;
read(path: string, onError?: (error: any) => any): any;
readAsync(path: string): Promise<any>;
readSync(path: string, onError?: (error: any) => any): any;
writeText(path: string, content: string, onError?: (error: any) => any, encoding?: any);
writeTextAsync(path: string, content: string, encoding?: any): Promise<void>;
writeTextSync(path: string, content: string, onError?: (error: any) => any, encoding?: any);
write(path: string, content: any, onError?: (error: any) => any);
writeAsync(path: string, content: any): Promise<void>;
writeSync(path: string, content: any, onError?: (error: any) => any);
getFileExtension(path: string): string;
getPathSeparator(): string;
normalizePath(path: string): string;
joinPath(left: string, right: string): string;
joinPaths(paths: string[]): string;
}
export class FileSystemAccess29 extends FileSystemAccess {}

View File

@ -1,16 +1,20 @@
import { FileSystemAccess } from './file-system-access';
import { IFileSystemAccess, FileSystemAccess, FileSystemAccess29 } from './file-system-access';
import { Device } from '../platform';
// The FileSystemAccess implementation, used through all the APIs.
let fileAccess: FileSystemAccess;
let fileAccess: IFileSystemAccess;
/**
* Returns FileSystemAccess, a shared singleton utility class to provide methods to access and work with the file system. This is used under the hood of all the file system apis in @nativescript/core and provided as a lower level convenience if needed.
* @returns FileSystemAccess
*/
export function getFileAccess(): FileSystemAccess {
export function getFileAccess(): IFileSystemAccess {
if (!fileAccess) {
if (global.isAndroid && parseInt(Device.sdkVersion) >= 29) {
fileAccess = new FileSystemAccess29();
} else {
fileAccess = new FileSystemAccess();
}
}
return fileAccess;
}
@ -161,12 +165,7 @@ export class FileSystemEntity {
}
get lastModified(): Date {
let value = this._lastModified;
if (!this._lastModified) {
value = this._lastModified = getFileAccess().getLastModified(this.path);
}
return value;
return getFileAccess().getLastModified(this.path);
}
}
@ -204,7 +203,7 @@ export class File extends FileSystemEntity {
public read(): Promise<any> {
return new Promise<any>((resolve, reject) => {
try {
this.checkAccess();
this._checkAccess();
} catch (ex) {
reject(ex);
@ -229,7 +228,7 @@ export class File extends FileSystemEntity {
}
public readSync(onError?: (error: any) => any): any {
this.checkAccess();
this._checkAccess();
this._locked = true;
@ -251,7 +250,7 @@ export class File extends FileSystemEntity {
public write(content: any): Promise<void> {
return new Promise<void>((resolve, reject) => {
try {
this.checkAccess();
this._checkAccess();
} catch (ex) {
reject(ex);
@ -276,7 +275,7 @@ export class File extends FileSystemEntity {
}
public writeSync(content: any, onError?: (error: any) => any): void {
this.checkAccess();
this._checkAccess();
try {
this._locked = true;
@ -298,7 +297,7 @@ export class File extends FileSystemEntity {
public readText(encoding?: string): Promise<string> {
return new Promise((resolve, reject) => {
try {
this.checkAccess();
this._checkAccess();
} catch (ex) {
reject(ex);
@ -323,7 +322,7 @@ export class File extends FileSystemEntity {
}
public readTextSync(onError?: (error: any) => any, encoding?: string): string {
this.checkAccess();
this._checkAccess();
this._locked = true;
@ -344,7 +343,7 @@ export class File extends FileSystemEntity {
public writeText(content: string, encoding?: string): Promise<any> {
return new Promise((resolve, reject) => {
try {
this.checkAccess();
this._checkAccess();
} catch (ex) {
reject(ex);
@ -369,7 +368,7 @@ export class File extends FileSystemEntity {
}
public writeTextSync(content: string, onError?: (error: any) => any, encoding?: string): void {
this.checkAccess();
this._checkAccess();
try {
this._locked = true;
@ -388,7 +387,7 @@ export class File extends FileSystemEntity {
}
}
private checkAccess() {
_checkAccess() {
if (this.isLocked) {
throw new Error('Cannot access a locked file.');
}

View File

@ -635,6 +635,53 @@
}
}
declare module org {
export module nativescript {
export module widgets {
export class FileHelper {
public static class: java.lang.Class<org.nativescript.widgets.FileHelper>;
public readText(param0: globalAndroid.content.Context, param1: string, param2: org.nativescript.widgets.FileHelper.Callback): void;
public writeSync(param0: globalAndroid.content.Context, param1: androidNative.Array<number>, param2: org.nativescript.widgets.FileHelper.Callback): void;
public static fromString(param1: globalAndroid.content.Context, param0: string): org.nativescript.widgets.FileHelper;
public writeText(param0: globalAndroid.content.Context, param1: string, param2: string, param3: org.nativescript.widgets.FileHelper.Callback): void;
public writeTextSync(param0: globalAndroid.content.Context, param1: string, param2: string, param3: org.nativescript.widgets.FileHelper.Callback): void;
public copyToFileSync(param0: globalAndroid.content.Context, param1: java.io.File, param2: org.nativescript.widgets.FileHelper.Callback): boolean;
public getName(): string;
public read(param0: globalAndroid.content.Context, param1: org.nativescript.widgets.FileHelper.Callback): void;
public copyToFile(param0: globalAndroid.content.Context, param1: java.io.File, param2: org.nativescript.widgets.FileHelper.Callback): void;
public static fromUri(param0: globalAndroid.content.Context, param1: globalAndroid.net.Uri): org.nativescript.widgets.FileHelper;
public readSync(param0: globalAndroid.content.Context, param1: org.nativescript.widgets.FileHelper.Callback): androidNative.Array<number>;
public write(param0: globalAndroid.content.Context, param1: androidNative.Array<number>, param2: org.nativescript.widgets.FileHelper.Callback): void;
public getSize(): number;
public getMime(): string;
public readTextSync(param0: globalAndroid.content.Context, param1: string, param2: org.nativescript.widgets.FileHelper.Callback): string;
public delete(param0: globalAndroid.content.Context): boolean;
public static exists(param0: globalAndroid.content.Context, param1: string): boolean;
public static exists(param0: globalAndroid.content.Context, param1: globalAndroid.net.Uri): boolean;
public getExtension(): string;
public getLastModified(): number;
public renameSync(param0: globalAndroid.content.Context, param1: string, param2: org.nativescript.widgets.FileHelper.Callback): string;
public rename(param0: globalAndroid.content.Context, param1: string, param2: org.nativescript.widgets.FileHelper.Callback): string;
}
export module FileHelper {
export class Callback {
public static class: java.lang.Class<org.nativescript.widgets.FileHelper.Callback>;
/**
* Constructs a new instance of the org.nativescript.widgets.FileHelper$Callback interface with the provided implementation. An empty constructor exists calling super() when extending the interface class.
*/
public constructor(implementation: {
onError(param0: java.lang.Exception): void;
onSuccess(param0: any): void;
});
public constructor();
public onError(param0: java.lang.Exception): void;
public onSuccess(param0: any): void;
}
}
}
}
}
declare module org {
export module nativescript {
export module widgets {

View File

@ -0,0 +1,388 @@
package org.nativescript.widgets;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.provider.MediaStore;
import android.webkit.MimeTypeMap;
import androidx.annotation.Nullable;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FileHelper {
private Uri uri;
private long size;
private String name;
private String mime;
private long lastModified;
private ExecutorService executor = Executors.newSingleThreadExecutor();
private Handler handler;
public interface Callback {
void onError(Exception exception);
void onSuccess(@Nullable Object result);
}
FileHelper(Uri uri) {
handler = new Handler(Looper.getMainLooper());
this.uri = uri;
}
public static boolean exists(Context context, String string) {
try {
return exists(context, Uri.parse(string));
} catch (Exception ignored) {
return false;
}
}
public static boolean exists(Context context, Uri uri) {
Cursor cursor = context.getContentResolver()
.query(uri, null, null, null, null);
boolean exists = cursor.moveToFirst();
cursor.close();
return exists;
}
public static @Nullable
FileHelper fromString(Context context, String string) {
try {
return fromUri(context, Uri.parse(string));
} catch (Exception ignored) {
return null;
}
}
public static @Nullable
FileHelper fromUri(Context context, Uri uri) {
Cursor cursor = context.getContentResolver()
.query(uri, null, null, null, null);
int sizeIndex = cursor.getColumnIndex(
MediaStore.MediaColumns.SIZE
);
int nameIndex = cursor.getColumnIndex(
MediaStore.MediaColumns.DISPLAY_NAME
);
int lastModifiedIndex = cursor.getColumnIndex(
MediaStore.MediaColumns.DATE_MODIFIED
);
boolean moved = cursor.moveToFirst();
FileHelper helper = null;
if (moved) {
helper = new FileHelper(uri);
helper.size = cursor.getLong(sizeIndex);
helper.name = cursor.getString(nameIndex);
helper.mime = context.getContentResolver().getType(uri);
helper.lastModified = cursor.getLong(lastModifiedIndex);
}
cursor.close();
return helper;
}
private void updateInternal(Context context) {
updateInternal(context, true);
}
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);
} else {
context.getContentResolver().update(uri, values, null, null);
}
}
Cursor cursor = context.getContentResolver()
.query(uri, null, null, null, null);
int sizeIndex = cursor.getColumnIndex(
MediaStore.MediaColumns.SIZE
);
int nameIndex = cursor.getColumnIndex(
MediaStore.MediaColumns.DISPLAY_NAME
);
int lastModifiedIndex = cursor.getColumnIndex(
MediaStore.MediaColumns.DATE_MODIFIED
);
boolean moved = cursor.moveToFirst();
if (moved) {
size = cursor.getLong(sizeIndex);
name = cursor.getString(nameIndex);
mime = context.getContentResolver().getType(uri);
lastModified = cursor.getLong(lastModifiedIndex);
}
cursor.close();
}
public long getSize() {
return size;
}
public String getName() {
return name;
}
public String getMime() {
if (mime == null) {
return "application/octet-stream";
}
return mime;
}
public String getExtension() {
if (mime == null) {
return "";
}
return MimeTypeMap.getSingleton().getExtensionFromMimeType(mime);
}
public long getLastModified() {
return lastModified;
}
private byte[] readSyncInternal(Context context) throws Exception {
InputStream is = context.getContentResolver().openInputStream(uri);
byte[] array = new byte[(int) size];
is.read(array);
is.close();
return array;
}
public @Nullable
byte[] readSync(Context context, @Nullable Callback callback) {
try {
return readSyncInternal(context);
} catch (Exception e) {
if (callback != null) {
callback.onError(e);
}
}
return null;
}
public void read(Context context, Callback callback) {
executor.execute(() -> {
try {
byte[] result = readSyncInternal(context);
handler.post(() -> callback.onSuccess(result));
} catch (Exception e) {
handler.post(() -> callback.onError(e));
}
});
}
private String readTextSyncInternal(Context context, @Nullable String encoding) throws Exception {
String characterSet = encoding;
if (characterSet == null) {
characterSet = "UTF-8";
}
InputStream is = context.getContentResolver().openInputStream(uri);
InputStreamReader isr = new InputStreamReader(is, characterSet);
BufferedReader reader = new BufferedReader(isr);
char[] buf = new char[is.available()];
reader.read(buf);
reader.close();
return new String(buf);
}
public String readTextSync(Context context, @Nullable String encoding, @Nullable Callback callback) {
try {
return readTextSyncInternal(context, encoding);
} catch (Exception e) {
if (callback != null) {
callback.onError(e);
}
}
return null;
}
public void readText(Context context, @Nullable String encoding, Callback callback) {
executor.execute(() -> {
try {
String result = readTextSyncInternal(context, encoding);
handler.post(() -> callback.onSuccess(result));
} catch (Exception e) {
handler.post(() -> callback.onError(e));
}
});
}
private void writeSyncInternal(Context context, byte[] content) throws Exception {
OutputStream os = context.getContentResolver().openOutputStream(uri);
os.write(content, 0, content.length);
os.flush();
os.close();
updateInternal(context);
}
public void writeSync(Context context, byte[] content, @Nullable Callback callback) {
try {
writeSyncInternal(context, content);
} catch (Exception e) {
if (callback != null) {
callback.onError(e);
}
}
}
public void write(Context context, byte[] content, Callback callback) {
executor.execute(() -> {
try {
writeSyncInternal(context, content);
handler.post(() -> callback.onSuccess(null));
} catch (Exception e) {
handler.post(() -> callback.onError(e));
}
});
}
private void writeTextSyncInternal(Context context, String content, @Nullable String encoding) throws Exception {
OutputStream os = context.getContentResolver().openOutputStream(uri);
String characterSet = encoding;
if (characterSet == null) {
characterSet = "UTF-8";
}
OutputStreamWriter osw = new OutputStreamWriter(os, characterSet);
osw.write(content);
osw.flush();
osw.close();
updateInternal(context);
}
public void writeTextSync(Context context, String content, @Nullable String encoding, @Nullable Callback callback) {
try {
writeTextSyncInternal(context, content, encoding);
} catch (Exception e) {
if (callback != null) {
callback.onError(e);
}
}
}
public void writeText(Context context, String content, @Nullable String encoding, Callback callback) {
executor.execute(() -> {
try {
writeTextSyncInternal(context, content, encoding);
handler.post(() -> callback.onSuccess(null));
} catch (Exception e) {
handler.post(() -> callback.onError(e));
}
});
}
private void copyToFileInternal(InputStream is, OutputStream os) throws Exception {
int read;
byte[] buf = new byte[1024];
while ((read = is.read(buf)) != -1) {
os.write(buf, 0, read);
}
is.close();
os.flush();
os.close();
}
private void copyToFileInternal(Context context, File file) throws Exception {
InputStream is = context.getContentResolver().openInputStream(uri);
FileOutputStream os = new FileOutputStream(file);
copyToFileInternal(is, os);
}
public boolean copyToFileSync(Context context, File file, @Nullable Callback callback) {
boolean completed = false;
try {
copyToFileInternal(context, file);
completed = true;
} catch (Exception e) {
if (callback != null) {
callback.onError(e);
}
}
return completed;
}
public void copyToFile(Context context, File file, Callback callback) {
executor.execute(() -> {
try {
copyToFileInternal(context, file);
handler.post(() -> callback.onSuccess(true));
} catch (Exception e) {
handler.post(() -> callback.onError(e));
}
});
}
public boolean delete(Context context) {
try {
return context.getContentResolver().delete(uri, null, null) > 0;
} catch (SecurityException e) {
return false;
}
}
private void renameInternal(Context context, String newName) throws Exception {
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DISPLAY_NAME, newName);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
context.getContentResolver().update(uri, values, null);
} else {
context.getContentResolver().update(uri, values, null, null);
}
updateInternal(context, false);
}
public void renameSync(Context context, String newName, @Nullable Callback callback) {
try {
renameInternal(context, newName);
} catch (Exception e) {
if (callback != null) {
callback.onError(e);
}
}
}
public void rename(Context context, String newName, Callback callback) {
executor.execute(() -> {
try {
renameInternal(context, newName);
handler.post(() -> callback.onSuccess(null));
} catch (Exception e) {
handler.post(() -> callback.onError(e));
}
});
}
}