Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit e19ee72

Browse files
Expose asset lookup from plugin binding. (#42019) (#13743)
1 parent c9afadc commit e19ee72

File tree

6 files changed

+204
-4
lines changed

6 files changed

+204
-4
lines changed

shell/platform/android/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,7 @@ action("robolectric_tests") {
422422
"test/io/flutter/embedding/engine/FlutterEngineCacheTest.java",
423423
"test/io/flutter/embedding/engine/FlutterEngineTest.java",
424424
"test/io/flutter/embedding/engine/FlutterJNITest.java",
425+
"test/io/flutter/embedding/engine/PluginComponentTest.java",
425426
"test/io/flutter/embedding/engine/RenderingComponentTest.java",
426427
"test/io/flutter/embedding/engine/dart/DartExecutorTest.java",
427428
"test/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistryTest.java",

shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,8 @@ public FlutterEngine(
207207

208208
this.pluginRegistry = new FlutterEnginePluginRegistry(
209209
context.getApplicationContext(),
210-
this
210+
this,
211+
flutterLoader
211212
);
212213

213214
if (automaticallyRegisterPlugins) {

shell/platform/android/io/flutter/embedding/engine/FlutterEnginePluginRegistry.java

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Set;
2222

2323
import io.flutter.Log;
24+
import io.flutter.embedding.engine.loader.FlutterLoader;
2425
import io.flutter.embedding.engine.plugins.FlutterPlugin;
2526
import io.flutter.embedding.engine.plugins.PluginRegistry;
2627
import io.flutter.embedding.engine.plugins.activity.ActivityAware;
@@ -90,15 +91,17 @@ class FlutterEnginePluginRegistry implements PluginRegistry,
9091

9192
FlutterEnginePluginRegistry(
9293
@NonNull Context appContext,
93-
@NonNull FlutterEngine flutterEngine
94+
@NonNull FlutterEngine flutterEngine,
95+
@NonNull FlutterLoader flutterLoader
9496
) {
9597
this.flutterEngine = flutterEngine;
9698
pluginBinding = new FlutterPlugin.FlutterPluginBinding(
9799
appContext,
98100
flutterEngine,
99101
flutterEngine.getDartExecutor(),
100102
flutterEngine.getRenderer(),
101-
flutterEngine.getPlatformViewsController().getRegistry()
103+
flutterEngine.getPlatformViewsController().getRegistry(),
104+
new DefaultFlutterAssets(flutterLoader)
102105
);
103106
}
104107

@@ -532,6 +535,30 @@ public void detachFromContentProvider() {
532535
}
533536
//----- End ContentProviderControlSurface -----
534537

538+
private static class DefaultFlutterAssets implements FlutterPlugin.FlutterAssets {
539+
final FlutterLoader flutterLoader;
540+
541+
private DefaultFlutterAssets(@NonNull FlutterLoader flutterLoader) {
542+
this.flutterLoader = flutterLoader;
543+
}
544+
545+
public String getAssetFilePathByName(@NonNull String assetFileName) {
546+
return flutterLoader.getLookupKeyForAsset(assetFileName);
547+
}
548+
549+
public String getAssetFilePathByName(@NonNull String assetFileName, @NonNull String packageName) {
550+
return flutterLoader.getLookupKeyForAsset(assetFileName, packageName);
551+
}
552+
553+
public String getAssetFilePathBySubpath(@NonNull String assetSubpath) {
554+
return flutterLoader.getLookupKeyForAsset(assetSubpath);
555+
}
556+
557+
public String getAssetFilePathBySubpath(@NonNull String assetSubpath, @NonNull String packageName) {
558+
return flutterLoader.getLookupKeyForAsset(assetSubpath, packageName);
559+
}
560+
}
561+
535562
private static class FlutterEngineActivityPluginBinding implements ActivityPluginBinding {
536563
@NonNull
537564
private final Activity activity;

shell/platform/android/io/flutter/embedding/engine/plugins/FlutterPlugin.java

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import android.support.annotation.NonNull;
1010

1111
import io.flutter.embedding.engine.FlutterEngine;
12+
import io.flutter.embedding.engine.loader.FlutterLoader;
1213
import io.flutter.plugin.common.BinaryMessenger;
1314
import io.flutter.plugin.platform.PlatformViewRegistry;
1415
import io.flutter.view.TextureRegistry;
@@ -101,19 +102,22 @@ class FlutterPluginBinding {
101102
private final BinaryMessenger binaryMessenger;
102103
private final TextureRegistry textureRegistry;
103104
private final PlatformViewRegistry platformViewRegistry;
105+
private final FlutterAssets flutterAssets;
104106

105107
public FlutterPluginBinding(
106108
@NonNull Context applicationContext,
107109
@NonNull FlutterEngine flutterEngine,
108110
@NonNull BinaryMessenger binaryMessenger,
109111
@NonNull TextureRegistry textureRegistry,
110-
@NonNull PlatformViewRegistry platformViewRegistry
112+
@NonNull PlatformViewRegistry platformViewRegistry,
113+
@NonNull FlutterAssets flutterAssets
111114
) {
112115
this.applicationContext = applicationContext;
113116
this.flutterEngine = flutterEngine;
114117
this.binaryMessenger = binaryMessenger;
115118
this.textureRegistry = textureRegistry;
116119
this.platformViewRegistry = platformViewRegistry;
120+
this.flutterAssets = flutterAssets;
117121
}
118122

119123
@NonNull
@@ -146,5 +150,47 @@ public TextureRegistry getTextureRegistry() {
146150
public PlatformViewRegistry getPlatformViewRegistry() {
147151
return platformViewRegistry;
148152
}
153+
154+
@NonNull
155+
public FlutterAssets getFlutterAssets() {
156+
return flutterAssets;
157+
}
149158
}
159+
160+
/**
161+
* Provides Flutter plugins with access to Flutter asset information.
162+
*/
163+
interface FlutterAssets {
164+
/**
165+
* Returns the relative file path to the Flutter asset with the given name, including the file's
166+
* extension, e.g., {@code "myImage.jpg"}.
167+
*
168+
* <p>The returned file path is relative to the Android app's standard assets directory.
169+
* Therefore, the returned path is appropriate to pass to Android's {@code AssetManager},
170+
* but the path is not appropriate to load as an absolute path.
171+
*/
172+
String getAssetFilePathByName(@NonNull String assetFileName);
173+
174+
/**
175+
* Same as {@link #getAssetFilePathByName(String)} but with added support for an explicit
176+
* Android {@code packageName}.
177+
*/
178+
String getAssetFilePathByName(@NonNull String assetFileName, @NonNull String packageName);
179+
180+
/**
181+
* Returns the relative file path to the Flutter asset with the given subpath, including the file's
182+
* extension, e.g., {@code "/dir1/dir2/myImage.jpg"}.
183+
*
184+
* <p>The returned file path is relative to the Android app's standard assets directory.
185+
* Therefore, the returned path is appropriate to pass to Android's {@code AssetManager},
186+
* but the path is not appropriate to load as an absolute path.
187+
*/
188+
String getAssetFilePathBySubpath(@NonNull String assetSubpath);
189+
190+
/**
191+
* Same as {@link #getAssetFilePathBySubpath(String)} but with added support for an explicit
192+
* Android {@code packageName}.
193+
*/
194+
String getAssetFilePathBySubpath(@NonNull String assetSubpath, @NonNull String packageName);
195+
}
150196
}

shell/platform/android/test/io/flutter/FlutterTestSuite.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.flutter.plugin.platform.SingleViewPresentationTest;
2424
import io.flutter.util.PreconditionsTest;
2525
import test.io.flutter.embedding.engine.FlutterEngineTest;
26+
import test.io.flutter.embedding.engine.PluginComponentTest;
2627
import test.io.flutter.embedding.engine.dart.DartExecutorTest;
2728

2829
@RunWith(Suite.class)
@@ -38,6 +39,7 @@
3839
FlutterRendererTest.class,
3940
FlutterViewTest.class,
4041
PlatformChannelTest.class,
42+
PluginComponentTest.class,
4143
PreconditionsTest.class,
4244
RenderingComponentTest.class,
4345
StandardMessageCodecTest.class,
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package test.io.flutter.embedding.engine;
2+
3+
import android.support.annotation.NonNull;
4+
5+
import org.junit.Test;
6+
import org.junit.runner.RunWith;
7+
import org.mockito.invocation.InvocationOnMock;
8+
import org.mockito.stubbing.Answer;
9+
import org.robolectric.RobolectricTestRunner;
10+
import org.robolectric.RuntimeEnvironment;
11+
import org.robolectric.annotation.Config;
12+
13+
import io.flutter.embedding.engine.FlutterEngine;
14+
import io.flutter.embedding.engine.FlutterJNI;
15+
import io.flutter.embedding.engine.loader.FlutterLoader;
16+
import io.flutter.embedding.engine.plugins.FlutterPlugin;
17+
18+
import static junit.framework.TestCase.assertEquals;
19+
import static org.mockito.Matchers.any;
20+
import static org.mockito.Mockito.mock;
21+
import static org.mockito.Mockito.when;
22+
23+
@Config(manifest=Config.NONE)
24+
@RunWith(RobolectricTestRunner.class)
25+
public class PluginComponentTest {
26+
@Test
27+
public void pluginsCanAccessFlutterAssetPaths() {
28+
// Setup test.
29+
FlutterJNI flutterJNI = mock(FlutterJNI.class);
30+
when(flutterJNI.isAttached()).thenReturn(true);
31+
32+
// FlutterLoader is the object to which the PluginRegistry defers for obtaining
33+
// the path to a Flutter asset. Ideally in this component test we would use a
34+
// real FlutterLoader and directly verify the relationship between FlutterAssets
35+
// and FlutterLoader. However, a real FlutterLoader cannot be used in a JVM test
36+
// because it would attempt to load native libraries. Therefore, we create a fake
37+
// FlutterLoader, but then we defer the corresponding asset lookup methods to the
38+
// real FlutterLoader singleton. This test ends up verifying that when FlutterAssets
39+
// is queried for an asset path, it returns the real expected path based on real
40+
// FlutterLoader behavior.
41+
FlutterLoader flutterLoader = mock(FlutterLoader.class);
42+
when(flutterLoader.getLookupKeyForAsset(any(String.class))).thenAnswer(new Answer<String>() {
43+
@Override
44+
public String answer(InvocationOnMock invocation) throws Throwable {
45+
// Defer to a real FlutterLoader to return the asset path.
46+
String fileNameOrSubpath = (String) invocation.getArguments()[0];
47+
return FlutterLoader.getInstance().getLookupKeyForAsset(fileNameOrSubpath);
48+
}
49+
});
50+
when(flutterLoader.getLookupKeyForAsset(any(String.class), any(String.class))).thenAnswer(new Answer<String>() {
51+
@Override
52+
public String answer(InvocationOnMock invocation) throws Throwable {
53+
// Defer to a real FlutterLoader to return the asset path.
54+
String fileNameOrSubpath = (String) invocation.getArguments()[0];
55+
String packageName = (String) invocation.getArguments()[1];
56+
return FlutterLoader.getInstance().getLookupKeyForAsset(fileNameOrSubpath, packageName);
57+
}
58+
});
59+
60+
// Execute behavior under test.
61+
FlutterEngine flutterEngine = new FlutterEngine(
62+
RuntimeEnvironment.application,
63+
flutterLoader,
64+
flutterJNI
65+
);
66+
67+
// As soon as our plugin is registered it will look up asset paths and store them
68+
// for our verification.
69+
PluginThatAccessesAssets plugin = new PluginThatAccessesAssets();
70+
flutterEngine.getPlugins().add(plugin);
71+
72+
// Verify results.
73+
assertEquals("flutter_assets/fake_asset.jpg", plugin.getAssetPathBasedOnName());
74+
assertEquals("flutter_assets/packages/fakepackage/fake_asset.jpg", plugin.getAssetPathBasedOnNameAndPackage());
75+
assertEquals("flutter_assets/some/path/fake_asset.jpg", plugin.getAssetPathBasedOnSubpath());
76+
assertEquals("flutter_assets/packages/fakepackage/some/path/fake_asset.jpg", plugin.getAssetPathBasedOnSubpathAndPackage());
77+
}
78+
79+
private static class PluginThatAccessesAssets implements FlutterPlugin {
80+
private String assetPathBasedOnName;
81+
private String assetPathBasedOnNameAndPackage;
82+
private String assetPathBasedOnSubpath;
83+
private String assetPathBasedOnSubpathAndPackage;
84+
85+
public String getAssetPathBasedOnName() {
86+
return assetPathBasedOnName;
87+
}
88+
89+
public String getAssetPathBasedOnNameAndPackage() {
90+
return assetPathBasedOnNameAndPackage;
91+
}
92+
93+
public String getAssetPathBasedOnSubpath() {
94+
return assetPathBasedOnSubpath;
95+
}
96+
97+
public String getAssetPathBasedOnSubpathAndPackage() {
98+
return assetPathBasedOnSubpathAndPackage;
99+
}
100+
101+
@Override
102+
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
103+
assetPathBasedOnName = binding
104+
.getFlutterAssets()
105+
.getAssetFilePathByName("fake_asset.jpg");
106+
107+
assetPathBasedOnNameAndPackage = binding
108+
.getFlutterAssets()
109+
.getAssetFilePathByName("fake_asset.jpg", "fakepackage");
110+
111+
assetPathBasedOnSubpath = binding
112+
.getFlutterAssets()
113+
.getAssetFilePathByName("some/path/fake_asset.jpg");
114+
115+
assetPathBasedOnSubpathAndPackage = binding
116+
.getFlutterAssets()
117+
.getAssetFilePathByName("some/path/fake_asset.jpg", "fakepackage");
118+
}
119+
120+
@Override
121+
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {}
122+
}
123+
}

0 commit comments

Comments
 (0)