[path_provider] Android: Support multiple external storage options (#2049)

diff --git a/packages/path_provider/CHANGELOG.md b/packages/path_provider/CHANGELOG.md
index 3bab2b1..af0ec9f 100644
--- a/packages/path_provider/CHANGELOG.md
+++ b/packages/path_provider/CHANGELOG.md
@@ -1,3 +1,10 @@
+## 1.4.0
+
+* Support retrieving storage paths on Android devices with multiple external
+  storage options. This adds a new class `AndroidEnvironment` that shadows the
+  directory names from Androids `android.os.Environment` class.
+* Fixes `getLibraryDirectory` semantics & tests.
+
 ## 1.3.1
 
 * Define clang module for iOS.
diff --git a/packages/path_provider/android/build.gradle b/packages/path_provider/android/build.gradle
index 191930f..dbbe3b8 100644
--- a/packages/path_provider/android/build.gradle
+++ b/packages/path_provider/android/build.gradle
@@ -45,3 +45,8 @@
         disable 'InvalidPackage'
     }
 }
+
+dependencies {
+    implementation 'androidx.annotation:annotation:1.1.0'
+    testImplementation 'junit:junit:4.12'
+}
diff --git a/packages/path_provider/android/src/main/java/io/flutter/plugins/pathprovider/PathProviderPlugin.java b/packages/path_provider/android/src/main/java/io/flutter/plugins/pathprovider/PathProviderPlugin.java
index 271236b..30b00d0 100644
--- a/packages/path_provider/android/src/main/java/io/flutter/plugins/pathprovider/PathProviderPlugin.java
+++ b/packages/path_provider/android/src/main/java/io/flutter/plugins/pathprovider/PathProviderPlugin.java
@@ -4,6 +4,9 @@
 
 package io.flutter.plugins.pathprovider;
 
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import androidx.annotation.NonNull;
 import io.flutter.plugin.common.MethodCall;
 import io.flutter.plugin.common.MethodChannel;
 import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
@@ -11,8 +14,11 @@
 import io.flutter.plugin.common.PluginRegistry.Registrar;
 import io.flutter.util.PathUtils;
 import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
 
 public class PathProviderPlugin implements MethodCallHandler {
+
   private final Registrar mRegistrar;
 
   public static void registerWith(Registrar registrar) {
@@ -27,7 +33,7 @@
   }
 
   @Override
-  public void onMethodCall(MethodCall call, Result result) {
+  public void onMethodCall(MethodCall call, @NonNull Result result) {
     switch (call.method) {
       case "getTemporaryDirectory":
         result.success(getPathProviderTemporaryDirectory());
@@ -38,6 +44,14 @@
       case "getStorageDirectory":
         result.success(getPathProviderStorageDirectory());
         break;
+      case "getExternalCacheDirectories":
+        result.success(getPathProviderExternalCacheDirectories());
+        break;
+      case "getExternalStorageDirectories":
+        final Integer type = call.argument("type");
+        final String directoryName = StorageDirectoryMapper.androidType(type);
+        result.success(getPathProviderExternalStorageDirectories(directoryName));
+        break;
       case "getApplicationSupportDirectory":
         result.success(getApplicationSupportDirectory());
         break;
@@ -65,4 +79,42 @@
     }
     return dir.getAbsolutePath();
   }
+
+  private List<String> getPathProviderExternalCacheDirectories() {
+    final List<String> paths = new ArrayList<>();
+
+    if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
+      for (File dir : mRegistrar.context().getExternalCacheDirs()) {
+        if (dir != null) {
+          paths.add(dir.getAbsolutePath());
+        }
+      }
+    } else {
+      File dir = mRegistrar.context().getExternalCacheDir();
+      if (dir != null) {
+        paths.add(dir.getAbsolutePath());
+      }
+    }
+
+    return paths;
+  }
+
+  private List<String> getPathProviderExternalStorageDirectories(String type) {
+    final List<String> paths = new ArrayList<>();
+
+    if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
+      for (File dir : mRegistrar.context().getExternalFilesDirs(type)) {
+        if (dir != null) {
+          paths.add(dir.getAbsolutePath());
+        }
+      }
+    } else {
+      File dir = mRegistrar.context().getExternalFilesDir(type);
+      if (dir != null) {
+        paths.add(dir.getAbsolutePath());
+      }
+    }
+
+    return paths;
+  }
 }
