blob: 3ff2416527d03e4e437d8bbef1b31eafbbefe193 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package io.flutter.plugins.pathprovider;
import android.content.Context;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import androidx.annotation.NonNull;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.SettableFuture;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.MethodCodec;
import io.flutter.plugin.common.StandardMethodCodec;
import io.flutter.util.PathUtils;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class PathProviderPlugin implements FlutterPlugin, MethodCallHandler {
static final String TAG = "PathProviderPlugin";
private Context context;
private MethodChannel channel;
private PathProviderImpl impl;
/**
* An abstraction over how to access the paths in a thread-safe manner.
*
* <p>We need this so on versions of Flutter that support Background Platform Channels this plugin
* can take advantage of it.
*
* <p>This can be removed after https://github.com/flutter/engine/pull/29147 becomes available on
* the stable branch.
*/
private interface PathProviderImpl {
void getTemporaryDirectory(@NonNull Result result);
void getApplicationDocumentsDirectory(@NonNull Result result);
void getStorageDirectory(@NonNull Result result);
void getExternalCacheDirectories(@NonNull Result result);
void getExternalStorageDirectories(@NonNull String directoryName, @NonNull Result result);
void getApplicationSupportDirectory(@NonNull Result result);
}
/** The implementation for getting system paths that executes from the platform */
private class PathProviderPlatformThread implements PathProviderImpl {
private final Executor uiThreadExecutor = new UiThreadExecutor();
private final Executor executor =
Executors.newSingleThreadExecutor(
new ThreadFactoryBuilder()
.setNameFormat("path-provider-background-%d")
.setPriority(Thread.NORM_PRIORITY)
.build());
public void getTemporaryDirectory(@NonNull Result result) {
executeInBackground(() -> getPathProviderTemporaryDirectory(), result);
}
public void getApplicationDocumentsDirectory(@NonNull Result result) {
executeInBackground(() -> getPathProviderApplicationDocumentsDirectory(), result);
}
public void getStorageDirectory(@NonNull Result result) {
executeInBackground(() -> getPathProviderStorageDirectory(), result);
}
public void getExternalCacheDirectories(@NonNull Result result) {
executeInBackground(() -> getPathProviderExternalCacheDirectories(), result);
}
public void getExternalStorageDirectories(
@NonNull String directoryName, @NonNull Result result) {
executeInBackground(() -> getPathProviderExternalStorageDirectories(directoryName), result);
}
public void getApplicationSupportDirectory(@NonNull Result result) {
executeInBackground(() -> PathProviderPlugin.this.getApplicationSupportDirectory(), result);
}
private <T> void executeInBackground(Callable<T> task, Result result) {
final SettableFuture<T> future = SettableFuture.create();
Futures.addCallback(
future,
new FutureCallback<T>() {
public void onSuccess(T answer) {
result.success(answer);
}
public void onFailure(Throwable t) {
result.error(t.getClass().getName(), t.getMessage(), null);
}
},
uiThreadExecutor);
executor.execute(
() -> {
try {
future.set(task.call());
} catch (Throwable t) {
future.setException(t);
}
});
}
}
/** The implementation for getting system paths that executes from a background thread. */
private class PathProviderBackgroundThread implements PathProviderImpl {
public void getTemporaryDirectory(@NonNull Result result) {
result.success(getPathProviderTemporaryDirectory());
}
public void getApplicationDocumentsDirectory(@NonNull Result result) {
result.success(getPathProviderApplicationDocumentsDirectory());
}
public void getStorageDirectory(@NonNull Result result) {
result.success(getPathProviderStorageDirectory());
}
public void getExternalCacheDirectories(@NonNull Result result) {
result.success(getPathProviderExternalCacheDirectories());
}
public void getExternalStorageDirectories(
@NonNull String directoryName, @NonNull Result result) {
result.success(getPathProviderExternalStorageDirectories(directoryName));
}
public void getApplicationSupportDirectory(@NonNull Result result) {
result.success(PathProviderPlugin.this.getApplicationSupportDirectory());
}
}
public PathProviderPlugin() {}
private void setup(BinaryMessenger messenger, Context context) {
String channelName = "plugins.flutter.io/path_provider";
// TODO(gaaclarke): Remove reflection guard when https://github.com/flutter/engine/pull/29147
// becomes available on the stable branch.
try {
Class methodChannelClass = Class.forName("io.flutter.plugin.common.MethodChannel");
Class taskQueueClass = Class.forName("io.flutter.plugin.common.BinaryMessenger$TaskQueue");
Method makeBackgroundTaskQueue = messenger.getClass().getMethod("makeBackgroundTaskQueue");
Object taskQueue = makeBackgroundTaskQueue.invoke(messenger);
Constructor<MethodChannel> constructor =
methodChannelClass.getConstructor(
BinaryMessenger.class, String.class, MethodCodec.class, taskQueueClass);
channel =
constructor.newInstance(messenger, channelName, StandardMethodCodec.INSTANCE, taskQueue);
impl = new PathProviderBackgroundThread();
Log.d(TAG, "Use TaskQueues.");
} catch (Exception ex) {
channel = new MethodChannel(messenger, channelName);
impl = new PathProviderPlatformThread();
Log.d(TAG, "Don't use TaskQueues.");
}
this.context = context;
channel.setMethodCallHandler(this);
}
@SuppressWarnings("deprecation")
public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) {
PathProviderPlugin instance = new PathProviderPlugin();
instance.setup(registrar.messenger(), registrar.context());
}
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
setup(binding.getBinaryMessenger(), binding.getApplicationContext());
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
channel.setMethodCallHandler(null);
channel = null;
}
@Override
public void onMethodCall(MethodCall call, @NonNull Result result) {
switch (call.method) {
case "getTemporaryDirectory":
impl.getTemporaryDirectory(result);
break;
case "getApplicationDocumentsDirectory":
impl.getApplicationDocumentsDirectory(result);
break;
case "getStorageDirectory":
impl.getStorageDirectory(result);
break;
case "getExternalCacheDirectories":
impl.getExternalCacheDirectories(result);
break;
case "getExternalStorageDirectories":
final Integer type = call.argument("type");
final String directoryName = StorageDirectoryMapper.androidType(type);
impl.getExternalStorageDirectories(directoryName, result);
break;
case "getApplicationSupportDirectory":
impl.getApplicationSupportDirectory(result);
break;
default:
result.notImplemented();
}
}
private String getPathProviderTemporaryDirectory() {
return context.getCacheDir().getPath();
}
private String getApplicationSupportDirectory() {
return PathUtils.getFilesDir(context);
}
private String getPathProviderApplicationDocumentsDirectory() {
return PathUtils.getDataDirectory(context);
}
private String getPathProviderStorageDirectory() {
final File dir = context.getExternalFilesDir(null);
if (dir == null) {
return null;
}
return dir.getAbsolutePath();
}
private List<String> getPathProviderExternalCacheDirectories() {
final List<String> paths = new ArrayList<String>();
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
for (File dir : context.getExternalCacheDirs()) {
if (dir != null) {
paths.add(dir.getAbsolutePath());
}
}
} else {
File dir = context.getExternalCacheDir();
if (dir != null) {
paths.add(dir.getAbsolutePath());
}
}
return paths;
}
private List<String> getPathProviderExternalStorageDirectories(String type) {
final List<String> paths = new ArrayList<String>();
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
for (File dir : context.getExternalFilesDirs(type)) {
if (dir != null) {
paths.add(dir.getAbsolutePath());
}
}
} else {
File dir = context.getExternalFilesDir(type);
if (dir != null) {
paths.add(dir.getAbsolutePath());
}
}
return paths;
}
private static class UiThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable command) {
handler.post(command);
}
}
}