diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index 0b75d129c6cf6..2ce499870053c 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -9,6 +9,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -16,10 +17,13 @@ import android.support.v4.app.FragmentManager; import android.view.View; import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; import android.widget.FrameLayout; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterShellArgs; +import io.flutter.plugin.platform.PlatformPlugin; import io.flutter.view.FlutterMain; /** @@ -60,12 +64,16 @@ public class FlutterActivity extends FragmentActivity { private static final String TAG = "FlutterActivity"; // Meta-data arguments, processed from manifest XML. - private static final String DART_ENTRYPOINT_META_DATA_KEY = "io.flutter.Entrypoint"; - private static final String INITIAL_ROUTE_META_DATA_KEY = "io.flutter.InitialRoute"; + protected static final String DART_ENTRYPOINT_META_DATA_KEY = "io.flutter.Entrypoint"; + protected static final String INITIAL_ROUTE_META_DATA_KEY = "io.flutter.InitialRoute"; // Intent extra arguments. - public static final String EXTRA_DART_ENTRYPOINT = "dart_entrypoint"; - public static final String EXTRA_INITIAL_ROUTE = "initial_route"; + protected static final String EXTRA_DART_ENTRYPOINT = "dart_entrypoint"; + protected static final String EXTRA_INITIAL_ROUTE = "initial_route"; + + // Default configuration. + protected static final String DEFAULT_DART_ENTRYPOINT = "main"; + protected static final String DEFAULT_INITIAL_ROUTE = "/"; // FlutterFragment management. private static final String TAG_FLUTTER_FRAGMENT = "flutter_fragment"; @@ -73,13 +81,58 @@ public class FlutterActivity extends FragmentActivity { private static final int FRAGMENT_CONTAINER_ID = 609893468; // random number private FlutterFragment flutterFragment; + /** + * Builder to create an {@code Intent} that launches a {@code FlutterActivity} with the + * desired configuration. + */ + public static class IntentBuilder { + private String dartEntrypoint = DEFAULT_DART_ENTRYPOINT; + private String initialRoute = DEFAULT_INITIAL_ROUTE; + + /** + * The name of the initial Dart method to invoke, defaults to "main". + */ + @NonNull + public IntentBuilder dartEntrypoint(@NonNull String dartEntrypoint) { + this.dartEntrypoint = dartEntrypoint; + return this; + } + + /** + * The initial route that a Flutter app will render in this {@link FlutterFragment}, + * defaults to "/". + */ + @NonNull + public IntentBuilder initialRoute(@NonNull String initialRoute) { + this.initialRoute = initialRoute; + return this; + } + + @NonNull + public Intent build(@NonNull Context context) { + return new Intent(context, FlutterActivity.class) + .putExtra(EXTRA_DART_ENTRYPOINT, dartEntrypoint) + .putExtra(EXTRA_INITIAL_ROUTE, initialRoute); + } + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(createFragmentContainer()); + configureStatusBarForFullscreenFlutterExperience(); ensureFlutterFragmentCreated(); } + private void configureStatusBarForFullscreenFlutterExperience() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Window window = getWindow(); + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.setStatusBarColor(0x40000000); + window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI); + } + } + /** * Creates a {@link FrameLayout} with an ID of {@code #FRAGMENT_CONTAINER_ID} that will contain * the {@link FlutterFragment} displayed by this {@code FlutterActivity}. @@ -125,12 +178,13 @@ private void ensureFlutterFragmentCreated() { */ @NonNull protected FlutterFragment createFlutterFragment() { - return FlutterFragment.newInstance( - getDartEntrypoint(), - getInitialRoute(), - getAppBundlePath(), - FlutterShellArgs.fromIntent(getIntent()) - ); + return new FlutterFragment.Builder() + .dartEntrypoint(getDartEntrypoint()) + .initialRoute(getInitialRoute()) + .appBundlePath(getAppBundlePath()) + .flutterShellArgs(FlutterShellArgs.fromIntent(getIntent())) + .renderMode(FlutterView.RenderMode.surface) + .build(); } @Override @@ -216,7 +270,7 @@ protected String getAppBundlePath() { *
* Subclasses may override this method to directly control the Dart entrypoint. */ - @Nullable + @NonNull protected String getDartEntrypoint() { if (getIntent().hasExtra(EXTRA_DART_ENTRYPOINT)) { return getIntent().getStringExtra(EXTRA_DART_ENTRYPOINT); @@ -228,9 +282,10 @@ protected String getDartEntrypoint() { PackageManager.GET_META_DATA|PackageManager.GET_ACTIVITIES ); Bundle metadata = activityInfo.metaData; - return metadata != null ? metadata.getString(DART_ENTRYPOINT_META_DATA_KEY) : null; + String desiredDartEntrypoint = metadata != null ? metadata.getString(DART_ENTRYPOINT_META_DATA_KEY) : null; + return desiredDartEntrypoint != null ? desiredDartEntrypoint : DEFAULT_DART_ENTRYPOINT; } catch (PackageManager.NameNotFoundException e) { - return null; + return DEFAULT_DART_ENTRYPOINT; } } @@ -251,7 +306,7 @@ protected String getDartEntrypoint() { *
* Subclasses may override this method to directly control the initial route. */ - @Nullable + @NonNull protected String getInitialRoute() { if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) { return getIntent().getStringExtra(EXTRA_INITIAL_ROUTE); @@ -263,9 +318,10 @@ protected String getInitialRoute() { PackageManager.GET_META_DATA|PackageManager.GET_ACTIVITIES ); Bundle metadata = activityInfo.metaData; - return metadata != null ? metadata.getString(INITIAL_ROUTE_META_DATA_KEY) : null; + String desiredInitialRoute = metadata != null ? metadata.getString(INITIAL_ROUTE_META_DATA_KEY) : null; + return desiredInitialRoute != null ? desiredInitialRoute : DEFAULT_INITIAL_ROUTE; } catch (PackageManager.NameNotFoundException e) { - return null; + return DEFAULT_INITIAL_ROUTE; } } diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java index 53c283d934c1a..771928f866ed7 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java @@ -61,53 +61,92 @@ public class FlutterFragment extends Fragment { private static final String ARG_INITIAL_ROUTE = "initial_route"; private static final String ARG_APP_BUNDLE_PATH = "app_bundle_path"; private static final String ARG_FLUTTER_INITIALIZATION_ARGS = "initialization_args"; + private static final String ARG_FLUTTERVIEW_RENDER_MODE = "flutterview_render_mode"; /** - * Factory method that creates a new {@link FlutterFragment} with a default configuration. - *
- * @param dartEntrypoint the name of the initial Dart method to invoke, defaults to "main" - * @param initialRoute the first route that a Flutter app will render in this {@link FlutterFragment}, - * defaults to "/" - * @param appBundlePath the path to the app bundle which contains the Dart app to execute, defaults - * to {@link FlutterMain#findAppBundlePath(Context)} - * @param flutterShellArgs any special configuration arguments for the Flutter engine - * - * @return a new {@link FlutterFragment} + * To create a {@code FlutterFragment} with default {@code arguments}, invoke {@code build()} + * immeidately: + * {@code + * FlutterFragment fragment = new FlutterFragment.Builder().build(); + * } */ - public static FlutterFragment newInstance(@Nullable String dartEntrypoint, - @Nullable String initialRoute, - @Nullable String appBundlePath, - @Nullable FlutterShellArgs flutterShellArgs) { - FlutterFragment frag = new FlutterFragment(); - - Bundle args = createArgsBundle( - dartEntrypoint, - initialRoute, - appBundlePath, - flutterShellArgs - ); - frag.setArguments(args); + public static class Builder { + private String dartEntrypoint = "main"; + private String initialRoute = "/"; + private String appBundlePath = null; + private FlutterShellArgs shellArgs = null; + private FlutterView.RenderMode renderMode = FlutterView.RenderMode.surface; + + /** + * The name of the initial Dart method to invoke, defaults to "main". + */ + @NonNull + public Builder dartEntrypoint(@NonNull String dartEntrypoint) { + this.dartEntrypoint = dartEntrypoint; + return this; + } + + /** + * The initial route that a Flutter app will render in this {@link FlutterFragment}, + * defaults to "/". + */ + @NonNull + public Builder initialRoute(@NonNull String initialRoute) { + this.initialRoute = initialRoute; + return this; + } + + /** + * The path to the app bundle which contains the Dart app to execute, defaults + * to {@link FlutterMain#findAppBundlePath(Context)} + */ + @NonNull + public Builder appBundlePath(@NonNull String appBundlePath) { + this.appBundlePath = appBundlePath; + return this; + } + + /** + * Any special configuration arguments for the Flutter engine + */ + @NonNull + public Builder flutterShellArgs(@NonNull FlutterShellArgs shellArgs) { + this.shellArgs = shellArgs; + return this; + } + + /** + * Render Flutter either as a {@link FlutterView.RenderMode#surface} or a + * {@link FlutterView.RenderMode#texture}. You should use {@code surface} unless + * you have a specific reason to use {@code texture}. {@code texture} comes with + * a significant performance impact, but {@code texture} can be displayed + * beneath other Android {@code View}s and animated, whereas {@code surface} + * cannot. + */ + @NonNull + public Builder renderMode(@NonNull FlutterView.RenderMode renderMode) { + this.renderMode = renderMode; + return this; + } + + @NonNull + public FlutterFragment build() { + FlutterFragment frag = new FlutterFragment(); + + Bundle args = createArgsBundle( + dartEntrypoint, + initialRoute, + appBundlePath, + shellArgs, + renderMode + ); + frag.setArguments(args); - return frag; + return frag; + } } /** @@ -118,16 +157,16 @@ public static FlutterFragment newInstance(@Nullable String dartEntrypoint, * wants to this {@link Bundle}. Example: *
{@code
* public static MyFlutterFragment newInstance(String myNewArg) {
- * // Create an instance of our subclass Fragment.
+ * // Create an instance of your subclass Fragment.
* MyFlutterFragment myFrag = new MyFlutterFragment();
*
* // Create the Bundle or args that FlutterFragment understands.
* Bundle args = FlutterFragment.createArgsBundle(...);
*
- * // Add our new args to the bundle.
+ * // Add your new args to the bundle.
* args.putString(ARG_MY_NEW_ARG, myNewArg);
*
- * // Give the args to our subclass Fragment.
+ * // Give the args to your subclass Fragment.
* myFrag.setArguments(args);
*
* // Return the newly created subclass Fragment.
@@ -139,13 +178,20 @@ public static FlutterFragment newInstance(@Nullable String dartEntrypoint,
* @param initialRoute the first route that a Flutter app will render in this {@link FlutterFragment}, defaults to "/"
* @param appBundlePath the path to the app bundle which contains the Dart app to execute
* @param flutterShellArgs any special configuration arguments for the Flutter engine
+ * @param renderMode render Flutter either as a {@link FlutterView.RenderMode#surface} or a
+ * {@link FlutterView.RenderMode#texture}. You should use {@code surface} unless
+ * you have a specific reason to use {@code texture}. {@code texture} comes with
+ * a significant performance impact, but {@code texture} can be displayed
+ * beneath other Android {@code View}s and animated, whereas {@code surface}
+ * cannot.
*
* @return Bundle of arguments that configure a {@link FlutterFragment}
*/
protected static Bundle createArgsBundle(@Nullable String dartEntrypoint,
@Nullable String initialRoute,
@Nullable String appBundlePath,
- @Nullable FlutterShellArgs flutterShellArgs) {
+ @Nullable FlutterShellArgs flutterShellArgs,
+ @Nullable FlutterView.RenderMode renderMode) {
Bundle args = new Bundle();
args.putString(ARG_INITIAL_ROUTE, initialRoute);
args.putString(ARG_APP_BUNDLE_PATH, appBundlePath);
@@ -154,6 +200,7 @@ protected static Bundle createArgsBundle(@Nullable String dartEntrypoint,
if (null != flutterShellArgs) {
args.putStringArray(ARG_FLUTTER_INITIALIZATION_ARGS, flutterShellArgs.toArray());
}
+ args.putString(ARG_FLUTTERVIEW_RENDER_MODE, renderMode != null ? renderMode.name() : FlutterView.RenderMode.surface.name());
return args;
}
@@ -252,7 +299,7 @@ protected void onFlutterEngineCreated(@NonNull FlutterEngine flutterEngine) {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
- flutterView = new FlutterView(getContext());
+ flutterView = new FlutterView(getContext(), getRenderMode());
flutterView.attachToFlutterEngine(flutterEngine);
// TODO(mattcarroll): the following call should exist here, but the plugin system needs to be revamped.
@@ -326,6 +373,18 @@ protected String getDartEntrypointFunctionName() {
return getArguments().getString(ARG_DART_ENTRYPOINT, "main");
}
+ /**
+ * Returns the desired {@link FlutterView.RenderMode} for the {@link FlutterView} displayed in
+ * this {@code FlutterFragment}.
+ *
+ * Defaults to {@link FlutterView.RenderMode#surface}.
+ */
+ @NonNull
+ protected FlutterView.RenderMode getRenderMode() {
+ String renderModeName = getArguments().getString(ARG_FLUTTERVIEW_RENDER_MODE, FlutterView.RenderMode.surface.name());
+ return FlutterView.RenderMode.valueOf(renderModeName);
+ }
+
// TODO(mattcarroll): determine why this can't be in onResume(). Comment reason, or move if possible.
public void onPostResume() {
Log.d(TAG, "onPostResume()");
diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java b/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java
index c28eac9c5d1a1..ac19c5ae15798 100644
--- a/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java
+++ b/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java
@@ -90,6 +90,10 @@ private void init() {
// Grab a reference to our underlying Surface and register callbacks with that Surface so we
// can monitor changes and forward those changes on to native Flutter code.
getHolder().addCallback(surfaceCallback);
+
+ // Keep this SurfaceView transparent until Flutter has a frame ready to render. This avoids
+ // displaying a black rectangle in our place.
+ setAlpha(0.0f);
}
/**
@@ -136,6 +140,9 @@ public void detachFromRenderer() {
disconnectSurfaceFromRenderer();
}
+ // Make the SurfaceView invisible to avoid showing a black rectangle.
+ setAlpha(0.0f);
+
flutterRenderer = null;
isAttachedToFlutterRenderer = false;
} else {
@@ -174,5 +181,7 @@ private void disconnectSurfaceFromRenderer() {
public void onFirstFrameRendered() {
// TODO(mattcarroll): decide where this method should live and what it needs to do.
Log.d(TAG, "onFirstFrameRendered()");
+ // Now that a frame is ready to display, take this SurfaceView from transparent to opaque.
+ setAlpha(1.0f);
}
}