From 0b2a54db8fb6d582ad48737539e78891624685c4 Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Wed, 26 Aug 2020 11:27:06 -0700 Subject: [PATCH 1/6] Add a java injector for testing --- ci/licenses_golden/licenses_flutter | 1 + shell/platform/android/BUILD.gn | 2 + .../android/io/flutter/FlutterInjector.java | 105 ++++++++++++++++++ .../flutter/app/FlutterActivityDelegate.java | 7 +- .../io/flutter/app/FlutterApplication.java | 4 +- .../embedding/engine/FlutterEngine.java | 21 ++-- .../embedding/engine/FlutterShellArgs.java | 4 +- .../embedding/engine/dart/DartExecutor.java | 5 +- .../engine/plugins/shim/ShimRegistrar.java | 6 +- .../android/io/flutter/view/FlutterMain.java | 50 ++------- .../test/io/flutter/FlutterInjectorTest.java | 53 +++++++++ .../test/io/flutter/FlutterTestSuite.java | 1 + 12 files changed, 203 insertions(+), 56 deletions(-) create mode 100644 shell/platform/android/io/flutter/FlutterInjector.java create mode 100644 shell/platform/android/test/io/flutter/FlutterInjectorTest.java diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 09bc2ba6555c4..611cdc4dba0bf 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -694,6 +694,7 @@ FILE: ../../../flutter/shell/platform/android/external_view_embedder/surface_poo FILE: ../../../flutter/shell/platform/android/external_view_embedder/surface_pool_unittests.cc FILE: ../../../flutter/shell/platform/android/flutter_main.cc FILE: ../../../flutter/shell/platform/android/flutter_main.h +FILE: ../../../flutter/shell/platform/android/io/flutter/FlutterInjector.java FILE: ../../../flutter/shell/platform/android/io/flutter/Log.java FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivity.java FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 3fe51be12a214..33e261c624b18 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -117,6 +117,7 @@ embedding_sources_jar_filename = "$embedding_artifact_id-sources.jar" embedding_source_jar_path = "$root_out_dir/$embedding_sources_jar_filename" android_java_sources = [ + "io/flutter/FlutterInjector.java", "io/flutter/Log.java", "io/flutter/app/FlutterActivity.java", "io/flutter/app/FlutterActivityDelegate.java", @@ -415,6 +416,7 @@ action("robolectric_tests") { jar_path = "$root_out_dir/robolectric_tests.jar" sources = [ + "test/io/flutter/FlutterInjectorTest.java", "test/io/flutter/FlutterTestSuite.java", "test/io/flutter/SmokeTest.java", "test/io/flutter/embedding/android/AndroidKeyProcessorTest.java", diff --git a/shell/platform/android/io/flutter/FlutterInjector.java b/shell/platform/android/io/flutter/FlutterInjector.java new file mode 100644 index 0000000000000..2ecb5cd81b49e --- /dev/null +++ b/shell/platform/android/io/flutter/FlutterInjector.java @@ -0,0 +1,105 @@ +// 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. + +package io.flutter; + +import android.support.annotation.NonNull; +import android.support.annotation.VisibleForTesting; +import io.flutter.embedding.engine.loader.FlutterLoader; + +/* + * This class is a simple dependency injector for the relatively thin Android part of the Flutter engine. + * + * This simple solution is used facilitate testability without bringing in heavier app-development + * centric dependency injection frameworks such as Guice or Dagger2 or spreading construction + * injection everywhere. + */ +public final class FlutterInjector { + + private static FlutterInjector instance; + private static boolean accessed; + + /* + * Use {@link FlutterInjector.Builder} to specify members to be injected via the static + * {@code FlutterInjector}. + * + * This can only be called at the beginning of the program before the {@link #instance()} is + * accessed. + */ + public static void setInstance(@NonNull FlutterInjector injector) { + if (accessed) { + throw new IllegalStateException( + "Cannot change the FlutterInjector instance once it's been " + + "read. If you're trying to dependency inject, be sure to do so at the beginning of " + + "the program"); + } + instance = injector; + } + + /* + * Retrieve the static instance of the {@code FlutterInjector} to use in your program. + * + * Once you access it, you can no longer change the values injected. + * + * If no override is provided for the injector, reasonable defaults are provided. + */ + public static FlutterInjector instance() { + accessed = true; + if (instance == null) { + instance = new Builder().build(); + } + return instance; + } + + // This whole class is here to enable testing so to test the thing that lets you test, some degree + // of hack is needed. + @VisibleForTesting + /* Package default */ static void reset() { + accessed = false; + instance = null; + } + + private FlutterInjector(@NonNull FlutterLoader flutterLoader) { + this.flutterLoader = flutterLoader; + } + + private FlutterLoader flutterLoader; + + @NonNull + public FlutterLoader flutterLoader() { + return flutterLoader; + } + + /* + * Builder used to supply a custom FlutterInjector instance to + * {@link FlutterInjector#setInstance(FlutterInjector)}. + * + * Non-overriden values have reasonable defaults. + */ + public static final class Builder { + + private FlutterLoader flutterLoader; + /* + * Sets a {@link FlutterLoader} override. + * + * A reasonable default will be used if unspecified. + */ + public Builder setFlutterLoader(@NonNull FlutterLoader flutterLoader) { + this.flutterLoader = flutterLoader; + return this; + } + + private void fillDefaults() { + if (flutterLoader == null) { + flutterLoader = new FlutterLoader(); + } + } + + public FlutterInjector build() { + fillDefaults(); + + return new FlutterInjector(flutterLoader); + } + } +} diff --git a/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java b/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java index b1619baebf82b..ee96cd072443b 100644 --- a/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java +++ b/shell/platform/android/io/flutter/app/FlutterActivityDelegate.java @@ -35,7 +35,8 @@ import java.util.ArrayList; /** - * Class that performs the actual work of tying Android {@link Activity} instances to Flutter. + * Deprecated class that performs the actual work of tying Android {@link Activity} instances to + * Flutter. * *

This exists as a dedicated class (as opposed to being integrated directly into {@link * FlutterActivity}) to facilitate applications that don't wish to subclass {@code FlutterActivity}. @@ -48,6 +49,10 @@ * FlutterActivityEvents} from your activity to an instance of this class. Optionally, you can make * your activity implement {@link PluginRegistry} and/or {@link * io.flutter.view.FlutterView.Provider} and forward those methods to this class as well. + * + *

Deprecation: {@link io.flutter.embedding.android.FlutterActivity} is the new API that now + * replaces this class and {@link io.flutter.app.FlutterActivity}. See + * https://flutter.dev/go/android-project-migration for more migration details. */ public final class FlutterActivityDelegate implements FlutterActivityEvents, FlutterView.Provider, PluginRegistry { diff --git a/shell/platform/android/io/flutter/app/FlutterApplication.java b/shell/platform/android/io/flutter/app/FlutterApplication.java index cd55217176ef7..a211c268548cd 100644 --- a/shell/platform/android/io/flutter/app/FlutterApplication.java +++ b/shell/platform/android/io/flutter/app/FlutterApplication.java @@ -7,7 +7,7 @@ import android.app.Activity; import android.app.Application; import androidx.annotation.CallSuper; -import io.flutter.view.FlutterMain; +import io.flutter.FlutterInjector; /** * Flutter implementation of {@link android.app.Application}, managing application-level global @@ -21,7 +21,7 @@ public class FlutterApplication extends Application { @CallSuper public void onCreate() { super.onCreate(); - FlutterMain.startInitialization(this); + FlutterInjector.instance().flutterLoader().startInitialization(this); } private Activity mCurrentActivity = null; diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 1408156ebaf24..9ec50d7ef98eb 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -7,6 +7,7 @@ import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import io.flutter.FlutterInjector; import io.flutter.Log; import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.loader.FlutterLoader; @@ -131,7 +132,8 @@ public void onPreEngineRestart() { *

In order to pass Dart VM initialization arguments (see {@link * io.flutter.embedding.engine.FlutterShellArgs}) when creating the VM, manually set the * initialization arguments by calling {@link FlutterLoader#startInitialization(Context)} and - * {@link FlutterLoader#ensureInitializationComplete(Context, String[])}. + * {@link FlutterLoader#ensureInitializationComplete(Context, String[])} before constructing the + * engine. */ public FlutterEngine(@NonNull Context context) { this(context, null); @@ -143,7 +145,7 @@ public FlutterEngine(@NonNull Context context) { *

If the Dart VM has already started, the given arguments will have no effect. */ public FlutterEngine(@NonNull Context context, @Nullable String[] dartVmArgs) { - this(context, FlutterLoader.getInstance(), new FlutterJNI(), dartVmArgs, true); + this(context, /* flutterLoader */ null, new FlutterJNI(), dartVmArgs, true); } /** @@ -158,7 +160,7 @@ public FlutterEngine( boolean automaticallyRegisterPlugins) { this( context, - FlutterLoader.getInstance(), + /* flutterLoader */ null, new FlutterJNI(), dartVmArgs, automaticallyRegisterPlugins); @@ -189,7 +191,7 @@ public FlutterEngine( boolean waitForRestorationData) { this( context, - FlutterLoader.getInstance(), + /* flutterLoader */ null, new FlutterJNI(), new PlatformViewsController(), dartVmArgs, @@ -206,7 +208,7 @@ public FlutterEngine( */ public FlutterEngine( @NonNull Context context, - @NonNull FlutterLoader flutterLoader, + @Nullable FlutterLoader flutterLoader, @NonNull FlutterJNI flutterJNI) { this(context, flutterLoader, flutterJNI, null, true); } @@ -219,7 +221,7 @@ public FlutterEngine( */ public FlutterEngine( @NonNull Context context, - @NonNull FlutterLoader flutterLoader, + @Nullable FlutterLoader flutterLoader, @NonNull FlutterJNI flutterJNI, @Nullable String[] dartVmArgs, boolean automaticallyRegisterPlugins) { @@ -238,7 +240,7 @@ public FlutterEngine( */ public FlutterEngine( @NonNull Context context, - @NonNull FlutterLoader flutterLoader, + @Nullable FlutterLoader flutterLoader, @NonNull FlutterJNI flutterJNI, @NonNull PlatformViewsController platformViewsController, @Nullable String[] dartVmArgs, @@ -256,7 +258,7 @@ public FlutterEngine( /** Fully configurable {@code FlutterEngine} constructor. */ public FlutterEngine( @NonNull Context context, - @NonNull FlutterLoader flutterLoader, + @Nullable FlutterLoader flutterLoader, @NonNull FlutterJNI flutterJNI, @NonNull PlatformViewsController platformViewsController, @Nullable String[] dartVmArgs, @@ -280,6 +282,9 @@ public FlutterEngine( this.localizationPlugin = new LocalizationPlugin(context, localizationChannel); this.flutterJNI = flutterJNI; + if (flutterLoader == null) { + flutterLoader = FlutterInjector.instance().flutterLoader(); + } flutterLoader.startInitialization(context.getApplicationContext()); flutterLoader.ensureInitializationComplete(context, dartVmArgs); diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java b/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java index 9f0c4eef07856..b064ef8f17eaf 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java @@ -15,8 +15,8 @@ *

The term "shell" refers to the native code that adapts Flutter to different platforms. * Flutter's Android Java code initializes a native "shell" and passes these arguments to that * native shell when it is initialized. See {@link - * io.flutter.view.FlutterMain#ensureInitializationComplete(Context, String[])} for more - * information. + * io.flutter.embedding.engine.loader.FlutterLoader#ensureInitializationComplete(Context, String[])} + * for more information. */ @SuppressWarnings({"WeakerAccess", "unused"}) public class FlutterShellArgs { diff --git a/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java b/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java index 13b3ff05a62ea..9efaa60fe3abf 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java +++ b/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java @@ -8,12 +8,12 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; +import io.flutter.FlutterInjector; import io.flutter.Log; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.StringCodec; import io.flutter.view.FlutterCallbackInformation; -import io.flutter.view.FlutterMain; import java.nio.ByteBuffer; /** @@ -252,7 +252,8 @@ public void notifyLowMemoryWarning() { public static class DartEntrypoint { @NonNull public static DartEntrypoint createDefault() { - return new DartEntrypoint(FlutterMain.findAppBundlePath(), "main"); + return new DartEntrypoint( + FlutterInjector.instance().flutterLoader().findAppBundlePath(), "main"); } /** The path within the AssetManager where the app will look for assets. */ diff --git a/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java b/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java index 9ec464e4f5736..9c40d0a016f21 100644 --- a/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java +++ b/shell/platform/android/io/flutter/embedding/engine/plugins/shim/ShimRegistrar.java @@ -7,6 +7,7 @@ import android.app.Activity; import android.content.Context; import androidx.annotation.NonNull; +import io.flutter.FlutterInjector; import io.flutter.Log; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityAware; @@ -14,7 +15,6 @@ import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.platform.PlatformViewRegistry; -import io.flutter.view.FlutterMain; import io.flutter.view.FlutterView; import io.flutter.view.TextureRegistry; import java.util.HashSet; @@ -85,12 +85,12 @@ public FlutterView view() { @Override public String lookupKeyForAsset(String asset) { - return FlutterMain.getLookupKeyForAsset(asset); + return FlutterInjector.instance().flutterLoader().getLookupKeyForAsset(asset); } @Override public String lookupKeyForAsset(String asset, String packageName) { - return FlutterMain.getLookupKeyForAsset(asset, packageName); + return FlutterInjector.instance().flutterLoader().getLookupKeyForAsset(asset, packageName); } @Override diff --git a/shell/platform/android/io/flutter/view/FlutterMain.java b/shell/platform/android/io/flutter/view/FlutterMain.java index 8cc80ec8dc96f..1b13b45aee208 100644 --- a/shell/platform/android/io/flutter/view/FlutterMain.java +++ b/shell/platform/android/io/flutter/view/FlutterMain.java @@ -8,7 +8,7 @@ import android.os.Handler; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; +import io.flutter.FlutterInjector; import io.flutter.embedding.engine.loader.FlutterLoader; /** @@ -42,10 +42,7 @@ public void setLogTag(String tag) { * @param applicationContext The Android application context. */ public static void startInitialization(@NonNull Context applicationContext) { - if (isRunningInRobolectricTest) { - return; - } - FlutterLoader.getInstance().startInitialization(applicationContext); + FlutterInjector.instance().flutterLoader().startInitialization(applicationContext); } /** @@ -61,12 +58,9 @@ public static void startInitialization(@NonNull Context applicationContext) { */ public static void startInitialization( @NonNull Context applicationContext, @NonNull Settings settings) { - if (isRunningInRobolectricTest) { - return; - } FlutterLoader.Settings newSettings = new FlutterLoader.Settings(); newSettings.setLogTag(settings.getLogTag()); - FlutterLoader.getInstance().startInitialization(applicationContext, newSettings); + FlutterInjector.instance().flutterLoader().startInitialization(applicationContext, newSettings); } /** @@ -79,10 +73,9 @@ public static void startInitialization( */ public static void ensureInitializationComplete( @NonNull Context applicationContext, @Nullable String[] args) { - if (isRunningInRobolectricTest) { - return; - } - FlutterLoader.getInstance().ensureInitializationComplete(applicationContext, args); + FlutterInjector.instance() + .flutterLoader() + .ensureInitializationComplete(applicationContext, args); } /** @@ -94,22 +87,20 @@ public static void ensureInitializationCompleteAsync( @Nullable String[] args, @NonNull Handler callbackHandler, @NonNull Runnable callback) { - if (isRunningInRobolectricTest) { - return; - } - FlutterLoader.getInstance() + FlutterInjector.instance() + .flutterLoader() .ensureInitializationCompleteAsync(applicationContext, args, callbackHandler, callback); } @NonNull public static String findAppBundlePath() { - return FlutterLoader.getInstance().findAppBundlePath(); + return FlutterInjector.instance().flutterLoader().findAppBundlePath(); } @Deprecated @Nullable public static String findAppBundlePath(@NonNull Context applicationContext) { - return FlutterLoader.getInstance().findAppBundlePath(); + return FlutterInjector.instance().flutterLoader().findAppBundlePath(); } /** @@ -121,7 +112,7 @@ public static String findAppBundlePath(@NonNull Context applicationContext) { */ @NonNull public static String getLookupKeyForAsset(@NonNull String asset) { - return FlutterLoader.getInstance().getLookupKeyForAsset(asset); + return FlutterInjector.instance().flutterLoader().getLookupKeyForAsset(asset); } /** @@ -135,23 +126,6 @@ public static String getLookupKeyForAsset(@NonNull String asset) { */ @NonNull public static String getLookupKeyForAsset(@NonNull String asset, @NonNull String packageName) { - return FlutterLoader.getInstance().getLookupKeyForAsset(asset, packageName); - } - - private static boolean isRunningInRobolectricTest = false; - - /* - * Indicates whether we are currently running in a Robolectric Test. - * - *

Flutter cannot be initialized inside a Robolectric environment since it cannot load - * native libraries. - * - * @deprecated Use the new embedding (io.flutter.embedding) instead which provides better - * modularity for testing. - */ - @Deprecated - @VisibleForTesting - public static void setIsRunningInRobolectricTest(boolean isRunningInRobolectricTest) { - FlutterMain.isRunningInRobolectricTest = isRunningInRobolectricTest; + return FlutterInjector.instance().flutterLoader().getLookupKeyForAsset(asset, packageName); } } diff --git a/shell/platform/android/test/io/flutter/FlutterInjectorTest.java b/shell/platform/android/test/io/flutter/FlutterInjectorTest.java new file mode 100644 index 0000000000000..1b81779d6a26b --- /dev/null +++ b/shell/platform/android/test/io/flutter/FlutterInjectorTest.java @@ -0,0 +1,53 @@ +// 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. + +package io.flutter; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import io.flutter.embedding.engine.loader.FlutterLoader; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +@Config(manifest = Config.NONE) +@RunWith(RobolectricTestRunner.class) +public class FlutterInjectorTest { + @Mock FlutterLoader mockFlutterLoader; + + @Before + public void setUp() { + // Since the intent is to have a convenient static class to use for production. + FlutterInjector.reset(); + MockitoAnnotations.initMocks(this); + } + + @Test + public void itHasSomeReasonableDefaults() { + // Implicitly builds when first accessed. + FlutterInjector injector = FlutterInjector.instance(); + assertNotNull(injector.flutterLoader()); + } + + @Test + public void canPartiallyOverride() { + FlutterInjector.setInstance( + new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build()); + FlutterInjector injector = FlutterInjector.instance(); + assertEquals(injector.flutterLoader(), mockFlutterLoader); + } + + @Test(expected = IllegalStateException.class) + public void cannotBeChangedOnceRead() { + FlutterInjector.instance(); + FlutterInjector.setInstance( + new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build()); + } +} diff --git a/shell/platform/android/test/io/flutter/FlutterTestSuite.java b/shell/platform/android/test/io/flutter/FlutterTestSuite.java index 67611eac687b8..49a905234bd37 100644 --- a/shell/platform/android/test/io/flutter/FlutterTestSuite.java +++ b/shell/platform/android/test/io/flutter/FlutterTestSuite.java @@ -53,6 +53,7 @@ FlutterEngineTest.class, FlutterFragmentActivityTest.class, FlutterFragmentTest.class, + FlutterInjectorTest.class, FlutterJNITest.class, FlutterLaunchTests.class, FlutterRendererTest.class, From 027a635225b232a715c087649dccd8343fbb1bb5 Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Wed, 26 Aug 2020 11:47:11 -0700 Subject: [PATCH 2/6] add another test for engine --- .../android/io/flutter/FlutterInjector.java | 2 +- .../embedding/engine/FlutterEngineTest.java | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/shell/platform/android/io/flutter/FlutterInjector.java b/shell/platform/android/io/flutter/FlutterInjector.java index 2ecb5cd81b49e..85ca30fd1ae33 100644 --- a/shell/platform/android/io/flutter/FlutterInjector.java +++ b/shell/platform/android/io/flutter/FlutterInjector.java @@ -55,7 +55,7 @@ public static FlutterInjector instance() { // This whole class is here to enable testing so to test the thing that lets you test, some degree // of hack is needed. @VisibleForTesting - /* Package default */ static void reset() { + public static void reset() { accessed = false; instance = null; } diff --git a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java index 9a15364373b7a..e753436c12390 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java @@ -3,6 +3,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -10,6 +11,7 @@ import static org.mockito.Mockito.when; import android.content.Context; +import io.flutter.FlutterInjector; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.loader.FlutterLoader; @@ -137,4 +139,18 @@ public void itUsesApplicationContext() { verify(context, atLeast(1)).getApplicationContext(); } + + @Test + public void itCanUseFlutterLoaderInjectionViaFlutterInjector() { + FlutterInjector.reset(); + FlutterLoader mockFlutterLoader = mock(FlutterLoader.class); + FlutterInjector.setInstance( + new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build()); + Context mockContext = mock(Context.class); + + new FlutterEngine(mockContext, null, flutterJNI); + + verify(mockFlutterLoader, times(1)).startInitialization(any()); + verify(mockFlutterLoader, times(1)).ensureInitializationComplete(any(), any()); + } } From d7cc97edfa9abf72df81d5620998529303ae00e9 Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Wed, 26 Aug 2020 11:56:41 -0700 Subject: [PATCH 3/6] autoformat --- shell/platform/android/test/io/flutter/FlutterInjectorTest.java | 1 - .../test/io/flutter/embedding/engine/FlutterEngineTest.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/shell/platform/android/test/io/flutter/FlutterInjectorTest.java b/shell/platform/android/test/io/flutter/FlutterInjectorTest.java index 1b81779d6a26b..db42ea9baebf2 100644 --- a/shell/platform/android/test/io/flutter/FlutterInjectorTest.java +++ b/shell/platform/android/test/io/flutter/FlutterInjectorTest.java @@ -4,7 +4,6 @@ package io.flutter; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; diff --git a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java index e753436c12390..6f141fa1aa192 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java @@ -145,7 +145,7 @@ public void itCanUseFlutterLoaderInjectionViaFlutterInjector() { FlutterInjector.reset(); FlutterLoader mockFlutterLoader = mock(FlutterLoader.class); FlutterInjector.setInstance( - new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build()); + new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build()); Context mockContext = mock(Context.class); new FlutterEngine(mockContext, null, flutterJNI); From 2e13e2c3880ced39e10f90801a1c59ea7a02f91d Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Thu, 27 Aug 2020 23:17:55 -0700 Subject: [PATCH 4/6] review --- .../engine/loader/FlutterLoader.java | 19 ++++---- .../embedding/engine/PluginComponentTest.java | 46 ++++--------------- 2 files changed, 21 insertions(+), 44 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java index 3df6fe097f81f..cfd45ab6db2cc 100644 --- a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java +++ b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java @@ -14,6 +14,7 @@ import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import io.flutter.BuildConfig; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.util.PathUtils; @@ -47,7 +48,10 @@ public class FlutterLoader { *

The returned instance loads Flutter native libraries in the standard way. A singleton object * is used instead of static methods to facilitate testing without actually running native library * linking. + * + * @deprecated Use the {@link io.flutter.FlutterInjector} instead. */ + @Deprecated @NonNull public static FlutterLoader getInstance() { if (instance == null) { @@ -56,13 +60,6 @@ public static FlutterLoader getInstance() { return instance; } - @NonNull - public static FlutterLoader getInstanceForTest(FlutterApplicationInfo flutterApplicationInfo) { - FlutterLoader loader = new FlutterLoader(); - loader.flutterApplicationInfo = flutterApplicationInfo; - return loader; - } - private boolean initialized = false; @Nullable private Settings settings; private long initStartTimestampMillis; @@ -91,6 +88,12 @@ public void startInitialization(@NonNull Context applicationContext) { startInitialization(applicationContext, new Settings()); } + @VisibleForTesting + public void loadNativeLibrary() { + // Let this be mockable. + System.loadLibrary("flutter"); + } + /** * Starts initialization of the native system. * @@ -128,7 +131,7 @@ public void startInitialization(@NonNull Context applicationContext, @NonNull Se public InitResult call() { ResourceExtractor resourceExtractor = initResources(appContext); - System.loadLibrary("flutter"); + loadNativeLibrary(); // Prefetch the default font manager as soon as possible on a background thread. // It helps to reduce time cost of engine setup that blocks the platform thread. diff --git a/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java b/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java index 01d012c38b83a..421b81f54e720 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java @@ -2,7 +2,9 @@ import static junit.framework.TestCase.assertEquals; import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import androidx.annotation.NonNull; @@ -13,8 +15,6 @@ import io.flutter.embedding.engine.plugins.FlutterPlugin; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @@ -30,43 +30,17 @@ public void pluginsCanAccessFlutterAssetPaths() { FlutterApplicationInfo emptyInfo = new FlutterApplicationInfo(null, null, null, null, null, null, false, false); - // FlutterLoader is the object to which the PluginRegistry defers for obtaining - // the path to a Flutter asset. Ideally in this component test we would use a - // real FlutterLoader and directly verify the relationship between FlutterAssets - // and FlutterLoader. However, a real FlutterLoader cannot be used in a JVM test - // because it would attempt to load native libraries. Therefore, we create a fake - // FlutterLoader, but then we defer the corresponding asset lookup methods to the - // real FlutterLoader singleton. This test ends up verifying that when FlutterAssets - // is queried for an asset path, it returns the real expected path based on real - // FlutterLoader behavior. - FlutterLoader flutterLoader = mock(FlutterLoader.class); - when(flutterLoader.getLookupKeyForAsset(any(String.class))) - .thenAnswer( - new Answer() { - @Override - public String answer(InvocationOnMock invocation) throws Throwable { - // Defer to a real FlutterLoader to return the asset path. - String fileNameOrSubpath = (String) invocation.getArguments()[0]; - return FlutterLoader.getInstanceForTest(emptyInfo) - .getLookupKeyForAsset(fileNameOrSubpath); - } - }); - when(flutterLoader.getLookupKeyForAsset(any(String.class), any(String.class))) - .thenAnswer( - new Answer() { - @Override - public String answer(InvocationOnMock invocation) throws Throwable { - // Defer to a real FlutterLoader to return the asset path. - String fileNameOrSubpath = (String) invocation.getArguments()[0]; - String packageName = (String) invocation.getArguments()[1]; - return FlutterLoader.getInstanceForTest(emptyInfo) - .getLookupKeyForAsset(fileNameOrSubpath, packageName); - } - }); + FlutterLoader realFlutterLoader = new FlutterLoader(); + FlutterLoader spyFlutterLoader = spy(realFlutterLoader); + + // Let the real startInitialization be called, but mock the rest so it doesn't try to load + // the real native library. + doNothing().when(spyFlutterLoader).loadNativeLibrary(); + doNothing().when(spyFlutterLoader).ensureInitializationComplete(any(), any()); // Execute behavior under test. FlutterEngine flutterEngine = - new FlutterEngine(RuntimeEnvironment.application, flutterLoader, flutterJNI); + new FlutterEngine(RuntimeEnvironment.application, spyFlutterLoader, flutterJNI); // As soon as our plugin is registered it will look up asset paths and store them // for our verification. From 860994de71c0a2b03040b1ae13697d33b00ae54e Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Fri, 28 Aug 2020 13:08:57 -0700 Subject: [PATCH 5/6] move native loading override into the injector, add assertion for dart entrypoint construction --- shell/platform/android/BUILD.gn | 1 + .../android/io/flutter/FlutterInjector.java | 45 +++++++++++--- .../embedding/engine/dart/DartExecutor.java | 12 +++- .../engine/loader/FlutterLoader.java | 34 ++++++----- .../test/io/flutter/FlutterInjectorTest.java | 12 +++- .../test/io/flutter/FlutterTestSuite.java | 5 ++ .../embedding/engine/PluginComponentTest.java | 25 ++++---- .../engine/dart/DartExecutorTest.java | 34 +++++++++++ .../loader/ApplicationInfoLoaderTest.java | 4 ++ .../engine/loader/FlutterLoaderTest.java | 58 +++++++++++++++++++ 10 files changed, 196 insertions(+), 34 deletions(-) create mode 100644 shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 33e261c624b18..23300cf01a4d3 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -436,6 +436,7 @@ action("robolectric_tests") { "test/io/flutter/embedding/engine/RenderingComponentTest.java", "test/io/flutter/embedding/engine/dart/DartExecutorTest.java", "test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java", + "test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java", "test/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistryTest.java", "test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java", "test/io/flutter/embedding/engine/systemchannels/KeyEventChannelTest.java", diff --git a/shell/platform/android/io/flutter/FlutterInjector.java b/shell/platform/android/io/flutter/FlutterInjector.java index 85ca30fd1ae33..3f12d4febecf5 100644 --- a/shell/platform/android/io/flutter/FlutterInjector.java +++ b/shell/platform/android/io/flutter/FlutterInjector.java @@ -8,7 +8,7 @@ import android.support.annotation.VisibleForTesting; import io.flutter.embedding.engine.loader.FlutterLoader; -/* +/** * This class is a simple dependency injector for the relatively thin Android part of the Flutter engine. * * This simple solution is used facilitate testability without bringing in heavier app-development @@ -20,7 +20,7 @@ public final class FlutterInjector { private static FlutterInjector instance; private static boolean accessed; - /* + /** * Use {@link FlutterInjector.Builder} to specify members to be injected via the static * {@code FlutterInjector}. * @@ -37,7 +37,7 @@ public static void setInstance(@NonNull FlutterInjector injector) { instance = injector; } - /* + /** * Retrieve the static instance of the {@code FlutterInjector} to use in your program. * * Once you access it, you can no longer change the values injected. @@ -60,18 +60,32 @@ public static void reset() { instance = null; } - private FlutterInjector(@NonNull FlutterLoader flutterLoader) { + private FlutterInjector(boolean shouldLoadNative, @NonNull FlutterLoader flutterLoader) { + this.shouldLoadNative = shouldLoadNative; this.flutterLoader = flutterLoader; } + private boolean shouldLoadNative; private FlutterLoader flutterLoader; + /** + * Returns whether the Flutter Android engine embedding should load the native C++ engine. + * + * Useful for testing since JVM tests via Robolectric can't load native libraries. + */ + public boolean shouldLoadNative() { + return shouldLoadNative; + } + + /** + * Returns the {@link FlutterLoader} instance to use for the Flutter Android engine embedding. + */ @NonNull public FlutterLoader flutterLoader() { return flutterLoader; } - /* + /** * Builder used to supply a custom FlutterInjector instance to * {@link FlutterInjector#setInstance(FlutterInjector)}. * @@ -79,8 +93,21 @@ public FlutterLoader flutterLoader() { */ public static final class Builder { + private boolean shouldLoadNative = true; + /** + * Sets whether the Flutter Android engine embedding should load the native C++ engine. + * + * Useful for testing since JVM tests via Robolectric can't load native libraries. + * + * Defaults to true. + */ + public Builder setShouldLoadNative(boolean shouldLoadNative) { + this.shouldLoadNative = shouldLoadNative; + return this; + } + private FlutterLoader flutterLoader; - /* + /** * Sets a {@link FlutterLoader} override. * * A reasonable default will be used if unspecified. @@ -96,10 +123,14 @@ private void fillDefaults() { } } + /** Builds a {@link FlutterInjector} from the builder. Unspecified properties will have + * reasonable defaults. + */ public FlutterInjector build() { fillDefaults(); - return new FlutterInjector(flutterLoader); + System.out.println("should load native is " + shouldLoadNative); + return new FlutterInjector(shouldLoadNative, flutterLoader); } } } diff --git a/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java b/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java index 9efaa60fe3abf..177205d39c991 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java +++ b/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java @@ -11,6 +11,7 @@ import io.flutter.FlutterInjector; import io.flutter.Log; import io.flutter.embedding.engine.FlutterJNI; +import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.StringCodec; import io.flutter.view.FlutterCallbackInformation; @@ -250,10 +251,19 @@ public void notifyLowMemoryWarning() { * that entrypoint and other assets required for Dart execution. */ public static class DartEntrypoint { + /** + * Create a DartEntrypoint pointing to the default Flutter assets location with a default Dart + * entrypoint. + */ @NonNull public static DartEntrypoint createDefault() { + FlutterLoader flutterLoader = FlutterInjector.instance().flutterLoader(); + + if (!flutterLoader.initialized()) { + throw new AssertionError("DartEntrypoints can only be created once a FlutterEngine is created."); + } return new DartEntrypoint( - FlutterInjector.instance().flutterLoader().findAppBundlePath(), "main"); + flutterLoader.findAppBundlePath(), "main"); } /** The path within the AssetManager where the app will look for assets. */ diff --git a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java index cfd45ab6db2cc..d9518843318fa 100644 --- a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java +++ b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java @@ -16,6 +16,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import io.flutter.BuildConfig; +import io.flutter.FlutterInjector; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.util.PathUtils; import io.flutter.view.VsyncWaiter; @@ -88,12 +89,6 @@ public void startInitialization(@NonNull Context applicationContext) { startInitialization(applicationContext, new Settings()); } - @VisibleForTesting - public void loadNativeLibrary() { - // Let this be mockable. - System.loadLibrary("flutter"); - } - /** * Starts initialization of the native system. * @@ -131,7 +126,9 @@ public void startInitialization(@NonNull Context applicationContext, @NonNull Se public InitResult call() { ResourceExtractor resourceExtractor = initResources(appContext); - loadNativeLibrary(); + if (FlutterInjector.instance().shouldLoadNative()) { + System.loadLibrary("flutter"); + } // Prefetch the default font manager as soon as possible on a background thread. // It helps to reduce time cost of engine setup that blocks the platform thread. @@ -234,13 +231,15 @@ public void ensureInitializationComplete( long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis; - FlutterJNI.nativeInit( - applicationContext, - shellArgs.toArray(new String[0]), - kernelPath, - result.appStoragePath, - result.engineCachesPath, - initTimeMillis); + if (FlutterInjector.instance().shouldLoadNative()) { + FlutterJNI.nativeInit( + applicationContext, + shellArgs.toArray(new String[0]), + kernelPath, + result.appStoragePath, + result.engineCachesPath, + initTimeMillis); + } initialized = true; } catch (Exception e) { @@ -296,6 +295,13 @@ public void run() { }); } + /** + * Returns whether the FlutterLoader has finished loading the native library. + */ + public boolean initialized() { + return initialized; + } + /** Extract assets out of the APK that need to be cached as uncompressed files on disk. */ private ResourceExtractor initResources(@NonNull Context applicationContext) { ResourceExtractor resourceExtractor = null; diff --git a/shell/platform/android/test/io/flutter/FlutterInjectorTest.java b/shell/platform/android/test/io/flutter/FlutterInjectorTest.java index db42ea9baebf2..fea55649267ee 100644 --- a/shell/platform/android/test/io/flutter/FlutterInjectorTest.java +++ b/shell/platform/android/test/io/flutter/FlutterInjectorTest.java @@ -6,7 +6,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import io.flutter.FlutterInjector; import io.flutter.embedding.engine.loader.FlutterLoader; import org.junit.Before; import org.junit.Test; @@ -33,6 +36,7 @@ public void itHasSomeReasonableDefaults() { // Implicitly builds when first accessed. FlutterInjector injector = FlutterInjector.instance(); assertNotNull(injector.flutterLoader()); + assertTrue(injector.shouldLoadNative()); } @Test @@ -41,12 +45,16 @@ public void canPartiallyOverride() { new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build()); FlutterInjector injector = FlutterInjector.instance(); assertEquals(injector.flutterLoader(), mockFlutterLoader); + assertTrue(injector.shouldLoadNative()); } - @Test(expected = IllegalStateException.class) + @Test() public void cannotBeChangedOnceRead() { FlutterInjector.instance(); - FlutterInjector.setInstance( + + assertThrows(IllegalStateException.class, () -> { + FlutterInjector.setInstance( new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build()); + }); } } diff --git a/shell/platform/android/test/io/flutter/FlutterTestSuite.java b/shell/platform/android/test/io/flutter/FlutterTestSuite.java index 49a905234bd37..bb5c44c6568df 100644 --- a/shell/platform/android/test/io/flutter/FlutterTestSuite.java +++ b/shell/platform/android/test/io/flutter/FlutterTestSuite.java @@ -16,6 +16,8 @@ import io.flutter.embedding.engine.FlutterJNITest; import io.flutter.embedding.engine.LocalizationPluginTest; import io.flutter.embedding.engine.RenderingComponentTest; +import io.flutter.embedding.engine.loader.ApplicationInfoLoaderTest; +import io.flutter.embedding.engine.loader.FlutterLoaderTest; import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistryTest; import io.flutter.embedding.engine.renderer.FlutterRendererTest; import io.flutter.embedding.engine.systemchannels.KeyEventChannelTest; @@ -44,6 +46,7 @@ @SuiteClasses({ AccessibilityBridgeTest.class, AndroidKeyProcessorTest.class, + ApplicationInfoLoaderTest.class, DartExecutorTest.class, FlutterActivityAndFragmentDelegateTest.class, FlutterActivityTest.class, @@ -56,6 +59,8 @@ FlutterInjectorTest.class, FlutterJNITest.class, FlutterLaunchTests.class, + FlutterLoaderTest.class, + FlutterShellArgsTest.class, FlutterRendererTest.class, FlutterShellArgsTest.class, FlutterViewTest.class, diff --git a/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java b/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java index 421b81f54e720..7038342ce75b5 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java @@ -1,3 +1,7 @@ +// 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. + package test.io.flutter.embedding.engine; import static junit.framework.TestCase.assertEquals; @@ -8,11 +12,13 @@ import static org.mockito.Mockito.when; import androidx.annotation.NonNull; +import io.flutter.FlutterInjector; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.loader.FlutterApplicationInfo; import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.plugins.FlutterPlugin; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -22,25 +28,23 @@ @Config(manifest = Config.NONE) @RunWith(RobolectricTestRunner.class) public class PluginComponentTest { + @Before + public void setUp() { + FlutterInjector.reset(); + } + @Test public void pluginsCanAccessFlutterAssetPaths() { // Setup test. + FlutterInjector.setInstance(new FlutterInjector.Builder().setShouldLoadNative(false).build()); FlutterJNI flutterJNI = mock(FlutterJNI.class); when(flutterJNI.isAttached()).thenReturn(true); - FlutterApplicationInfo emptyInfo = - new FlutterApplicationInfo(null, null, null, null, null, null, false, false); - - FlutterLoader realFlutterLoader = new FlutterLoader(); - FlutterLoader spyFlutterLoader = spy(realFlutterLoader); - // Let the real startInitialization be called, but mock the rest so it doesn't try to load - // the real native library. - doNothing().when(spyFlutterLoader).loadNativeLibrary(); - doNothing().when(spyFlutterLoader).ensureInitializationComplete(any(), any()); + FlutterLoader flutterLoader = new FlutterLoader(); // Execute behavior under test. FlutterEngine flutterEngine = - new FlutterEngine(RuntimeEnvironment.application, spyFlutterLoader, flutterJNI); + new FlutterEngine(RuntimeEnvironment.application, flutterLoader, flutterJNI); // As soon as our plugin is registered it will look up asset paths and store them // for our verification. @@ -56,6 +60,7 @@ public void pluginsCanAccessFlutterAssetPaths() { assertEquals( "flutter_assets/packages/fakepackage/some/path/fake_asset.jpg", plugin.getAssetPathBasedOnSubpathAndPackage()); + FlutterInjector.reset(); } private static class PluginThatAccessesAssets implements FlutterPlugin { diff --git a/shell/platform/android/test/io/flutter/embedding/engine/dart/DartExecutorTest.java b/shell/platform/android/test/io/flutter/embedding/engine/dart/DartExecutorTest.java index 6470651db3ed9..72307bf371136 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/dart/DartExecutorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/dart/DartExecutorTest.java @@ -1,6 +1,8 @@ package test.io.flutter.embedding.engine.dart; import static junit.framework.TestCase.assertNotNull; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; @@ -9,17 +11,33 @@ import static org.mockito.Mockito.when; import android.content.res.AssetManager; +import io.flutter.FlutterInjector; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint; +import io.flutter.embedding.engine.loader.FlutterLoader; + import java.nio.ByteBuffer; + +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; @Config(manifest = Config.NONE) @RunWith(RobolectricTestRunner.class) public class DartExecutorTest { + @Mock FlutterLoader mockFlutterLoader; + + @Before + public void setUp() { + FlutterInjector.reset(); + MockitoAnnotations.initMocks(this); + } + @Test public void itSendsBinaryMessages() { // Setup test. @@ -49,4 +67,20 @@ public void itNotifiesLowMemoryWarning() { dartExecutor.notifyLowMemoryWarning(); verify(mockFlutterJNI, times(1)).notifyLowMemoryWarning(); } + + @Test + public void itThrowsWhenCreatingADefaultDartEntrypointWithAnUninitializedFlutterLoader() { + assertThrows(AssertionError.class, () -> { + DartEntrypoint.createDefault(); + }); + } + + @Test + public void itHasReasonableDefaultsWhenFlutterLoaderIsInitialized() { + when(mockFlutterLoader.initialized()).thenReturn(true); + when(mockFlutterLoader.findAppBundlePath()).thenReturn("my/custom/path"); + DartEntrypoint entrypoint = DartEntrypoint.createDefault(); + assertEquals(entrypoint.pathToBundle, "my/custom/path"); + assertEquals(entrypoint.dartEntrypointFunctionName, "main"); + } } diff --git a/shell/platform/android/test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java b/shell/platform/android/test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java index 769525f7db3c9..69ead8182be4b 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java @@ -1,3 +1,7 @@ +// 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. + package io.flutter.embedding.engine.loader; import static org.junit.Assert.assertEquals; diff --git a/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java b/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java new file mode 100644 index 0000000000000..88b0575c1ca36 --- /dev/null +++ b/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java @@ -0,0 +1,58 @@ +// 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. + +package io.flutter.embedding.engine.loader; + +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertTrue; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.res.AssetManager; +import io.flutter.FlutterInjector; +import io.flutter.embedding.engine.FlutterJNI; +import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.embedding.engine.loader.FlutterLoader; + +import java.nio.ByteBuffer; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@Config(manifest = Config.NONE) +@RunWith(RobolectricTestRunner.class) +public class FlutterLoaderTest { + @Before + public void setUp() { + FlutterInjector.reset(); + } + + @Test + public void itReportsUninitializedAfterCreating() { + FlutterLoader flutterLoader = new FlutterLoader(); + assertFalse(flutterLoader.initialized()); + } + + @Test + public void itReportsInitializedAfterInitializing() { + FlutterInjector.setInstance(new FlutterInjector.Builder().setShouldLoadNative(false).build()); + FlutterLoader flutterLoader = new FlutterLoader(); + + assertFalse(flutterLoader.initialized()); + flutterLoader.startInitialization(RuntimeEnvironment.application); + flutterLoader.ensureInitializationComplete(RuntimeEnvironment.application, null); + assertTrue(flutterLoader.initialized()); + FlutterInjector.reset(); + } +} From efcc33e5dd2f8de54d674e3f8347e66dc2efc7ef Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Fri, 28 Aug 2020 13:13:42 -0700 Subject: [PATCH 6/6] autoformat --- .../android/io/flutter/FlutterInjector.java | 40 +++++++++---------- .../embedding/engine/dart/DartExecutor.java | 6 +-- .../engine/loader/FlutterLoader.java | 5 +-- .../test/io/flutter/FlutterInjectorTest.java | 11 ++--- .../embedding/engine/PluginComponentTest.java | 4 -- .../engine/dart/DartExecutorTest.java | 12 +++--- .../engine/loader/FlutterLoaderTest.java | 15 ------- 7 files changed, 37 insertions(+), 56 deletions(-) diff --git a/shell/platform/android/io/flutter/FlutterInjector.java b/shell/platform/android/io/flutter/FlutterInjector.java index 3f12d4febecf5..864dfffb526e3 100644 --- a/shell/platform/android/io/flutter/FlutterInjector.java +++ b/shell/platform/android/io/flutter/FlutterInjector.java @@ -9,11 +9,12 @@ import io.flutter.embedding.engine.loader.FlutterLoader; /** - * This class is a simple dependency injector for the relatively thin Android part of the Flutter engine. + * This class is a simple dependency injector for the relatively thin Android part of the Flutter + * engine. * - * This simple solution is used facilitate testability without bringing in heavier app-development - * centric dependency injection frameworks such as Guice or Dagger2 or spreading construction - * injection everywhere. + *

This simple solution is used facilitate testability without bringing in heavier + * app-development centric dependency injection frameworks such as Guice or Dagger2 or spreading + * construction injection everywhere. */ public final class FlutterInjector { @@ -21,10 +22,10 @@ public final class FlutterInjector { private static boolean accessed; /** - * Use {@link FlutterInjector.Builder} to specify members to be injected via the static - * {@code FlutterInjector}. + * Use {@link FlutterInjector.Builder} to specify members to be injected via the static {@code + * FlutterInjector}. * - * This can only be called at the beginning of the program before the {@link #instance()} is + *

This can only be called at the beginning of the program before the {@link #instance()} is * accessed. */ public static void setInstance(@NonNull FlutterInjector injector) { @@ -40,9 +41,9 @@ public static void setInstance(@NonNull FlutterInjector injector) { /** * Retrieve the static instance of the {@code FlutterInjector} to use in your program. * - * Once you access it, you can no longer change the values injected. + *

Once you access it, you can no longer change the values injected. * - * If no override is provided for the injector, reasonable defaults are provided. + *

If no override is provided for the injector, reasonable defaults are provided. */ public static FlutterInjector instance() { accessed = true; @@ -71,25 +72,23 @@ private FlutterInjector(boolean shouldLoadNative, @NonNull FlutterLoader flutter /** * Returns whether the Flutter Android engine embedding should load the native C++ engine. * - * Useful for testing since JVM tests via Robolectric can't load native libraries. + *

Useful for testing since JVM tests via Robolectric can't load native libraries. */ public boolean shouldLoadNative() { return shouldLoadNative; } - /** - * Returns the {@link FlutterLoader} instance to use for the Flutter Android engine embedding. - */ + /** Returns the {@link FlutterLoader} instance to use for the Flutter Android engine embedding. */ @NonNull public FlutterLoader flutterLoader() { return flutterLoader; } /** - * Builder used to supply a custom FlutterInjector instance to - * {@link FlutterInjector#setInstance(FlutterInjector)}. + * Builder used to supply a custom FlutterInjector instance to {@link + * FlutterInjector#setInstance(FlutterInjector)}. * - * Non-overriden values have reasonable defaults. + *

Non-overriden values have reasonable defaults. */ public static final class Builder { @@ -97,9 +96,9 @@ public static final class Builder { /** * Sets whether the Flutter Android engine embedding should load the native C++ engine. * - * Useful for testing since JVM tests via Robolectric can't load native libraries. + *

Useful for testing since JVM tests via Robolectric can't load native libraries. * - * Defaults to true. + *

Defaults to true. */ public Builder setShouldLoadNative(boolean shouldLoadNative) { this.shouldLoadNative = shouldLoadNative; @@ -110,7 +109,7 @@ public Builder setShouldLoadNative(boolean shouldLoadNative) { /** * Sets a {@link FlutterLoader} override. * - * A reasonable default will be used if unspecified. + *

A reasonable default will be used if unspecified. */ public Builder setFlutterLoader(@NonNull FlutterLoader flutterLoader) { this.flutterLoader = flutterLoader; @@ -123,7 +122,8 @@ private void fillDefaults() { } } - /** Builds a {@link FlutterInjector} from the builder. Unspecified properties will have + /** + * Builds a {@link FlutterInjector} from the builder. Unspecified properties will have * reasonable defaults. */ public FlutterInjector build() { diff --git a/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java b/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java index 177205d39c991..3d6cb8416e21d 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java +++ b/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java @@ -260,10 +260,10 @@ public static DartEntrypoint createDefault() { FlutterLoader flutterLoader = FlutterInjector.instance().flutterLoader(); if (!flutterLoader.initialized()) { - throw new AssertionError("DartEntrypoints can only be created once a FlutterEngine is created."); + throw new AssertionError( + "DartEntrypoints can only be created once a FlutterEngine is created."); } - return new DartEntrypoint( - flutterLoader.findAppBundlePath(), "main"); + return new DartEntrypoint(flutterLoader.findAppBundlePath(), "main"); } /** The path within the AssetManager where the app will look for assets. */ diff --git a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java index d9518843318fa..8af9539c94ea8 100644 --- a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java +++ b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java @@ -14,7 +14,6 @@ import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; import io.flutter.BuildConfig; import io.flutter.FlutterInjector; import io.flutter.embedding.engine.FlutterJNI; @@ -295,9 +294,7 @@ public void run() { }); } - /** - * Returns whether the FlutterLoader has finished loading the native library. - */ + /** Returns whether the FlutterLoader has finished loading the native library. */ public boolean initialized() { return initialized; } diff --git a/shell/platform/android/test/io/flutter/FlutterInjectorTest.java b/shell/platform/android/test/io/flutter/FlutterInjectorTest.java index fea55649267ee..225c24959e136 100644 --- a/shell/platform/android/test/io/flutter/FlutterInjectorTest.java +++ b/shell/platform/android/test/io/flutter/FlutterInjectorTest.java @@ -9,7 +9,6 @@ import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; -import io.flutter.FlutterInjector; import io.flutter.embedding.engine.loader.FlutterLoader; import org.junit.Before; import org.junit.Test; @@ -52,9 +51,11 @@ public void canPartiallyOverride() { public void cannotBeChangedOnceRead() { FlutterInjector.instance(); - assertThrows(IllegalStateException.class, () -> { - FlutterInjector.setInstance( - new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build()); - }); + assertThrows( + IllegalStateException.class, + () -> { + FlutterInjector.setInstance( + new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build()); + }); } } diff --git a/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java b/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java index 7038342ce75b5..71f47890245bc 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java @@ -5,17 +5,13 @@ package test.io.flutter.embedding.engine; import static junit.framework.TestCase.assertEquals; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import androidx.annotation.NonNull; import io.flutter.FlutterInjector; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterJNI; -import io.flutter.embedding.engine.loader.FlutterApplicationInfo; import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.plugins.FlutterPlugin; import org.junit.Before; diff --git a/shell/platform/android/test/io/flutter/embedding/engine/dart/DartExecutorTest.java b/shell/platform/android/test/io/flutter/embedding/engine/dart/DartExecutorTest.java index 72307bf371136..6c8e766bd557b 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/dart/DartExecutorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/dart/DartExecutorTest.java @@ -16,9 +16,7 @@ import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint; import io.flutter.embedding.engine.loader.FlutterLoader; - import java.nio.ByteBuffer; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -70,15 +68,19 @@ public void itNotifiesLowMemoryWarning() { @Test public void itThrowsWhenCreatingADefaultDartEntrypointWithAnUninitializedFlutterLoader() { - assertThrows(AssertionError.class, () -> { - DartEntrypoint.createDefault(); - }); + assertThrows( + AssertionError.class, + () -> { + DartEntrypoint.createDefault(); + }); } @Test public void itHasReasonableDefaultsWhenFlutterLoaderIsInitialized() { when(mockFlutterLoader.initialized()).thenReturn(true); when(mockFlutterLoader.findAppBundlePath()).thenReturn("my/custom/path"); + FlutterInjector.setInstance( + new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build()); DartEntrypoint entrypoint = DartEntrypoint.createDefault(); assertEquals(entrypoint.pathToBundle, "my/custom/path"); assertEquals(entrypoint.dartEntrypointFunctionName, "main"); diff --git a/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java b/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java index 88b0575c1ca36..4ffd848d855b7 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java @@ -6,23 +6,8 @@ import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import android.content.res.AssetManager; import io.flutter.FlutterInjector; -import io.flutter.embedding.engine.FlutterJNI; -import io.flutter.embedding.engine.dart.DartExecutor; -import io.flutter.embedding.engine.loader.FlutterLoader; - -import java.nio.ByteBuffer; - import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith;