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 {