[path_provider]: Migrated path_provider for android to pigeon. (#5959)

diff --git a/packages/path_provider/path_provider_android/CHANGELOG.md b/packages/path_provider/path_provider_android/CHANGELOG.md
index 4b15e26..6b1b661 100644
--- a/packages/path_provider/path_provider_android/CHANGELOG.md
+++ b/packages/path_provider/path_provider_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.0.15
+
+* Switches the medium from MethodChannels to Pigeon.
+
 ## 2.0.14
 
 * Fixes library_private_types_in_public_api, sort_child_properties_last and use_key_in_widget_constructors
diff --git a/packages/path_provider/path_provider_android/android/src/main/java/io/flutter/plugins/pathprovider/Messages.java b/packages/path_provider/path_provider_android/android/src/main/java/io/flutter/plugins/pathprovider/Messages.java
new file mode 100644
index 0000000..1e78916
--- /dev/null
+++ b/packages/path_provider/path_provider_android/android/src/main/java/io/flutter/plugins/pathprovider/Messages.java
@@ -0,0 +1,241 @@
+// 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.
+// Autogenerated from Pigeon (v3.1.5), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+
+package io.flutter.plugins.pathprovider;
+
+import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import io.flutter.plugin.common.BasicMessageChannel;
+import io.flutter.plugin.common.BinaryMessenger;
+import io.flutter.plugin.common.MessageCodec;
+import io.flutter.plugin.common.StandardMessageCodec;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Generated class from Pigeon. */
+@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"})
+public class Messages {
+
+  public enum StorageDirectory {
+    music(0),
+    podcasts(1),
+    ringtones(2),
+    alarms(3),
+    notifications(4),
+    pictures(5),
+    movies(6),
+    downloads(7),
+    dcim(8),
+    documents(9);
+
+    private int index;
+
+    private StorageDirectory(final int index) {
+      this.index = index;
+    }
+  }
+
+  private static class PathProviderApiCodec extends StandardMessageCodec {
+    public static final PathProviderApiCodec INSTANCE = new PathProviderApiCodec();
+
+    private PathProviderApiCodec() {}
+  }
+
+  /** Generated interface from Pigeon that represents a handler of messages from Flutter. */
+  public interface PathProviderApi {
+    @Nullable
+    String getTemporaryPath();
+
+    @Nullable
+    String getApplicationSupportPath();
+
+    @Nullable
+    String getApplicationDocumentsPath();
+
+    @Nullable
+    String getExternalStoragePath();
+
+    @NonNull
+    List<String> getExternalCachePaths();
+
+    @NonNull
+    List<String> getExternalStoragePaths(@NonNull StorageDirectory directory);
+
+    /** The codec used by PathProviderApi. */
+    static MessageCodec<Object> getCodec() {
+      return PathProviderApiCodec.INSTANCE;
+    }
+
+    /**
+     * Sets up an instance of `PathProviderApi` to handle messages through the `binaryMessenger`.
+     */
+    static void setup(BinaryMessenger binaryMessenger, PathProviderApi api) {
+      {
+        BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue();
+        BasicMessageChannel<Object> channel =
+            new BasicMessageChannel<>(
+                binaryMessenger,
+                "dev.flutter.pigeon.PathProviderApi.getTemporaryPath",
+                getCodec(),
+                taskQueue);
+        if (api != null) {
+          channel.setMessageHandler(
+              (message, reply) -> {
+                Map<String, Object> wrapped = new HashMap<>();
+                try {
+                  String output = api.getTemporaryPath();
+                  wrapped.put("result", output);
+                } catch (Error | RuntimeException exception) {
+                  wrapped.put("error", wrapError(exception));
+                }
+                reply.reply(wrapped);
+              });
+        } else {
+          channel.setMessageHandler(null);
+        }
+      }
+      {
+        BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue();
+        BasicMessageChannel<Object> channel =
+            new BasicMessageChannel<>(
+                binaryMessenger,
+                "dev.flutter.pigeon.PathProviderApi.getApplicationSupportPath",
+                getCodec(),
+                taskQueue);
+        if (api != null) {
+          channel.setMessageHandler(
+              (message, reply) -> {
+                Map<String, Object> wrapped = new HashMap<>();
+                try {
+                  String output = api.getApplicationSupportPath();
+                  wrapped.put("result", output);
+                } catch (Error | RuntimeException exception) {
+                  wrapped.put("error", wrapError(exception));
+                }
+                reply.reply(wrapped);
+              });
+        } else {
+          channel.setMessageHandler(null);
+        }
+      }
+      {
+        BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue();
+        BasicMessageChannel<Object> channel =
+            new BasicMessageChannel<>(
+                binaryMessenger,
+                "dev.flutter.pigeon.PathProviderApi.getApplicationDocumentsPath",
+                getCodec(),
+                taskQueue);
+        if (api != null) {
+          channel.setMessageHandler(
+              (message, reply) -> {
+                Map<String, Object> wrapped = new HashMap<>();
+                try {
+                  String output = api.getApplicationDocumentsPath();
+                  wrapped.put("result", output);
+                } catch (Error | RuntimeException exception) {
+                  wrapped.put("error", wrapError(exception));
+                }
+                reply.reply(wrapped);
+              });
+        } else {
+          channel.setMessageHandler(null);
+        }
+      }
+      {
+        BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue();
+        BasicMessageChannel<Object> channel =
+            new BasicMessageChannel<>(
+                binaryMessenger,
+                "dev.flutter.pigeon.PathProviderApi.getExternalStoragePath",
+                getCodec(),
+                taskQueue);
+        if (api != null) {
+          channel.setMessageHandler(
+              (message, reply) -> {
+                Map<String, Object> wrapped = new HashMap<>();
+                try {
+                  String output = api.getExternalStoragePath();
+                  wrapped.put("result", output);
+                } catch (Error | RuntimeException exception) {
+                  wrapped.put("error", wrapError(exception));
+                }
+                reply.reply(wrapped);
+              });
+        } else {
+          channel.setMessageHandler(null);
+        }
+      }
+      {
+        BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue();
+        BasicMessageChannel<Object> channel =
+            new BasicMessageChannel<>(
+                binaryMessenger,
+                "dev.flutter.pigeon.PathProviderApi.getExternalCachePaths",
+                getCodec(),
+                taskQueue);
+        if (api != null) {
+          channel.setMessageHandler(
+              (message, reply) -> {
+                Map<String, Object> wrapped = new HashMap<>();
+                try {
+                  List<String> output = api.getExternalCachePaths();
+                  wrapped.put("result", output);
+                } catch (Error | RuntimeException exception) {
+                  wrapped.put("error", wrapError(exception));
+                }
+                reply.reply(wrapped);
+              });
+        } else {
+          channel.setMessageHandler(null);
+        }
+      }
+      {
+        BinaryMessenger.TaskQueue taskQueue = binaryMessenger.makeBackgroundTaskQueue();
+        BasicMessageChannel<Object> channel =
+            new BasicMessageChannel<>(
+                binaryMessenger,
+                "dev.flutter.pigeon.PathProviderApi.getExternalStoragePaths",
+                getCodec(),
+                taskQueue);
+        if (api != null) {
+          channel.setMessageHandler(
+              (message, reply) -> {
+                Map<String, Object> wrapped = new HashMap<>();
+                try {
+                  ArrayList<Object> args = (ArrayList<Object>) message;
+                  StorageDirectory directoryArg =
+                      args.get(0) == null ? null : StorageDirectory.values()[(int) args.get(0)];
+                  if (directoryArg == null) {
+                    throw new NullPointerException("directoryArg unexpectedly null.");
+                  }
+                  List<String> output = api.getExternalStoragePaths(directoryArg);
+                  wrapped.put("result", output);
+                } catch (Error | RuntimeException exception) {
+                  wrapped.put("error", wrapError(exception));
+                }
+                reply.reply(wrapped);
+              });
+        } else {
+          channel.setMessageHandler(null);
+        }
+      }
+    }
+  }
+
+  private static Map<String, Object> wrapError(Throwable exception) {
+    Map<String, Object> errorMap = new HashMap<>();
+    errorMap.put("message", exception.toString());
+    errorMap.put("code", exception.getClass().getSimpleName());
+    errorMap.put(
+        "details",
+        "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception));
+    return errorMap;
+  }
+}
diff --git a/packages/path_provider/path_provider_android/android/src/main/java/io/flutter/plugins/pathprovider/PathProviderPlugin.java b/packages/path_provider/path_provider_android/android/src/main/java/io/flutter/plugins/pathprovider/PathProviderPlugin.java
index 6dcf959..8a5ff48 100644
--- a/packages/path_provider/path_provider_android/android/src/main/java/io/flutter/plugins/pathprovider/PathProviderPlugin.java
+++ b/packages/path_provider/path_provider_android/android/src/main/java/io/flutter/plugins/pathprovider/PathProviderPlugin.java
@@ -7,164 +7,34 @@
 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.BinaryMessenger.TaskQueue;
-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.StandardMethodCodec;
+import io.flutter.plugins.pathprovider.Messages.PathProviderApi;
 import io.flutter.util.PathUtils;
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
+import javax.annotation.Nullable;
 
-public class PathProviderPlugin implements FlutterPlugin, MethodCallHandler {
+public class PathProviderPlugin implements FlutterPlugin, PathProviderApi {
   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_android";
     TaskQueue taskQueue = messenger.makeBackgroundTaskQueue();
 
     try {
-      channel =
-          (MethodChannel)
-              new MethodChannel(messenger, channelName, StandardMethodCodec.INSTANCE, taskQueue);
-      impl = new PathProviderBackgroundThread();
+      PathProviderApi.setup(messenger, this);
     } catch (Exception ex) {
       Log.e(TAG, "Received exception while setting up PathProviderPlugin", ex);
     }
 
     this.context = context;
-    channel.setMethodCallHandler(this);
   }
 
   @SuppressWarnings("deprecation")
@@ -180,36 +50,38 @@
 
   @Override
   public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
-    channel.setMethodCallHandler(null);
-    channel = null;
+    PathProviderApi.setup(binding.getBinaryMessenger(), 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();
-    }
+  public @Nullable String getTemporaryPath() {
+    return getPathProviderTemporaryDirectory();
+  }
+
+  @Override
+  public @Nullable String getApplicationSupportPath() {
+    return getApplicationSupportDirectory();
+  }
+
+  @Override
+  public @Nullable String getApplicationDocumentsPath() {
+    return getPathProviderApplicationDocumentsDirectory();
+  }
+
+  @Override
+  public @Nullable String getExternalStoragePath() {
+    return getPathProviderStorageDirectory();
+  }
+
+  @Override
+  public @NonNull List<String> getExternalCachePaths() {
+    return getPathProviderExternalCacheDirectories();
+  }
+
+  @Override
+  public @NonNull List<String> getExternalStoragePaths(
+      @NonNull Messages.StorageDirectory directory) {
+    return getPathProviderExternalStorageDirectories(directory);
   }
 
   private String getPathProviderTemporaryDirectory() {
@@ -251,17 +123,45 @@
     return paths;
   }
 
-  private List<String> getPathProviderExternalStorageDirectories(String type) {
+  private String getStorageDirectoryString(@NonNull Messages.StorageDirectory directory) {
+    switch (directory) {
+      case music:
+        return "music";
+      case podcasts:
+        return "podcasts";
+      case ringtones:
+        return "ringtones";
+      case alarms:
+        return "alarms";
+      case notifications:
+        return "notifications";
+      case pictures:
+        return "pictures";
+      case movies:
+        return "movies";
+      case downloads:
+        return "downloads";
+      case dcim:
+        return "dcim";
+      case documents:
+        return "documents";
+      default:
+        throw new RuntimeException("Unrecognized directory: " + directory);
+    }
+  }
+
+  private List<String> getPathProviderExternalStorageDirectories(
+      @NonNull Messages.StorageDirectory directory) {
     final List<String> paths = new ArrayList<String>();
 
     if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
-      for (File dir : context.getExternalFilesDirs(type)) {
+      for (File dir : context.getExternalFilesDirs(getStorageDirectoryString(directory))) {
         if (dir != null) {
           paths.add(dir.getAbsolutePath());
         }
       }
     } else {
-      File dir = context.getExternalFilesDir(type);
+      File dir = context.getExternalFilesDir(getStorageDirectoryString(directory));
       if (dir != null) {
         paths.add(dir.getAbsolutePath());
       }
@@ -269,13 +169,4 @@
 
     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);
-    }
-  }
 }
diff --git a/packages/path_provider/path_provider_android/lib/messages.g.dart b/packages/path_provider/path_provider_android/lib/messages.g.dart
new file mode 100644
index 0000000..50009f4
--- /dev/null
+++ b/packages/path_provider/path_provider_android/lib/messages.g.dart
@@ -0,0 +1,196 @@
+// 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.
+// Autogenerated from Pigeon (v3.1.5), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name
+// @dart = 2.12
+import 'dart:async';
+import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List;
+
+import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer;
+import 'package:flutter/services.dart';
+
+enum StorageDirectory {
+  music,
+  podcasts,
+  ringtones,
+  alarms,
+  notifications,
+  pictures,
+  movies,
+  downloads,
+  dcim,
+  documents,
+}
+
+class _PathProviderApiCodec extends StandardMessageCodec {
+  const _PathProviderApiCodec();
+}
+
+class PathProviderApi {
+  /// Constructor for [PathProviderApi].  The [binaryMessenger] named argument is
+  /// available for dependency injection.  If it is left null, the default
+  /// BinaryMessenger will be used which routes to the host platform.
+  PathProviderApi({BinaryMessenger? binaryMessenger})
+      : _binaryMessenger = binaryMessenger;
+
+  final BinaryMessenger? _binaryMessenger;
+
+  static const MessageCodec<Object?> codec = _PathProviderApiCodec();
+
+  Future<String?> getTemporaryPath() async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.PathProviderApi.getTemporaryPath', codec,
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap =
+        await channel.send(null) as Map<Object?, Object?>?;
+    if (replyMap == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+      );
+    } else if (replyMap['error'] != null) {
+      final Map<Object?, Object?> error =
+          (replyMap['error'] as Map<Object?, Object?>?)!;
+      throw PlatformException(
+        code: (error['code'] as String?)!,
+        message: error['message'] as String?,
+        details: error['details'],
+      );
+    } else {
+      return (replyMap['result'] as String?);
+    }
+  }
+
+  Future<String?> getApplicationSupportPath() async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.PathProviderApi.getApplicationSupportPath', codec,
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap =
+        await channel.send(null) as Map<Object?, Object?>?;
+    if (replyMap == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+      );
+    } else if (replyMap['error'] != null) {
+      final Map<Object?, Object?> error =
+          (replyMap['error'] as Map<Object?, Object?>?)!;
+      throw PlatformException(
+        code: (error['code'] as String?)!,
+        message: error['message'] as String?,
+        details: error['details'],
+      );
+    } else {
+      return (replyMap['result'] as String?);
+    }
+  }
+
+  Future<String?> getApplicationDocumentsPath() async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.PathProviderApi.getApplicationDocumentsPath', codec,
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap =
+        await channel.send(null) as Map<Object?, Object?>?;
+    if (replyMap == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+      );
+    } else if (replyMap['error'] != null) {
+      final Map<Object?, Object?> error =
+          (replyMap['error'] as Map<Object?, Object?>?)!;
+      throw PlatformException(
+        code: (error['code'] as String?)!,
+        message: error['message'] as String?,
+        details: error['details'],
+      );
+    } else {
+      return (replyMap['result'] as String?);
+    }
+  }
+
+  Future<String?> getExternalStoragePath() async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.PathProviderApi.getExternalStoragePath', codec,
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap =
+        await channel.send(null) as Map<Object?, Object?>?;
+    if (replyMap == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+      );
+    } else if (replyMap['error'] != null) {
+      final Map<Object?, Object?> error =
+          (replyMap['error'] as Map<Object?, Object?>?)!;
+      throw PlatformException(
+        code: (error['code'] as String?)!,
+        message: error['message'] as String?,
+        details: error['details'],
+      );
+    } else {
+      return (replyMap['result'] as String?);
+    }
+  }
+
+  Future<List<String?>> getExternalCachePaths() async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.PathProviderApi.getExternalCachePaths', codec,
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap =
+        await channel.send(null) as Map<Object?, Object?>?;
+    if (replyMap == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+      );
+    } else if (replyMap['error'] != null) {
+      final Map<Object?, Object?> error =
+          (replyMap['error'] as Map<Object?, Object?>?)!;
+      throw PlatformException(
+        code: (error['code'] as String?)!,
+        message: error['message'] as String?,
+        details: error['details'],
+      );
+    } else if (replyMap['result'] == null) {
+      throw PlatformException(
+        code: 'null-error',
+        message: 'Host platform returned null value for non-null return value.',
+      );
+    } else {
+      return (replyMap['result'] as List<Object?>?)!.cast<String?>();
+    }
+  }
+
+  Future<List<String?>> getExternalStoragePaths(
+      StorageDirectory arg_directory) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.PathProviderApi.getExternalStoragePaths', codec,
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap = await channel
+        .send(<Object?>[arg_directory.index]) as Map<Object?, Object?>?;
+    if (replyMap == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+      );
+    } else if (replyMap['error'] != null) {
+      final Map<Object?, Object?> error =
+          (replyMap['error'] as Map<Object?, Object?>?)!;
+      throw PlatformException(
+        code: (error['code'] as String?)!,
+        message: error['message'] as String?,
+        details: error['details'],
+      );
+    } else if (replyMap['result'] == null) {
+      throw PlatformException(
+        code: 'null-error',
+        message: 'Host platform returned null value for non-null return value.',
+      );
+    } else {
+      return (replyMap['result'] as List<Object?>?)!.cast<String?>();
+    }
+  }
+}
diff --git a/packages/path_provider/path_provider_android/lib/path_provider_android.dart b/packages/path_provider/path_provider_android/lib/path_provider_android.dart
index b0f3808..4f08d7a 100644
--- a/packages/path_provider/path_provider_android/lib/path_provider_android.dart
+++ b/packages/path_provider/path_provider_android/lib/path_provider_android.dart
@@ -2,16 +2,37 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'package:flutter/foundation.dart';
-import 'package:flutter/services.dart';
 import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
