Add Android embedding version analytics (#44043)
diff --git a/packages/flutter_tools/lib/src/commands/packages.dart b/packages/flutter_tools/lib/src/commands/packages.dart
index d6aeeb0..cf2689c 100644
--- a/packages/flutter_tools/lib/src/commands/packages.dart
+++ b/packages/flutter_tools/lib/src/commands/packages.dart
@@ -87,6 +87,8 @@
usageValues[CustomDimensions.commandPackagesNumberPlugins] = '0';
}
usageValues[CustomDimensions.commandPackagesProjectModule] = '${rootProject.isModule}';
+ usageValues[CustomDimensions.commandPackagesAndroidEmbeddingVersion] =
+ rootProject.android.getEmbeddingVersion().toString().split('.').last;
return usageValues;
}
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index fd6dc53..6bd40df 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -250,6 +250,7 @@
CustomDimensions.commandRunModeName: modeName,
CustomDimensions.commandRunProjectModule: '${FlutterProject.current().isModule}',
CustomDimensions.commandRunProjectHostLanguage: hostLanguage.join(','),
+ CustomDimensions.commandRunAndroidEmbeddingVersion: androidProject.getEmbeddingVersion().toString().split('.').last,
};
}
diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart
index da994b4..613400c 100644
--- a/packages/flutter_tools/lib/src/plugins.dart
+++ b/packages/flutter_tools/lib/src/plugins.dart
@@ -5,7 +5,6 @@
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';
@@ -364,30 +363,10 @@
/// Returns the version of the Android embedding that the current
/// [project] is using.
-String _getAndroidEmbeddingVersion(FlutterProject project) {
+AndroidEmbeddingVersion _getAndroidEmbeddingVersion(FlutterProject project) {
assert(project.android != null);
- final File androidManifest = project.android.appManifestFile;
- if (androidManifest == null || !androidManifest.existsSync()) {
- return '1';
- }
- 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';
+ return project.android.getEmbeddingVersion();
}
Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
@@ -412,9 +391,9 @@
'GeneratedPluginRegistrant.java',
);
String templateContent;
- final String appEmbeddingVersion = _getAndroidEmbeddingVersion(project);
+ final AndroidEmbeddingVersion appEmbeddingVersion = _getAndroidEmbeddingVersion(project);
switch (appEmbeddingVersion) {
- case '2':
+ case AndroidEmbeddingVersion.v2:
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.
@@ -425,8 +404,9 @@
}
}
templateContent = _androidPluginRegistryTemplateNewEmbedding;
- break;
- case '1':
+ break;
+ case AndroidEmbeddingVersion.v1:
+ default:
for (Map<String, dynamic> plugin in androidPlugins) {
if (!plugin['supportsEmbeddingV1'] && plugin['supportsEmbeddingV2']) {
throwToolExit(
@@ -437,9 +417,7 @@
}
}
templateContent = _androidPluginRegistryTemplateOldEmbedding;
- break;
- default:
- throwToolExit('Unsupported Android embedding');
+ break;
}
printTrace('Generating $registryPath');
_renderTemplateToFile(
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index 5bc5c77..f8e8576 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -5,6 +5,7 @@
import 'dart:async';
import 'package:meta/meta.dart';
+import 'package:xml/xml.dart' as xml;
import 'package:yaml/yaml.dart';
import 'android/gradle_utils.dart' as gradle;
@@ -642,6 +643,43 @@
overwriteExisting: true,
);
}
+
+ AndroidEmbeddingVersion getEmbeddingVersion() {
+ if (appManifestFile == null || !appManifestFile.existsSync()) {
+ return AndroidEmbeddingVersion.v1;
+ }
+ xml.XmlDocument document;
+ try {
+ document = xml.parse(appManifestFile.readAsStringSync());
+ } on xml.XmlParserException {
+ throwToolExit('Error parsing $appManifestFile '
+ 'Please ensure that the android manifest is a valid XML document and try again.');
+ } on FileSystemException {
+ throwToolExit('Error reading $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') {
+ final String embeddingVersionString = metaData.getAttribute('android:value');
+ if (embeddingVersionString == '1') {
+ return AndroidEmbeddingVersion.v1;
+ }
+ if (embeddingVersionString == '2') {
+ return AndroidEmbeddingVersion.v2;
+ }
+ }
+ }
+ return AndroidEmbeddingVersion.v1;
+ }
+}
+
+/// Iteration of the embedding Java API in the engine used by the Android project.
+enum AndroidEmbeddingVersion {
+ /// V1 APIs based on io.flutter.app.FlutterActivity.
+ v1,
+ /// V2 APIs based on io.flutter.embedding.android.FlutterActivity.
+ v2,
}
/// Represents the web sub-project of a Flutter project.
diff --git a/packages/flutter_tools/lib/src/reporting/usage.dart b/packages/flutter_tools/lib/src/reporting/usage.dart
index e737439..d352632 100644
--- a/packages/flutter_tools/lib/src/reporting/usage.dart
+++ b/packages/flutter_tools/lib/src/reporting/usage.dart
@@ -55,6 +55,8 @@
commandBuildAppBundleBuildMode, // cd42
buildEventError, // cd43
commandResultEventMaxRss, // cd44
+ commandRunAndroidEmbeddingVersion, // cd45
+ commandPackagesAndroidEmbeddingVersion, // cd46
}
String cdKey(CustomDimensions cd) => 'cd${cd.index + 1}';
diff --git a/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart b/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart
index a9197ed..3df82bf 100644
--- a/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart
+++ b/packages/flutter_tools/test/commands.shard/permeable/packages_test.dart
@@ -11,12 +11,14 @@
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/packages.dart';
import 'package:flutter_tools/src/dart/pub.dart';
+import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:process/process.dart';
import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/mocks.dart' show MockProcessManager, MockStdio, PromptingProcess;
+import '../../src/testbed.dart';
class AlwaysTrueBotDetector implements BotDetector {
const AlwaysTrueBotDetector();
@@ -266,6 +268,36 @@
Pub: () => const Pub(),
});
+ testUsingContext('indicate that Android project reports v1 in usage value', () async {
+ final String projectPath = await createProject(tempDir,
+ arguments: <String>['--no-pub']);
+ removeGeneratedFiles(projectPath);
+
+ final PackagesCommand command = await runCommandIn(projectPath, 'get');
+ final PackagesGetCommand getCommand = command.subcommands['get'] as PackagesGetCommand;
+
+ expect(await getCommand.usageValues,
+ containsPair(CustomDimensions.commandPackagesAndroidEmbeddingVersion, 'v1'));
+ }, overrides: <Type, Generator>{
+ FeatureFlags: () => TestFeatureFlags(isAndroidEmbeddingV2Enabled: false),
+ Pub: () => const Pub(),
+ });
+
+ testUsingContext('indicate that Android project reports v2 in usage value', () async {
+ final String projectPath = await createProject(tempDir,
+ arguments: <String>['--no-pub']);
+ removeGeneratedFiles(projectPath);
+
+ final PackagesCommand command = await runCommandIn(projectPath, 'get');
+ final PackagesGetCommand getCommand = command.subcommands['get'] as PackagesGetCommand;
+
+ expect(await getCommand.usageValues,
+ containsPair(CustomDimensions.commandPackagesAndroidEmbeddingVersion, 'v2'));
+ }, overrides: <Type, Generator>{
+ FeatureFlags: () => TestFeatureFlags(isAndroidEmbeddingV2Enabled: true),
+ Pub: () => const Pub(),
+ });
+
testUsingContext('upgrade fetches packages', () async {
final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=module']);
diff --git a/packages/flutter_tools/test/general.shard/plugins_test.dart b/packages/flutter_tools/test/general.shard/plugins_test.dart
index 59c19c4..0dea334 100644
--- a/packages/flutter_tools/test/general.shard/plugins_test.dart
+++ b/packages/flutter_tools/test/general.shard/plugins_test.dart
@@ -125,22 +125,6 @@
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);
@@ -154,13 +138,7 @@
testUsingContext('Registrant uses old embedding in app project', () async {
when(flutterProject.isModule).thenReturn(false);
-
- final File androidManifest = flutterProject.directory
- .childDirectory('android')
- .childFile('AndroidManifest.xml')
- ..createSync(recursive: true)
- ..writeAsStringSync(kAndroidManifestUsingOldEmbedding);
- when(androidProject.appManifestFile).thenReturn(androidManifest);
+ when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
await injectPlugins(flutterProject);
@@ -179,13 +157,7 @@
testUsingContext('Registrant uses new embedding if app uses new embedding', () async {
when(flutterProject.isModule).thenReturn(false);
-
- final File androidManifest = flutterProject.directory
- .childDirectory('android')
- .childFile('AndroidManifest.xml')
- ..createSync(recursive: true)
- ..writeAsStringSync(kAndroidManifestUsingNewEmbedding);
- when(androidProject.appManifestFile).thenReturn(androidManifest);
+ when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
await injectPlugins(flutterProject);
@@ -204,13 +176,7 @@
testUsingContext('Registrant uses shim for plugins using old embedding if app uses new embedding', () async {
when(flutterProject.isModule).thenReturn(false);
-
- final File androidManifest = flutterProject.directory
- .childDirectory('android')
- .childFile('AndroidManifest.xml')
- ..createSync(recursive: true)
- ..writeAsStringSync(kAndroidManifestUsingNewEmbedding);
- when(androidProject.appManifestFile).thenReturn(androidManifest);
+ when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
final Directory pluginUsingJavaAndNewEmbeddingDir =
fs.systemTempDirectory.createTempSync('flutter_plugin_using_java_and_new_embedding_dir.');
@@ -301,13 +267,7 @@
testUsingContext('exits the tool if an app uses the v1 embedding and a plugin only supports the v2 embedding', () async {
when(flutterProject.isModule).thenReturn(false);
-
- final File androidManifest = flutterProject.directory
- .childDirectory('android')
- .childFile('AndroidManifest.xml')
- ..createSync(recursive: true)
- ..writeAsStringSync(kAndroidManifestUsingOldEmbedding);
- when(androidProject.appManifestFile).thenReturn(androidManifest);
+ when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
final Directory pluginUsingJavaAndNewEmbeddingDir =
fs.systemTempDirectory.createTempSync('flutter_plugin_using_java_and_new_embedding_dir.');
@@ -352,13 +312,7 @@
testUsingContext('allows app use a plugin that supports v1 and v2 embedding', () async {
when(flutterProject.isModule).thenReturn(false);
-
- final File androidManifest = flutterProject.directory
- .childDirectory('android')
- .childFile('AndroidManifest.xml')
- ..createSync(recursive: true)
- ..writeAsStringSync(kAndroidManifestUsingOldEmbedding);
- when(androidProject.appManifestFile).thenReturn(androidManifest);
+ when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
final Directory pluginUsingJavaAndNewEmbeddingDir =
fs.systemTempDirectory.createTempSync('flutter_plugin_using_java_and_new_embedding_dir.');
@@ -406,13 +360,7 @@
testUsingContext('Registrant doesn\'t use new embedding if app doesn\'t use new embedding', () async {
when(flutterProject.isModule).thenReturn(false);
-
- final File androidManifest = flutterProject.directory
- .childDirectory('android')
- .childFile('AndroidManifest.xml')
- ..createSync(recursive: true)
- ..writeAsStringSync(kAndroidManifestUsingOldEmbedding);
- when(androidProject.appManifestFile).thenReturn(androidManifest);
+ when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
await injectPlugins(flutterProject);
@@ -431,13 +379,7 @@
testUsingContext('Registrant uses old embedding in module project', () async {
when(flutterProject.isModule).thenReturn(true);
-
- final File androidManifest = flutterProject.directory
- .childDirectory('android')
- .childFile('AndroidManifest.xml')
- ..createSync(recursive: true)
- ..writeAsStringSync(kAndroidManifestUsingOldEmbedding);
- when(androidProject.appManifestFile).thenReturn(androidManifest);
+ when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
await injectPlugins(flutterProject);
@@ -456,13 +398,7 @@
testUsingContext('Registrant uses new embedding if module uses new embedding', () async {
when(flutterProject.isModule).thenReturn(true);
-
- final File androidManifest = flutterProject.directory
- .childDirectory('android')
- .childFile('AndroidManifest.xml')
- ..createSync(recursive: true)
- ..writeAsStringSync(kAndroidManifestUsingNewEmbedding);
- when(androidProject.appManifestFile).thenReturn(androidManifest);
+ when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
await injectPlugins(flutterProject);
@@ -481,13 +417,7 @@
testUsingContext('Registrant doesn\'t use new embedding if module doesn\'t use new embedding', () async {
when(flutterProject.isModule).thenReturn(true);
-
- final File androidManifest = flutterProject.directory
- .childDirectory('android')
- .childFile('AndroidManifest.xml')
- ..createSync(recursive: true)
- ..writeAsStringSync(kAndroidManifestUsingOldEmbedding);
- when(androidProject.appManifestFile).thenReturn(androidManifest);
+ when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
await injectPlugins(flutterProject);
@@ -522,14 +452,6 @@
when(flutterProject.isModule).thenReturn(true);
when(featureFlags.isWebEnabled).thenReturn(true);
- // injectPlugins will crash if there is no AndroidManifest
- final File androidManifest = flutterProject.directory
- .childDirectory('android')
- .childFile('AndroidManifest.xml')
- ..createSync(recursive: true)
- ..writeAsStringSync(kAndroidManifestUsingOldEmbedding);
- when(androidProject.appManifestFile).thenReturn(androidManifest);
-
final Directory webPluginWithNestedFile =
fs.systemTempDirectory.createTempSync('web_plugin_with_nested');
webPluginWithNestedFile.childFile('pubspec.yaml').writeAsStringSync('''