diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index bfc4c4245fb9e..12d3b72cb894b 100644
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -597,6 +597,10 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/Flutte
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/PlatformMessageHandler.java
+FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java
+FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/loader/ResourceCleaner.java
+FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/loader/ResourceExtractor.java
+FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/loader/ResourcePaths.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/FlutterPlugin.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/PluginRegistry.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/plugins/activity/ActivityAware.java
@@ -666,9 +670,6 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterMain.java
FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterNativeView.java
FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterRunArguments.java
FILE: ../../../flutter/shell/platform/android/io/flutter/view/FlutterView.java
-FILE: ../../../flutter/shell/platform/android/io/flutter/view/ResourceCleaner.java
-FILE: ../../../flutter/shell/platform/android/io/flutter/view/ResourceExtractor.java
-FILE: ../../../flutter/shell/platform/android/io/flutter/view/ResourcePaths.java
FILE: ../../../flutter/shell/platform/android/io/flutter/view/TextureRegistry.java
FILE: ../../../flutter/shell/platform/android/io/flutter/view/VsyncWaiter.java
FILE: ../../../flutter/shell/platform/android/library_loader.cc
diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn
index cd57506e623b6..c2f3d1f41e32f 100644
--- a/shell/platform/android/BUILD.gn
+++ b/shell/platform/android/BUILD.gn
@@ -149,6 +149,10 @@ android_java_sources = [
"io/flutter/embedding/engine/dart/DartExecutor.java",
"io/flutter/embedding/engine/dart/DartMessenger.java",
"io/flutter/embedding/engine/dart/PlatformMessageHandler.java",
+ "io/flutter/embedding/engine/loader/FlutterLoader.java",
+ "io/flutter/embedding/engine/loader/ResourceCleaner.java",
+ "io/flutter/embedding/engine/loader/ResourceExtractor.java",
+ "io/flutter/embedding/engine/loader/ResourcePaths.java",
"io/flutter/embedding/engine/plugins/FlutterPlugin.java",
"io/flutter/embedding/engine/plugins/PluginRegistry.java",
"io/flutter/embedding/engine/plugins/activity/ActivityAware.java",
@@ -218,9 +222,6 @@ android_java_sources = [
"io/flutter/view/FlutterNativeView.java",
"io/flutter/view/FlutterRunArguments.java",
"io/flutter/view/FlutterView.java",
- "io/flutter/view/ResourceCleaner.java",
- "io/flutter/view/ResourceExtractor.java",
- "io/flutter/view/ResourcePaths.java",
"io/flutter/view/TextureRegistry.java",
"io/flutter/view/VsyncWaiter.java",
]
diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java
index 618dec5a1f99e..42b0129d7298f 100644
--- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java
+++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java
@@ -14,6 +14,7 @@
import io.flutter.Log;
import io.flutter.embedding.engine.dart.DartExecutor;
+import io.flutter.embedding.engine.loader.FlutterLoader;
import io.flutter.embedding.engine.plugins.PluginRegistry;
import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface;
import io.flutter.embedding.engine.plugins.broadcastreceiver.BroadcastReceiverControlSurface;
@@ -34,28 +35,38 @@
/**
* A single Flutter execution environment.
- *
- * WARNING: THIS CLASS IS EXPERIMENTAL. DO NOT SHIP A DEPENDENCY ON THIS CODE.
- * IF YOU USE IT, WE WILL BREAK YOU.
- *
- * A {@code FlutterEngine} can execute in the background, or it can be rendered to the screen by
- * using the accompanying {@link FlutterRenderer}. Rendering can be started and stopped, thus
- * allowing a {@code FlutterEngine} to move from UI interaction to data-only processing and then
- * back to UI interaction.
- *
+ *
+ * WARNING: THIS CLASS IS CURRENTLY EXPERIMENTAL. USE AT YOUR OWN RISK.
+ *
+ * The {@code FlutterEngine} is the container through which Dart code can be run in an Android
+ * application.
+ *
+ * Dart code in a {@code FlutterEngine} can execute in the background, or it can be render to the
+ * screen by using the accompanying {@link FlutterRenderer} and Dart code using the Flutter
+ * framework on the Dart side. Rendering can be started and stopped, thus allowing a
+ * {@code FlutterEngine} to move from UI interaction to data-only processing and then back to UI
+ * interaction.
+ *
* Multiple {@code FlutterEngine}s may exist, execute Dart code, and render UIs within a single
* Android app.
- *
- * To start running Flutter within this {@code FlutterEngine}, get a reference to this engine's
- * {@link DartExecutor} and then use {@link DartExecutor#executeDartEntrypoint(DartExecutor.DartEntrypoint)}.
- * The {@link DartExecutor#executeDartEntrypoint(DartExecutor.DartEntrypoint)} method must not be
+ *
+ * To start running Dart and/or Flutter within this {@code FlutterEngine}, get a reference to this
+ * engine's {@link DartExecutor} and then use
+ * {@link DartExecutor#executeDartEntrypoint(DartExecutor.DartEntrypoint)}. The
+ * {@link DartExecutor#executeDartEntrypoint(DartExecutor.DartEntrypoint)} method must not be
* invoked twice on the same {@code FlutterEngine}.
- *
+ *
* To start rendering Flutter content to the screen, use {@link #getRenderer()} to obtain a
- * {@link FlutterRenderer} and then attach a {@link RenderSurface}. Consider using
- * a {@link io.flutter.embedding.android.FlutterView} as a {@link RenderSurface}.
+ * {@link FlutterRenderer} and then attach a {@link RenderSurface}. Consider using a
+ * {@link io.flutter.embedding.android.FlutterView} as a {@link RenderSurface}.
+ *
+ * Instatiating the first {@code FlutterEngine} per process will also load the Flutter engine's
+ * native library and start the Dart VM. Subsequent {@code FlutterEngine}s will run on the same VM
+ * instance but will have their own Dart Isolate when the
+ * {@link DartExecutor} is run. Each Isolate is a self-contained Dart environment and cannot
+ * communicate with each other except via Isolate ports.
*/
-// TODO(mattcarroll): re-evaluate system channel APIs - some are not well named or differentiated
public class FlutterEngine implements LifecycleOwner {
private static final String TAG = "FlutterEngine";
@@ -110,24 +121,35 @@ public void onPreEngineRestart() {
/**
* Constructs a new {@code FlutterEngine}.
- *
- * {@code FlutterMain.startInitialization} must be called before constructing a {@code FlutterEngine}
- * to load the native libraries needed to attach to JNI.
- *
+ *
* A new {@code FlutterEngine} does not execute any Dart code automatically. See
* {@link #getDartExecutor()} and {@link DartExecutor#executeDartEntrypoint(DartExecutor.DartEntrypoint)}
* to begin executing Dart code within this {@code FlutterEngine}.
- *
+ *
* A new {@code FlutterEngine} will not display any UI until a
* {@link RenderSurface} is registered. See
* {@link #getRenderer()} and {@link FlutterRenderer#startRenderingToSurface(RenderSurface)}.
- *
+ *
* A new {@code FlutterEngine} does not come with any Flutter plugins attached. To attach plugins,
* see {@link #getPlugins()}.
- *
+ *
* A new {@code FlutterEngine} does come with all default system channels attached.
+ *
+ * The first {@code FlutterEngine} instance constructed per process will also load the Flutter
+ * native library and start a Dart VM.
+ *
+ * In order to pass Dart VM initialization arguments (see {@link io.flutter.embedding.engine.FlutterShellArgs})
+ * when creating the VM, manually set the initialization arguments by calling {@link FlutterLoader#startInitialization(Context)}
+ * and {@link FlutterLoader#ensureInitializationComplete(Context, String[])}.
*/
public FlutterEngine(@NonNull Context context) {
+ this(context, FlutterLoader.getInstance());
+ }
+
+ /* package */ FlutterEngine(@NonNull Context context, @NonNull FlutterLoader flutterLoader) {
+ flutterLoader.startInitialization(context);
+ flutterLoader.ensureInitializationComplete(context, null);
+
this.flutterJNI = new FlutterJNI();
flutterJNI.addEngineLifecycleListener(engineLifecycleListener);
attachToJni();
@@ -174,9 +196,9 @@ private boolean isAttachedToJni() {
}
/**
- * Cleans up all components within this {@code FlutterEngine} and then detaches from Flutter's
- * native implementation.
- *
+ * Cleans up all components within this {@code FlutterEngine} and destroys the associated Dart
+ * Isolate. All state held by the Dart Isolate, such as the Flutter Elements tree, is lost.
+ *
* This {@code FlutterEngine} instance should be discarded after invoking this method.
*/
public void destroy() {
@@ -206,10 +228,10 @@ public void removeEngineLifecycleListener(@NonNull EngineLifecycleListener liste
/**
* The Dart execution context associated with this {@code FlutterEngine}.
- *
+ *
* The {@link DartExecutor} can be used to start executing Dart code from a given entrypoint.
* See {@link DartExecutor#executeDartEntrypoint(DartExecutor.DartEntrypoint)}.
- *
+ *
* Use the {@link DartExecutor} to connect any desired message channels and method channels
* to facilitate communication between Android and Dart/Flutter.
*/
@@ -220,7 +242,7 @@ public DartExecutor getDartExecutor() {
/**
* The rendering system associated with this {@code FlutterEngine}.
- *
+ *
* To render a Flutter UI that is produced by this {@code FlutterEngine}'s Dart code, attach
* a {@link RenderSurface} to this
* {@link FlutterRenderer}.
diff --git a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java
new file mode 100644
index 0000000000000..35f4098597842
--- /dev/null
+++ b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java
@@ -0,0 +1,349 @@
+// 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.loader;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.AssetManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.view.WindowManager;
+
+import io.flutter.BuildConfig;
+import io.flutter.embedding.engine.FlutterJNI;
+import io.flutter.util.PathUtils;
+import io.flutter.view.VsyncWaiter;
+
+import java.io.File;
+import java.util.*;
+
+/**
+ * Finds Flutter resources in an application APK and also loads Flutter's native library.
+ */
+public class FlutterLoader {
+ private static final String TAG = "FlutterLoader";
+
+ // Must match values in flutter::switches
+ private static final String AOT_SHARED_LIBRARY_NAME = "aot-shared-library-name";
+ private static final String SNAPSHOT_ASSET_PATH_KEY = "snapshot-asset-path";
+ private static final String VM_SNAPSHOT_DATA_KEY = "vm-snapshot-data";
+ private static final String ISOLATE_SNAPSHOT_DATA_KEY = "isolate-snapshot-data";
+ private static final String FLUTTER_ASSETS_DIR_KEY = "flutter-assets-dir";
+
+ // XML Attribute keys supported in AndroidManifest.xml
+ private static final String PUBLIC_AOT_SHARED_LIBRARY_NAME =
+ FlutterLoader.class.getName() + '.' + AOT_SHARED_LIBRARY_NAME;
+ private static final String PUBLIC_VM_SNAPSHOT_DATA_KEY =
+ FlutterLoader.class.getName() + '.' + VM_SNAPSHOT_DATA_KEY;
+ private static final String PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY =
+ FlutterLoader.class.getName() + '.' + ISOLATE_SNAPSHOT_DATA_KEY;
+ private static final String PUBLIC_FLUTTER_ASSETS_DIR_KEY =
+ FlutterLoader.class.getName() + '.' + FLUTTER_ASSETS_DIR_KEY;
+
+ // Resource names used for components of the precompiled snapshot.
+ private static final String DEFAULT_AOT_SHARED_LIBRARY_NAME = "libapp.so";
+ private static final String DEFAULT_VM_SNAPSHOT_DATA = "vm_snapshot_data";
+ private static final String DEFAULT_ISOLATE_SNAPSHOT_DATA = "isolate_snapshot_data";
+ private static final String DEFAULT_LIBRARY = "libflutter.so";
+ private static final String DEFAULT_KERNEL_BLOB = "kernel_blob.bin";
+ private static final String DEFAULT_FLUTTER_ASSETS_DIR = "flutter_assets";
+
+ // Mutable because default values can be overridden via config properties
+ private String aotSharedLibraryName = DEFAULT_AOT_SHARED_LIBRARY_NAME;
+ private String vmSnapshotData = DEFAULT_VM_SNAPSHOT_DATA;
+ private String isolateSnapshotData = DEFAULT_ISOLATE_SNAPSHOT_DATA;
+ private String flutterAssetsDir = DEFAULT_FLUTTER_ASSETS_DIR;
+
+ private static FlutterLoader instance;
+
+ /**
+ * Returns a singleton {@code FlutterLoader} instance.
+ *
+ * The returned instance loads Flutter native libraries in the standard way. A singleton object
+ * is used instead of static methods to facilitate testing without actually running native
+ * library linking.
+ */
+ @NonNull
+ public static FlutterLoader getInstance() {
+ if (instance == null) {
+ instance = new FlutterLoader();
+ }
+ return instance;
+ }
+
+ private boolean initialized = false;
+ @Nullable
+ private ResourceExtractor resourceExtractor;
+ @Nullable
+ private Settings settings;
+
+ /**
+ * Starts initialization of the native system.
+ * @param applicationContext The Android application context.
+ */
+ public void startInitialization(@NonNull Context applicationContext) {
+ startInitialization(applicationContext, new Settings());
+ }
+
+ /**
+ * Starts initialization of the native system.
+ *
+ * This loads the Flutter engine's native library to enable subsequent JNI calls. This also
+ * starts locating and unpacking Dart resources packaged in the app's APK.
+ *
+ * Calling this method multiple times has no effect.
+ *
+ * @param applicationContext The Android application context.
+ * @param settings Configuration settings.
+ */
+ public void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) {
+ // Do not run startInitialization more than once.
+ if (this.settings != null) {
+ return;
+ }
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ throw new IllegalStateException("startInitialization must be called on the main thread");
+ }
+
+ this.settings = settings;
+
+ long initStartTimestampMillis = SystemClock.uptimeMillis();
+ initConfig(applicationContext);
+ initResources(applicationContext);
+
+ System.loadLibrary("flutter");
+
+ VsyncWaiter
+ .getInstance((WindowManager) applicationContext.getSystemService(Context.WINDOW_SERVICE))
+ .init();
+
+ // We record the initialization time using SystemClock because at the start of the
+ // initialization we have not yet loaded the native library to call into dart_tools_api.h.
+ // To get Timeline timestamp of the start of initialization we simply subtract the delta
+ // from the Timeline timestamp at the current moment (the assumption is that the overhead
+ // of the JNI call is negligible).
+ long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
+ FlutterJNI.nativeRecordStartTimestamp(initTimeMillis);
+ }
+
+ /**
+ * Blocks until initialization of the native system has completed.
+ *
+ * Calling this method multiple times has no effect.
+ *
+ * @param applicationContext The Android application context.
+ * @param args Flags sent to the Flutter runtime.
+ */
+ public void ensureInitializationComplete(@NonNull Context applicationContext, @Nullable String[] args) {
+ if (initialized) {
+ return;
+ }
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ throw new IllegalStateException("ensureInitializationComplete must be called on the main thread");
+ }
+ if (settings == null) {
+ throw new IllegalStateException("ensureInitializationComplete must be called after startInitialization");
+ }
+ try {
+ if (resourceExtractor != null) {
+ resourceExtractor.waitForCompletion();
+ }
+
+ List shellArgs = new ArrayList<>();
+ shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat");
+
+ ApplicationInfo applicationInfo = getApplicationInfo(applicationContext);
+ shellArgs.add("--icu-native-lib-path=" + applicationInfo.nativeLibraryDir + File.separator + DEFAULT_LIBRARY);
+
+ if (args != null) {
+ Collections.addAll(shellArgs, args);
+ }
+
+ String kernelPath = null;
+ if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
+ String snapshotAssetPath = PathUtils.getDataDirectory(applicationContext) + File.separator + flutterAssetsDir;
+ kernelPath = snapshotAssetPath + File.separator + DEFAULT_KERNEL_BLOB;
+ shellArgs.add("--" + SNAPSHOT_ASSET_PATH_KEY + "=" + snapshotAssetPath);
+ shellArgs.add("--" + VM_SNAPSHOT_DATA_KEY + "=" + vmSnapshotData);
+ shellArgs.add("--" + ISOLATE_SNAPSHOT_DATA_KEY + "=" + isolateSnapshotData);
+ } else {
+ shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME + "=" + aotSharedLibraryName);
+
+ // Most devices can load the AOT shared library based on the library name
+ // with no directory path. Provide a fully qualified path to the library
+ // as a workaround for devices where that fails.
+ shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME + "=" + applicationInfo.nativeLibraryDir + File.separator + aotSharedLibraryName);
+ }
+
+ shellArgs.add("--cache-dir-path=" + PathUtils.getCacheDirectory(applicationContext));
+ if (settings.getLogTag() != null) {
+ shellArgs.add("--log-tag=" + settings.getLogTag());
+ }
+
+ String appStoragePath = PathUtils.getFilesDir(applicationContext);
+ String engineCachesPath = PathUtils.getCacheDirectory(applicationContext);
+ FlutterJNI.nativeInit(applicationContext, shellArgs.toArray(new String[0]),
+ kernelPath, appStoragePath, engineCachesPath);
+
+ initialized = true;
+ } catch (Exception e) {
+ Log.e(TAG, "Flutter initialization failed.", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Same as {@link #ensureInitializationComplete(Context, String[])} but waiting on a background
+ * thread, then invoking {@code callback} on the {@code callbackHandler}.
+ */
+ public void ensureInitializationCompleteAsync(
+ @NonNull Context applicationContext,
+ @Nullable String[] args,
+ @NonNull Handler callbackHandler,
+ @NonNull Runnable callback
+ ) {
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ throw new IllegalStateException("ensureInitializationComplete must be called on the main thread");
+ }
+ if (settings == null) {
+ throw new IllegalStateException("ensureInitializationComplete must be called after startInitialization");
+ }
+ if (initialized) {
+ return;
+ }
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ if (resourceExtractor != null) {
+ resourceExtractor.waitForCompletion();
+ }
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ ensureInitializationComplete(applicationContext.getApplicationContext(), args);
+ callbackHandler.post(callback);
+ }
+ });
+ }
+ }).start();
+ }
+
+ @NonNull
+ private ApplicationInfo getApplicationInfo(@NonNull Context applicationContext) {
+ try {
+ return applicationContext
+ .getPackageManager()
+ .getApplicationInfo(applicationContext.getPackageName(), PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Initialize our Flutter config values by obtaining them from the
+ * manifest XML file, falling back to default values.
+ */
+ private void initConfig(@NonNull Context applicationContext) {
+ Bundle metadata = getApplicationInfo(applicationContext).metaData;
+
+ // There isn't a `` tag as a direct child of `` in
+ // `AndroidManifest.xml`.
+ if (metadata == null) {
+ return;
+ }
+
+ aotSharedLibraryName = metadata.getString(PUBLIC_AOT_SHARED_LIBRARY_NAME, DEFAULT_AOT_SHARED_LIBRARY_NAME);
+ flutterAssetsDir = metadata.getString(PUBLIC_FLUTTER_ASSETS_DIR_KEY, DEFAULT_FLUTTER_ASSETS_DIR);
+
+ vmSnapshotData = metadata.getString(PUBLIC_VM_SNAPSHOT_DATA_KEY, DEFAULT_VM_SNAPSHOT_DATA);
+ isolateSnapshotData = metadata.getString(PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY, DEFAULT_ISOLATE_SNAPSHOT_DATA);
+ }
+
+ /**
+ * Extract assets out of the APK that need to be cached as uncompressed
+ * files on disk.
+ */
+ private void initResources(@NonNull Context applicationContext) {
+ new ResourceCleaner(applicationContext).start();
+
+ if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
+ final String dataDirPath = PathUtils.getDataDirectory(applicationContext);
+ final String packageName = applicationContext.getPackageName();
+ final PackageManager packageManager = applicationContext.getPackageManager();
+ final AssetManager assetManager = applicationContext.getResources().getAssets();
+ resourceExtractor = new ResourceExtractor(dataDirPath, packageName, packageManager, assetManager);
+
+ // In debug/JIT mode these assets will be written to disk and then
+ // mapped into memory so they can be provided to the Dart VM.
+ resourceExtractor
+ .addResource(fullAssetPathFrom(vmSnapshotData))
+ .addResource(fullAssetPathFrom(isolateSnapshotData))
+ .addResource(fullAssetPathFrom(DEFAULT_KERNEL_BLOB));
+
+ resourceExtractor.start();
+ }
+ }
+
+ @NonNull
+ public String findAppBundlePath() {
+ return flutterAssetsDir;
+ }
+
+ /**
+ * Returns the file name for the given asset.
+ * The returned file name can be used to access the asset in the APK
+ * through the {@link android.content.res.AssetManager} API.
+ *
+ * @param asset the name of the asset. The name can be hierarchical
+ * @return the filename to be used with {@link android.content.res.AssetManager}
+ */
+ @NonNull
+ public String getLookupKeyForAsset(@NonNull String asset) {
+ return fullAssetPathFrom(asset);
+ }
+
+ /**
+ * Returns the file name for the given asset which originates from the
+ * specified packageName. The returned file name can be used to access
+ * the asset in the APK through the {@link android.content.res.AssetManager} API.
+ *
+ * @param asset the name of the asset. The name can be hierarchical
+ * @param packageName the name of the package from which the asset originates
+ * @return the file name to be used with {@link android.content.res.AssetManager}
+ */
+ @NonNull
+ public String getLookupKeyForAsset(@NonNull String asset, @NonNull String packageName) {
+ return getLookupKeyForAsset(
+ "packages" + File.separator + packageName + File.separator + asset);
+ }
+
+ @NonNull
+ private String fullAssetPathFrom(@NonNull String filePath) {
+ return flutterAssetsDir + File.separator + filePath;
+ }
+
+ public static class Settings {
+ private String logTag;
+
+ @Nullable
+ public String getLogTag() {
+ return logTag;
+ }
+
+ /**
+ * Set the tag associated with Flutter app log messages.
+ * @param tag Log tag.
+ */
+ public void setLogTag(String tag) {
+ logTag = tag;
+ }
+ }
+}
diff --git a/shell/platform/android/io/flutter/view/ResourceCleaner.java b/shell/platform/android/io/flutter/embedding/engine/loader/ResourceCleaner.java
similarity index 98%
rename from shell/platform/android/io/flutter/view/ResourceCleaner.java
rename to shell/platform/android/io/flutter/embedding/engine/loader/ResourceCleaner.java
index c3ac6a325b598..f038726dc8e87 100644
--- a/shell/platform/android/io/flutter/view/ResourceCleaner.java
+++ b/shell/platform/android/io/flutter/embedding/engine/loader/ResourceCleaner.java
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-package io.flutter.view;
+package io.flutter.embedding.engine.loader;
import android.content.Context;
import android.os.AsyncTask;
diff --git a/shell/platform/android/io/flutter/view/ResourceExtractor.java b/shell/platform/android/io/flutter/embedding/engine/loader/ResourceExtractor.java
similarity index 99%
rename from shell/platform/android/io/flutter/view/ResourceExtractor.java
rename to shell/platform/android/io/flutter/embedding/engine/loader/ResourceExtractor.java
index dfba572ee835c..974d0034da1e6 100644
--- a/shell/platform/android/io/flutter/view/ResourceExtractor.java
+++ b/shell/platform/android/io/flutter/embedding/engine/loader/ResourceExtractor.java
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-package io.flutter.view;
+package io.flutter.embedding.engine.loader;
import static java.util.Arrays.asList;
diff --git a/shell/platform/android/io/flutter/view/ResourcePaths.java b/shell/platform/android/io/flutter/embedding/engine/loader/ResourcePaths.java
similarity index 94%
rename from shell/platform/android/io/flutter/view/ResourcePaths.java
rename to shell/platform/android/io/flutter/embedding/engine/loader/ResourcePaths.java
index 2e2305e0945a6..b2d4ca45c0f9e 100644
--- a/shell/platform/android/io/flutter/view/ResourcePaths.java
+++ b/shell/platform/android/io/flutter/embedding/engine/loader/ResourcePaths.java
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-package io.flutter.view;
+package io.flutter.embedding.engine.loader;
import android.content.Context;
diff --git a/shell/platform/android/io/flutter/view/FlutterMain.java b/shell/platform/android/io/flutter/view/FlutterMain.java
index 5e1d00b132ba1..accd854eb65e6 100644
--- a/shell/platform/android/io/flutter/view/FlutterMain.java
+++ b/shell/platform/android/io/flutter/view/FlutterMain.java
@@ -5,81 +5,16 @@
package io.flutter.view;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.res.AssetManager;
-import android.os.Bundle;
import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
-import android.util.Log;
-import android.view.WindowManager;
-import io.flutter.BuildConfig;
-import io.flutter.embedding.engine.FlutterJNI;
-import io.flutter.util.PathUtils;
-
-import java.io.File;
-import java.util.*;
+import io.flutter.embedding.engine.loader.FlutterLoader;
/**
* A class to intialize the Flutter engine.
*/
public class FlutterMain {
- private static final String TAG = "FlutterMain";
-
- // Must match values in sky::switches
- private static final String AOT_SHARED_LIBRARY_NAME = "aot-shared-library-name";
- private static final String SNAPSHOT_ASSET_PATH_KEY = "snapshot-asset-path";
- private static final String VM_SNAPSHOT_DATA_KEY = "vm-snapshot-data";
- private static final String ISOLATE_SNAPSHOT_DATA_KEY = "isolate-snapshot-data";
- private static final String FLUTTER_ASSETS_DIR_KEY = "flutter-assets-dir";
-
- // XML Attribute keys supported in AndroidManifest.xml
- public static final String PUBLIC_AOT_SHARED_LIBRARY_NAME =
- FlutterMain.class.getName() + '.' + AOT_SHARED_LIBRARY_NAME;
- public static final String PUBLIC_VM_SNAPSHOT_DATA_KEY =
- FlutterMain.class.getName() + '.' + VM_SNAPSHOT_DATA_KEY;
- public static final String PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY =
- FlutterMain.class.getName() + '.' + ISOLATE_SNAPSHOT_DATA_KEY;
- public static final String PUBLIC_FLUTTER_ASSETS_DIR_KEY =
- FlutterMain.class.getName() + '.' + FLUTTER_ASSETS_DIR_KEY;
-
- // Resource names used for components of the precompiled snapshot.
- private static final String DEFAULT_AOT_SHARED_LIBRARY_NAME = "libapp.so";
- private static final String DEFAULT_VM_SNAPSHOT_DATA = "vm_snapshot_data";
- private static final String DEFAULT_ISOLATE_SNAPSHOT_DATA = "isolate_snapshot_data";
- private static final String DEFAULT_LIBRARY = "libflutter.so";
- private static final String DEFAULT_KERNEL_BLOB = "kernel_blob.bin";
- private static final String DEFAULT_FLUTTER_ASSETS_DIR = "flutter_assets";
-
- private static boolean isRunningInRobolectricTest = false;
-
- @VisibleForTesting
- public static void setIsRunningInRobolectricTest(boolean isRunningInRobolectricTest) {
- FlutterMain.isRunningInRobolectricTest = isRunningInRobolectricTest;
- }
-
- @NonNull
- private static String fromFlutterAssets(@NonNull String filePath) {
- return sFlutterAssetsDir + File.separator + filePath;
- }
-
- // Mutable because default values can be overridden via config properties
- private static String sAotSharedLibraryName = DEFAULT_AOT_SHARED_LIBRARY_NAME;
- private static String sVmSnapshotData = DEFAULT_VM_SNAPSHOT_DATA;
- private static String sIsolateSnapshotData = DEFAULT_ISOLATE_SNAPSHOT_DATA;
- private static String sFlutterAssetsDir = DEFAULT_FLUTTER_ASSETS_DIR;
-
- private static boolean sInitialized = false;
-
- @Nullable
- private static ResourceExtractor sResourceExtractor;
- @Nullable
- private static Settings sSettings;
public static class Settings {
private String logTag;
@@ -103,119 +38,36 @@ public void setLogTag(String tag) {
* @param applicationContext The Android application context.
*/
public static void startInitialization(@NonNull Context applicationContext) {
- // Do nothing if we're running this in a Robolectric test.
- if (isRunningInRobolectricTest) {
- return;
- }
- startInitialization(applicationContext, new Settings());
+ FlutterLoader.getInstance().startInitialization(applicationContext);
}
/**
* Starts initialization of the native system.
+ *
+ * This loads the Flutter engine's native library to enable subsequent JNI calls. This also
+ * starts locating and unpacking Dart resources packaged in the app's APK.
+ *
+ * Calling this method multiple times has no effect.
+ *
* @param applicationContext The Android application context.
* @param settings Configuration settings.
*/
public static void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) {
- // Do nothing if we're running this in a Robolectric test.
- if (isRunningInRobolectricTest) {
- return;
- }
-
- if (Looper.myLooper() != Looper.getMainLooper()) {
- throw new IllegalStateException("startInitialization must be called on the main thread");
- }
- // Do not run startInitialization more than once.
- if (sSettings != null) {
- return;
- }
-
- sSettings = settings;
-
- long initStartTimestampMillis = SystemClock.uptimeMillis();
- initConfig(applicationContext);
- initResources(applicationContext);
-
- System.loadLibrary("flutter");
-
- VsyncWaiter
- .getInstance((WindowManager) applicationContext.getSystemService(Context.WINDOW_SERVICE))
- .init();
-
- // We record the initialization time using SystemClock because at the start of the
- // initialization we have not yet loaded the native library to call into dart_tools_api.h.
- // To get Timeline timestamp of the start of initialization we simply subtract the delta
- // from the Timeline timestamp at the current moment (the assumption is that the overhead
- // of the JNI call is negligible).
- long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
- FlutterJNI.nativeRecordStartTimestamp(initTimeMillis);
+ FlutterLoader.Settings newSettings = new FlutterLoader.Settings();
+ newSettings.setLogTag(settings.getLogTag());
+ FlutterLoader.getInstance().startInitialization(applicationContext, newSettings);
}
/**
* Blocks until initialization of the native system has completed.
+ *
+ * Calling this method multiple times has no effect.
+ *
* @param applicationContext The Android application context.
* @param args Flags sent to the Flutter runtime.
*/
public static void ensureInitializationComplete(@NonNull Context applicationContext, @Nullable String[] args) {
- // Do nothing if we're running this in a Robolectric test.
- if (isRunningInRobolectricTest) {
- return;
- }
-
- if (Looper.myLooper() != Looper.getMainLooper()) {
- throw new IllegalStateException("ensureInitializationComplete must be called on the main thread");
- }
- if (sSettings == null) {
- throw new IllegalStateException("ensureInitializationComplete must be called after startInitialization");
- }
- if (sInitialized) {
- return;
- }
- try {
- if (sResourceExtractor != null) {
- sResourceExtractor.waitForCompletion();
- }
-
- List shellArgs = new ArrayList<>();
- shellArgs.add("--icu-symbol-prefix=_binary_icudtl_dat");
-
- ApplicationInfo applicationInfo = getApplicationInfo(applicationContext);
- shellArgs.add("--icu-native-lib-path=" + applicationInfo.nativeLibraryDir + File.separator + DEFAULT_LIBRARY);
-
- if (args != null) {
- Collections.addAll(shellArgs, args);
- }
-
- String kernelPath = null;
- if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
- String snapshotAssetPath = PathUtils.getDataDirectory(applicationContext) + File.separator + sFlutterAssetsDir;
- kernelPath = snapshotAssetPath + File.separator + DEFAULT_KERNEL_BLOB;
- shellArgs.add("--" + SNAPSHOT_ASSET_PATH_KEY + "=" + snapshotAssetPath);
- shellArgs.add("--" + VM_SNAPSHOT_DATA_KEY + "=" + sVmSnapshotData);
- shellArgs.add("--" + ISOLATE_SNAPSHOT_DATA_KEY + "=" + sIsolateSnapshotData);
- } else {
- shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME + "=" + sAotSharedLibraryName);
-
- // Most devices can load the AOT shared library based on the library name
- // with no directory path. Provide a fully qualified path to the library
- // as a workaround for devices where that fails.
- shellArgs.add("--" + AOT_SHARED_LIBRARY_NAME + "=" + applicationInfo.nativeLibraryDir + File.separator + sAotSharedLibraryName);
- }
-
- shellArgs.add("--cache-dir-path=" + PathUtils.getCacheDirectory(applicationContext));
- if (sSettings.getLogTag() != null) {
- shellArgs.add("--log-tag=" + sSettings.getLogTag());
- }
-
- String appStoragePath = PathUtils.getFilesDir(applicationContext);
- String engineCachesPath = PathUtils.getCacheDirectory(applicationContext);
- FlutterJNI.nativeInit(applicationContext, shellArgs.toArray(new String[0]),
- kernelPath, appStoragePath, engineCachesPath);
-
- sInitialized = true;
- } catch (Exception e) {
- Log.e(TAG, "Flutter initialization failed.", e);
- throw new RuntimeException(e);
- }
+ FlutterLoader.getInstance().ensureInitializationComplete(applicationContext, args);
}
/**
@@ -228,102 +80,19 @@ public static void ensureInitializationCompleteAsync(
@NonNull Handler callbackHandler,
@NonNull Runnable callback
) {
- // Do nothing if we're running this in a Robolectric test.
- if (isRunningInRobolectricTest) {
- return;
- }
-
- if (Looper.myLooper() != Looper.getMainLooper()) {
- throw new IllegalStateException("ensureInitializationComplete must be called on the main thread");
- }
- if (sSettings == null) {
- throw new IllegalStateException("ensureInitializationComplete must be called after startInitialization");
- }
- if (sInitialized) {
- return;
- }
- new Thread(new Runnable() {
- @Override
- public void run() {
- if (sResourceExtractor != null) {
- sResourceExtractor.waitForCompletion();
- }
- new Handler(Looper.getMainLooper()).post(new Runnable() {
- @Override
- public void run() {
- ensureInitializationComplete(applicationContext.getApplicationContext(), args);
- callbackHandler.post(callback);
- }
- });
- }
- }).start();
- }
-
- @NonNull
- private static ApplicationInfo getApplicationInfo(@NonNull Context applicationContext) {
- try {
- return applicationContext
- .getPackageManager()
- .getApplicationInfo(applicationContext.getPackageName(), PackageManager.GET_META_DATA);
- } catch (PackageManager.NameNotFoundException e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * Initialize our Flutter config values by obtaining them from the
- * manifest XML file, falling back to default values.
- */
- private static void initConfig(@NonNull Context applicationContext) {
- Bundle metadata = getApplicationInfo(applicationContext).metaData;
-
- // There isn't a `` tag as a direct child of `` in
- // `AndroidManifest.xml`.
- if (metadata == null) {
- return;
- }
-
- sAotSharedLibraryName = metadata.getString(PUBLIC_AOT_SHARED_LIBRARY_NAME, DEFAULT_AOT_SHARED_LIBRARY_NAME);
- sFlutterAssetsDir = metadata.getString(PUBLIC_FLUTTER_ASSETS_DIR_KEY, DEFAULT_FLUTTER_ASSETS_DIR);
-
- sVmSnapshotData = metadata.getString(PUBLIC_VM_SNAPSHOT_DATA_KEY, DEFAULT_VM_SNAPSHOT_DATA);
- sIsolateSnapshotData = metadata.getString(PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY, DEFAULT_ISOLATE_SNAPSHOT_DATA);
- }
-
- /**
- * Extract assets out of the APK that need to be cached as uncompressed
- * files on disk.
- */
- private static void initResources(@NonNull Context applicationContext) {
- new ResourceCleaner(applicationContext).start();
-
- if (BuildConfig.DEBUG || BuildConfig.JIT_RELEASE) {
- final String dataDirPath = PathUtils.getDataDirectory(applicationContext);
- final String packageName = applicationContext.getPackageName();
- final PackageManager packageManager = applicationContext.getPackageManager();
- final AssetManager assetManager = applicationContext.getResources().getAssets();
- sResourceExtractor = new ResourceExtractor(dataDirPath, packageName, packageManager, assetManager);
-
- // In debug/JIT mode these assets will be written to disk and then
- // mapped into memory so they can be provided to the Dart VM.
- sResourceExtractor
- .addResource(fromFlutterAssets(sVmSnapshotData))
- .addResource(fromFlutterAssets(sIsolateSnapshotData))
- .addResource(fromFlutterAssets(DEFAULT_KERNEL_BLOB));
-
- sResourceExtractor.start();
- }
+ FlutterLoader.getInstance().ensureInitializationCompleteAsync(
+ applicationContext, args, callbackHandler, callback);
}
@NonNull
public static String findAppBundlePath() {
- return sFlutterAssetsDir;
+ return FlutterLoader.getInstance().findAppBundlePath();
}
@Deprecated
@Nullable
public static String findAppBundlePath(@NonNull Context applicationContext) {
- return sFlutterAssetsDir;
+ return FlutterLoader.getInstance().findAppBundlePath();
}
/**
@@ -336,7 +105,7 @@ public static String findAppBundlePath(@NonNull Context applicationContext) {
*/
@NonNull
public static String getLookupKeyForAsset(@NonNull String asset) {
- return fromFlutterAssets(asset);
+ return FlutterLoader.getInstance().getLookupKeyForAsset(asset);
}
/**
@@ -350,7 +119,6 @@ public static String getLookupKeyForAsset(@NonNull String asset) {
*/
@NonNull
public static String getLookupKeyForAsset(@NonNull String asset, @NonNull String packageName) {
- return getLookupKeyForAsset(
- "packages" + File.separator + packageName + File.separator + asset);
+ return FlutterLoader.getInstance().getLookupKeyForAsset(asset, packageName);
}
}
diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java
index 953abb5a053e9..154c59f173191 100644
--- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java
+++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java
@@ -6,7 +6,6 @@
import android.content.Intent;
import android.support.annotation.NonNull;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -28,7 +27,6 @@
import io.flutter.embedding.engine.systemchannels.SettingsChannel;
import io.flutter.embedding.engine.systemchannels.SystemChannel;
import io.flutter.plugin.platform.PlatformViewsController;
-import io.flutter.view.FlutterMain;
import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;
import static org.junit.Assert.assertEquals;
@@ -49,10 +47,6 @@ public class FlutterActivityAndFragmentDelegateTest {
@Before
public void setup() {
- // FlutterMain is utilized statically, therefore we need to inform it to behave differently
- // for testing purposes.
- FlutterMain.setIsRunningInRobolectricTest(true);
-
// Create a mocked FlutterEngine for the various interactions required by the delegate
// being tested.
mockFlutterEngine = mockFlutterEngine();
@@ -73,12 +67,6 @@ public void setup() {
when(mockHost.shouldDestroyEngineWithHost()).thenReturn(true);
}
- @After
- public void teardown() {
- // Return FlutterMain to normal.
- FlutterMain.setIsRunningInRobolectricTest(false);
- }
-
@Test
public void itSendsLifecycleEventsToFlutter() {
// ---- Test setup ----
diff --git a/testing/scenario_app/README.md b/testing/scenario_app/README.md
index 8ecbf6c0312e0..082794fb8e0da 100644
--- a/testing/scenario_app/README.md
+++ b/testing/scenario_app/README.md
@@ -45,3 +45,8 @@ the app in the `android/` folder. The app can be run by opening it in Android
Studio and running it, or by running `./gradlew assemble` in the `android/`
folder and installing the APK from the correct folder in
`android/app/build/outputs/apk`.
+
+## Changing dart:ui code
+
+If you change the dart:ui interface, remember to point the sky_engine and
+sky_services clauses to your local engine's output path before compiling.
\ No newline at end of file
diff --git a/testing/scenario_app/android/app/build.gradle b/testing/scenario_app/android/app/build.gradle
index 473a71e1f008f..0fbd03be46952 100644
--- a/testing/scenario_app/android/app/build.gradle
+++ b/testing/scenario_app/android/app/build.gradle
@@ -27,8 +27,9 @@ dependencies {
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.android.support:design:28.0.0'
- implementation 'android.arch.lifecycle:common-java8:1.1.0'
+ implementation 'android.arch.lifecycle:common-java8:1.1.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support.test:rules:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
diff --git a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenarios/EngineLaunchE2ETest.java b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenarios/EngineLaunchE2ETest.java
new file mode 100644
index 0000000000000..2f86d55fedfa5
--- /dev/null
+++ b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenarios/EngineLaunchE2ETest.java
@@ -0,0 +1,69 @@
+// 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 dev.flutter.scenarios;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.internal.runner.junit4.statement.UiThreadStatement;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import io.flutter.embedding.engine.FlutterEngine;
+import io.flutter.embedding.engine.dart.DartExecutor;
+
+import static org.junit.Assert.fail;
+
+@RunWith(AndroidJUnit4.class)
+public class EngineLaunchE2ETest {
+ @Test
+ public void smokeTestEngineLaunch() throws Throwable {
+ Context applicationContext = InstrumentationRegistry.getTargetContext();
+ // Specifically, create the engine without running FlutterMain first.
+ final AtomicReference engine = new AtomicReference<>();
+
+ // Run the production under test on the UI thread instead of annotating the whole test
+ // as @UiThreadTest because having the message handler and the CompletableFuture both being
+ // on the same thread will create deadlocks.
+ UiThreadStatement.runOnUiThread(
+ () -> engine.set(new FlutterEngine(applicationContext))
+ );
+ CompletableFuture statusReceived = new CompletableFuture<>();
+
+ // The default Dart main entrypoint sends back a platform message on the "scenario_status"
+ // channel. That will be our launch success assertion condition.
+ engine.get().getDartExecutor().setMessageHandler(
+ "scenario_status",
+ (byteBuffer, binaryReply) -> statusReceived.complete(Boolean.TRUE)
+ );
+
+ // Launching the entrypoint will run the Dart code that sends the "scenario_status" platform
+ // message.
+ UiThreadStatement.runOnUiThread(
+ () -> engine.get().getDartExecutor().executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault())
+ );
+
+ try {
+ Boolean result = statusReceived.get(10, TimeUnit.SECONDS);
+ if (!result) {
+ fail("expected message on scenario_status not received");
+ }
+ } catch (ExecutionException e) {
+ fail(e.getMessage());
+ } catch (InterruptedException e) {
+ fail(e.getMessage());
+ } catch (TimeoutException e) {
+ fail("timed out waiting for engine started signal");
+ }
+ // If it gets to here, statusReceived is true.
+ }
+}
diff --git a/testing/scenario_app/android/app/src/main/AndroidManifest.xml b/testing/scenario_app/android/app/src/main/AndroidManifest.xml
index 039498df0e9e0..5b9a040276c88 100644
--- a/testing/scenario_app/android/app/src/main/AndroidManifest.xml
+++ b/testing/scenario_app/android/app/src/main/AndroidManifest.xml
@@ -1,7 +1,7 @@
-
+
-
-
-
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/BlankActivity.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/BlankActivity.java
new file mode 100644
index 0000000000000..01560e71902cb
--- /dev/null
+++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/BlankActivity.java
@@ -0,0 +1,11 @@
+package dev.flutter.scenarios;
+
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+
+public class BlankActivity extends AppCompatActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+}
diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/MainActivity.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TextPlatformViewActivity.java
similarity index 94%
rename from testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/MainActivity.java
rename to testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TextPlatformViewActivity.java
index 2d237bdbb10cc..cf98dabc8b12d 100644
--- a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/MainActivity.java
+++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TextPlatformViewActivity.java
@@ -15,14 +15,12 @@
import io.flutter.Log;
import io.flutter.embedding.android.FlutterActivity;
-import io.flutter.embedding.android.FlutterFragment;
-import io.flutter.embedding.android.FlutterView;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterShellArgs;
import io.flutter.plugin.common.BasicMessageChannel;
import io.flutter.plugin.common.BinaryCodec;
-public class MainActivity extends FlutterActivity {
+public class TextPlatformViewActivity extends FlutterActivity {
final static String TAG = "Scenarios";
@Override
diff --git a/testing/scenario_app/android/build.gradle b/testing/scenario_app/android/build.gradle
index e11a5b354c4d4..f5fb2ccce69da 100644
--- a/testing/scenario_app/android/build.gradle
+++ b/testing/scenario_app/android/build.gradle
@@ -7,7 +7,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.3.2'
+ classpath 'com.android.tools.build:gradle:3.5.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/testing/scenario_app/run_android_tests.sh b/testing/scenario_app/run_android_tests.sh
new file mode 100755
index 0000000000000..fc173e11af796
--- /dev/null
+++ b/testing/scenario_app/run_android_tests.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+# 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.
+
+# Runs the Android scenario tests on a connected device.
+
+set -e
+
+FLUTTER_ENGINE=android_profile_unopt_arm64
+
+if [ $# -eq 1 ]; then
+ FLUTTER_ENGINE=$1
+fi
+
+cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd
+
+./compile_android_aot.sh ../../../out/host_profile_unopt_arm64 ../../../out/$FLUTTER_ENGINE/clang_x64
+
+pushd android
+
+set -o pipefail && ./gradlew assembleAndroidTest && ./gradlew connectedAndroidTest
+
+popd