Add to app measurement (#33458)
diff --git a/packages/flutter_tools/lib/src/commands/attach.dart b/packages/flutter_tools/lib/src/commands/attach.dart index c822fcd..fcb6924 100644 --- a/packages/flutter_tools/lib/src/commands/attach.dart +++ b/packages/flutter_tools/lib/src/commands/attach.dart
@@ -26,6 +26,7 @@ import '../run_cold.dart'; import '../run_hot.dart'; import '../runner/flutter_command.dart'; +import '../usage.dart'; /// A Flutter-command that attaches to applications that have been launched /// without `flutter run`. @@ -315,8 +316,12 @@ result = await runner.attach(); assert(result != null); } - if (result != 0) + if (result == 0) { + flutterUsage.sendEvent('attach', 'success'); + } else { + flutterUsage.sendEvent('attach', 'failure'); throwToolExit(null, exitCode: result); + } } finally { final List<ForwardedPort> ports = device.portForwarder.forwardedPorts.toList(); for (ForwardedPort port in ports) {
diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart index 771ea15..45b612d 100644 --- a/packages/flutter_tools/lib/src/commands/create.dart +++ b/packages/flutter_tools/lib/src/commands/create.dart
@@ -24,6 +24,7 @@ import '../project.dart'; import '../runner/flutter_command.dart'; import '../template.dart'; +import '../usage.dart'; import '../version.dart'; enum _ProjectType { @@ -148,6 +149,15 @@ @override String get invocation => '${runner.executableName} $name <output directory>'; + @override + Future<Map<String, String>> get usageValues async { + return <String, String>{ + kCommandCreateProjectType: argResults['template'], + kCommandCreateAndroidLanguage: argResults['android-language'], + kCommandCreateIosLanguage: argResults['ios-language'], + }; + } + // If it has a .metadata file with the project_type in it, use that. // If it has an android dir and an android/app dir, it's a legacy app // If it has an ios dir and an ios/Flutter dir, it's a legacy app @@ -228,6 +238,36 @@ } } + _ProjectType _getProjectType(Directory projectDir) { + _ProjectType template; + _ProjectType detectedProjectType; + final bool metadataExists = projectDir.absolute.childFile('.metadata').existsSync(); + if (argResults['template'] != null) { + template = _stringToProjectType(argResults['template']); + } else { + // If the project directory exists and isn't empty, then try to determine the template + // type from the project directory. + if (projectDir.existsSync() && projectDir.listSync().isNotEmpty) { + detectedProjectType = _determineTemplateType(projectDir); + if (detectedProjectType == null && metadataExists) { + // We can only be definitive that this is the wrong type if the .metadata file + // exists and contains a type that we don't understand, or doesn't contain a type. + throwToolExit('Sorry, unable to detect the type of project to recreate. ' + 'Try creating a fresh project and migrating your existing code to ' + 'the new project manually.'); + } + } + } + template ??= detectedProjectType ?? _ProjectType.app; + if (detectedProjectType != null && template != detectedProjectType && metadataExists) { + // We can only be definitive that this is the wrong type if the .metadata file + // exists and contains a type that doesn't match. + throwToolExit("The requested template type '${getEnumName(template)}' doesn't match the " + "existing template type of '${getEnumName(detectedProjectType)}'."); + } + return template; + } + @override Future<FlutterCommandResult> runCommand() async { if (argResults['list-samples'] != null) { @@ -283,31 +323,7 @@ sampleCode = await _fetchSampleFromServer(argResults['sample']); } - _ProjectType template; - _ProjectType detectedProjectType; - final bool metadataExists = projectDir.absolute.childFile('.metadata').existsSync(); - if (argResults['template'] != null) { - template = _stringToProjectType(argResults['template']); - } else { - if (projectDir.existsSync() && projectDir.listSync().isNotEmpty) { - detectedProjectType = _determineTemplateType(projectDir); - if (detectedProjectType == null && metadataExists) { - // We can only be definitive that this is the wrong type if the .metadata file - // exists and contains a type that we don't understand, or doesn't contain a type. - throwToolExit('Sorry, unable to detect the type of project to recreate. ' - 'Try creating a fresh project and migrating your existing code to ' - 'the new project manually.'); - } - } - } - template ??= detectedProjectType ?? _ProjectType.app; - if (detectedProjectType != null && template != detectedProjectType && metadataExists) { - // We can only be definitive that this is the wrong type if the .metadata file - // exists and contains a type that doesn't match. - throwToolExit("The requested template type '${getEnumName(template)}' doesn't match the " - "existing template type of '${getEnumName(detectedProjectType)}'."); - } - + final _ProjectType template = _getProjectType(projectDir); final bool generateModule = template == _ProjectType.module; final bool generatePlugin = template == _ProjectType.plugin; final bool generatePackage = template == _ProjectType.package;
diff --git a/packages/flutter_tools/lib/src/commands/packages.dart b/packages/flutter_tools/lib/src/commands/packages.dart index bdf266f..4a3da9f 100644 --- a/packages/flutter_tools/lib/src/commands/packages.dart +++ b/packages/flutter_tools/lib/src/commands/packages.dart
@@ -9,6 +9,7 @@ import '../dart/pub.dart'; import '../project.dart'; import '../runner/flutter_command.dart'; +import '../usage.dart'; class PackagesCommand extends FlutterCommand { PackagesCommand() { @@ -68,13 +69,44 @@ return '${runner.executableName} pub $name [<target directory>]'; } - Future<void> _runPubGet (String directory) async { - await pubGet(context: PubContext.pubGet, - directory: directory, - upgrade: upgrade , - offline: argResults['offline'], - checkLastModified: false, - ); + @override + Future<Map<String, String>> get usageValues async { + final Map<String, String> usageValues = <String, String>{}; + final String workingDirectory = argResults.rest.length == 1 ? argResults.rest[0] : null; + final String target = findProjectRoot(workingDirectory); + if (target == null) { + return usageValues; + } + final FlutterProject rootProject = FlutterProject.fromPath(target); + final bool hasPlugins = await rootProject.flutterPluginsFile.exists(); + if (hasPlugins) { + final int numberOfPlugins = (await rootProject.flutterPluginsFile.readAsLines()).length; + usageValues[kCommandPackagesNumberPlugins] = '$numberOfPlugins'; + } else { + usageValues[kCommandPackagesNumberPlugins] = '0'; + } + usageValues[kCommandPackagesProjectModule] = '${rootProject.isModule}'; + return usageValues; + } + + Future<void> _runPubGet(String directory) async { + final Stopwatch pubGetTimer = Stopwatch()..start(); + try { + await pubGet(context: PubContext.pubGet, + directory: directory, + upgrade: upgrade , + offline: argResults['offline'], + checkLastModified: false, + ); + pubGetTimer.stop(); + flutterUsage.sendEvent('packages-pub-get', 'success'); + flutterUsage.sendTiming('packages-pub-get', 'success', pubGetTimer.elapsed); + } catch (_) { + pubGetTimer.stop(); + flutterUsage.sendEvent('packages-pub-get', 'failure'); + flutterUsage.sendTiming('packages-pub-get', 'failure', pubGetTimer.elapsed); + rethrow; + } } @override @@ -82,13 +114,12 @@ if (argResults.rest.length > 1) throwToolExit('Too many arguments.\n$usage'); - final String target = findProjectRoot( - argResults.rest.length == 1 ? argResults.rest[0] : null - ); + final String workingDirectory = argResults.rest.length == 1 ? argResults.rest[0] : null; + final String target = findProjectRoot(workingDirectory); if (target == null) { throwToolExit( 'Expected to find project root in ' - '${ argResults.rest.length == 1 ? argResults.rest[0] : "current working directory" }.' + '${ workingDirectory ?? "current working directory" }.' ); }
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index 01adc7b..4e08dd1 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -19,6 +19,7 @@ import '../run_hot.dart'; import '../runner/flutter_command.dart'; import '../tracing.dart'; +import '../usage.dart'; import 'daemon.dart'; abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopmentArtifacts { @@ -208,8 +209,23 @@ final String deviceType = devices.length == 1 ? getNameForTargetPlatform(await devices[0].targetPlatform) : 'multiple'; + final AndroidProject androidProject = FlutterProject.current().android; + final IosProject iosProject = FlutterProject.current().ios; + final List<String> hostLanguage = <String>[]; - return <String, String>{'cd3': '$isEmulator', 'cd4': deviceType}; + if (androidProject != null && androidProject.existsSync()) { + hostLanguage.add(androidProject.isKotlin ? 'kotlin' : 'java'); + } + if (iosProject != null && iosProject.exists) { + hostLanguage.add(iosProject.isSwift ? 'swift' : 'objc'); + } + + return <String, String>{ + kCommandRunIsEmulator: '$isEmulator', + kCommandRunTargetName: deviceType, + kCommandRunProjectModule: '${FlutterProject.current().isModule}', + kCommandRunProjectHostLanguage: hostLanguage.join(','), + }; } @override
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index 60c8f12..46cec9b 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart
@@ -397,6 +397,7 @@ final FlutterProject parent; static final RegExp _applicationIdPattern = RegExp('^\\s*applicationId\\s+[\'\"](.*)[\'\"]\\s*\$'); + static final RegExp _kotlinPluginPattern = RegExp('^\\s*apply plugin\:\\s+[\'\"]kotlin-android[\'\"]\\s*\$'); static final RegExp _groupPattern = RegExp('^\\s*group\\s+[\'\"](.*)[\'\"]\\s*\$'); /// The Gradle root directory of the Android host app. This is the directory @@ -419,6 +420,12 @@ /// True if the parent Flutter project is a module. bool get isModule => parent.isModule; + /// True, if the app project is using Kotlin. + bool get isKotlin { + final File gradleFile = hostAppGradleRoot.childDirectory('app').childFile('build.gradle'); + return _firstMatchInFile(gradleFile, _kotlinPluginPattern) != null; + } + File get appManifestFile { return isUsingGradle ? fs.file(fs.path.join(hostAppGradleRoot.path, 'app', 'src', 'main', 'AndroidManifest.xml'))
diff --git a/packages/flutter_tools/lib/src/usage.dart b/packages/flutter_tools/lib/src/usage.dart index 1902940..beeb9c2 100644 --- a/packages/flutter_tools/lib/src/usage.dart +++ b/packages/flutter_tools/lib/src/usage.dart
@@ -26,6 +26,19 @@ const String kEventReloadTransferTimeInMs = 'cd12'; const String kEventReloadOverallTimeInMs = 'cd13'; +const String kCommandRunIsEmulator = 'cd3'; +const String kCommandRunTargetName = 'cd4'; +const String kCommandRunProjectType = 'cd14'; +const String kCommandRunProjectHostLanguage = 'cd15'; +const String kCommandRunProjectModule = 'cd18'; + +const String kCommandCreateAndroidLanguage = 'cd16'; +const String kCommandCreateIosLanguage = 'cd17'; +const String kCommandCreateProjectType = 'cd19'; + +const String kCommandPackagesNumberPlugins = 'cd20'; +const String kCommandPackagesProjectModule = 'cd21'; + Usage get flutterUsage => Usage.instance; class Usage {