diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index d77a06f1fcc88..49b9cc976bfb3 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -10,6 +10,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; @@ -75,10 +76,12 @@ public class FlutterActivity extends FragmentActivity implements OnFirstFrameRen // Intent extra arguments. protected static final String EXTRA_DART_ENTRYPOINT = "dart_entrypoint"; protected static final String EXTRA_INITIAL_ROUTE = "initial_route"; + protected static final String EXTRA_BACKGROUND_MODE = "background_mode"; // Default configuration. protected static final String DEFAULT_DART_ENTRYPOINT = "main"; protected static final String DEFAULT_INITIAL_ROUTE = "/"; + protected static final String DEFAULT_BACKGROUND_MODE = BackgroundMode.opaque.name(); // FlutterFragment management. private static final String TAG_FLUTTER_FRAGMENT = "flutter_fragment"; @@ -114,6 +117,7 @@ public static class IntentBuilder { private final Class activityClass; private String dartEntrypoint = DEFAULT_DART_ENTRYPOINT; private String initialRoute = DEFAULT_INITIAL_ROUTE; + private String backgroundMode = DEFAULT_BACKGROUND_MODE; protected IntentBuilder(@NonNull Class activityClass) { this.activityClass = activityClass; @@ -138,6 +142,28 @@ public IntentBuilder initialRoute(@NonNull String initialRoute) { return this; } + /** + * The mode of {@code FlutterActivity}'s background, either {@link BackgroundMode#opaque} or + * {@link BackgroundMode#transparent}. + *

+ * The default background mode is {@link BackgroundMode#opaque}. + *

+ * Choosing a background mode of {@link BackgroundMode#transparent} will configure the inner + * {@link FlutterView} of this {@code FlutterActivity} to be configured with a + * {@link FlutterTextureView} to support transparency. This choice has a non-trivial performance + * impact. A transparent background should only be used if it is necessary for the app design + * being implemented. + *

+ * A {@code FlutterActivity} that is configured with a background mode of + * {@link BackgroundMode#transparent} must have a theme applied to it that includes the + * following property: {@code true}. + */ + @NonNull + public IntentBuilder backgroundMode(@NonNull BackgroundMode backgroundMode) { + this.backgroundMode = backgroundMode.name(); + return this; + } + /** * Creates and returns an {@link Intent} that will launch a {@code FlutterActivity} with * the desired configuration. @@ -146,7 +172,8 @@ public IntentBuilder initialRoute(@NonNull String initialRoute) { public Intent build(@NonNull Context context) { return new Intent(context, activityClass) .putExtra(EXTRA_DART_ENTRYPOINT, dartEntrypoint) - .putExtra(EXTRA_INITIAL_ROUTE, initialRoute); + .putExtra(EXTRA_INITIAL_ROUTE, initialRoute) + .putExtra(EXTRA_BACKGROUND_MODE, backgroundMode); } } @@ -154,12 +181,31 @@ public Intent build(@NonNull Context context) { public void onCreate(Bundle savedInstanceState) { Log.d(TAG, "onCreate()"); super.onCreate(savedInstanceState); + configureWindowForTransparency(); setContentView(createFragmentContainer()); showCoverView(); configureStatusBarForFullscreenFlutterExperience(); ensureFlutterFragmentCreated(); } + /** + * Sets this {@code Activity}'s {@code Window} background to be transparent, and hides the status + * bar, if this {@code Activity}'s desired {@link BackgroundMode} is {@link BackgroundMode#transparent}. + *

+ * For {@code Activity} transparency to work as expected, the theme applied to this {@code Activity} + * must include {@code true}. + */ + private void configureWindowForTransparency() { + BackgroundMode backgroundMode = getBackgroundMode(); + if (backgroundMode == BackgroundMode.transparent) { + getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + getWindow().setFlags( + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS + ); + } + } + /** * Cover all visible {@code Activity} area with a {@code View} that paints everything the same * color as the {@code Window}. @@ -170,6 +216,11 @@ public void onCreate(Bundle savedInstanceState) { * itself transparent. */ private void showCoverView() { + if (getBackgroundMode() == BackgroundMode.transparent) { + // Don't display an opaque cover view if the Activity is intended to be transparent. + return; + } + // Create the coverView. if (coverView == null) { coverView = new View(this); @@ -210,7 +261,9 @@ private Drawable createCoverViewBackground() { * for details. */ private void hideCoverView() { - coverView.setVisibility(View.GONE); + if (coverView != null) { + coverView.setVisibility(View.GONE); + } } private void configureStatusBarForFullscreenFlutterExperience() { @@ -267,13 +320,19 @@ private void ensureFlutterFragmentCreated() { */ @NonNull protected FlutterFragment createFlutterFragment() { + BackgroundMode backgroundMode = getBackgroundMode(); + return new FlutterFragment.Builder() .dartEntrypoint(getDartEntrypoint()) .initialRoute(getInitialRoute()) .appBundlePath(getAppBundlePath()) .flutterShellArgs(FlutterShellArgs.fromIntent(getIntent())) - .renderMode(FlutterView.RenderMode.surface) - .transparencyMode(FlutterView.TransparencyMode.opaque) + .renderMode(backgroundMode == BackgroundMode.opaque + ? FlutterView.RenderMode.surface + : FlutterView.RenderMode.texture) + .transparencyMode(backgroundMode == BackgroundMode.opaque + ? FlutterView.TransparencyMode.opaque + : FlutterView.TransparencyMode.transparent) .shouldAttachEngineToActivity(shouldAttachEngineToActivity()) .build(); } @@ -432,6 +491,19 @@ protected String getInitialRoute() { } } + /** + * The desired window background mode of this {@code Activity}, which defaults to + * {@link BackgroundMode#opaque}. + */ + @NonNull + protected BackgroundMode getBackgroundMode() { + if (getIntent().hasExtra(EXTRA_BACKGROUND_MODE)) { + return BackgroundMode.valueOf(getIntent().getStringExtra(EXTRA_BACKGROUND_MODE)); + } else { + return BackgroundMode.opaque; + } + } + /** * Returns true if Flutter is running in "debug mode", and false otherwise. *

@@ -445,4 +517,14 @@ private boolean isDebuggable() { public void onFirstFrameRendered() { hideCoverView(); } + + /** + * The mode of the background of a {@code FlutterActivity}, either opaque or transparent. + */ + public enum BackgroundMode { + /** Indicates a FlutterActivity with an opaque background. This is the default. */ + opaque, + /** Indicates a FlutterActivity with a transparent background. */ + transparent + } }