diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index dbb8643280c0d..cb05b04cbbb6a 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -567,9 +567,11 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/Andro FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/DrawableSplashScreen.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterEngineConfigurator.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterEngineProvider.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java +FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterSplashView.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/FlutterTextureView.java diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 522fafd631b74..b273cc773caa4 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -135,9 +135,11 @@ action("flutter_shell_java") { "io/flutter/embedding/android/DrawableSplashScreen.java", "io/flutter/embedding/android/FlutterActivity.java", "io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java", + "io/flutter/embedding/android/FlutterActivityLaunchConfigs.java", "io/flutter/embedding/android/FlutterEngineConfigurator.java", "io/flutter/embedding/android/FlutterEngineProvider.java", "io/flutter/embedding/android/FlutterFragment.java", + "io/flutter/embedding/android/FlutterFragmentActivity.java", "io/flutter/embedding/android/FlutterSplashView.java", "io/flutter/embedding/android/FlutterSurfaceView.java", "io/flutter/embedding/android/FlutterTextureView.java", diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index 396ad89a407ac..5a505de678cc3 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -25,12 +25,25 @@ import android.view.WindowManager; import io.flutter.Log; +import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface; import io.flutter.plugin.platform.PlatformPlugin; import io.flutter.view.FlutterMain; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DART_ENTRYPOINT_META_DATA_KEY; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_BACKGROUND_MODE; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_DART_ENTRYPOINT; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_INITIAL_ROUTE; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_BACKGROUND_MODE; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_CACHED_ENGINE_ID; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DESTROY_ENGINE_WITH_ACTIVITY; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_INITIAL_ROUTE; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.INITIAL_ROUTE_META_DATA_KEY; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.NORMAL_THEME_META_DATA_KEY; +import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.SPLASH_SCREEN_META_DATA_KEY; + /** * {@code Activity} which displays a fullscreen Flutter UI. *
@@ -45,7 +58,7 @@ *
* The Flutter route that is initially loaded within this {@code Activity} is "/". The initial * route may be specified explicitly by passing the name of the route as a {@code String} in - * {@link #EXTRA_INITIAL_ROUTE}, e.g., "my/deep/link". + * {@link FlutterActivityLaunchConfigs#EXTRA_INITIAL_ROUTE}, e.g., "my/deep/link". *
* The initial route can each be controlled using a {@link NewEngineIntentBuilder} via * {@link NewEngineIntentBuilder#initialRoute}. @@ -180,23 +193,6 @@ public class FlutterActivity extends Activity LifecycleOwner { private static final String TAG = "FlutterActivity"; - // Meta-data arguments, processed from manifest XML. - protected static final String DART_ENTRYPOINT_META_DATA_KEY = "io.flutter.Entrypoint"; - protected static final String INITIAL_ROUTE_META_DATA_KEY = "io.flutter.InitialRoute"; - protected static final String SPLASH_SCREEN_META_DATA_KEY = "io.flutter.embedding.android.SplashScreenDrawable"; - protected static final String NORMAL_THEME_META_DATA_KEY = "io.flutter.embedding.android.NormalTheme"; - - // Intent extra arguments. - protected static final String EXTRA_INITIAL_ROUTE = "initial_route"; - protected static final String EXTRA_BACKGROUND_MODE = "background_mode"; - protected static final String EXTRA_CACHED_ENGINE_ID = "cached_engine_id"; - protected static final String EXTRA_DESTROY_ENGINE_WITH_ACTIVITY = "destroy_engine_with_activity"; - - // 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(); - /** * Creates an {@link Intent} that launches a {@code FlutterActivity}, which executes * a {@code main()} Dart entrypoint, and displays the "/" route as Flutter's initial route. @@ -460,8 +456,8 @@ public SplashScreen provideSplashScreen() { * Returns a {@link Drawable} to be used as a splash screen as requested by meta-data in the * {@code AndroidManifest.xml} file, or null if no such splash screen is requested. *
- * See {@link #SPLASH_SCREEN_META_DATA_KEY} for the meta-data key to be used in a - * manifest file. + * See {@link FlutterActivityLaunchConfigs#SPLASH_SCREEN_META_DATA_KEY} for the meta-data key to + * be used in a manifest file. */ @Nullable @SuppressWarnings("deprecation") @@ -670,8 +666,8 @@ public boolean shouldDestroyEngineWithHost() { * The Dart entrypoint that will be executed as soon as the Dart snapshot is loaded. *
* This preference can be controlled by setting a {@code
* Subclasses may override this method to directly control the Dart entrypoint.
*/
@@ -695,9 +691,11 @@ public String getDartEntrypointFunctionName() {
*
* This preference can be controlled with 2 methods:
*
@@ -899,13 +897,4 @@ public void onFlutterUiNoLongerDisplayed() {
// no-op
}
- /**
- * 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
- }
}
diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java
new file mode 100644
index 0000000000000..6634ce7c3ec24
--- /dev/null
+++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityLaunchConfigs.java
@@ -0,0 +1,34 @@
+// 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.android;
+
+class FlutterActivityLaunchConfigs {
+ // Meta-data arguments, processed from manifest XML.
+ static final String DART_ENTRYPOINT_META_DATA_KEY = "io.flutter.Entrypoint";
+ static final String INITIAL_ROUTE_META_DATA_KEY = "io.flutter.InitialRoute";
+ static final String SPLASH_SCREEN_META_DATA_KEY = "io.flutter.embedding.android.SplashScreenDrawable";
+ static final String NORMAL_THEME_META_DATA_KEY = "io.flutter.embedding.android.NormalTheme";
+
+ // Intent extra arguments.
+ static final String EXTRA_INITIAL_ROUTE = "initial_route";
+ static final String EXTRA_BACKGROUND_MODE = "background_mode";
+ static final String EXTRA_CACHED_ENGINE_ID = "cached_engine_id";
+ static final String EXTRA_DESTROY_ENGINE_WITH_ACTIVITY = "destroy_engine_with_activity";
+
+ // Default configuration.
+ static final String DEFAULT_DART_ENTRYPOINT = "main";
+ static final String DEFAULT_INITIAL_ROUTE = "/";
+ static final String DEFAULT_BACKGROUND_MODE = BackgroundMode.opaque.name();
+
+ /**
+ * The mode of the background of a Flutter {@code Activity}, 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
+ }
+}
diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java
new file mode 100644
index 0000000000000..136d7cf6fdae9
--- /dev/null
+++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragmentActivity.java
@@ -0,0 +1,671 @@
+// 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.android;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+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;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.FragmentActivity;
+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.Log;
+import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode;
+import io.flutter.embedding.engine.FlutterEngine;
+import io.flutter.embedding.engine.FlutterShellArgs;
+import io.flutter.plugin.platform.PlatformPlugin;
+import io.flutter.view.FlutterMain;
+
+import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DART_ENTRYPOINT_META_DATA_KEY;
+import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_BACKGROUND_MODE;
+import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_DART_ENTRYPOINT;
+import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.DEFAULT_INITIAL_ROUTE;
+import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_BACKGROUND_MODE;
+import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_CACHED_ENGINE_ID;
+import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_DESTROY_ENGINE_WITH_ACTIVITY;
+import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.EXTRA_INITIAL_ROUTE;
+import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.INITIAL_ROUTE_META_DATA_KEY;
+import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.NORMAL_THEME_META_DATA_KEY;
+import static io.flutter.embedding.android.FlutterActivityLaunchConfigs.SPLASH_SCREEN_META_DATA_KEY;
+
+/**
+ * A Flutter {@code Activity} that is based upon {@link FragmentActivity}.
+ *
+ * {@code FlutterFragmentActivity} exists because there are some Android APIs
+ * in the ecosystem that only accept a {@link FragmentActivity}. If a
+ * {@link FragmentActivity} is not required, you should consider using a
+ * regular {@link FlutterActivity} instead, because {@link FlutterActivity}
+ * is considered to be the standard, canonical implementation of a Flutter
+ * {@code Activity}.
+ */
+public class FlutterFragmentActivity extends FragmentActivity
+ implements SplashScreenProvider,
+ FlutterEngineProvider,
+ FlutterEngineConfigurator {
+ private static final String TAG = "FlutterFragmentActivity";
+
+ // FlutterFragment management.
+ private static final String TAG_FLUTTER_FRAGMENT = "flutter_fragment";
+ // TODO(mattcarroll): replace ID with R.id when build system supports R.java
+ private static final int FRAGMENT_CONTAINER_ID = 609893468; // random number
+
+ /**
+ * Creates an {@link Intent} that launches a {@code FlutterFragmentActivity}, which executes
+ * a {@code main()} Dart entrypoint, and displays the "/" route as Flutter's initial route.
+ */
+ @NonNull
+ public static Intent createDefaultIntent(@NonNull Context launchContext) {
+ return withNewEngine().build(launchContext);
+ }
+
+ /**
+ * Creates an {@link FlutterFragmentActivity.NewEngineIntentBuilder}, which can be used to
+ * configure an {@link Intent} to launch a {@code FlutterFragmentActivity} that internally creates a new
+ * {@link FlutterEngine} using the desired Dart entrypoint, initial route, etc.
+ */
+ @NonNull
+ public static NewEngineIntentBuilder withNewEngine() {
+ return new NewEngineIntentBuilder(FlutterFragmentActivity.class);
+ }
+
+ /**
+ * Builder to create an {@code Intent} that launches a {@code FlutterFragmentActivity} with a new
+ * {@link FlutterEngine} and the desired configuration.
+ */
+ public static class NewEngineIntentBuilder {
+ private final Class extends FlutterFragmentActivity> activityClass;
+ private String initialRoute = DEFAULT_INITIAL_ROUTE;
+ private String backgroundMode = DEFAULT_BACKGROUND_MODE;
+
+ /**
+ * Constructor that allows this {@code NewEngineIntentBuilder} to be used by subclasses of
+ * {@code FlutterFragmentActivity}.
+ *
+ * Subclasses of {@code FlutterFragmentActivity} should provide their own static version of
+ * {@link #withNewEngine()}, which returns an instance of {@code NewEngineIntentBuilder}
+ * constructed with a {@code Class} reference to the {@code FlutterFragmentActivity} subclass,
+ * e.g.:
+ *
+ * {@code
+ * return new NewEngineIntentBuilder(MyFlutterActivity.class);
+ * }
+ */
+ protected NewEngineIntentBuilder(@NonNull Class extends FlutterFragmentActivity> activityClass) {
+ this.activityClass = activityClass;
+ }
+
+ /**
+ * The initial route that a Flutter app will render in this {@code FlutterFragmentActivity},
+ * defaults to "/".
+ */
+ @NonNull
+ public NewEngineIntentBuilder initialRoute(@NonNull String initialRoute) {
+ this.initialRoute = initialRoute;
+ return this;
+ }
+
+ /**
+ * The mode of {@code FlutterFragmentActivity}'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 FlutterFragmentActivity} 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 FlutterFragmentActivity} that is configured with a background mode of
+ * {@link BackgroundMode#transparent} must have a theme applied to it that includes the
+ * following property: {@code
+ * Subclasses of {@code FlutterFragmentActivity} should provide their own static version of
+ * {@link #withNewEngine()}, which returns an instance of {@code CachedEngineIntentBuilder}
+ * constructed with a {@code Class} reference to the {@code FlutterFragmentActivity} subclass,
+ * e.g.:
+ *
+ * {@code
+ * return new CachedEngineIntentBuilder(MyFlutterActivity.class, engineId);
+ * }
+ */
+ protected CachedEngineIntentBuilder(
+ @NonNull Class extends FlutterFragmentActivity> activityClass,
+ @NonNull String engineId
+ ) {
+ this.activityClass = activityClass;
+ this.cachedEngineId = engineId;
+ }
+
+ /**
+ * Returns true if the cached {@link FlutterEngine} should be destroyed and removed from the
+ * cache when this {@code FlutterFragmentActivity} is destroyed.
+ *
+ * The default value is {@code false}.
+ */
+ public CachedEngineIntentBuilder destroyEngineWithActivity(boolean destroyEngineWithActivity) {
+ this.destroyEngineWithActivity = destroyEngineWithActivity;
+ return this;
+ }
+
+ /**
+ * The mode of {@code FlutterFragmentActivity}'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 FlutterFragmentActivity} 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 FlutterFragmentActivity} that is configured with a background mode of
+ * {@link BackgroundMode#transparent} must have a theme applied to it that includes the
+ * following property: {@code
+ * This behavior is offered so that a "launch screen" can be displayed while the
+ * application initially loads. To utilize this behavior in an app, do the following:
+ *
+ * Do not change aspects of system chrome between a launch theme and normal theme. Either define
+ * both themes to be fullscreen or not, and define both themes to display the same status bar and
+ * navigation bar settings. If you wish to adjust system chrome once your Flutter app renders, use
+ * platform channels to instruct Android to do so at the appropriate time. This will avoid any
+ * jarring visual changes during app startup.
+ */
+ private void switchLaunchThemeForNormalTheme() {
+ try {
+ ActivityInfo activityInfo = getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
+ if (activityInfo.metaData != null) {
+ int normalThemeRID = activityInfo.metaData.getInt(NORMAL_THEME_META_DATA_KEY, -1);
+ if (normalThemeRID != -1) {
+ setTheme(normalThemeRID);
+ }
+ } else {
+ Log.d(TAG, "Using the launch theme as normal theme.");
+ }
+ } catch (PackageManager.NameNotFoundException exception) {
+ Log.e(TAG, "Could not read meta-data for FlutterFragmentActivity. Using the launch theme as normal theme.");
+ }
+ }
+
+ @Nullable
+ @Override
+ public SplashScreen provideSplashScreen() {
+ Drawable manifestSplashDrawable = getSplashScreenFromManifest();
+ if (manifestSplashDrawable != null) {
+ return new DrawableSplashScreen(manifestSplashDrawable);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns a {@link Drawable} to be used as a splash screen as requested by meta-data in the
+ * {@code AndroidManifest.xml} file, or null if no such splash screen is requested.
+ *
+ * See {@link FlutterActivityLaunchConfigs#SPLASH_SCREEN_META_DATA_KEY} for the meta-data key to
+ * be used in a manifest file.
+ */
+ @Nullable
+ @SuppressWarnings("deprecation")
+ private Drawable getSplashScreenFromManifest() {
+ try {
+ ActivityInfo activityInfo = getPackageManager().getActivityInfo(
+ getComponentName(),
+ PackageManager.GET_META_DATA|PackageManager.GET_ACTIVITIES
+ );
+ Bundle metadata = activityInfo.metaData;
+ Integer splashScreenId = metadata != null ? metadata.getInt(SPLASH_SCREEN_META_DATA_KEY) : null;
+ return splashScreenId != null
+ ? Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP
+ ? getResources().getDrawable(splashScreenId, getTheme())
+ : getResources().getDrawable(splashScreenId)
+ : null;
+ } catch (PackageManager.NameNotFoundException e) {
+ // This is never expected to happen.
+ return null;
+ }
+ }
+
+ /**
+ * 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
+ * @return the FrameLayout container
+ */
+ @NonNull
+ private View createFragmentContainer() {
+ FrameLayout container = new FrameLayout(this);
+ container.setId(FRAGMENT_CONTAINER_ID);
+ container.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT
+ ));
+ return container;
+ }
+
+ /**
+ * Ensure that a {@link FlutterFragment} is attached to this {@code FlutterFragmentActivity}.
+ *
+ * If no {@link FlutterFragment} exists in this {@code FlutterFragmentActivity}, then a
+ * {@link FlutterFragment} is created and added. If a {@link FlutterFragment} does exist in this
+ * {@code FlutterFragmentActivity}, then a reference to that {@link FlutterFragment} is retained
+ * in {@code #flutterFragment}.
+ */
+ private void ensureFlutterFragmentCreated() {
+ FragmentManager fragmentManager = getSupportFragmentManager();
+ flutterFragment = (FlutterFragment) fragmentManager.findFragmentByTag(TAG_FLUTTER_FRAGMENT);
+ if (flutterFragment == null) {
+ // No FlutterFragment exists yet. This must be the initial Activity creation. We will create
+ // and add a new FlutterFragment to this Activity.
+ flutterFragment = createFlutterFragment();
+ fragmentManager
+ .beginTransaction()
+ .add(FRAGMENT_CONTAINER_ID, flutterFragment, TAG_FLUTTER_FRAGMENT)
+ .commit();
+ }
+ }
+
+ /**
+ * Creates the instance of the {@link FlutterFragment} that this {@code FlutterFragmentActivity}
+ * displays.
+ *
+ * Subclasses may override this method to return a specialization of {@link FlutterFragment}.
+ */
+ @NonNull
+ protected FlutterFragment createFlutterFragment() {
+ BackgroundMode backgroundMode = getBackgroundMode();
+ FlutterView.RenderMode renderMode = backgroundMode == BackgroundMode.opaque
+ ? FlutterView.RenderMode.surface
+ : FlutterView.RenderMode.texture;
+ FlutterView.TransparencyMode transparencyMode = backgroundMode == BackgroundMode.opaque
+ ? FlutterView.TransparencyMode.opaque
+ : FlutterView.TransparencyMode.transparent;
+
+ if (getCachedEngineId() != null) {
+ Log.d(TAG, "Creating FlutterFragment with cached engine:\n"
+ + "Cached engine ID: " + getCachedEngineId() + "\n"
+ + "Will destroy engine when Activity is destroyed: " + shouldDestroyEngineWithHost() + "\n"
+ + "Background transparency mode: " + backgroundMode + "\n"
+ + "Will attach FlutterEngine to Activity: " + shouldAttachEngineToActivity());
+
+ return FlutterFragment.withCachedEngine(getCachedEngineId())
+ .renderMode(renderMode)
+ .transparencyMode(transparencyMode)
+ .shouldAttachEngineToActivity(shouldAttachEngineToActivity())
+ .destroyEngineWithFragment(shouldDestroyEngineWithHost())
+ .build();
+ } else {
+ Log.d(TAG, "Creating FlutterFragment with new engine:\n"
+ + "Background transparency mode: " + backgroundMode + "\n"
+ + "Dart entrypoint: " + getDartEntrypointFunctionName() + "\n"
+ + "Initial route: " + getInitialRoute() + "\n"
+ + "App bundle path: " + getAppBundlePath() + "\n"
+ + "Will attach FlutterEngine to Activity: " + shouldAttachEngineToActivity());
+
+ return FlutterFragment.withNewEngine()
+ .dartEntrypoint(getDartEntrypointFunctionName())
+ .initialRoute(getInitialRoute())
+ .appBundlePath(getAppBundlePath())
+ .flutterShellArgs(FlutterShellArgs.fromIntent(getIntent()))
+ .renderMode(renderMode)
+ .transparencyMode(transparencyMode)
+ .shouldAttachEngineToActivity(shouldAttachEngineToActivity())
+ .build();
+ }
+ }
+
+ 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);
+ }
+ }
+
+ @Override
+ public void onPostResume() {
+ super.onPostResume();
+ flutterFragment.onPostResume();
+ }
+
+ @Override
+ protected void onNewIntent(@NonNull Intent intent) {
+ // Forward Intents to our FlutterFragment in case it cares.
+ flutterFragment.onNewIntent(intent);
+ super.onNewIntent(intent);
+ }
+
+ @Override
+ public void onBackPressed() {
+ flutterFragment.onBackPressed();
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ flutterFragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+
+ @Override
+ public void onUserLeaveHint() {
+ flutterFragment.onUserLeaveHint();
+ }
+
+ @Override
+ public void onTrimMemory(int level) {
+ super.onTrimMemory(level);
+ flutterFragment.onTrimMemory(level);
+ }
+
+ @SuppressWarnings("unused")
+ @Nullable
+ protected FlutterEngine getFlutterEngine() {
+ return flutterFragment.getFlutterEngine();
+ }
+
+ /**
+ * Returns false if the {@link FlutterEngine} backing this {@code FlutterFragmentActivity} should
+ * outlive this {@code FlutterFragmentActivity}, or true to be destroyed when the
+ * {@code FlutterFragmentActivity} is destroyed.
+ *
+ * The default value is {@code true} in cases where {@code FlutterFragmentActivity} created its
+ * own {@link FlutterEngine}, and {@code false} in cases where a cached {@link FlutterEngine} was
+ * provided.
+ */
+ public boolean shouldDestroyEngineWithHost() {
+ return getIntent().getBooleanExtra(EXTRA_DESTROY_ENGINE_WITH_ACTIVITY, false);
+ }
+
+ /**
+ * Hook for subclasses to control whether or not the {@link FlutterFragment} within this
+ * {@code Activity} automatically attaches its {@link FlutterEngine} to this {@code Activity}.
+ *
+ * For an explanation of why this control exists, see
+ * {@link FlutterFragment.NewEngineFragmentBuilder#shouldAttachEngineToActivity()}.
+ *
+ * This property is controlled with a protected method instead of an {@code Intent} argument
+ * because the only situation where changing this value would help, is a situation in which
+ * {@code FlutterFragmentActivity} is being subclassed to utilize a custom and/or cached
+ * {@link FlutterEngine}.
+ *
+ * Defaults to {@code true}.
+ */
+ protected boolean shouldAttachEngineToActivity() {
+ return true;
+ }
+
+ /**
+ * Hook for subclasses to easily provide a custom {@code FlutterEngine}.
+ */
+ @Nullable
+ @Override
+ public FlutterEngine provideFlutterEngine(@NonNull Context context) {
+ // No-op. Hook for subclasses.
+ return null;
+ }
+
+ /**
+ * Hook for subclasses to easily configure a {@code FlutterEngine}, e.g., register
+ * plugins.
+ *
+ * This method is called after {@link #provideFlutterEngine(Context)}.
+ */
+ @Override
+ public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
+ // No-op. Hook for subclasses.
+ }
+
+ /**
+ * The path to the bundle that contains this Flutter app's resources, e.g., Dart code snapshots.
+ *
+ * When this {@code FlutterFragmentActivity} is run by Flutter tooling and a data String is
+ * included in the launching {@code Intent}, that data String is interpreted as an app bundle
+ * path.
+ *
+ * By default, the app bundle path is obtained from {@link FlutterMain#findAppBundlePath(Context)}.
+ *
+ * Subclasses may override this method to return a custom app bundle path.
+ */
+ @NonNull
+ protected String getAppBundlePath() {
+ // If this Activity was launched from tooling, and the incoming Intent contains
+ // a custom app bundle path, return that path.
+ // TODO(mattcarroll): determine if we should have an explicit FlutterTestActivity instead of conflating.
+ if (isDebuggable() && Intent.ACTION_RUN.equals(getIntent().getAction())) {
+ String appBundlePath = getIntent().getDataString();
+ if (appBundlePath != null) {
+ return appBundlePath;
+ }
+ }
+
+ // Return the default app bundle path.
+ // TODO(mattcarroll): move app bundle resolution into an appropriately named class.
+ return FlutterMain.findAppBundlePath();
+ }
+
+ /**
+ * The Dart entrypoint that will be executed as soon as the Dart snapshot is loaded.
+ *
+ * This preference can be controlled by setting a {@code
+ * Subclasses may override this method to directly control the Dart entrypoint.
+ */
+ @NonNull
+ public String getDartEntrypointFunctionName() {
+ try {
+ ActivityInfo activityInfo = getPackageManager().getActivityInfo(
+ getComponentName(),
+ PackageManager.GET_META_DATA|PackageManager.GET_ACTIVITIES
+ );
+ Bundle metadata = activityInfo.metaData;
+ String desiredDartEntrypoint = metadata != null ? metadata.getString(DART_ENTRYPOINT_META_DATA_KEY) : null;
+ return desiredDartEntrypoint != null ? desiredDartEntrypoint : DEFAULT_DART_ENTRYPOINT;
+ } catch (PackageManager.NameNotFoundException e) {
+ return DEFAULT_DART_ENTRYPOINT;
+ }
+ }
+
+ /**
+ * The initial route that a Flutter app will render upon loading and executing its Dart code.
+ *
+ * This preference can be controlled with 2 methods:
+ *
+ * The reason that a {@code
+ * Subclasses may override this method to directly control the initial route.
+ */
+ @NonNull
+ protected String getInitialRoute() {
+ if (getIntent().hasExtra(EXTRA_INITIAL_ROUTE)) {
+ return getIntent().getStringExtra(EXTRA_INITIAL_ROUTE);
+ }
+
+ try {
+ ActivityInfo activityInfo = getPackageManager().getActivityInfo(
+ getComponentName(),
+ PackageManager.GET_META_DATA|PackageManager.GET_ACTIVITIES
+ );
+ Bundle metadata = activityInfo.metaData;
+ String desiredInitialRoute = metadata != null ? metadata.getString(INITIAL_ROUTE_META_DATA_KEY) : null;
+ return desiredInitialRoute != null ? desiredInitialRoute : DEFAULT_INITIAL_ROUTE;
+ } catch (PackageManager.NameNotFoundException e) {
+ return DEFAULT_INITIAL_ROUTE;
+ }
+ }
+
+ /**
+ * Returns the ID of a statically cached {@link FlutterEngine} to use within this
+ * {@code FlutterFragmentActivity}, or {@code null} if this {@code FlutterFragmentActivity} does not want
+ * to use a cached {@link FlutterEngine}.
+ */
+ @Nullable
+ protected String getCachedEngineId() {
+ return getIntent().getStringExtra(EXTRA_CACHED_ENGINE_ID);
+ }
+
+ /**
+ * 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.
+ *
+ * Debug mode allows Flutter to operate with hot reload and hot restart. Release mode does not.
+ */
+ private boolean isDebuggable() {
+ return (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+ }
+}
diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java
index 9e6b2985199ed..b16673b1dce6c 100644
--- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java
+++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityTest.java
@@ -16,6 +16,8 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import io.flutter.embedding.android.FlutterActivityLaunchConfigs.BackgroundMode;
+
@Config(manifest=Config.NONE)
@RunWith(RobolectricTestRunner.class)
public class FlutterActivityTest {
@@ -31,7 +33,7 @@ public void itCreatesDefaultIntentWithExpectedDefaults() {
assertTrue(flutterActivity.shouldAttachEngineToActivity());
assertNull(flutterActivity.getCachedEngineId());
assertTrue(flutterActivity.shouldDestroyEngineWithHost());
- assertEquals(FlutterActivity.BackgroundMode.opaque, flutterActivity.getBackgroundMode());
+ assertEquals(BackgroundMode.opaque, flutterActivity.getBackgroundMode());
assertEquals(FlutterView.RenderMode.surface, flutterActivity.getRenderMode());
assertEquals(FlutterView.TransparencyMode.opaque, flutterActivity.getTransparencyMode());
}
@@ -40,7 +42,7 @@ public void itCreatesDefaultIntentWithExpectedDefaults() {
public void itCreatesNewEngineIntentWithRequestedSettings() {
Intent intent = FlutterActivity.withNewEngine()
.initialRoute("/custom/route")
- .backgroundMode(FlutterActivity.BackgroundMode.transparent)
+ .backgroundMode(BackgroundMode.transparent)
.build(RuntimeEnvironment.application);
ActivityController
- *
* If both preferences are set, the {@code Intent} preference takes priority.
*
+ *
+ * With the above settings, your launch theme will be used when loading the app, and
+ * then the theme will be switched to your normal theme once the app has initialized.
+ *
+ *
+ * If both preferences are set, the {@code Intent} preference takes priority.
+ *