+import 'messages.g.dart' as messages;
+
+messages.StorageDirectory _convertStorageDirectory(StorageDirectory directory) {
+  switch (directory) {
+    case StorageDirectory.music:
+      return messages.StorageDirectory.music;
+    case StorageDirectory.podcasts:
+      return messages.StorageDirectory.podcasts;
+    case StorageDirectory.ringtones:
+      return messages.StorageDirectory.ringtones;
+    case StorageDirectory.alarms:
+      return messages.StorageDirectory.alarms;
+    case StorageDirectory.notifications:
+      return messages.StorageDirectory.notifications;
+    case StorageDirectory.pictures:
+      return messages.StorageDirectory.pictures;
+    case StorageDirectory.movies:
+      return messages.StorageDirectory.movies;
+    case StorageDirectory.downloads:
+      return messages.StorageDirectory.downloads;
+    case StorageDirectory.dcim:
+      return messages.StorageDirectory.dcim;
+    case StorageDirectory.documents:
+      return messages.StorageDirectory.documents;
+  }
+}
 
 /// The Android implementation of [PathProviderPlatform].
 class PathProviderAndroid extends PathProviderPlatform {
-  /// The method channel used to interact with the native platform.
-  @visibleForTesting
-  MethodChannel methodChannel =
-      const MethodChannel('plugins.flutter.io/path_provider_android');
+  final messages.PathProviderApi _api = messages.PathProviderApi();
 
   /// Registers this class as the default instance of [PathProviderPlatform].
   static void registerWith() {
@@ -20,12 +41,12 @@
 
   @override
   Future<String?> getTemporaryPath() {
-    return methodChannel.invokeMethod<String>('getTemporaryDirectory');
+    return _api.getTemporaryPath();
   }
 
   @override
   Future<String?> getApplicationSupportPath() {
-    return methodChannel.invokeMethod<String>('getApplicationSupportDirectory');
+    return _api.getApplicationSupportPath();
   }
 
   @override
@@ -35,29 +56,27 @@
 
   @override
   Future<String?> getApplicationDocumentsPath() {
-    return methodChannel
-        .invokeMethod<String>('getApplicationDocumentsDirectory');
+    return _api.getApplicationDocumentsPath();
   }
 
   @override
   Future<String?> getExternalStoragePath() {
-    return methodChannel.invokeMethod<String>('getStorageDirectory');
+    return _api.getExternalStoragePath();
   }
 
   @override
-  Future<List<String>?> getExternalCachePaths() {
-    return methodChannel
-        .invokeListMethod<String>('getExternalCacheDirectories');
+  Future<List<String>?> getExternalCachePaths() async {
+    return (await _api.getExternalCachePaths()).cast<String>();
   }
 
   @override
   Future<List<String>?> getExternalStoragePaths({
     StorageDirectory? type,
   }) async {
-    return methodChannel.invokeListMethod<String>(
-      'getExternalStorageDirectories',
-      <String, dynamic>{'type': type?.index},
-    );
+    return type == null
+        ? <String>[]
+        : (await _api.getExternalStoragePaths(_convertStorageDirectory(type)))
+            .cast<String>();
   }
 
   @override
diff --git a/packages/path_provider/path_provider_android/pigeons/copyright.txt b/packages/path_provider/path_provider_android/pigeons/copyright.txt
new file mode 100644
index 0000000..1236b63
--- /dev/null
+++ b/packages/path_provider/path_provider_android/pigeons/copyright.txt
@@ -0,0 +1,3 @@
+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.
diff --git a/packages/path_provider/path_provider_android/pigeons/messages.dart b/packages/path_provider/path_provider_android/pigeons/messages.dart
new file mode 100644
index 0000000..4641667
--- /dev/null
+++ b/packages/path_provider/path_provider_android/pigeons/messages.dart
@@ -0,0 +1,43 @@
+// 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.
+import 'package:pigeon/pigeon.dart';
+
+@ConfigurePigeon(PigeonOptions(
+  input: 'pigeons/messages.dart',
+  javaOut:
+      'android/src/main/java/io/flutter/plugins/pathprovider/Messages.java',
+  javaOptions: JavaOptions(
+      className: 'Messages', package: 'io.flutter.plugins.pathprovider'),
+  dartOut: 'lib/messages.g.dart',
+  dartTestOut: 'test/messages_test.g.dart',
+  copyrightHeader: 'pigeons/copyright.txt',
+))
+enum StorageDirectory {
+  music,
+  podcasts,
+  ringtones,
+  alarms,
+  notifications,
+  pictures,
+  movies,
+  downloads,
+  dcim,
+  documents,
+}
+
+@HostApi(dartHostTestHandler: 'TestPathProviderApi')
+abstract class PathProviderApi {
+  @TaskQueue(type: TaskQueueType.serialBackgroundThread)
+  String? getTemporaryPath();
+  @TaskQueue(type: TaskQueueType.serialBackgroundThread)
+  String? getApplicationSupportPath();
+  @TaskQueue(type: TaskQueueType.serialBackgroundThread)
+  String? getApplicationDocumentsPath();
+  @TaskQueue(type: TaskQueueType.serialBackgroundThread)
+  String? getExternalStoragePath();
+  @TaskQueue(type: TaskQueueType.serialBackgroundThread)
+  List<String?> getExternalCachePaths();
+  @TaskQueue(type: TaskQueueType.serialBackgroundThread)
+  List<String?> getExternalStoragePaths(StorageDirectory directory);
+}
diff --git a/packages/path_provider/path_provider_android/pubspec.yaml b/packages/path_provider/path_provider_android/pubspec.yaml
index f1dc92a..64b953b 100644
--- a/packages/path_provider/path_provider_android/pubspec.yaml
+++ b/packages/path_provider/path_provider_android/pubspec.yaml
@@ -2,11 +2,11 @@
 description: Android implementation of the path_provider plugin.
 repository: https://github.com/flutter/plugins/tree/main/packages/path_provider/path_provider_android
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+path_provider%22
-version: 2.0.14
+version: 2.0.15
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
-  flutter: ">=2.8.1"
+  flutter: ">=3.0.0"
 
 flutter:
   plugin:
@@ -29,4 +29,5 @@
     sdk: flutter
   integration_test:
     sdk: flutter
+  pigeon: ^3.1.5  
   test: ^1.16.0
diff --git a/packages/path_provider/path_provider_android/test/messages_test.g.dart b/packages/path_provider/path_provider_android/test/messages_test.g.dart
new file mode 100644
index 0000000..dc8ee55
--- /dev/null
+++ b/packages/path_provider/path_provider_android/test/messages_test.g.dart
@@ -0,0 +1,131 @@
+// 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.
+// Autogenerated from Pigeon (v3.1.5), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis
+// ignore_for_file: avoid_relative_lib_imports
+// @dart = 2.12
+import 'dart:async';
+import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List;
+import 'package:flutter/foundation.dart' show WriteBuffer, ReadBuffer;
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+// The following line is edited by hand to avoid confusing dart with overloaded types.
+import 'package:path_provider_android/messages.g.dart';
+
+class _TestPathProviderApiCodec extends StandardMessageCodec {
+  const _TestPathProviderApiCodec();
+}
+
+abstract class TestPathProviderApi {
+  static const MessageCodec<Object?> codec = _TestPathProviderApiCodec();
+
+  String? getTemporaryPath();
+  String? getApplicationSupportPath();
+  String? getApplicationDocumentsPath();
+  String? getExternalStoragePath();
+  List<String?> getExternalCachePaths();
+  List<String?> getExternalStoragePaths(StorageDirectory directory);
+  static void setup(TestPathProviderApi? api,
+      {BinaryMessenger? binaryMessenger}) {
+    {
+      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.PathProviderApi.getTemporaryPath', codec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        channel.setMockMessageHandler(null);
+      } else {
+        channel.setMockMessageHandler((Object? message) async {
+          // ignore message
+          final String? output = api.getTemporaryPath();
+          return <Object?, Object?>{'result': output};
+        });
+      }
+    }
+    {
+      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.PathProviderApi.getApplicationSupportPath', codec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        channel.setMockMessageHandler(null);
+      } else {
+        channel.setMockMessageHandler((Object? message) async {
+          // ignore message
+          final String? output = api.getApplicationSupportPath();
+          return <Object?, Object?>{'result': output};
+        });
+      }
+    }
+    {
+      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.PathProviderApi.getApplicationDocumentsPath',
+          codec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        channel.setMockMessageHandler(null);
+      } else {
+        channel.setMockMessageHandler((Object? message) async {
+          // ignore message
+          final String? output = api.getApplicationDocumentsPath();
+          return <Object?, Object?>{'result': output};
+        });
+      }
+    }
+    {
+      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.PathProviderApi.getExternalStoragePath', codec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        channel.setMockMessageHandler(null);
+      } else {
+        channel.setMockMessageHandler((Object? message) async {
+          // ignore message
+          final String? output = api.getExternalStoragePath();
+          return <Object?, Object?>{'result': output};
+        });
+      }
+    }
+    {
+      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.PathProviderApi.getExternalCachePaths', codec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        channel.setMockMessageHandler(null);
+      } else {
+        channel.setMockMessageHandler((Object? message) async {
+          // ignore message
+          final List<String?> output = api.getExternalCachePaths();
+          return <Object?, Object?>{'result': output};
+        });
+      }
+    }
+    {
+      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.PathProviderApi.getExternalStoragePaths', codec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        channel.setMockMessageHandler(null);
+      } else {
+        channel.setMockMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.PathProviderApi.getExternalStoragePaths was null.');
+          final List<Object?> args = (message as List<Object?>?)!;
+
+          /// TODO(gaaclarke): The following line was tweaked by hand to address
+          /// https://github.com/flutter/flutter/issues/105742.  Alternatively
+          /// the tests could be written with a mock BinaryMessenger but this is
+          /// how we want to address it eventually.
+          final StorageDirectory? arg_directory =
+              StorageDirectory.values[args[0] as int];
+          assert(arg_directory != null,
+              'Argument for dev.flutter.pigeon.PathProviderApi.getExternalStoragePaths was null, expected non-null StorageDirectory.');
+          final List<String?> output =
+              api.getExternalStoragePaths(arg_directory!);
+          return <Object?, Object?>{'result': output};
+        });
+      }
+    }
+  }
+}
diff --git a/packages/path_provider/path_provider_android/test/path_provider_android_test.dart b/packages/path_provider/path_provider_android/test/path_provider_android_test.dart
index d2f9682..2fe4a51 100644
--- a/packages/path_provider/path_provider_android/test/path_provider_android_test.dart
+++ b/packages/path_provider/path_provider_android/test/path_provider_android_test.dart
@@ -2,73 +2,59 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart';
+import 'package:path_provider_android/messages.g.dart' as messages;
 import 'package:path_provider_android/path_provider_android.dart';
 import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