diff --git a/packages/path_provider/android/src/main/java/io/flutter/plugins/pathprovider/StorageDirectoryMapper.java b/packages/path_provider/android/src/main/java/io/flutter/plugins/pathprovider/StorageDirectoryMapper.java
new file mode 100644
index 0000000..820509b
--- /dev/null
+++ b/packages/path_provider/android/src/main/java/io/flutter/plugins/pathprovider/StorageDirectoryMapper.java
@@ -0,0 +1,51 @@
+package io.flutter.plugins.pathprovider;
+
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.os.Environment;
+
+/** Helps to map the Dart `StorageDirectory` enum to a Android system constant. */
+class StorageDirectoryMapper {
+
+  /**
+   * Return a Android Environment constant for a Dart Index.
+   *
+   * @return The correct Android Environment constant or null, if the index is null.
+   * @throws IllegalArgumentException If `dartIndex` is not null but also not matches any known
+   *     index.
+   */
+  static String androidType(Integer dartIndex) throws IllegalArgumentException {
+    if (dartIndex == null) {
+      return null;
+    }
+
+    switch (dartIndex) {
+      case 0:
+        return Environment.DIRECTORY_MUSIC;
+      case 1:
+        return Environment.DIRECTORY_PODCASTS;
+      case 2:
+        return Environment.DIRECTORY_RINGTONES;
+      case 3:
+        return Environment.DIRECTORY_ALARMS;
+      case 4:
+        return Environment.DIRECTORY_NOTIFICATIONS;
+      case 5:
+        return Environment.DIRECTORY_PICTURES;
+      case 6:
+        return Environment.DIRECTORY_MOVIES;
+      case 7:
+        return Environment.DIRECTORY_DOWNLOADS;
+      case 8:
+        return Environment.DIRECTORY_DCIM;
+      case 9:
+        if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
+          return Environment.DIRECTORY_DOCUMENTS;
+        } else {
+          throw new IllegalArgumentException("Documents directory is unsupported.");
+        }
+      default:
+        throw new IllegalArgumentException("Unknown index: " + dartIndex);
+    }
+  }
+}
diff --git a/packages/path_provider/android/src/test/java/io/flutter/plugins/pathprovider/StorageDirectoryMapperTest.java b/packages/path_provider/android/src/test/java/io/flutter/plugins/pathprovider/StorageDirectoryMapperTest.java
new file mode 100644
index 0000000..51695cf
--- /dev/null
+++ b/packages/path_provider/android/src/test/java/io/flutter/plugins/pathprovider/StorageDirectoryMapperTest.java
@@ -0,0 +1,40 @@
+package io.flutter.plugins.pathprovider;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import android.os.Environment;
+import org.junit.Test;
+
+public class StorageDirectoryMapperTest {
+
+  @org.junit.Test
+  public void testAndroidType_null() {
+    assertNull(StorageDirectoryMapper.androidType(null));
+  }
+
+  @org.junit.Test
+  public void testAndroidType_valid() {
+    assertEquals(Environment.DIRECTORY_MUSIC, StorageDirectoryMapper.androidType(0));
+    assertEquals(Environment.DIRECTORY_PODCASTS, StorageDirectoryMapper.androidType(1));
+    assertEquals(Environment.DIRECTORY_MUSIC, StorageDirectoryMapper.androidType(2));
+    assertEquals(Environment.DIRECTORY_RINGTONES, StorageDirectoryMapper.androidType(3));
+    assertEquals(Environment.DIRECTORY_ALARMS, StorageDirectoryMapper.androidType(4));
+    assertEquals(Environment.DIRECTORY_NOTIFICATIONS, StorageDirectoryMapper.androidType(5));
+    assertEquals(Environment.DIRECTORY_PICTURES, StorageDirectoryMapper.androidType(6));
+    assertEquals(Environment.DIRECTORY_MOVIES, StorageDirectoryMapper.androidType(7));
+    assertEquals(Environment.DIRECTORY_DOWNLOADS, StorageDirectoryMapper.androidType(8));
+    assertEquals(Environment.DIRECTORY_DCIM, StorageDirectoryMapper.androidType(9));
+  }
+
+  @Test
+  public void testAndroidType_invalid() {
+    try {
+      assertEquals(Environment.DIRECTORY_DCIM, StorageDirectoryMapper.androidType(10));
+      fail();
+    } catch (IllegalArgumentException e) {
+      assertEquals("Unknown index: " + 10, e.getMessage());
+    }
+  }
+}
diff --git a/packages/path_provider/example/android/gradle.properties b/packages/path_provider/example/android/gradle.properties
index 8bd86f6..7be3d8b 100644
--- a/packages/path_provider/example/android/gradle.properties
+++ b/packages/path_provider/example/android/gradle.properties
@@ -1 +1,2 @@
 org.gradle.jvmargs=-Xmx1536M
