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('''