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. - *

- * @return new {@link FlutterFragment} - */ - public static FlutterFragment newInstance() { - return newInstance( - null, - null, - null, - null - ); - } - - /** - * Factory method that creates a new {@link FlutterFragment} with the given configuration. + * Builder that creates a new {@code FlutterFragment} with {@code arguments} that correspond + * to the values set on this {@code Builder}. *

- * @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);
   }
 }