diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter index 802f6b0b67..f03f4dff62 100644 --- a/engine/src/flutter/ci/licenses_golden/licenses_flutter +++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter @@ -471,7 +471,10 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterFragmentActi FILE: ../../../flutter/shell/platform/android/io/flutter/app/FlutterPluginRegistry.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/android/AndroidKeyProcessor.java +FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/android/FlutterActivity.java +FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/android/FlutterFragment.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/android/FlutterSurfaceView.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/android/FlutterTextureView.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/android/FlutterView.java diff --git a/engine/src/flutter/shell/platform/android/BUILD.gn b/engine/src/flutter/shell/platform/android/BUILD.gn index 9b430a0092..fa55b71b96 100644 --- a/engine/src/flutter/shell/platform/android/BUILD.gn +++ b/engine/src/flutter/shell/platform/android/BUILD.gn @@ -108,7 +108,10 @@ java_library("flutter_shell_java") { "io/flutter/app/FlutterPluginRegistry.java", "io/flutter/embedding/engine/FlutterEngine.java", "io/flutter/embedding/engine/FlutterJNI.java", + "io/flutter/embedding/engine/FlutterShellArgs.java", "io/flutter/embedding/engine/android/AndroidKeyProcessor.java", + "io/flutter/embedding/engine/android/FlutterActivity.java", + "io/flutter/embedding/engine/android/FlutterFragment.java", "io/flutter/embedding/engine/android/FlutterSurfaceView.java", "io/flutter/embedding/engine/android/FlutterTextureView.java", "io/flutter/embedding/engine/android/FlutterView.java", diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java new file mode 100644 index 0000000000..c3b2c9b715 --- /dev/null +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java @@ -0,0 +1,131 @@ +// 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; + +import android.content.Context; +import android.content.Intent; +import android.support.annotation.NonNull; + +import java.util.*; + +/** + * Arguments that can be delivered to the Flutter shell when it is created. + *
+ * WARNING: THIS CLASS IS EXPERIMENTAL. DO NOT SHIP A DEPENDENCY ON THIS CODE. + * IF YOU USE IT, WE WILL BREAK YOU. + *
+ * 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.
+ */
+@SuppressWarnings({"WeakerAccess", "unused"})
+public class FlutterShellArgs {
+ public static final String ARG_KEY_TRACE_STARTUP = "trace-startup";
+ public static final String ARG_TRACE_STARTUP = "--trace-startup";
+ public static final String ARG_KEY_START_PAUSED = "start-paused";
+ public static final String ARG_START_PAUSED = "--start-paused";
+ public static final String ARG_KEY_USE_TEST_FONTS = "use-test-fonts";
+ public static final String ARG_USE_TEST_FONTS = "--use-test-fonts";
+ public static final String ARG_KEY_ENABLE_DART_PROFILING = "enable-dart-profiling";
+ public static final String ARG_ENABLE_DART_PROFILING = "--enable-dart-profiling";
+ public static final String ARG_KEY_ENABLE_SOFTWARE_RENDERING = "enable-software-rendering";
+ public static final String ARG_ENABLE_SOFTWARE_RENDERING = "--enable-software-rendering";
+ public static final String ARG_KEY_SKIA_DETERMINISTIC_RENDERING = "skia-deterministic-rendering";
+ public static final String ARG_SKIA_DETERMINISTIC_RENDERING = "--skia-deterministic-rendering";
+ public static final String ARG_KEY_TRACE_SKIA = "trace-skia";
+ public static final String ARG_TRACE_SKIA = "--trace-skia";
+ public static final String ARG_KEY_VERBOSE_LOGGING = "verbose-logging";
+ public static final String ARG_VERBOSE_LOGGING = "--verbose-logging";
+
+ @NonNull
+ public static FlutterShellArgs fromIntent(@NonNull Intent intent) {
+ // Before adding more entries to this list, consider that arbitrary
+ // Android applications can generate intents with extra data and that
+ // there are many security-sensitive args in the binary.
+ // TODO(mattcarroll): I left this warning as-is, but we should clarify what exactly this warning is warning against.
+ ArrayList
+ * WARNING: THIS CLASS IS EXPERIMENTAL. DO NOT SHIP A DEPENDENCY ON THIS CODE.
+ * IF YOU USE IT, WE WILL BREAK YOU.
+ *
+ * {@code FlutterActivity} is the simplest and most direct way to integrate Flutter within an
+ * Android app.
+ *
+ * The Dart entrypoint executed within this {@code Activity} is "main()" by default. The entrypoint
+ * may be specified explicitly by passing the name of the entrypoint method as a {@code String} in
+ * {@link #EXTRA_DART_ENTRYPOINT}, e.g., "myEntrypoint".
+ *
+ * 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".
+ *
+ * The app bundle path, Dart entrypoint, and initial route can each be controlled in a subclass of
+ * {@code FlutterActivity} by overriding their respective methods:
+ *
+ * If Flutter is needed in a location that can only use a {@code View}, consider using a
+ * {@link FlutterView}. Using a {@link FlutterView} requires forwarding some calls from an
+ * {@code Activity}, as well as forwarding lifecycle calls from an {@code Activity} or a
+ * {@code Fragment}.
+ */
+// TODO(mattcarroll): explain each call forwarded to Fragment (first requires resolution of PluginRegistry API).
+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";
+
+ // Intent extra arguments.
+ public static final String EXTRA_DART_ENTRYPOINT = "dart_entrypoint";
+ public static final String EXTRA_INITIAL_ROUTE = "initial_route";
+
+ // 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
+ private FlutterFragment flutterFragment;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(createFragmentContainer());
+ ensureFlutterFragmentCreated();
+ }
+
+ /**
+ * Creates a {@link FrameLayout} with an ID of {@code #FRAGMENT_CONTAINER_ID} that will contain
+ * the {@link FlutterFragment} displayed by this {@code FlutterActivity}.
+ *
+ * @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 FlutterActivity}.
+ *
+ * If no {@link FlutterFragment} exists in this {@code FlutterActivity}, then a {@link FlutterFragment}
+ * is created and added. If a {@link FlutterFragment} does exist in this {@code FlutterActivity}, 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 FlutterActivity} displays.
+ *
+ * Subclasses may override this method to return a specialization of {@link FlutterFragment}.
+ */
+ @NonNull
+ protected FlutterFragment createFlutterFragment() {
+ return FlutterFragment.newInstance(
+ getDartEntrypoint(),
+ getInitialRoute(),
+ getAppBundlePath(),
+ FlutterShellArgs.fromIntent(getIntent())
+ );
+ }
+
+ /**
+ * The path to the bundle that contains this Flutter app's resources, e.g., Dart code snapshots.
+ *
+ * When this {@code FlutterActivity} 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(getApplicationContext());
+ }
+
+ /**
+ * The Dart entrypoint that will be executed as soon as the Dart snapshot is loaded.
+ *
+ * This preference can be controlled with 2 methods:
+ *
+ * The reason that a {@code
+ * Subclasses may override this method to directly control the Dart entrypoint.
+ */
+ @Nullable
+ protected String getDartEntrypoint() {
+ if (getIntent().hasExtra(EXTRA_DART_ENTRYPOINT)) {
+ return getIntent().getStringExtra(EXTRA_DART_ENTRYPOINT);
+ }
+
+ try {
+ ActivityInfo activityInfo = getPackageManager().getActivityInfo(
+ getComponentName(),
+ PackageManager.GET_META_DATA|PackageManager.GET_ACTIVITIES
+ );
+ Bundle metadata = activityInfo.metaData;
+ return metadata != null ? metadata.getString(DART_ENTRYPOINT_META_DATA_KEY) : null;
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * 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.
+ */
+ @Nullable
+ 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;
+ return metadata != null ? metadata.getString(INITIAL_ROUTE_META_DATA_KEY) : null;
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * 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/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/android/FlutterFragment.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/android/FlutterFragment.java
new file mode 100644
index 0000000000..89f25e4dd5
--- /dev/null
+++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/android/FlutterFragment.java
@@ -0,0 +1,152 @@
+// 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.android;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+
+import io.flutter.embedding.engine.FlutterShellArgs;
+
+/**
+ * {@code Fragment} which displays a Flutter UI that takes up all available {@code Fragment} space.
+ *
+ * WARNING: THIS CLASS IS EXPERIMENTAL. DO NOT SHIP A DEPENDENCY ON THIS CODE.
+ * IF YOU USE IT, WE WILL BREAK YOU.
+ *
+ * Using a {@code FlutterFragment} requires forwarding a number of calls from an {@code Activity} to
+ * ensure that the internal Flutter app behaves as expected:
+ *
+ * If convenient, consider using a {@link FlutterActivity} instead of a {@code FlutterFragment} to
+ * avoid the work of forwarding calls.
+ *
+ * If Flutter is needed in a location that can only use a {@code View}, consider using a
+ * {@link FlutterView}. Using a {@link FlutterView} requires forwarding some calls from an
+ * {@code Activity}, as well as forwarding lifecycle calls from an {@code Activity} or a
+ * {@code Fragment}.
+ */
+public class FlutterFragment extends Fragment {
+ private static final String TAG = "FlutterFragment";
+
+ private static final String ARG_DART_ENTRYPOINT = "dart_entrypoint";
+ 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";
+
+ /**
+ * 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}
+ */
+ 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);
+
+ return frag;
+ }
+
+ /**
+ * Creates a {@link Bundle} of arguments that can be used to configure a {@link FlutterFragment}.
+ * This method is exposed so that developers can create subclasses of {@link FlutterFragment}.
+ * Subclasses should declare static factories that use this method to create arguments that will
+ * be understood by the base class, and then the subclass can add any additional arguments it
+ * wants to this {@link Bundle}. Example:
+ *
+ *
+ * If Flutter is needed in a location that cannot use an {@code Activity}, consider using
+ * a {@link FlutterFragment}. Using a {@link FlutterFragment} requires forwarding some calls from
+ * an {@code Activity} to the {@link FlutterFragment}.
+ *
+ *
+ * If both preferences are set, the {@code Intent} preference takes priority.
+ *
+ *
+ * If both preferences are set, the {@code Intent} preference takes priority.
+ *
+ *
+ * Additionally, when starting an {@code Activity} for a result from this {@code Fragment}, be sure
+ * to invoke {@link Fragment#startActivityForResult(Intent, int)} rather than
+ * {@link Activity#startActivityForResult(Intent, int)}. If the {@code Activity} version of the
+ * method is invoked then this {@code Fragment} will never receive its
+ * {@link Fragment#onActivityResult(int, int, Intent)} callback.
+ *
+ *
+ * @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.
+ * {@code
+ * public static MyFlutterFragment newInstance(String myNewArg) {
+ * // Create an instance of our 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.
+ * args.putString(ARG_MY_NEW_ARG, myNewArg);
+ *
+ * // Give the args to our subclass Fragment.
+ * myFrag.setArguments(args);
+ *
+ * // Return the newly created subclass Fragment.
+ * return myFrag;
+ * }
+ * }
+ *
+ * @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
+ * @param flutterShellArgs any special configuration arguments for the Flutter engine
+ *
+ * @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) {
+ Bundle args = new Bundle();
+ args.putString(ARG_INITIAL_ROUTE, initialRoute);
+ args.putString(ARG_APP_BUNDLE_PATH, appBundlePath);
+ args.putString(ARG_DART_ENTRYPOINT, dartEntrypoint);
+ // TODO(mattcarroll): determine if we should have an explicit FlutterTestFragment instead of conflating.
+ if (null != flutterShellArgs) {
+ args.putStringArray(ARG_FLUTTER_INITIALIZATION_ARGS, flutterShellArgs.toArray());
+ }
+ return args;
+ }
+
+ public FlutterFragment() {
+ // Ensure that we at least have an empty Bundle of arguments so that we don't
+ // need to continually check for null arguments before grabbing one.
+ setArguments(new Bundle());
+ }
+}