[webview_flutter] Enable setAllowFileAccess on Android setting when loading files (#4601)

diff --git a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
index 1337bab..818a134 100644
--- a/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.8.2
+
+* Adds the `WebSettings.setAllowFileAccess()` method and ensure that file access is allowed when the `WebViewAndroidWidget.loadFile()` method is executed.
+
 ## 2.8.1
 
 * Fixes bug where the default user agent string was being set for every rebuild. See
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java
index 8ef0b8d..15b78b7 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/GeneratedAndroidWebView.java
@@ -1152,6 +1152,8 @@
 
     void setBuiltInZoomControls(Long instanceId, Boolean enabled);
 
+    void setAllowFileAccess(Long instanceId, Boolean enabled);
+
     /** The codec used by WebSettingsHostApi. */
     static MessageCodec<Object> getCodec() {
       return WebSettingsHostApiCodec.INSTANCE;
@@ -1556,6 +1558,37 @@
           channel.setMessageHandler(null);
         }
       }
+      {
+        BasicMessageChannel<Object> channel =
+            new BasicMessageChannel<>(
+                binaryMessenger,
+                "dev.flutter.pigeon.WebSettingsHostApi.setAllowFileAccess",
+                getCodec());
+        if (api != null) {
+          channel.setMessageHandler(
+              (message, reply) -> {
+                Map<String, Object> wrapped = new HashMap<>();
+                try {
+                  ArrayList<Object> args = (ArrayList<Object>) message;
+                  Number instanceIdArg = (Number) args.get(0);
+                  if (instanceIdArg == null) {
+                    throw new NullPointerException("instanceIdArg unexpectedly null.");
+                  }
+                  Boolean enabledArg = (Boolean) args.get(1);
+                  if (enabledArg == null) {
+                    throw new NullPointerException("enabledArg unexpectedly null.");
+                  }
+                  api.setAllowFileAccess(instanceIdArg.longValue(), enabledArg);
+                  wrapped.put("result", null);
+                } catch (Error | RuntimeException exception) {
+                  wrapped.put("error", wrapError(exception));
+                }
+                reply.reply(wrapped);
+              });
+        } else {
+          channel.setMessageHandler(null);
+        }
+      }
     }
   }
 
diff --git a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java
index 239ef47..b168e20 100644
--- a/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java
+++ b/packages/webview_flutter/webview_flutter_android/android/src/main/java/io/flutter/plugins/webviewflutter/WebSettingsHostApiImpl.java
@@ -118,4 +118,10 @@
     final WebSettings webSettings = (WebSettings) instanceManager.getInstance(instanceId);
     webSettings.setBuiltInZoomControls(enabled);
   }
+
+  @Override
+  public void setAllowFileAccess(Long instanceId, Boolean enabled) {
+    final WebSettings webSettings = (WebSettings) instanceManager.getInstance(instanceId);
+    webSettings.setAllowFileAccess(enabled);
+  }
 }
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart
index dfa05cd..1098932 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.dart
@@ -546,6 +546,16 @@
   Future<void> setBuiltInZoomControls(bool enabled) {
     return api.setBuiltInZoomControlsFromInstance(this, enabled);
   }
+
+  /// Enables or disables file access within WebView.
+  ///
+  /// This enables or disables file system access only. Assets and resources are
+  /// still accessible using file:///android_asset and file:///android_res. The
+  /// default value is true for apps targeting Build.VERSION_CODES.Q and below,
+  /// and false when targeting Build.VERSION_CODES.R and above.
+  Future<void> setAllowFileAccess(bool enabled) {
+    return api.setAllowFileAccessFromInstance(this, enabled);
+  }
 }
 
 /// Exposes a channel to receive calls from javaScript.
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart
index 810a717..20391c4 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview.pigeon.dart
@@ -1179,6 +1179,31 @@
       return;
     }
   }
+
+  Future<void> setAllowFileAccess(int arg_instanceId, bool arg_enabled) async {
+    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+        'dev.flutter.pigeon.WebSettingsHostApi.setAllowFileAccess', codec,
+        binaryMessenger: _binaryMessenger);
+    final Map<Object?, Object?>? replyMap = await channel
+        .send(<Object>[arg_instanceId, arg_enabled]) as Map<Object?, Object?>?;
+    if (replyMap == null) {
+      throw PlatformException(
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+        details: null,
+      );
+    } 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;
+    }
+  }
 }
 
 class _JavaScriptChannelHostApiCodec extends StandardMessageCodec {
diff --git a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart
index 1db5ed4..ead60f6 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/src/android_webview_api_impls.dart
@@ -437,6 +437,17 @@
       enabled,
     );
   }
+
+  /// Helper method to convert instances ids to objects.
+  Future<void> setAllowFileAccessFromInstance(
+    WebSettings instance,
+    bool enabled,
+  ) {
+    return setAllowFileAccess(
+      instanceManager.getInstanceId(instance)!,
+      enabled,
+    );
+  }
 }
 
 /// Host api implementation for [JavaScriptChannel].
diff --git a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
index de55b52..bf85ac9 100644
--- a/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
+++ b/packages/webview_flutter/webview_flutter_android/lib/webview_android_widget.dart
@@ -179,6 +179,7 @@
         ? absoluteFilePath
         : 'file://$absoluteFilePath';
 
+    webView.settings.setAllowFileAccess(true);
     return webView.loadUrl(url, <String, String>{});
   }
 