+import 'messages_test.g.dart';
+
+const String kTemporaryPath = 'temporaryPath';
+const String kApplicationSupportPath = 'applicationSupportPath';
+const String kLibraryPath = 'libraryPath';
+const String kApplicationDocumentsPath = 'applicationDocumentsPath';
+const String kExternalCachePaths = 'externalCachePaths';
+const String kExternalStoragePaths = 'externalStoragePaths';
+const String kDownloadsPath = 'downloadsPath';
+
+class _Api implements TestPathProviderApi {
+  @override
+  String? getApplicationDocumentsPath() => kApplicationDocumentsPath;
+
+  @override
+  String? getApplicationSupportPath() => kApplicationSupportPath;
+
+  @override
+  List<String?> getExternalCachePaths() => <String>[kExternalCachePaths];
+
+  @override
+  String? getExternalStoragePath() => kExternalStoragePaths;
+
+  @override
+  List<String?> getExternalStoragePaths(messages.StorageDirectory directory) =>
+      <String>[kExternalStoragePaths];
+
+  @override
+  String? getTemporaryPath() => kTemporaryPath;
+}
 
 void main() {
   TestWidgetsFlutterBinding.ensureInitialized();
-  const String kTemporaryPath = 'temporaryPath';
-  const String kApplicationSupportPath = 'applicationSupportPath';
-  const String kLibraryPath = 'libraryPath';
-  const String kApplicationDocumentsPath = 'applicationDocumentsPath';
-  const String kExternalCachePaths = 'externalCachePaths';
-  const String kExternalStoragePaths = 'externalStoragePaths';
-  const String kDownloadsPath = 'downloadsPath';
 
   group('PathProviderAndroid', () {
     late PathProviderAndroid pathProvider;
-    final List<MethodCall> log = <MethodCall>[];
 
     setUp(() async {
       pathProvider = PathProviderAndroid();
-      TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger
-          .setMockMethodCallHandler(pathProvider.methodChannel,
-              (MethodCall methodCall) async {
-        log.add(methodCall);
-        switch (methodCall.method) {
-          case 'getTemporaryDirectory':
-            return kTemporaryPath;
-          case 'getApplicationSupportDirectory':
-            return kApplicationSupportPath;
-          case 'getLibraryDirectory':
-            return kLibraryPath;
-          case 'getApplicationDocumentsDirectory':
-            return kApplicationDocumentsPath;
-          case 'getExternalStorageDirectories':
-            return <String>[kExternalStoragePaths];
-          case 'getExternalCacheDirectories':
-            return <String>[kExternalCachePaths];
-          case 'getDownloadsDirectory':
-            return kDownloadsPath;
-          default:
-            return null;
-        }
-      });
-    });
-
-    tearDown(() {
-      log.clear();
+      TestPathProviderApi.setup(_Api());
     });
 
     test('getTemporaryPath', () async {
       final String? path = await pathProvider.getTemporaryPath();
-      expect(
-        log,
-        <Matcher>[isMethodCall('getTemporaryDirectory', arguments: null)],
-      );
       expect(path, kTemporaryPath);
     });
 
     test('getApplicationSupportPath', () async {
       final String? path = await pathProvider.getApplicationSupportPath();
-      expect(
-        log,
-        <Matcher>[
-          isMethodCall('getApplicationSupportDirectory', arguments: null)
-        ],
-      );
       expect(path, kApplicationSupportPath);
     });
 
@@ -83,47 +69,32 @@
 
     test('getApplicationDocumentsPath', () async {
       final String? path = await pathProvider.getApplicationDocumentsPath();
-      expect(
-        log,
-        <Matcher>[
-          isMethodCall('getApplicationDocumentsDirectory', arguments: null)
-        ],
-      );
       expect(path, kApplicationDocumentsPath);
     });
 
     test('getExternalCachePaths succeeds', () async {
       final List<String>? result = await pathProvider.getExternalCachePaths();
-      expect(
-        log,
-        <Matcher>[isMethodCall('getExternalCacheDirectories', arguments: null)],
-      );
       expect(result!.length, 1);
       expect(result.first, kExternalCachePaths);
     });
 
     for (final StorageDirectory? type in <StorageDirectory?>[
-      null,
       ...StorageDirectory.values
     ]) {
       test('getExternalStoragePaths (type: $type) android succeeds', () async {
         final List<String>? result =
             await pathProvider.getExternalStoragePaths(type: type);
-        expect(
-          log,
-          <Matcher>[
-            isMethodCall(
-              'getExternalStorageDirectories',
-              arguments: <String, dynamic>{'type': type?.index},
-            )
-          ],
-        );
-
         expect(result!.length, 1);
         expect(result.first, kExternalStoragePaths);
       });
     } // end of for-loop
 
+    test('getExternalStoragePaths with null android succeeds', () async {
+      final List<String>? result =
+          await pathProvider.getExternalStoragePaths(type: null);
+      expect(result!.length, 0);
+    });
+
     test('getDownloadsPath fails', () async {
       try {
         await pathProvider.getDownloadsPath();