Generate projects using the new Android embedding (#41666)
* Generate projects using the new Android embedding
* Add comment about usesNewEmbedding:true
* Feedback
* Rework way to detect new embedding in new apps
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index ed64c50..71205ff 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -940,7 +940,6 @@
/// Returns [true] if the current app uses AndroidX.
// TODO(egarciad): https://github.com/flutter/flutter/issues/40800
// Remove `FlutterManifest.usesAndroidX` and provide a unified `AndroidProject.usesAndroidX`.
-@visibleForTesting
bool isAppUsingAndroidX(Directory androidDirectory) {
final File properties = androidDirectory.childFile('gradle.properties');
if (!properties.existsSync()) {
diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart
index 4c35620..849205a 100644
--- a/packages/flutter_tools/lib/src/commands/create.dart
+++ b/packages/flutter_tools/lib/src/commands/create.dart
@@ -623,6 +623,7 @@
'description': projectDescription,
'dartSdk': '$flutterRoot/bin/cache/dart-sdk',
'androidX': androidX,
+ 'useNewAndroidEmbedding': featureFlags.isNewAndroidEmbeddingEnabled,
'androidMinApiLevel': android.minApiLevel,
'androidSdkVersion': android_sdk.minimumAndroidSdkVersion,
'androidFlutterJar': '$flutterRoot/bin/cache/artifacts/engine/android-arm/flutter.jar',
diff --git a/packages/flutter_tools/lib/src/features.dart b/packages/flutter_tools/lib/src/features.dart
index 1701267..b0d979c 100644
--- a/packages/flutter_tools/lib/src/features.dart
+++ b/packages/flutter_tools/lib/src/features.dart
@@ -36,6 +36,9 @@
/// Whether flutter desktop for Windows is enabled.
bool get isWindowsEnabled => _isEnabled(flutterWindowsDesktopFeature);
+ /// Whether the new Android embedding is enabled.
+ bool get isNewAndroidEmbeddingEnabled => _isEnabled(flutterNewAndroidEmbeddingFeature);
+
// Calculate whether a particular feature is enabled for the current channel.
static bool _isEnabled(Feature feature) {
final String currentChannel = FlutterVersion.instance.channel;
@@ -66,6 +69,7 @@
flutterMacOSDesktopFeature,
flutterWindowsDesktopFeature,
flutterBuildPluginAsAarFeature,
+ flutterNewAndroidEmbeddingFeature,
];
/// The [Feature] for flutter web.
@@ -126,6 +130,16 @@
),
);
+/// The [Feature] for generating projects using the new Android embedding.
+const Feature flutterNewAndroidEmbeddingFeature = Feature(
+ name: 'flutter create generates projects using the new Android embedding',
+ configSetting: 'enable-new-android-embedding',
+ master: FeatureChannelSetting(
+ available: true,
+ enabledByDefault: false,
+ ),
+);
+
/// A [Feature] is a process for conditionally enabling tool features.
///
/// All settings are optional, and if not provided will generally default to
diff --git a/packages/flutter_tools/lib/src/platform_plugins.dart b/packages/flutter_tools/lib/src/platform_plugins.dart
index 2b9976b..aba2087 100644
--- a/packages/flutter_tools/lib/src/platform_plugins.dart
+++ b/packages/flutter_tools/lib/src/platform_plugins.dart
@@ -5,6 +5,10 @@
import 'package:meta/meta.dart';
import 'package:yaml/yaml.dart';
+import 'base/common.dart';
+import 'base/file_system.dart';
+import 'features.dart';
+
/// Marker interface for all platform specific plugin config impls.
abstract class PluginPlatform {
const PluginPlatform();
@@ -17,18 +21,20 @@
/// The required fields include: [name] of the plugin, [package] of the plugin and
/// the [pluginClass] that will be the entry point to the plugin's native code.
class AndroidPlugin extends PluginPlatform {
- const AndroidPlugin({
+ AndroidPlugin({
@required this.name,
@required this.package,
@required this.pluginClass,
+ @required this.pluginPath,
});
- factory AndroidPlugin.fromYaml(String name, YamlMap yaml) {
+ factory AndroidPlugin.fromYaml(String name, YamlMap yaml, String pluginPath) {
assert(validate(yaml));
return AndroidPlugin(
name: name,
package: yaml['package'],
pluginClass: yaml['pluginClass'],
+ pluginPath: pluginPath,
);
}
@@ -41,18 +47,80 @@
static const String kConfigKey = 'android';
+ /// The plugin name defined in pubspec.yaml.
final String name;
+
+ /// The plugin package name defined in pubspec.yaml.
final String package;
+
+ /// The plugin main class defined in pubspec.yaml.
final String pluginClass;
+ /// The absolute path to the plugin in the pub cache.
+ final String pluginPath;
+
@override
Map<String, dynamic> toMap() {
return <String, dynamic>{
'name': name,
'package': package,
'class': pluginClass,
+ 'usesEmbedding2': _embeddingVersion == '2',
};
}
+
+ String _cachedEmbeddingVersion;
+
+ /// Returns the version of the Android embedding.
+ String get _embeddingVersion => _cachedEmbeddingVersion ??= _getEmbeddingVersion();
+
+ String _getEmbeddingVersion() {
+ if (!featureFlags.isNewAndroidEmbeddingEnabled) {
+ return '1';
+ }
+ assert(pluginPath != null);
+ final String baseMainPath = fs.path.join(
+ pluginPath,
+ 'android',
+ 'src',
+ 'main',
+ );
+ File mainPluginClass = fs.file(
+ fs.path.join(
+ baseMainPath,
+ 'java',
+ package.replaceAll('.', fs.path.separator),
+ '$pluginClass.java',
+ )
+ );
+ // Check if the plugin is implemented in Kotlin since the plugin's pubspec.yaml
+ // doesn't include this information.
+ if (!mainPluginClass.existsSync()) {
+ mainPluginClass = fs.file(
+ fs.path.join(
+ baseMainPath,
+ 'kotlin',
+ package.replaceAll('.', fs.path.separator),
+ '$pluginClass.kt',
+ )
+ );
+ }
+ assert(mainPluginClass.existsSync());
+ String mainClassContent;
+ try {
+ mainClassContent = mainPluginClass.readAsStringSync();
+ } on FileSystemException {
+ throwToolExit(
+ 'Couldn\'t read file $mainPluginClass even though it exists. '
+ 'Please verify that this file has read permission and try again.'
+ );
+ }
+ if (mainClassContent
+ .contains('io.flutter.embedding.engine.plugins.FlutterPlugin')) {
+ return '2';
+ }
+ return '1';
+ }
}
/// Contains the parameters to template an iOS plugin.
diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart
index 56ebd0d..997188f 100644
--- a/packages/flutter_tools/lib/src/plugins.dart
+++ b/packages/flutter_tools/lib/src/plugins.dart
@@ -5,8 +5,10 @@
import 'dart:async';
import 'package:mustache/mustache.dart' as mustache;
+import 'package:xml/xml.dart' as xml;
import 'package:yaml/yaml.dart';
+import 'android/gradle.dart';
import 'base/common.dart';
import 'base/file_system.dart';
import 'dart/package_map.dart';
@@ -63,9 +65,8 @@
}
if (pluginYaml != null && pluginYaml['platforms'] != null) {
return Plugin._fromMultiPlatformYaml(name, path, pluginYaml);
- } else {
- return Plugin._fromLegacyYaml(name, path, pluginYaml); // ignore: deprecated_member_use_from_same_package
}
+ return Plugin._fromLegacyYaml(name, path, pluginYaml); // ignore: deprecated_member_use_from_same_package
}
factory Plugin._fromMultiPlatformYaml(String name, String path, dynamic pluginYaml) {
@@ -79,8 +80,11 @@
final Map<String, PluginPlatform> platforms = <String, PluginPlatform>{};
if (platformsYaml[AndroidPlugin.kConfigKey] != null) {
- platforms[AndroidPlugin.kConfigKey] =
- AndroidPlugin.fromYaml(name, platformsYaml[AndroidPlugin.kConfigKey]);
+ platforms[AndroidPlugin.kConfigKey] = AndroidPlugin.fromYaml(
+ name,
+ platformsYaml[AndroidPlugin.kConfigKey],
+ path,
+ );
}
if (platformsYaml[IOSPlugin.kConfigKey] != null) {
@@ -122,12 +126,12 @@
if (pluginYaml != null && pluginClass != null) {
final String androidPackage = pluginYaml['androidPackage'];
if (androidPackage != null) {
- platforms[AndroidPlugin.kConfigKey] =
- AndroidPlugin(
- name: name,
- package: pluginYaml['androidPackage'],
- pluginClass: pluginClass,
- );
+ platforms[AndroidPlugin.kConfigKey] = AndroidPlugin(
+ name: name,
+ package: pluginYaml['androidPackage'],
+ pluginClass: pluginClass,
+ pluginPath: path,
+ );
}
final String iosPrefix = pluginYaml['iosPrefix'] ?? '';
@@ -221,14 +225,21 @@
}
final String packageRootPath = fs.path.fromUri(packageRoot);
printTrace('Found plugin $name at $packageRootPath');
- return Plugin.fromYaml(name, packageRootPath, flutterConfig['plugin']);
+ return Plugin.fromYaml(
+ name,
+ packageRootPath,
+ flutterConfig['plugin'],
+ );
}
List<Plugin> findPlugins(FlutterProject project) {
final List<Plugin> plugins = <Plugin>[];
Map<String, Uri> packages;
try {
- final String packagesFile = fs.path.join(project.directory.path, PackageMap.globalPackagesPath);
+ final String packagesFile = fs.path.join(
+ project.directory.path,
+ PackageMap.globalPackagesPath,
+ );
packages = PackageMap(packagesFile).map;
} on FormatException catch (e) {
printTrace('Invalid .packages file: $e');
@@ -269,7 +280,7 @@
: null;
}
-const String _androidPluginRegistryTemplate = '''package io.flutter.plugins;
+const String _androidPluginRegistryTemplateOldEmbedding = '''package io.flutter.plugins;
import io.flutter.plugin.common.PluginRegistry;
{{#plugins}}
@@ -300,6 +311,41 @@
}
''';
+const String _androidPluginRegistryTemplateNewEmbedding = '''package dev.flutter.plugins;
+
+{{#androidX}}
+import androidx.annotation.NonNull;
+{{/androidX}}
+{{^androidX}}
+import android.support.annotation.NonNull;
+{{/androidX}}
+import io.flutter.embedding.engine.FlutterEngine;
+{{#needsShim}}
+import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry;
+{{/needsShim}}
+
+/**
+ * Generated file. Do not edit.
+ * This file is generated by the Flutter tool based on the
+ * plugins that support the Android platform.
+ */
+public final class GeneratedPluginRegistrant {
+ public static void registerWith(@NonNull FlutterEngine flutterEngine) {
+{{#needsShim}}
+ ShimPluginRegistry shimPluginRegistry = new ShimPluginRegistry(flutterEngine);
+{{/needsShim}}
+{{#plugins}}
+ {{#usesEmbedding2}}
+ flutterEngine.getPlugins().add(new {{package}}.{{class}}());
+ {{/usesEmbedding2}}
+ {{^usesEmbedding2}}
+ {{package}}.{{class}}.registerWith(shimPluginRegistry.registrarFor("{{package}}.{{class}}"));
+ {{/usesEmbedding2}}
+{{/plugins}}
+ }
+}
+''';
+
List<Map<String, dynamic>> _extractPlatformMaps(List<Plugin> plugins, String type) {
final List<Map<String, dynamic>> pluginConfigs = <Map<String, dynamic>>[];
for (Plugin p in plugins) {
@@ -311,26 +357,92 @@
return pluginConfigs;
}
-Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
- final List<Map<String, dynamic>> androidPlugins = _extractPlatformMaps(plugins, AndroidPlugin.kConfigKey);
- final Map<String, dynamic> context = <String, dynamic>{
- 'plugins': androidPlugins,
- };
+/// Returns the version of the Android embedding that the current
+/// [project] is using.
+String _getAndroidEmbeddingVersion(FlutterProject project) {
+ if (!featureFlags.isNewAndroidEmbeddingEnabled) {
+ return '1';
+ }
+ assert(project.android != null);
+ final File androidManifest = project.android.appManifestFile;
+ assert(androidManifest.existsSync());
+ xml.XmlDocument document;
+ try {
+ document = xml.parse(androidManifest.readAsStringSync());
+ } on xml.XmlParserException {
+ throwToolExit('Error parsing ${project.android.appManifestFile} '
+ 'Please ensure that the android manifest is a valid XML document and try again.');
+ } on FileSystemException {
+ throwToolExit('Error reading ${project.android.appManifestFile} even though it exists. '
+ 'Please ensure that you have read permission to this file and try again.');
+ }
+ for (xml.XmlElement metaData in document.findAllElements('meta-data')) {
+ final String name = metaData.getAttribute('android:name');
+ if (name == 'flutterEmbedding') {
+ return metaData.getAttribute('android:value');
+ }
+ }
+ return '1';
+}
+Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
+ final List<Map<String, dynamic>> androidPlugins =
+ _extractPlatformMaps(plugins, AndroidPlugin.kConfigKey);
+
+ final Map<String, dynamic> templateContext = <String, dynamic>{
+ 'plugins': androidPlugins,
+ 'androidX': isAppUsingAndroidX(project.android.hostAppGradleRoot),
+ };
final String javaSourcePath = fs.path.join(
project.android.pluginRegistrantHost.path,
'src',
'main',
'java',
);
- final String registryPath = fs.path.join(
- javaSourcePath,
- 'io',
- 'flutter',
- 'plugins',
- 'GeneratedPluginRegistrant.java',
+
+ String registryPath;
+ String templateContent;
+
+ final String appEmbeddingVersion = _getAndroidEmbeddingVersion(project);
+ switch (appEmbeddingVersion) {
+ case '2':
+ templateContext['needsShim'] = false;
+ // If a plugin is using an embedding version older than 2.0 and the app is using 2.0,
+ // then add shim for the old plugins.
+ for (Map<String, dynamic> plugin in androidPlugins) {
+ if (!plugin['usesEmbedding2']) {
+ templateContext['needsShim'] = true;
+ break;
+ }
+ }
+ registryPath = fs.path.join(
+ javaSourcePath,
+ 'dev',
+ 'flutter',
+ 'plugins',
+ 'GeneratedPluginRegistrant.java',
+ );
+ templateContent = _androidPluginRegistryTemplateNewEmbedding;
+ break;
+ case '1':
+ registryPath = fs.path.join(
+ javaSourcePath,
+ 'io',
+ 'flutter',
+ 'plugins',
+ 'GeneratedPluginRegistrant.java',
+ );
+ templateContent = _androidPluginRegistryTemplateOldEmbedding;
+ break;
+ default:
+ throwToolExit('Unsupported Android embedding');
+ }
+ printTrace('Generating $registryPath');
+ _renderTemplateToFile(
+ templateContent,
+ templateContext,
+ registryPath,
);
- _renderTemplateToFile(_androidPluginRegistryTemplate, context, registryPath);
}
const String _objcPluginRegistryHeaderTemplate = '''//
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index dd5ce15..3b7ad27 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -608,7 +608,11 @@
void _regenerateLibrary() {
_deleteIfExistsSync(ephemeralDirectory);
- _overwriteFromTemplate(fs.path.join('module', 'android', 'library'), ephemeralDirectory);
+ _overwriteFromTemplate(fs.path.join(
+ 'module',
+ 'android',
+ featureFlags.isNewAndroidEmbeddingEnabled ? 'library_new_embedding' : 'library',
+ ), ephemeralDirectory);
_overwriteFromTemplate(fs.path.join('module', 'android', 'gradle'), ephemeralDirectory);
gradle.injectGradleWrapperIfNeeded(ephemeralDirectory);
}
@@ -621,6 +625,7 @@
'projectName': parent.manifest.appName,
'androidIdentifier': parent.manifest.androidPackage,
'androidX': usesAndroidX,
+ 'useNewAndroidEmbedding': featureFlags.isNewAndroidEmbeddingEnabled,
},
printStatusWhenWriting: false,
overwriteExisting: true,
diff --git a/packages/flutter_tools/templates/app/android-java.tmpl/app/src/main/java/androidIdentifier/MainActivity.java.tmpl b/packages/flutter_tools/templates/app/android-java.tmpl/app/src/main/java/androidIdentifier/MainActivity.java.tmpl
index 226d9ff..6506de1 100644
--- a/packages/flutter_tools/templates/app/android-java.tmpl/app/src/main/java/androidIdentifier/MainActivity.java.tmpl
+++ b/packages/flutter_tools/templates/app/android-java.tmpl/app/src/main/java/androidIdentifier/MainActivity.java.tmpl
@@ -1,5 +1,24 @@
package {{androidIdentifier}};
+{{#useNewAndroidEmbedding}}
+{{#androidX}}
+import androidx.annotation.NonNull;
+{{/androidX}}
+{{^androidX}}
+import android.support.annotation.NonNull;
+{{/androidX}}
+import dev.flutter.plugins.GeneratedPluginRegistrant;
+import io.flutter.embedding.android.FlutterActivity;
+import io.flutter.embedding.engine.FlutterEngine;
+
+public class MainActivity extends FlutterActivity {
+ @Override
+ public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
+ GeneratedPluginRegistrant.registerWith(flutterEngine);
+ }
+}
+{{/useNewAndroidEmbedding}}
+{{^useNewAndroidEmbedding}}
import android.os.Bundle;
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
@@ -11,3 +30,4 @@
GeneratedPluginRegistrant.registerWith(this);
}
}
+{{/useNewAndroidEmbedding}}
diff --git a/packages/flutter_tools/templates/app/android-kotlin.tmpl/app/src/main/kotlin/androidIdentifier/MainActivity.kt.tmpl b/packages/flutter_tools/templates/app/android-kotlin.tmpl/app/src/main/kotlin/androidIdentifier/MainActivity.kt.tmpl
index ec654f4..fe04735 100644
--- a/packages/flutter_tools/templates/app/android-kotlin.tmpl/app/src/main/kotlin/androidIdentifier/MainActivity.kt.tmpl
+++ b/packages/flutter_tools/templates/app/android-kotlin.tmpl/app/src/main/kotlin/androidIdentifier/MainActivity.kt.tmpl
@@ -1,7 +1,24 @@
package {{androidIdentifier}}
-import android.os.Bundle
+{{#useNewAndroidEmbedding}}
+{{#androidX}}
+import androidx.annotation.NonNull;
+{{/androidX}}
+{{^androidX}}
+import android.support.annotation.NonNull;
+{{/androidX}}
+import dev.flutter.plugins.GeneratedPluginRegistrant
+import io.flutter.embedding.android.FlutterActivity
+import io.flutter.embedding.engine.FlutterEngine
+class MainActivity: FlutterActivity() {
+ override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
+ GeneratedPluginRegistrant.registerWith(flutterEngine);
+ }
+}
+{{/useNewAndroidEmbedding}}
+{{^useNewAndroidEmbedding}}
+import android.os.Bundle
import io.flutter.app.FlutterActivity
import io.flutter.plugins.GeneratedPluginRegistrant
@@ -11,3 +28,4 @@
GeneratedPluginRegistrant.registerWith(this)
}
}
+{{/useNewAndroidEmbedding}}
diff --git a/packages/flutter_tools/templates/app/android.tmpl/app/src/main/AndroidManifest.xml.tmpl b/packages/flutter_tools/templates/app/android.tmpl/app/src/main/AndroidManifest.xml.tmpl
index 4778a20..bc1305e 100644
--- a/packages/flutter_tools/templates/app/android.tmpl/app/src/main/AndroidManifest.xml.tmpl
+++ b/packages/flutter_tools/templates/app/android.tmpl/app/src/main/AndroidManifest.xml.tmpl
@@ -1,6 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="{{androidIdentifier}}">
-
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
@@ -29,5 +28,12 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ {{#useNewAndroidEmbedding}}
+ <!-- Don't delete the meta-data below.
+ This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
+ <meta-data
+ android:name="flutterEmbedding"
+ android:value="2" />
+ {{/useNewAndroidEmbedding}}
</application>
</manifest>
diff --git a/packages/flutter_tools/templates/module/android/host_app_common/app.tmpl/src/main/AndroidManifest.xml.tmpl b/packages/flutter_tools/templates/module/android/host_app_common/app.tmpl/src/main/AndroidManifest.xml.tmpl
index a78b20a..3ef4d25 100644
--- a/packages/flutter_tools/templates/module/android/host_app_common/app.tmpl/src/main/AndroidManifest.xml.tmpl
+++ b/packages/flutter_tools/templates/module/android/host_app_common/app.tmpl/src/main/AndroidManifest.xml.tmpl
@@ -36,5 +36,12 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ {{#useNewAndroidEmbedding}}
+ <!-- Don't delete the meta-data below.
+ This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
+ <meta-data
+ android:name="flutterEmbedding"
+ android:value="2" />
+ {{/useNewAndroidEmbedding}}
</application>
</manifest>
diff --git a/packages/flutter_tools/templates/module/android/host_app_common/app.tmpl/src/main/java/androidIdentifier/host/MainActivity.java.tmpl b/packages/flutter_tools/templates/module/android/host_app_common/app.tmpl/src/main/java/androidIdentifier/host/MainActivity.java.tmpl
index 3441c3a..bc7daff 100644
--- a/packages/flutter_tools/templates/module/android/host_app_common/app.tmpl/src/main/java/androidIdentifier/host/MainActivity.java.tmpl
+++ b/packages/flutter_tools/templates/module/android/host_app_common/app.tmpl/src/main/java/androidIdentifier/host/MainActivity.java.tmpl
@@ -1,6 +1,25 @@
package {{androidIdentifier}}.host;
import android.os.Bundle;
+{{#useNewAndroidEmbedding}}
+{{#androidX}}
+import androidx.annotation.NonNull;
+{{/androidX}}
+{{^androidX}}
+import android.support.annotation.NonNull;
+{{/androidX}}
+import dev.flutter.plugins.GeneratedPluginRegistrant;
+import io.flutter.embedding.android.FlutterActivity;
+import io.flutter.embedding.engine.FlutterEngine;
+
+public class MainActivity extends FlutterActivity {
+ @Override
+ public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
+ GeneratedPluginRegistrant.registerWith(flutterEngine);
+ }
+}
+{{/useNewAndroidEmbedding}}
+{{^useNewAndroidEmbedding}}
import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant;
@@ -11,3 +30,4 @@
GeneratedPluginRegistrant.registerWith(this);
}
}
+{{/useNewAndroidEmbedding}}
diff --git a/packages/flutter_tools/templates/module/android/library_new_embedding/Flutter.tmpl/build.gradle.tmpl b/packages/flutter_tools/templates/module/android/library_new_embedding/Flutter.tmpl/build.gradle.tmpl
new file mode 100644
index 0000000..0e9b8fd
--- /dev/null
+++ b/packages/flutter_tools/templates/module/android/library_new_embedding/Flutter.tmpl/build.gradle.tmpl
@@ -0,0 +1,55 @@
+// Generated file. Do not edit.
+
+def localProperties = new Properties()
+def localPropertiesFile = new File(buildscript.sourceFile.parentFile.parentFile, 'local.properties')
+if (localPropertiesFile.exists()) {
+ localPropertiesFile.withReader('UTF-8') { reader ->
+ localProperties.load(reader)
+ }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+ throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+ flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+ flutterVersionName = '1.0'
+}
+
+apply plugin: 'com.android.library'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+group '{{androidIdentifier}}'
+version '1.0'
+
+android {
+ compileSdkVersion 28
+
+ defaultConfig {
+ minSdkVersion 16
+ targetSdkVersion 28
+ versionCode flutterVersionCode.toInteger()
+ versionName flutterVersionName
+ {{#androidX}}
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ {{/androidX}}
+ {{^androidX}}
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ {{/androidX}}
+ }
+}
+
+flutter {
+ source '../..'
+}
+
+dependencies {
+ testImplementation 'junit:junit:4.12'
+}
diff --git a/packages/flutter_tools/templates/module/android/library_new_embedding/Flutter.tmpl/flutter.iml.copy.tmpl b/packages/flutter_tools/templates/module/android/library_new_embedding/Flutter.tmpl/flutter.iml.copy.tmpl
new file mode 100644
index 0000000..29411bd
--- /dev/null
+++ b/packages/flutter_tools/templates/module/android/library_new_embedding/Flutter.tmpl/flutter.iml.copy.tmpl
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module external.linked.project.id=":flutter" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/../../.." external.system.id="GRADLE" type="JAVA_MODULE" version="4">
+ <component name="FacetManager">
+ <facet type="android-gradle" name="Android-Gradle">
+ <configuration>
+ <option name="GRADLE_PROJECT_PATH" value=":flutter" />
+ </configuration>
+ </facet>
+ <facet type="android" name="Android">
+ <configuration>
+ <option name="ALLOW_USER_CONFIGURATION" value="false" />
+ <option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" />
+ </configuration>
+ </facet>
+ </component>
+ <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
+ <output url="file://$MODULE_DIR$/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes" />
+ <output-test url="file://$MODULE_DIR$/build/intermediates/javac/debugUnitTest/compileDebugUnitTestJavaWithJavac/classes" />
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+ </content>
+ <orderEntry type="jdk" jdkName="Android API 28 Platform" jdkType="Android SDK" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/packages/flutter_tools/templates/module/android/library_new_embedding/Flutter.tmpl/src/main/AndroidManifest.xml.tmpl b/packages/flutter_tools/templates/module/android/library_new_embedding/Flutter.tmpl/src/main/AndroidManifest.xml.tmpl
new file mode 100644
index 0000000..7e9f4f0
--- /dev/null
+++ b/packages/flutter_tools/templates/module/android/library_new_embedding/Flutter.tmpl/src/main/AndroidManifest.xml.tmpl
@@ -0,0 +1,16 @@
+<!-- Generated file. Do not edit. -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="{{androidIdentifier}}"
+ xmlns:tools="http://schemas.android.com/tools">
+ <uses-permission android:name="android.permission.INTERNET"/>
+ <application tools:node="merge">
+ <meta-data
+ android:name="flutterProjectType"
+ android:value="module" />
+ <!-- Don't delete the meta-data below.
+ It is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
+ <meta-data
+ android:name="flutterEmbedding"
+ android:value="2" />
+ </application>
+</manifest>
diff --git a/packages/flutter_tools/templates/module/android/library_new_embedding/include_flutter.groovy.copy.tmpl b/packages/flutter_tools/templates/module/android/library_new_embedding/include_flutter.groovy.copy.tmpl
new file mode 100644
index 0000000..7be7efb
--- /dev/null
+++ b/packages/flutter_tools/templates/module/android/library_new_embedding/include_flutter.groovy.copy.tmpl
@@ -0,0 +1,48 @@
+// Generated file. Do not edit.
+
+def scriptFile = getClass().protectionDomain.codeSource.location.toURI()
+def flutterProjectRoot = new File(scriptFile).parentFile.parentFile
+
+gradle.include ':flutter'
+gradle.project(':flutter').projectDir = new File(flutterProjectRoot, '.android/Flutter')
+
+if (System.getProperty('build-plugins-as-aars') != 'true') {
+ def plugins = new Properties()
+ def pluginsFile = new File(flutterProjectRoot, '.flutter-plugins')
+ if (pluginsFile.exists()) {
+ pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
+ }
+
+ plugins.each { name, path ->
+ def pluginDirectory = flutterProjectRoot.toPath().resolve(path).resolve('android').toFile()
+ gradle.include ":$name"
+ gradle.project(":$name").projectDir = pluginDirectory
+ }
+}
+gradle.getGradle().projectsLoaded { g ->
+ g.rootProject.beforeEvaluate { p ->
+ _mainModuleName = binding.variables['mainModuleName']
+ if (_mainModuleName != null && !_mainModuleName.empty) {
+ p.ext.mainModuleName = _mainModuleName
+ }
+ def subprojects = []
+ def flutterProject
+ p.subprojects { sp ->
+ if (sp.name == 'flutter') {
+ flutterProject = sp
+ } else {
+ subprojects.add(sp)
+ }
+ }
+ assert flutterProject != null
+ flutterProject.ext.hostProjects = subprojects
+ flutterProject.ext.pluginBuildDir = new File(flutterProjectRoot, 'build/host')
+ }
+ g.rootProject.afterEvaluate { p ->
+ p.subprojects { sp ->
+ if (sp.name != 'flutter') {
+ sp.evaluationDependsOn(':flutter')
+ }
+ }
+ }
+}
diff --git a/packages/flutter_tools/templates/module/android/library_new_embedding/settings.gradle.copy.tmpl b/packages/flutter_tools/templates/module/android/library_new_embedding/settings.gradle.copy.tmpl
new file mode 100644
index 0000000..22b1cdd
--- /dev/null
+++ b/packages/flutter_tools/templates/module/android/library_new_embedding/settings.gradle.copy.tmpl
@@ -0,0 +1,5 @@
+// Generated file. Do not edit.
+
+rootProject.name = 'android_generated'
+setBinding(new Binding([gradle: this]))
+evaluate(new File(settingsDir, 'include_flutter.groovy'))
diff --git a/packages/flutter_tools/templates/plugin/android-java.tmpl/src/main/java/androidIdentifier/pluginClass.java.tmpl b/packages/flutter_tools/templates/plugin/android-java.tmpl/src/main/java/androidIdentifier/pluginClass.java.tmpl
index 01bb87f..f6c9d52 100644
--- a/packages/flutter_tools/templates/plugin/android-java.tmpl/src/main/java/androidIdentifier/pluginClass.java.tmpl
+++ b/packages/flutter_tools/templates/plugin/android-java.tmpl/src/main/java/androidIdentifier/pluginClass.java.tmpl
@@ -1,5 +1,37 @@
package {{androidIdentifier}};
+{{#useNewAndroidEmbedding}}
+{{#androidX}}
+import androidx.annotation.NonNull;
+{{/androidX}}
+{{^androidX}}
+import android.support.annotation.NonNull;
+{{/androidX}}
+import io.flutter.embedding.engine.plugins.FlutterPlugin;
+import io.flutter.plugin.common.MethodCall;
+import io.flutter.plugin.common.MethodChannel;
+import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
+import io.flutter.plugin.common.MethodChannel.Result;
+
+/** {{pluginClass}} */
+public class {{pluginClass}} implements FlutterPlugin, MethodCallHandler {
+ @Override
+ public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
+ final MethodChannel channel = new MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "{{projectName}}");
+ channel.setMethodCallHandler(new {{pluginClass}}());
+ }
+
+ @Override
+ public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
+ if (call.method.equals("getPlatformVersion")) {
+ result.success("Android " + android.os.Build.VERSION.RELEASE);
+ } else {
+ result.notImplemented();
+ }
+ }
+}
+{{/useNewAndroidEmbedding}}
+{{^useNewAndroidEmbedding}}
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
@@ -23,3 +55,4 @@
}
}
}
+{{/useNewAndroidEmbedding}}
diff --git a/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/main/kotlin/androidIdentifier/pluginClass.kt.tmpl b/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/main/kotlin/androidIdentifier/pluginClass.kt.tmpl
index 8fb848f..af45d9c 100644
--- a/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/main/kotlin/androidIdentifier/pluginClass.kt.tmpl
+++ b/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/main/kotlin/androidIdentifier/pluginClass.kt.tmpl
@@ -1,5 +1,35 @@
package {{androidIdentifier}}
+{{#useNewAndroidEmbedding}}
+{{#androidX}}
+import androidx.annotation.NonNull;
+{{/androidX}}
+{{^androidX}}
+import android.support.annotation.NonNull;
+{{/androidX}}
+import io.flutter.embedding.engine.plugins.FlutterPlugin
+import io.flutter.plugin.common.MethodCall
+import io.flutter.plugin.common.MethodChannel
+import io.flutter.plugin.common.MethodChannel.MethodCallHandler
+import io.flutter.plugin.common.MethodChannel.Result
+
+/** {{pluginClass}} */
+public class {{pluginClass}}: FlutterPlugin, MethodCallHandler {
+ override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPluginBinding) {
+ val channel = MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "{{projectName}}")
+ channel.setMethodCallHandler({{pluginClass}}());
+ }
+
+ override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
+ if (call.method == "getPlatformVersion") {
+ result.success("Android ${android.os.Build.VERSION.RELEASE}")
+ } else {
+ result.notImplemented()
+ }
+ }
+}
+{{/useNewAndroidEmbedding}}
+{{^useNewAndroidEmbedding}}
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
@@ -23,3 +53,4 @@
}
}
}
+{{/useNewAndroidEmbedding}}
diff --git a/packages/flutter_tools/test/general.shard/plugins_test.dart b/packages/flutter_tools/test/general.shard/plugins_test.dart
index f119fc8..1528828 100644
--- a/packages/flutter_tools/test/general.shard/plugins_test.dart
+++ b/packages/flutter_tools/test/general.shard/plugins_test.dart
@@ -5,22 +5,22 @@
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/dart/package_map.dart';
+import 'package:flutter_tools/src/features.dart';
+import 'package:flutter_tools/src/ios/xcodeproj.dart';
import 'package:flutter_tools/src/plugins.dart';
import 'package:flutter_tools/src/project.dart';
+
import 'package:mockito/mockito.dart';
import '../src/common.dart';
import '../src/context.dart';
-class MockFlutterProject extends Mock implements FlutterProject {}
-class MockIosProject extends Mock implements IosProject {}
-class MockMacOSProject extends Mock implements MacOSProject {}
-
void main() {
FileSystem fs;
MockFlutterProject flutterProject;
MockIosProject iosProject;
MockMacOSProject macosProject;
+ MockAndroidProject androidProject;
File packagesFile;
Directory dummyPackageDirectory;
@@ -33,10 +33,17 @@
when(flutterProject.flutterPluginsFile).thenReturn(flutterProject.directory.childFile('.plugins'));
iosProject = MockIosProject();
when(flutterProject.ios).thenReturn(iosProject);
+ when(iosProject.pluginRegistrantHost).thenReturn(flutterProject.directory.childDirectory('Runner'));
+ when(iosProject.podfile).thenReturn(flutterProject.directory.childDirectory('ios').childFile('Podfile'));
when(iosProject.podManifestLock).thenReturn(flutterProject.directory.childDirectory('ios').childFile('Podfile.lock'));
macosProject = MockMacOSProject();
when(flutterProject.macos).thenReturn(macosProject);
+ when(macosProject.podfile).thenReturn(flutterProject.directory.childDirectory('macos').childFile('Podfile'));
when(macosProject.podManifestLock).thenReturn(flutterProject.directory.childDirectory('macos').childFile('Podfile.lock'));
+ androidProject = MockAndroidProject();
+ when(flutterProject.android).thenReturn(androidProject);
+ when(androidProject.pluginRegistrantHost).thenReturn(flutterProject.directory.childDirectory('android').childDirectory('app'));
+ when(androidProject.hostAppGradleRoot).thenReturn(flutterProject.directory.childDirectory('android'));
// Set up a simple .packages file for all the tests to use, pointing to one package.
dummyPackageDirectory = fs.directory('/pubcache/apackage/lib/');
@@ -103,4 +110,276 @@
FileSystem: () => fs,
});
});
+
+ group('injectPlugins', () {
+ MockFeatureFlags featureFlags;
+ MockXcodeProjectInterpreter xcodeProjectInterpreter;
+
+ const String kAndroidManifestUsingOldEmbedding = '''
+<manifest>
+ <application>
+ </application>
+</manifest>
+''';
+ const String kAndroidManifestUsingNewEmbedding = '''
+<manifest>
+ <application>
+ <meta-data
+ android:name="flutterEmbedding"
+ android:value="2" />
+ </application>
+</manifest>
+''';
+
+ setUp(() {
+ featureFlags = MockFeatureFlags();
+ when(featureFlags.isLinuxEnabled).thenReturn(false);
+ when(featureFlags.isMacOSEnabled).thenReturn(false);
+ when(featureFlags.isWindowsEnabled).thenReturn(false);
+ when(featureFlags.isWebEnabled).thenReturn(false);
+
+ xcodeProjectInterpreter = MockXcodeProjectInterpreter();
+ when(xcodeProjectInterpreter.isInstalled).thenReturn(false);
+ });
+
+ testUsingContext('Registrant uses old embedding in app project', () async {
+ when(flutterProject.isModule).thenReturn(false);
+ when(featureFlags.isNewAndroidEmbeddingEnabled).thenReturn(false);
+
+ await injectPlugins(flutterProject);
+
+ final File registrant = flutterProject.directory
+ .childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
+ .childFile('GeneratedPluginRegistrant.java');
+
+ expect(registrant.existsSync(), isTrue);
+ expect(registrant.readAsStringSync(), contains('package io.flutter.plugins'));
+ expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ FeatureFlags: () => featureFlags,
+ });
+
+ testUsingContext('Registrant uses new embedding if app uses new embedding', () async {
+ when(flutterProject.isModule).thenReturn(false);
+ when(featureFlags.isNewAndroidEmbeddingEnabled).thenReturn(true);
+
+ final File androidManifest = flutterProject.directory
+ .childDirectory('android')
+ .childFile('AndroidManifest.xml')
+ ..createSync(recursive: true)
+ ..writeAsStringSync(kAndroidManifestUsingNewEmbedding);
+ when(androidProject.appManifestFile).thenReturn(androidManifest);
+
+ await injectPlugins(flutterProject);
+
+ final File registrant = flutterProject.directory
+ .childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'dev', 'flutter', 'plugins'))
+ .childFile('GeneratedPluginRegistrant.java');
+
+ expect(registrant.existsSync(), isTrue);
+ expect(registrant.readAsStringSync(), contains('package dev.flutter.plugins'));
+ expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ FeatureFlags: () => featureFlags,
+ });
+
+ testUsingContext('Registrant uses shim for plugins using old embedding if app uses new embedding', () async {
+ when(flutterProject.isModule).thenReturn(false);
+ when(featureFlags.isNewAndroidEmbeddingEnabled).thenReturn(true);
+
+ final File androidManifest = flutterProject.directory
+ .childDirectory('android')
+ .childFile('AndroidManifest.xml')
+ ..createSync(recursive: true)
+ ..writeAsStringSync(kAndroidManifestUsingNewEmbedding);
+ when(androidProject.appManifestFile).thenReturn(androidManifest);
+
+ final Directory pluginUsingJavaAndNewEmbeddingDir =
+ fs.systemTempDirectory.createTempSync('pluginUsingJavaAndNewEmbeddingDir.');
+ pluginUsingJavaAndNewEmbeddingDir
+ .childFile('pubspec.yaml')
+ .writeAsStringSync('''
+flutter:
+ plugin:
+ androidPackage: plugin1
+ pluginClass: UseNewEmbedding
+''');
+ pluginUsingJavaAndNewEmbeddingDir
+ .childDirectory('android')
+ .childDirectory('src')
+ .childDirectory('main')
+ .childDirectory('java')
+ .childDirectory('plugin1')
+ .childFile('UseNewEmbedding.java')
+ ..createSync(recursive: true)
+ ..writeAsStringSync('import io.flutter.embedding.engine.plugins.FlutterPlugin;');
+
+ final Directory pluginUsingKotlinAndNewEmbeddingDir =
+ fs.systemTempDirectory.createTempSync('pluginUsingKotlinAndNewEmbeddingDir.');
+ pluginUsingKotlinAndNewEmbeddingDir
+ .childFile('pubspec.yaml')
+ .writeAsStringSync('''
+flutter:
+ plugin:
+ androidPackage: plugin2
+ pluginClass: UseNewEmbedding
+''');
+ pluginUsingKotlinAndNewEmbeddingDir
+ .childDirectory('android')
+ .childDirectory('src')
+ .childDirectory('main')
+ .childDirectory('kotlin')
+ .childDirectory('plugin2')
+ .childFile('UseNewEmbedding.kt')
+ ..createSync(recursive: true)
+ ..writeAsStringSync('import io.flutter.embedding.engine.plugins.FlutterPlugin');
+
+ final Directory pluginUsingOldEmbeddingDir =
+ fs.systemTempDirectory.createTempSync('pluginUsingOldEmbeddingDir.');
+ pluginUsingOldEmbeddingDir
+ .childFile('pubspec.yaml')
+ .writeAsStringSync('''
+flutter:
+ plugin:
+ androidPackage: plugin3
+ pluginClass: UseOldEmbedding
+''');
+ pluginUsingOldEmbeddingDir
+ .childDirectory('android')
+ .childDirectory('src')
+ .childDirectory('main')
+ .childDirectory('java')
+ .childDirectory('plugin3')
+ .childFile('UseOldEmbedding.java')
+ ..createSync(recursive: true);
+
+ flutterProject.directory
+ .childFile('.packages')
+ .writeAsStringSync('''
+plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString()}
+plugin2:${pluginUsingKotlinAndNewEmbeddingDir.childDirectory('lib').uri.toString()}
+plugin3:${pluginUsingOldEmbeddingDir.childDirectory('lib').uri.toString()}
+''');
+
+ await injectPlugins(flutterProject);
+
+ final File registrant = flutterProject.directory
+ .childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'dev', 'flutter', 'plugins'))
+ .childFile('GeneratedPluginRegistrant.java');
+
+ expect(registrant.readAsStringSync(),
+ contains('flutterEngine.getPlugins().add(new plugin1.UseNewEmbedding());'));
+ expect(registrant.readAsStringSync(),
+ contains('flutterEngine.getPlugins().add(new plugin2.UseNewEmbedding());'));
+ expect(registrant.readAsStringSync(),
+ contains('plugin3.UseOldEmbedding.registerWith(shimPluginRegistry.registrarFor("plugin3.UseOldEmbedding"));'));
+
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ FeatureFlags: () => featureFlags,
+ XcodeProjectInterpreter: () => xcodeProjectInterpreter,
+ });
+
+ testUsingContext('Registrant doesn\'t use new embedding if app doesn\'t use new embedding', () async {
+ when(flutterProject.isModule).thenReturn(false);
+ when(featureFlags.isNewAndroidEmbeddingEnabled).thenReturn(true);
+
+ final File androidManifest = flutterProject.directory
+ .childDirectory('android')
+ .childFile('AndroidManifest.xml')
+ ..createSync(recursive: true)
+ ..writeAsStringSync(kAndroidManifestUsingOldEmbedding);
+ when(androidProject.appManifestFile).thenReturn(androidManifest);
+
+ await injectPlugins(flutterProject);
+
+ final File registrant = flutterProject.directory
+ .childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
+ .childFile('GeneratedPluginRegistrant.java');
+
+ expect(registrant.existsSync(), isTrue);
+ expect(registrant.readAsStringSync(), contains('package io.flutter.plugins'));
+ expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ FeatureFlags: () => featureFlags,
+ });
+
+ testUsingContext('Registrant uses old embedding in module project', () async {
+ when(flutterProject.isModule).thenReturn(true);
+ when(featureFlags.isNewAndroidEmbeddingEnabled).thenReturn(false);
+
+ await injectPlugins(flutterProject);
+
+ final File registrant = flutterProject.directory
+ .childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
+ .childFile('GeneratedPluginRegistrant.java');
+
+ expect(registrant.existsSync(), isTrue);
+ expect(registrant.readAsStringSync(), contains('package io.flutter.plugins'));
+ expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ FeatureFlags: () => featureFlags,
+ });
+
+ testUsingContext('Registrant uses new embedding if module uses new embedding', () async {
+ when(flutterProject.isModule).thenReturn(true);
+ when(featureFlags.isNewAndroidEmbeddingEnabled).thenReturn(true);
+
+ final File androidManifest = flutterProject.directory
+ .childDirectory('android')
+ .childFile('AndroidManifest.xml')
+ ..createSync(recursive: true)
+ ..writeAsStringSync(kAndroidManifestUsingNewEmbedding);
+ when(androidProject.appManifestFile).thenReturn(androidManifest);
+
+ await injectPlugins(flutterProject);
+
+ final File registrant = flutterProject.directory
+ .childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'dev', 'flutter', 'plugins'))
+ .childFile('GeneratedPluginRegistrant.java');
+
+ expect(registrant.existsSync(), isTrue);
+ expect(registrant.readAsStringSync(), contains('package dev.flutter.plugins'));
+ expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ FeatureFlags: () => featureFlags,
+ });
+
+ testUsingContext('Registrant doesn\'t use new embedding if module doesn\'t use new embedding', () async {
+ when(flutterProject.isModule).thenReturn(true);
+ when(featureFlags.isNewAndroidEmbeddingEnabled).thenReturn(true);
+
+ final File androidManifest = flutterProject.directory
+ .childDirectory('android')
+ .childFile('AndroidManifest.xml')
+ ..createSync(recursive: true)
+ ..writeAsStringSync(kAndroidManifestUsingOldEmbedding);
+ when(androidProject.appManifestFile).thenReturn(androidManifest);
+
+ await injectPlugins(flutterProject);
+
+ final File registrant = flutterProject.directory
+ .childDirectory(fs.path.join('android', 'app', 'src', 'main', 'java', 'io', 'flutter', 'plugins'))
+ .childFile('GeneratedPluginRegistrant.java');
+
+ expect(registrant.existsSync(), isTrue);
+ expect(registrant.readAsStringSync(), contains('package io.flutter.plugins'));
+ expect(registrant.readAsStringSync(), contains('class GeneratedPluginRegistrant'));
+ }, overrides: <Type, Generator>{
+ FileSystem: () => fs,
+ FeatureFlags: () => featureFlags,
+ });
+ });
}
+
+class MockAndroidProject extends Mock implements AndroidProject {}
+class MockFeatureFlags extends Mock implements FeatureFlags {}
+class MockFlutterProject extends Mock implements FlutterProject {}
+class MockIosProject extends Mock implements IosProject {}
+class MockMacOSProject extends Mock implements MacOSProject {}
+class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter {}
diff --git a/packages/flutter_tools/test/src/testbed.dart b/packages/flutter_tools/test/src/testbed.dart
index 33a27df..4c2fae1 100644
--- a/packages/flutter_tools/test/src/testbed.dart
+++ b/packages/flutter_tools/test/src/testbed.dart
@@ -689,6 +689,7 @@
this.isMacOSEnabled = false,
this.isWebEnabled = false,
this.isWindowsEnabled = false,
+ this.isNewAndroidEmbeddingEnabled = false,
});
@override
@@ -702,4 +703,7 @@
@override
final bool isWindowsEnabled;
+
+ @override
+ final bool isNewAndroidEmbeddingEnabled;
}