+android.enableR8=true
diff --git a/packages/path_provider/example/lib/main.dart b/packages/path_provider/example/lib/main.dart
index 2e9e951..ea12999 100644
--- a/packages/path_provider/example/lib/main.dart
+++ b/packages/path_provider/example/lib/main.dart
@@ -39,6 +39,8 @@
   Future<Directory> _appLibraryDirectory;
   Future<Directory> _appDocumentsDirectory;
   Future<Directory> _externalDocumentsDirectory;
+  Future<List<Directory>> _externalStorageDirectories;
+  Future<List<Directory>> _externalCacheDirectories;
 
   void _requestTempDirectory() {
     setState(() {
@@ -61,6 +63,23 @@
     return Padding(padding: const EdgeInsets.all(16.0), child: text);
   }
 
+  Widget _buildDirectories(
+      BuildContext context, AsyncSnapshot<List<Directory>> snapshot) {
+    Text text = const Text('');
+    if (snapshot.connectionState == ConnectionState.done) {
+      if (snapshot.hasError) {
+        text = Text('Error: ${snapshot.error}');
+      } else if (snapshot.hasData) {
+        final String combined =
+            snapshot.data.map((Directory d) => d.path).join(', ');
+        text = Text('paths: $combined');
+      } else {
+        text = const Text('path unavailable');
+      }
+    }
+    return Padding(padding: const EdgeInsets.all(16.0), child: text);
+  }
+
   void _requestAppDocumentsDirectory() {
     setState(() {
       _appDocumentsDirectory = getApplicationDocumentsDirectory();
@@ -85,6 +104,18 @@
     });
   }
 
+  void _requestExternalStorageDirectories(StorageDirectory type) {
+    setState(() {
+      _externalStorageDirectories = getExternalStorageDirectories(type: type);
+    });
+  }
+
+  void _requestExternalCacheDirectories() {
+    setState(() {
+      _externalCacheDirectories = getExternalCacheDirectories();
+    });
+  }
+
   @override
   Widget build(BuildContext context) {
     return Scaffold(
@@ -140,7 +171,39 @@
               ),
             ),
             FutureBuilder<Directory>(
-                future: _externalDocumentsDirectory, builder: _buildDirectory)
+                future: _externalDocumentsDirectory, builder: _buildDirectory),
+            Column(children: <Widget>[
+              Padding(
+                padding: const EdgeInsets.all(16.0),
+                child: RaisedButton(
+                  child: Text(
+                      '${Platform.isIOS ? "External directories are unavailable " "on iOS" : "Get External Storage Directories"}'),
+                  onPressed: Platform.isIOS
+                      ? null
+                      : () {
+                          _requestExternalStorageDirectories(
+                            StorageDirectory.music,
+                          );
+                        },
+                ),
+              ),
+            ]),
+            FutureBuilder<List<Directory>>(
+                future: _externalStorageDirectories,
+                builder: _buildDirectories),
+            Column(children: <Widget>[
+              Padding(
+                padding: const EdgeInsets.all(16.0),
+                child: RaisedButton(
+                  child: Text(
+                      '${Platform.isIOS ? "External directories are unavailable " "on iOS" : "Get External Cache Directories"}'),
+                  onPressed:
+                      Platform.isIOS ? null : _requestExternalCacheDirectories,
+                ),
+              ),
+            ]),
+            FutureBuilder<List<Directory>>(
+                future: _externalCacheDirectories, builder: _buildDirectories),
           ],
         ),
       ),
