URL encode asset paths to support reserved characters in paths (#16046)

Fixes https://github.com/flutter/flutter/issues/14864
diff --git a/packages/flutter/lib/src/services/asset_bundle.dart b/packages/flutter/lib/src/services/asset_bundle.dart
index 4227451..f6a933c 100644
--- a/packages/flutter/lib/src/services/asset_bundle.dart
+++ b/packages/flutter/lib/src/services/asset_bundle.dart
@@ -214,7 +214,7 @@
 class PlatformAssetBundle extends CachingAssetBundle {
   @override
   Future<ByteData> load(String key) async {
-    final Uint8List encoded = utf8.encoder.convert(new Uri(path: key).path);
+    final Uint8List encoded = utf8.encoder.convert(new Uri(path: Uri.encodeFull(key)).path);
     final ByteData asset =
         await BinaryMessages.send('flutter/assets', encoded.buffer.asByteData());
     if (asset == null)
diff --git a/packages/flutter_tools/lib/src/flutter_manifest.dart b/packages/flutter_tools/lib/src/flutter_manifest.dart
index 03cd721..1731ceb 100644
--- a/packages/flutter_tools/lib/src/flutter_manifest.dart
+++ b/packages/flutter_tools/lib/src/flutter_manifest.dart
@@ -59,7 +59,7 @@
   }
 
   List<Uri> get assets {
-    return _flutterDescriptor['assets']?.map(Uri.parse)?.toList() ?? const <Uri>[];
+    return _flutterDescriptor['assets']?.map(Uri.encodeFull)?.map(Uri.parse)?.toList() ?? const <Uri>[];
   }
 
   List<Font> _fonts;
diff --git a/packages/flutter_tools/test/asset_bundle_package_test.dart b/packages/flutter_tools/test/asset_bundle_package_test.dart
index ab7e2a4..baf92ea 100644
--- a/packages/flutter_tools/test/asset_bundle_package_test.dart
+++ b/packages/flutter_tools/test/asset_bundle_package_test.dart
@@ -69,7 +69,7 @@
 
     for (String packageName in packages) {
       for (String asset in assets) {
-        final String entryKey = 'packages/$packageName/$asset';
+        final String entryKey = Uri.encodeFull('packages/$packageName/$asset');
         expect(bundle.entries.containsKey(entryKey), true);
         expect(
           utf8.decode(await bundle.entries[entryKey].contentsAsBytes()),
@@ -415,4 +415,29 @@
       );
     });
   });
+
+  testUsingContext('Asset paths can contain URL reserved characters', () async {
+    establishFlutterRoot();
+
+    writePubspecFile('pubspec.yaml', 'test');
+    writePackagesFile('test_package:p/p/lib/');
+
+    final List<String> assets = <String>['a/foo', 'a/foo[x]'];
+    writePubspecFile(
+      'p/p/pubspec.yaml',
+      'test_package',
+      assets: assets,
+    );
+
+    writeAssets('p/p/', assets);
+    const String expectedAssetManifest =
+        '{"packages/test_package/a/foo":["packages/test_package/a/foo"],'
+        '"packages/test_package/a/foo%5Bx%5D":["packages/test_package/a/foo%5Bx%5D"]}';
+
+    await buildAndVerifyAssets(
+      assets,
+      <String>['test_package'],
+      expectedAssetManifest,
+    );
+  });
 }