diff --git a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart
index 36862f7..b298352 100644
--- a/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart
+++ b/packages/webview_flutter/webview_flutter_android/pigeons/android_webview.dart
@@ -132,6 +132,8 @@
   void setDisplayZoomControls(int instanceId, bool enabled);
 
   void setBuiltInZoomControls(int instanceId, bool enabled);
+
+  void setAllowFileAccess(int instanceId, bool enabled);
 }
 
 @HostApi(dartHostTestHandler: 'TestJavaScriptChannelHostApi')
diff --git a/packages/webview_flutter/webview_flutter_android/pubspec.yaml b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
index 0b78c72..8905d7f 100644
--- a/packages/webview_flutter/webview_flutter_android/pubspec.yaml
+++ b/packages/webview_flutter/webview_flutter_android/pubspec.yaml
@@ -2,7 +2,7 @@
 description: A Flutter plugin that provides a WebView widget on Android.
 repository: https://github.com/flutter/plugins/tree/master/packages/webview_flutter/webview_flutter_android
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+webview%22
-version: 2.8.1
+version: 2.8.2
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart
index 1e47c79..720fe40 100644
--- a/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/android_webview.pigeon.dart
@@ -649,6 +649,7 @@
   void setUseWideViewPort(int instanceId, bool use);
   void setDisplayZoomControls(int instanceId, bool enabled);
   void setBuiltInZoomControls(int instanceId, bool enabled);
+  void setAllowFileAccess(int instanceId, bool enabled);
   static void setup(TestWebSettingsHostApi? api,
       {BinaryMessenger? binaryMessenger}) {
     {
@@ -940,6 +941,28 @@
         });
       }
     }
+    {
+      final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
+          'dev.flutter.pigeon.WebSettingsHostApi.setAllowFileAccess', codec,
+          binaryMessenger: binaryMessenger);
+      if (api == null) {
+        channel.setMockMessageHandler(null);
+      } else {
+        channel.setMockMessageHandler((Object? message) async {
+          assert(message != null,
+              'Argument for dev.flutter.pigeon.WebSettingsHostApi.setAllowFileAccess was null.');
+          final List<Object?> args = (message as List<Object?>?)!;
+          final int? arg_instanceId = (args[0] as int?);
+          assert(arg_instanceId != null,
+              'Argument for dev.flutter.pigeon.WebSettingsHostApi.setAllowFileAccess was null, expected non-null int.');
+          final bool? arg_enabled = (args[1] as bool?);
+          assert(arg_enabled != null,
+              'Argument for dev.flutter.pigeon.WebSettingsHostApi.setAllowFileAccess was null, expected non-null bool.');
+          api.setAllowFileAccess(arg_instanceId!, arg_enabled!);
+          return <Object?, Object?>{};
+        });
+      }
+    }
   }
 }
 
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart
index cc29fc7..8688a19 100644
--- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.dart
@@ -430,6 +430,14 @@
           true,
         ));
       });
+
+      test('setAllowFileAccess', () {
+        webSettings.setAllowFileAccess(true);
+        verify(mockPlatformHostApi.setAllowFileAccess(
+          webSettingsInstanceId,
+          true,
+        ));
+      });
     });
 
     group('$JavaScriptChannel', () {
diff --git a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart
index d25d233..2134de5 100644
--- a/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/android_webview_test.mocks.dart
@@ -1,7 +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.
-
 // Mocks generated by Mockito 5.0.16 from annotations
 // in webview_flutter_android/test/android_webview_test.dart.
 // Do not manually edit this file.
@@ -211,6 +207,10 @@
           Invocation.method(#setBuiltInZoomControls, [instanceId, enabled]),
           returnValueForMissingStub: null);
   @override
+  void setAllowFileAccess(int? instanceId, bool? enabled) => super.noSuchMethod(
+      Invocation.method(#setAllowFileAccess, [instanceId, enabled]),
+      returnValueForMissingStub: null);
+  @override
   String toString() => super.toString();
 }
 
diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart
index 2b25022..fed1c11 100644
--- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.dart
@@ -304,6 +304,15 @@
         ));
       });
 
+      testWidgets('loadFile should setAllowFileAccess to true',
+          (WidgetTester tester) async {
+        await buildWidget(tester);
+
+        await testController.loadFile('file:///path/to/file.html');
+
+        verify(mockWebSettings.setAllowFileAccess(true));
+      });
+
       testWidgets('loadFlutterAsset', (WidgetTester tester) async {
         await buildWidget(tester);
         const String assetKey = 'test_assets/index.html';
diff --git a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart
index ece17ad..12e993b 100644
--- a/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart
+++ b/packages/webview_flutter/webview_flutter_android/test/webview_android_widget_test.mocks.dart
@@ -1,7 +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.
-
 // Mocks generated by Mockito 5.0.16 from annotations
 // in webview_flutter_android/test/webview_android_widget_test.dart.
 // Do not manually edit this file.
@@ -120,6 +116,11 @@
           returnValue: Future<void>.value(),
           returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
   @override
+  _i4.Future<void> setAllowFileAccess(bool? enabled) =>
+      (super.noSuchMethod(Invocation.method(#setAllowFileAccess, [enabled]),
+          returnValue: Future<void>.value(),
+          returnValueForMissingStub: Future<void>.value()) as _i4.Future<void>);
+  @override
   String toString() => super.toString();
 }