diff --git a/packages/path_provider/example/pubspec.yaml b/packages/path_provider/example/pubspec.yaml
index 9daa92b..015a4fb 100644
--- a/packages/path_provider/example/pubspec.yaml
+++ b/packages/path_provider/example/pubspec.yaml
@@ -6,7 +6,6 @@
     sdk: flutter
   path_provider:
     path: ../
-  uuid: "^1.0.0"
 
 dev_dependencies:
   flutter_driver:
diff --git a/packages/path_provider/example/test_driver/path_provider.dart b/packages/path_provider/example/test_driver/path_provider.dart
index 8438965..ccbc625 100644
--- a/packages/path_provider/example/test_driver/path_provider.dart
+++ b/packages/path_provider/example/test_driver/path_provider.dart
@@ -8,7 +8,6 @@
 import 'package:flutter_driver/driver_extension.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:path_provider/path_provider.dart';
-import 'package:uuid/uuid.dart';
 
 void main() {
   final Completer<String> allTestsCompleter = Completer<String>();
@@ -17,43 +16,23 @@
 
   test('getTemporaryDirectory', () async {
     final Directory result = await getTemporaryDirectory();
-    final String uuid = Uuid().v1();
-    final File file = File('${result.path}/$uuid.txt');
-    file.writeAsStringSync('Hello world!');
-    expect(file.readAsStringSync(), 'Hello world!');
-    expect(result.listSync(), isNotEmpty);
-    file.deleteSync();
+    _verifySampleFile(result, 'temporaryDirectory');
   });
 
   test('getApplicationDocumentsDirectory', () async {
     final Directory result = await getApplicationDocumentsDirectory();
-    final String uuid = Uuid().v1();
-    final File file = File('${result.path}/$uuid.txt');
-    file.writeAsStringSync('Hello world!');
-    expect(file.readAsStringSync(), 'Hello world!');
-    expect(result.listSync(), isNotEmpty);
-    file.deleteSync();
+    _verifySampleFile(result, 'applicationDocuments');
   });
 
   test('getApplicationSupportDirectory', () async {
     final Directory result = await getApplicationSupportDirectory();
-    final String uuid = Uuid().v1();
-    final File file = File('${result.path}/$uuid.txt');
-    file.writeAsStringSync('Hello world!');
-    expect(file.readAsStringSync(), 'Hello world!');
-    expect(result.listSync(), isNotEmpty);
-    file.deleteSync();
+    _verifySampleFile(result, 'applicationSupport');
   });
 
   test('getLibraryDirectory', () async {
     if (Platform.isIOS) {
       final Directory result = await getLibraryDirectory();
-      final String uuid = Uuid().v1();
-      final File file = File('${result.path}/$uuid.txt');
-      file.writeAsStringSync('Hello world!');
-      expect(file.readAsStringSync(), 'Hello world!');
-      expect(result.listSync(), isNotEmpty);
-      file.deleteSync();
+      _verifySampleFile(result, 'library');
     } else if (Platform.isAndroid) {
       final Future<Directory> result = getLibraryDirectory();
       expect(result, throwsA(isInstanceOf<UnsupportedError>()));
@@ -66,12 +45,62 @@
       expect(result, throwsA(isInstanceOf<UnsupportedError>()));
     } else if (Platform.isAndroid) {
       final Directory result = await getExternalStorageDirectory();
-      final String uuid = Uuid().v1();
-      final File file = File('${result.path}/$uuid.txt');
-      file.writeAsStringSync('Hello world!');
-      expect(file.readAsStringSync(), 'Hello world!');
-      expect(result.listSync(), isNotEmpty);
-      file.deleteSync();
+      _verifySampleFile(result, 'externalStorage');
     }
   });
+
+  test('getExternalCacheDirectories', () async {
+    if (Platform.isIOS) {
+      final Future<List<Directory>> result = getExternalCacheDirectories();
+      expect(result, throwsA(isInstanceOf<UnsupportedError>()));
+    } else if (Platform.isAndroid) {
+      final List<Directory> directories = await getExternalCacheDirectories();
+      for (Directory result in directories) {
+        _verifySampleFile(result, 'externalCache');
+      }
+    }
+  });
+
+  final List<StorageDirectory> _allDirs = <StorageDirectory>[
+    null,
+    StorageDirectory.music,
+    StorageDirectory.podcasts,
+    StorageDirectory.ringtones,
+    StorageDirectory.alarms,
+    StorageDirectory.notifications,
+    StorageDirectory.pictures,
+    StorageDirectory.movies,
+  ];
+
+  for (StorageDirectory type in _allDirs) {
+    test('getExternalStorageDirectories (type: $type)', () async {
+      if (Platform.isIOS) {
+        final Future<List<Directory>> result =
+            getExternalStorageDirectories(type: null);
+        expect(result, throwsA(isInstanceOf<UnsupportedError>()));
+      } else if (Platform.isAndroid) {
+        final List<Directory> directories =
+            await getExternalStorageDirectories(type: type);
+        for (Directory result in directories) {
+          _verifySampleFile(result, '$type');
+        }
+      }
+    });
+  }
+}
+
+/// Verify a file called [name] in [directory] by recreating it with test
+/// contents when necessary.
+void _verifySampleFile(Directory directory, String name) {
+  final File file = File('${directory.path}/$name');
+
+  if (file.existsSync()) {
+    file.deleteSync();
+    expect(file.existsSync(), isFalse);
+  }
+
+  file.writeAsStringSync('Hello world!');
+  expect(file.readAsStringSync(), 'Hello world!');
+  expect(directory.listSync(), isNotEmpty);
+  file.deleteSync();
 }
