Unverified Commit ba26f926 authored by xster's avatar xster Committed by GitHub

Add Android embedding version analytics (#44043)

parent 14ac88d3
...@@ -87,6 +87,8 @@ class PackagesGetCommand extends FlutterCommand { ...@@ -87,6 +87,8 @@ class PackagesGetCommand extends FlutterCommand {
usageValues[CustomDimensions.commandPackagesNumberPlugins] = '0'; usageValues[CustomDimensions.commandPackagesNumberPlugins] = '0';
} }
usageValues[CustomDimensions.commandPackagesProjectModule] = '${rootProject.isModule}'; usageValues[CustomDimensions.commandPackagesProjectModule] = '${rootProject.isModule}';
usageValues[CustomDimensions.commandPackagesAndroidEmbeddingVersion] =
rootProject.android.getEmbeddingVersion().toString().split('.').last;
return usageValues; return usageValues;
} }
......
...@@ -250,6 +250,7 @@ class RunCommand extends RunCommandBase { ...@@ -250,6 +250,7 @@ class RunCommand extends RunCommandBase {
CustomDimensions.commandRunModeName: modeName, CustomDimensions.commandRunModeName: modeName,
CustomDimensions.commandRunProjectModule: '${FlutterProject.current().isModule}', CustomDimensions.commandRunProjectModule: '${FlutterProject.current().isModule}',
CustomDimensions.commandRunProjectHostLanguage: hostLanguage.join(','), CustomDimensions.commandRunProjectHostLanguage: hostLanguage.join(','),
CustomDimensions.commandRunAndroidEmbeddingVersion: androidProject.getEmbeddingVersion().toString().split('.').last,
}; };
} }
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
import 'dart:async'; import 'dart:async';
import 'package:mustache/mustache.dart' as mustache; import 'package:mustache/mustache.dart' as mustache;
import 'package:xml/xml.dart' as xml;
import 'package:yaml/yaml.dart'; import 'package:yaml/yaml.dart';
import 'android/gradle.dart'; import 'android/gradle.dart';
...@@ -364,30 +363,10 @@ List<Map<String, dynamic>> _extractPlatformMaps(List<Plugin> plugins, String typ ...@@ -364,30 +363,10 @@ List<Map<String, dynamic>> _extractPlatformMaps(List<Plugin> plugins, String typ
/// Returns the version of the Android embedding that the current /// Returns the version of the Android embedding that the current
/// [project] is using. /// [project] is using.
String _getAndroidEmbeddingVersion(FlutterProject project) { AndroidEmbeddingVersion _getAndroidEmbeddingVersion(FlutterProject project) {
assert(project.android != null); assert(project.android != null);
final File androidManifest = project.android.appManifestFile; return project.android.getEmbeddingVersion();
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';
} }
Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin> plugins) async { Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
...@@ -412,9 +391,9 @@ Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin> ...@@ -412,9 +391,9 @@ Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin>
'GeneratedPluginRegistrant.java', 'GeneratedPluginRegistrant.java',
); );
String templateContent; String templateContent;
final String appEmbeddingVersion = _getAndroidEmbeddingVersion(project); final AndroidEmbeddingVersion appEmbeddingVersion = _getAndroidEmbeddingVersion(project);
switch (appEmbeddingVersion) { switch (appEmbeddingVersion) {
case '2': case AndroidEmbeddingVersion.v2:
templateContext['needsShim'] = false; templateContext['needsShim'] = false;
// If a plugin is using an embedding version older than 2.0 and the app is using 2.0, // 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. // then add shim for the old plugins.
...@@ -425,8 +404,9 @@ Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin> ...@@ -425,8 +404,9 @@ Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin>
} }
} }
templateContent = _androidPluginRegistryTemplateNewEmbedding; templateContent = _androidPluginRegistryTemplateNewEmbedding;
break; break;
case '1': case AndroidEmbeddingVersion.v1:
default:
for (Map<String, dynamic> plugin in androidPlugins) { for (Map<String, dynamic> plugin in androidPlugins) {
if (!plugin['supportsEmbeddingV1'] && plugin['supportsEmbeddingV2']) { if (!plugin['supportsEmbeddingV1'] && plugin['supportsEmbeddingV2']) {
throwToolExit( throwToolExit(
...@@ -437,9 +417,7 @@ Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin> ...@@ -437,9 +417,7 @@ Future<void> _writeAndroidPluginRegistrant(FlutterProject project, List<Plugin>
} }
} }
templateContent = _androidPluginRegistryTemplateOldEmbedding; templateContent = _androidPluginRegistryTemplateOldEmbedding;
break; break;
default:
throwToolExit('Unsupported Android embedding');
} }
printTrace('Generating $registryPath'); printTrace('Generating $registryPath');
_renderTemplateToFile( _renderTemplateToFile(
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'dart:async'; import 'dart:async';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:xml/xml.dart' as xml;
import 'package:yaml/yaml.dart'; import 'package:yaml/yaml.dart';
import 'android/gradle_utils.dart' as gradle; import 'android/gradle_utils.dart' as gradle;
...@@ -642,6 +643,43 @@ class AndroidProject { ...@@ -642,6 +643,43 @@ class AndroidProject {
overwriteExisting: true, 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. /// Represents the web sub-project of a Flutter project.
......
...@@ -55,6 +55,8 @@ enum CustomDimensions { ...@@ -55,6 +55,8 @@ enum CustomDimensions {
commandBuildAppBundleBuildMode, // cd42 commandBuildAppBundleBuildMode, // cd42
buildEventError, // cd43 buildEventError, // cd43
commandResultEventMaxRss, // cd44 commandResultEventMaxRss, // cd44
commandRunAndroidEmbeddingVersion, // cd45
commandPackagesAndroidEmbeddingVersion, // cd46
} }
String cdKey(CustomDimensions cd) => 'cd${cd.index + 1}'; String cdKey(CustomDimensions cd) => 'cd${cd.index + 1}';
......
...@@ -11,12 +11,14 @@ import 'package:flutter_tools/src/base/utils.dart'; ...@@ -11,12 +11,14 @@ import 'package:flutter_tools/src/base/utils.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/packages.dart'; import 'package:flutter_tools/src/commands/packages.dart';
import 'package:flutter_tools/src/dart/pub.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:flutter_tools/src/reporting/reporting.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
import '../../src/mocks.dart' show MockProcessManager, MockStdio, PromptingProcess; import '../../src/mocks.dart' show MockProcessManager, MockStdio, PromptingProcess;
import '../../src/testbed.dart';
class AlwaysTrueBotDetector implements BotDetector { class AlwaysTrueBotDetector implements BotDetector {
const AlwaysTrueBotDetector(); const AlwaysTrueBotDetector();
...@@ -266,6 +268,36 @@ void main() { ...@@ -266,6 +268,36 @@ void main() {
Pub: () => const Pub(), 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 { testUsingContext('upgrade fetches packages', () async {
final String projectPath = await createProject(tempDir, final String projectPath = await createProject(tempDir,
arguments: <String>['--no-pub', '--template=module']); arguments: <String>['--no-pub', '--template=module']);
......
...@@ -125,22 +125,6 @@ void main() { ...@@ -125,22 +125,6 @@ void main() {
MockFeatureFlags featureFlags; MockFeatureFlags featureFlags;
MockXcodeProjectInterpreter xcodeProjectInterpreter; 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(() { setUp(() {
featureFlags = MockFeatureFlags(); featureFlags = MockFeatureFlags();
when(featureFlags.isLinuxEnabled).thenReturn(false); when(featureFlags.isLinuxEnabled).thenReturn(false);
...@@ -154,13 +138,7 @@ void main() { ...@@ -154,13 +138,7 @@ void main() {
testUsingContext('Registrant uses old embedding in app project', () async { testUsingContext('Registrant uses old embedding in app project', () async {
when(flutterProject.isModule).thenReturn(false); when(flutterProject.isModule).thenReturn(false);
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
final File androidManifest = flutterProject.directory
.childDirectory('android')
.childFile('AndroidManifest.xml')
..createSync(recursive: true)
..writeAsStringSync(kAndroidManifestUsingOldEmbedding);
when(androidProject.appManifestFile).thenReturn(androidManifest);
await injectPlugins(flutterProject); await injectPlugins(flutterProject);
...@@ -179,13 +157,7 @@ void main() { ...@@ -179,13 +157,7 @@ void main() {
testUsingContext('Registrant uses new embedding if app uses new embedding', () async { testUsingContext('Registrant uses new embedding if app uses new embedding', () async {
when(flutterProject.isModule).thenReturn(false); when(flutterProject.isModule).thenReturn(false);
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
final File androidManifest = flutterProject.directory
.childDirectory('android')
.childFile('AndroidManifest.xml')
..createSync(recursive: true)
..writeAsStringSync(kAndroidManifestUsingNewEmbedding);
when(androidProject.appManifestFile).thenReturn(androidManifest);
await injectPlugins(flutterProject); await injectPlugins(flutterProject);
...@@ -204,13 +176,7 @@ void main() { ...@@ -204,13 +176,7 @@ void main() {
testUsingContext('Registrant uses shim for plugins using old embedding if app uses new embedding', () async { testUsingContext('Registrant uses shim for plugins using old embedding if app uses new embedding', () async {
when(flutterProject.isModule).thenReturn(false); when(flutterProject.isModule).thenReturn(false);
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
final File androidManifest = flutterProject.directory
.childDirectory('android')
.childFile('AndroidManifest.xml')
..createSync(recursive: true)
..writeAsStringSync(kAndroidManifestUsingNewEmbedding);
when(androidProject.appManifestFile).thenReturn(androidManifest);
final Directory pluginUsingJavaAndNewEmbeddingDir = final Directory pluginUsingJavaAndNewEmbeddingDir =
fs.systemTempDirectory.createTempSync('flutter_plugin_using_java_and_new_embedding_dir.'); fs.systemTempDirectory.createTempSync('flutter_plugin_using_java_and_new_embedding_dir.');
...@@ -301,13 +267,7 @@ plugin3:${pluginUsingOldEmbeddingDir.childDirectory('lib').uri.toString()} ...@@ -301,13 +267,7 @@ plugin3:${pluginUsingOldEmbeddingDir.childDirectory('lib').uri.toString()}
testUsingContext('exits the tool if an app uses the v1 embedding and a plugin only supports the v2 embedding', () async { 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); when(flutterProject.isModule).thenReturn(false);
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
final File androidManifest = flutterProject.directory
.childDirectory('android')
.childFile('AndroidManifest.xml')
..createSync(recursive: true)
..writeAsStringSync(kAndroidManifestUsingOldEmbedding);
when(androidProject.appManifestFile).thenReturn(androidManifest);
final Directory pluginUsingJavaAndNewEmbeddingDir = final Directory pluginUsingJavaAndNewEmbeddingDir =
fs.systemTempDirectory.createTempSync('flutter_plugin_using_java_and_new_embedding_dir.'); fs.systemTempDirectory.createTempSync('flutter_plugin_using_java_and_new_embedding_dir.');
...@@ -352,13 +312,7 @@ plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString() ...@@ -352,13 +312,7 @@ plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString()
testUsingContext('allows app use a plugin that supports v1 and v2 embedding', () async { testUsingContext('allows app use a plugin that supports v1 and v2 embedding', () async {
when(flutterProject.isModule).thenReturn(false); when(flutterProject.isModule).thenReturn(false);
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
final File androidManifest = flutterProject.directory
.childDirectory('android')
.childFile('AndroidManifest.xml')
..createSync(recursive: true)
..writeAsStringSync(kAndroidManifestUsingOldEmbedding);
when(androidProject.appManifestFile).thenReturn(androidManifest);
final Directory pluginUsingJavaAndNewEmbeddingDir = final Directory pluginUsingJavaAndNewEmbeddingDir =
fs.systemTempDirectory.createTempSync('flutter_plugin_using_java_and_new_embedding_dir.'); fs.systemTempDirectory.createTempSync('flutter_plugin_using_java_and_new_embedding_dir.');
...@@ -406,13 +360,7 @@ plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString() ...@@ -406,13 +360,7 @@ plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString()
testUsingContext('Registrant doesn\'t use new embedding if app doesn\'t use new embedding', () async { testUsingContext('Registrant doesn\'t use new embedding if app doesn\'t use new embedding', () async {
when(flutterProject.isModule).thenReturn(false); when(flutterProject.isModule).thenReturn(false);
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
final File androidManifest = flutterProject.directory
.childDirectory('android')
.childFile('AndroidManifest.xml')
..createSync(recursive: true)
..writeAsStringSync(kAndroidManifestUsingOldEmbedding);
when(androidProject.appManifestFile).thenReturn(androidManifest);
await injectPlugins(flutterProject); await injectPlugins(flutterProject);
...@@ -431,13 +379,7 @@ plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString() ...@@ -431,13 +379,7 @@ plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString()
testUsingContext('Registrant uses old embedding in module project', () async { testUsingContext('Registrant uses old embedding in module project', () async {
when(flutterProject.isModule).thenReturn(true); when(flutterProject.isModule).thenReturn(true);
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
final File androidManifest = flutterProject.directory
.childDirectory('android')
.childFile('AndroidManifest.xml')
..createSync(recursive: true)
..writeAsStringSync(kAndroidManifestUsingOldEmbedding);
when(androidProject.appManifestFile).thenReturn(androidManifest);
await injectPlugins(flutterProject); await injectPlugins(flutterProject);
...@@ -456,13 +398,7 @@ plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString() ...@@ -456,13 +398,7 @@ plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString()
testUsingContext('Registrant uses new embedding if module uses new embedding', () async { testUsingContext('Registrant uses new embedding if module uses new embedding', () async {
when(flutterProject.isModule).thenReturn(true); when(flutterProject.isModule).thenReturn(true);
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v2);
final File androidManifest = flutterProject.directory
.childDirectory('android')
.childFile('AndroidManifest.xml')
..createSync(recursive: true)
..writeAsStringSync(kAndroidManifestUsingNewEmbedding);
when(androidProject.appManifestFile).thenReturn(androidManifest);
await injectPlugins(flutterProject); await injectPlugins(flutterProject);
...@@ -481,13 +417,7 @@ plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString() ...@@ -481,13 +417,7 @@ plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString()
testUsingContext('Registrant doesn\'t use new embedding if module doesn\'t use new embedding', () async { testUsingContext('Registrant doesn\'t use new embedding if module doesn\'t use new embedding', () async {
when(flutterProject.isModule).thenReturn(true); when(flutterProject.isModule).thenReturn(true);
when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
final File androidManifest = flutterProject.directory
.childDirectory('android')
.childFile('AndroidManifest.xml')
..createSync(recursive: true)
..writeAsStringSync(kAndroidManifestUsingOldEmbedding);
when(androidProject.appManifestFile).thenReturn(androidManifest);
await injectPlugins(flutterProject); await injectPlugins(flutterProject);
...@@ -522,14 +452,6 @@ plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString() ...@@ -522,14 +452,6 @@ plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString()
when(flutterProject.isModule).thenReturn(true); when(flutterProject.isModule).thenReturn(true);
when(featureFlags.isWebEnabled).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 = final Directory webPluginWithNestedFile =
fs.systemTempDirectory.createTempSync('web_plugin_with_nested'); fs.systemTempDirectory.createTempSync('web_plugin_with_nested');
webPluginWithNestedFile.childFile('pubspec.yaml').writeAsStringSync(''' webPluginWithNestedFile.childFile('pubspec.yaml').writeAsStringSync('''
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment