flutter_file_picker/android/src/main/java/com/mr/flutter/plugin/filepicker/FilePickerDelegate.java

306 lines
11 KiB
Java

package com.mr.flutter.plugin.filepicker;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.provider.DocumentsContract;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import androidx.core.app.ActivityCompat;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;
public class FilePickerDelegate implements PluginRegistry.ActivityResultListener, PluginRegistry.RequestPermissionsResultListener {
private static final String TAG = "FilePickerDelegate";
private static final int REQUEST_CODE = (FilePickerPlugin.class.hashCode() + 43) & 0x0000ffff;
private final Activity activity;
private final PermissionManager permissionManager;
private MethodChannel.Result pendingResult;
private boolean isMultipleSelection = false;
private boolean loadDataToMemory = false;
private String type;
private String[] allowedExtensions;
private EventChannel.EventSink eventSink;
public FilePickerDelegate(final Activity activity) {
this(
activity,
null,
new PermissionManager() {
@Override
public boolean isPermissionGranted(final String permissionName) {
return ActivityCompat.checkSelfPermission(activity, permissionName)
== PackageManager.PERMISSION_GRANTED;
}
@Override
public void askForPermission(final String permissionName, final int requestCode) {
ActivityCompat.requestPermissions(activity, new String[]{permissionName}, requestCode);
}
}
);
}
public void setEventHandler(final EventChannel.EventSink eventSink) {
this.eventSink = eventSink;
}
@VisibleForTesting
FilePickerDelegate(final Activity activity, final MethodChannel.Result result, final PermissionManager permissionManager) {
this.activity = activity;
this.pendingResult = result;
this.permissionManager = permissionManager;
}
@Override
public boolean onActivityResult(final int requestCode, final int resultCode, final Intent data) {
if(type == null) {
return false;
}
if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) {
if (eventSink != null) {
eventSink.success(true);
}
new Thread(new Runnable() {
@Override
public void run() {
if (data != null) {
final ArrayList<FileInfo> files = new ArrayList<>();
if (data.getClipData() != null) {
final int count = data.getClipData().getItemCount();
int currentItem = 0;
while (currentItem < count) {
final Uri currentUri = data.getClipData().getItemAt(currentItem).getUri();
final FileInfo file = FileUtils.openFileStream(FilePickerDelegate.this.activity, currentUri, loadDataToMemory);
if(file != null) {
files.add(file);
Log.d(FilePickerDelegate.TAG, "[MultiFilePick] File #" + currentItem + " - URI: " + currentUri.getPath());
}
currentItem++;
}
finishWithSuccess(files);
} else if (data.getData() != null) {
Uri uri = data.getData();
if (type.equals("dir") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
uri = DocumentsContract.buildDocumentUriUsingTree(uri, DocumentsContract.getTreeDocumentId(uri));
Log.d(FilePickerDelegate.TAG, "[SingleFilePick] File URI:" + uri.toString());
final String dirPath = FileUtils.getFullPathFromTreeUri(uri, activity);
if(dirPath != null) {
finishWithSuccess(dirPath);
} else {
finishWithError("unknown_path", "Failed to retrieve directory path.");
}
return;
}
final FileInfo file = FileUtils.openFileStream(FilePickerDelegate.this.activity, uri, loadDataToMemory);
if(file != null) {
files.add(file);
}
if (!files.isEmpty()) {
Log.d(FilePickerDelegate.TAG, "File path:" + files.toString());
finishWithSuccess(files);
} else {
finishWithError("unknown_path", "Failed to retrieve path.");
}
} else {
finishWithError("unknown_activity", "Unknown activity error, please fill an issue.");
}
} else {
finishWithError("unknown_activity", "Unknown activity error, please fill an issue.");
}
}
}).start();
return true;
} else if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_CANCELED) {
Log.i(TAG, "User cancelled the picker request");
finishWithSuccess(null);
return true;
} else if (requestCode == REQUEST_CODE) {
finishWithError("unknown_activity", "Unknown activity error, please fill an issue.");
}
return false;
}
@Override
public boolean onRequestPermissionsResult(final int requestCode, final String[] permissions, final int[] grantResults) {
if (REQUEST_CODE != requestCode) {
return false;
}
final boolean permissionGranted =
grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
if (permissionGranted) {
this.startFileExplorer();
} else {
finishWithError("read_external_storage_denied", "User did not allowed reading external storage");
}
return true;
}
private boolean setPendingMethodCallAndResult(final MethodChannel.Result result) {
if (this.pendingResult != null) {
return false;
}
this.pendingResult = result;
return true;
}
private static void finishWithAlreadyActiveError(final MethodChannel.Result result) {
result.error("already_active", "File picker is already active", null);
}
@SuppressWarnings("deprecation")
private void startFileExplorer() {
final Intent intent;
// Temporary fix, remove this null-check after Flutter Engine 1.14 has landed on stable
if (type == null) {
return;
}
if (type.equals("dir")) {
intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
} else {
if (type.equals("image/*")) {
intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
} else {
intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
}
final Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath() + File.separator);
Log.d(TAG, "Selected type " + type);
intent.setDataAndType(uri, this.type);
intent.setType(this.type);
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, this.isMultipleSelection);
if (type.contains(",")) {
allowedExtensions = type.split(",");
}
if (allowedExtensions != null) {
intent.putExtra(Intent.EXTRA_MIME_TYPES, allowedExtensions);
}
}
if (intent.resolveActivity(this.activity.getPackageManager()) != null) {
this.activity.startActivityForResult(intent, REQUEST_CODE);
} else {
Log.e(TAG, "Can't find a valid activity to handle the request. Make sure you've a file explorer installed.");
finishWithError("invalid_format_type", "Can't handle the provided file type.");
}
}
@SuppressWarnings("deprecation")
public void startFileExplorer(final String type, final boolean isMultipleSelection, final boolean withData, final String[] allowedExtensions, final MethodChannel.Result result) {
if (!this.setPendingMethodCallAndResult(result)) {
finishWithAlreadyActiveError(result);
return;
}
this.type = type;
this.isMultipleSelection = isMultipleSelection;
this.loadDataToMemory = withData;
this.allowedExtensions = allowedExtensions;
if (!this.permissionManager.isPermissionGranted(Manifest.permission.READ_EXTERNAL_STORAGE)) {
this.permissionManager.askForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, REQUEST_CODE);
return;
}
this.startFileExplorer();
}
@SuppressWarnings("unchecked")
private void finishWithSuccess(Object data) {
if (eventSink != null) {
this.dispatchEventStatus(false);
}
// Temporary fix, remove this null-check after Flutter Engine 1.14 has landed on stable
if (this.pendingResult != null) {
if(data != null && !(data instanceof String)) {
final ArrayList<HashMap<String, Object>> files = new ArrayList<>();
for (FileInfo file : (ArrayList<FileInfo>)data) {
files.add(file.toMap());
}
data = files;
}
this.pendingResult.success(data);
this.clearPendingResult();
}
}
private void finishWithError(final String errorCode, final String errorMessage) {
if (this.pendingResult == null) {
return;
}
if (eventSink != null) {
this.dispatchEventStatus(false);
}
this.pendingResult.error(errorCode, errorMessage, null);
this.clearPendingResult();
}
private void dispatchEventStatus(final boolean status) {
new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(final Message message) {
eventSink.success(status);
}
}.obtainMessage().sendToTarget();
}
private void clearPendingResult() {
this.pendingResult = null;
}
interface PermissionManager {
boolean isPermissionGranted(String permissionName);
void askForPermission(String permissionName, int requestCode);
}
}