diff --git a/packages/path_provider/lib/path_provider.dart b/packages/path_provider/lib/path_provider.dart
index f058722..3840121 100644
--- a/packages/path_provider/lib/path_provider.dart
+++ b/packages/path_provider/lib/path_provider.dart
@@ -61,7 +61,13 @@
 
 /// Path to the directory where application can store files that are persistent,
 /// backed up, and not visible to the user, such as sqlite.db.
+///
+/// On Android, this function throws an [UnsupportedError] as no equivalent
+/// path exists.
 Future<Directory> getLibraryDirectory() async {
+  if (_platform.isAndroid) {
+    throw UnsupportedError('Functionality not available on Android');
+  }
   final String path =
       await _channel.invokeMethod<String>('getLibraryDirectory');
   if (path == null) {
@@ -97,8 +103,9 @@
 ///
 /// On Android this uses the `getExternalFilesDir(null)`.
 Future<Directory> getExternalStorageDirectory() async {
-  if (_platform.isIOS)
-    throw UnsupportedError("Functionality not available on iOS");
+  if (_platform.isIOS) {
+    throw UnsupportedError('Functionality not available on iOS');
+  }
   final String path =
       await _channel.invokeMethod<String>('getStorageDirectory');
   if (path == null) {
@@ -106,3 +113,70 @@
   }
   return Directory(path);
 }
+
+/// Paths to directories where application specific external cache data can be
+/// stored. These paths typically reside on external storage like separate
+/// partitions or SD cards. Phones may have multiple storage directories
+/// available.
+///
+/// The current operating system should be determined before issuing this
+/// function call, as this functionality is only available on Android.
+///
+/// On iOS, this function throws an UnsupportedError as it is not possible
+/// to access outside the app's sandbox.
+///
+/// On Android this returns Context.getExternalCacheDirs() or
+/// Context.getExternalCacheDir() on API levels below 19.
+Future<List<Directory>> getExternalCacheDirectories() async {
+  if (_platform.isIOS) {
+    throw UnsupportedError('Functionality not available on iOS');
+  }
+  final List<String> paths =
+      await _channel.invokeListMethod<String>('getExternalCacheDirectories');
+
+  return paths.map((String path) => Directory(path)).toList();
+}
+
+/// Corresponds to constants defined in Androids `android.os.Environment` class.
+///
+/// https://developer.android.com/reference/android/os/Environment.html#fields_1
+enum StorageDirectory {
+  music,
+  podcasts,
+  ringtones,
+  alarms,
+  notifications,
+  pictures,
+  movies,
+  downloads,
+  dcim,
+  documents,
+}
+
+/// Paths to directories where application specific data can be stored.
+/// These paths typically reside on external storage like separate partitions
+/// or SD cards. Phones may have multiple storage directories available.
+///
+/// The current operating system should be determined before issuing this
+/// function call, as this functionality is only available on Android.
+///
+/// On iOS, this function throws an UnsupportedError as it is not possible
+/// to access outside the app's sandbox.
+///
+/// On Android this returns Context.getExternalFilesDirs(String type) or
+/// Context.getExternalFilesDir(String type) on API levels below 19.
+Future<List<Directory>> getExternalStorageDirectories({
+  /// Optional parameter. See [StorageDirectory] for more informations on
+  /// how this type translates to Android storage directories.
+  StorageDirectory type,
+}) async {
+  if (_platform.isIOS) {
+    throw UnsupportedError('Functionality not available on iOS');
+  }
+  final List<String> paths = await _channel.invokeListMethod<String>(
+    'getExternalStorageDirectories',
+    <String, dynamic>{'type': type?.index},
+  );
+
+  return paths.map((String path) => Directory(path)).toList();
+}
diff --git a/packages/path_provider/pubspec.yaml b/packages/path_provider/pubspec.yaml
index 1624592..4517eba 100644
--- a/packages/path_provider/pubspec.yaml
+++ b/packages/path_provider/pubspec.yaml
@@ -3,7 +3,7 @@
   iOS file systems, such as the temp and app data directories.
 author: Flutter Team <flutter-dev@googlegroups.com>
 homepage: https://github.com/flutter/plugins/tree/master/packages/path_provider
-version: 1.3.1
+version: 1.4.0
 
 flutter:
   plugin:
diff --git a/packages/path_provider/test/path_provider_test.dart b/packages/path_provider/test/path_provider_test.dart
index 24368c0..7af3788 100644
--- a/packages/path_provider/test/path_provider_test.dart
+++ b/packages/path_provider/test/path_provider_test.dart
@@ -15,7 +15,7 @@
   const MethodChannel channel =
       MethodChannel('plugins.flutter.io/path_provider');
   final List<MethodCall> log = <MethodCall>[];
-  String response;
+  dynamic response;
 
   channel.setMockMethodCallHandler((MethodCall methodCall) async {
     log.add(methodCall);
@@ -86,8 +86,17 @@
     expect(directory, isNull);
   });
 
-  test('getLibraryDirectory test', () async {
-    response = null;
+  test('getLibraryDirectory Android test', () async {
+    try {
+      await getLibraryDirectory();
+      fail('should throw UnsupportedError');
+    } catch (e) {
+      expect(e, isUnsupportedError);
+    }
+  });
+  test('getLibraryDirectory iOS test', () async {
+    setMockPathProviderPlatform(FakePlatform(operatingSystem: 'ios'));
+
     final Directory directory = await getLibraryDirectory();
     expect(
       log,
@@ -99,7 +108,6 @@
   test('getExternalStorageDirectory iOS test', () async {
     setMockPathProviderPlatform(FakePlatform(operatingSystem: 'ios'));
 
-    response = null;
     try {
       await getExternalStorageDirectory();
       fail('should throw UnsupportedError');
@@ -108,6 +116,57 @@
     }
   });
 
+  test('getExternalCacheDirectories test', () async {
+    response = <String>[];
+    final List<Directory> directories = await getExternalCacheDirectories();
+    expect(
+      log,
+      <Matcher>[isMethodCall('getExternalCacheDirectories', arguments: null)],
+    );
+    expect(directories, <Directory>[]);
+  });
+
+  test('getExternalCacheDirectories iOS test', () async {
+    setMockPathProviderPlatform(FakePlatform(operatingSystem: 'ios'));
+
+    try {
+      await getExternalCacheDirectories();
+      fail('should throw UnsupportedError');
+    } catch (e) {
+      expect(e, isUnsupportedError);
+    }
+  });
+
+  for (StorageDirectory type
+      in StorageDirectory.values + <StorageDirectory>[null]) {
+    test('getExternalStorageDirectories test (type: $type)', () async {
+      response = <String>[];
+      final List<Directory> directories =
+          await getExternalStorageDirectories(type: type);
+      expect(
+        log,
+        <Matcher>[
+          isMethodCall(
+            'getExternalStorageDirectories',
+            arguments: <String, dynamic>{'type': type?.index},
+          )
+        ],
+      );
+      expect(directories, <Directory>[]);
+    });
+  }
+
+  test('getExternalStorageDirectories iOS test', () async {
+    setMockPathProviderPlatform(FakePlatform(operatingSystem: 'ios'));
+
+    try {
+      await getExternalStorageDirectories(type: StorageDirectory.music);
+      fail('should throw UnsupportedError');
+    } catch (e) {
+      expect(e, isUnsupportedError);
+    }
+  });
+
   test('TemporaryDirectory path test', () async {
     final String fakePath = "/foo/bar/baz";
     response = fakePath;
@@ -137,6 +196,8 @@
   });
 
   test('ApplicationLibraryDirectory path test', () async {
+    setMockPathProviderPlatform(FakePlatform(operatingSystem: 'ios'));
+
     final String fakePath = "/foo/bar/baz";
     response = fakePath;
     final Directory directory = await getLibraryDirectory();
@@ -149,4 +210,20 @@
     final Directory directory = await getExternalStorageDirectory();
     expect(directory.path, equals(fakePath));
   });
+
+  test('ExternalCacheDirectories path test', () async {
+    final List<String> paths = <String>["/foo/bar/baz", "/foo/bar/baz2"];
+    response = paths;
+    final List<Directory> directories = await getExternalCacheDirectories();
+    expect(directories.map((Directory d) => d.path).toList(), equals(paths));
+  });
+
+  test('ExternalStorageDirectories path test', () async {
+    final List<String> paths = <String>["/foo/bar/baz", "/foo/bar/baz2"];
+    response = paths;
+    final List<Directory> directories = await getExternalStorageDirectories(
+      type: StorageDirectory.music,
+    );
+    expect(directories.map((Directory d) => d.path).toList(), equals(paths));
+  });
 }