Add android analyzer commands for applinks (#131009)

Since applink validation tool is going to be a static tool, It won't have access to vmservices.

[flutter.dev/go/static-tooling-in-devtools](http://flutter.dev/go/static-tooling-in-devtools)

I remove the vm services and also update the deeplink task to also include path pattern and custom scheme
http://go/android-applink-apis (internal only)
diff --git a/packages/flutter_tools/lib/src/commands/analyze.dart b/packages/flutter_tools/lib/src/commands/analyze.dart
index 2c1c637..dff347f 100644
--- a/packages/flutter_tools/lib/src/commands/analyze.dart
+++ b/packages/flutter_tools/lib/src/commands/analyze.dart
@@ -16,6 +16,7 @@
 import 'analyze_base.dart';
 import 'analyze_continuously.dart';
 import 'analyze_once.dart';
+import 'android_analyze.dart';
 import 'validate_project.dart';
 
 class AnalyzeCommand extends FlutterCommand {
@@ -99,6 +100,37 @@
     argParser.addFlag('fatal-warnings',
         help: 'Treat warning level issues as fatal.',
         defaultsTo: true);
+
+    argParser.addFlag('android',
+      negatable: false,
+      help: 'Analyze Android sub-project. Used by internal tools only.',
+      hide: !verboseHelp,
+    );
+
+    if (verboseHelp) {
+      argParser.addSeparator('Usage: flutter analyze --android [arguments]');
+    }
+
+    argParser.addFlag('list-build-variants',
+      negatable: false,
+      help: 'Print out a list of available build variants for the '
+          'Android sub-project.',
+      hide: !verboseHelp,
+    );
+
+    argParser.addFlag('output-app-link-settings',
+      negatable: false,
+      help: 'Output a JSON with Android app link settings into a file. '
+          'The "--build-variant" must also be set.',
+      hide: !verboseHelp,
+    );
+
+    argParser.addOption('build-variant',
+      help: 'Sets the Android build variant to be analyzed.',
+      valueHelp: 'use "flutter analyze --android --list-build-variants" to get '
+          'all available build variants',
+      hide: !verboseHelp,
+    );
   }
 
   /// The working directory for testing analysis using dartanalyzer.
@@ -142,12 +174,51 @@
       return false;
     }
 
+    // Don't run pub if asking for android analysis.
+    if (boolArg('android')) {
+      return false;
+    }
+
     return super.shouldRunPub;
   }
 
   @override
   Future<FlutterCommandResult> runCommand() async {
-    if (boolArg('suggestions')) {
+    if (boolArg('android')) {
+      final AndroidAnalyzeOption option;
+      final String? buildVariant;
+      if (argResults!['list-build-variants'] as bool && argResults!['output-app-link-settings'] as bool) {
+        throwToolExit('Only one of "--list-build-variants" or "--output-app-link-settings" can be provided');
+      }
+      if (argResults!['list-build-variants'] as bool) {
+        option = AndroidAnalyzeOption.listBuildVariant;
+        buildVariant = null;
+      } else if (argResults!['output-app-link-settings'] as bool) {
+        option = AndroidAnalyzeOption.outputAppLinkSettings;
+        buildVariant = argResults!['build-variant'] as String?;
+        if (buildVariant == null) {
+          throwToolExit('"--build-variant" must be provided');
+        }
+      } else {
+        throwToolExit('No argument is provided to analyze. Use -h to see available commands.');
+      }
+      final Set<String> items = findDirectories(argResults!, _fileSystem);
+      final String directoryPath;
+      if (items.isEmpty) { // user did not specify any path
+        directoryPath = _fileSystem.currentDirectory.path;
+      } else if (items.length > 1) { // if the user sends more than one path
+        throwToolExit('The Android analyze can process only one directory path');
+      } else {
+        directoryPath = items.first;
+      }
+      await AndroidAnalyze(
+        fileSystem: _fileSystem,
+        option: option,
+        userPath: directoryPath,
+        buildVariant: buildVariant,
+        logger: _logger,
+      ).analyze();
+    } else if (boolArg('suggestions')) {
       final String directoryPath;
       if (boolArg('watch')) {
         throwToolExit('flag --watch is not compatible with --suggestions');
diff --git a/packages/flutter_tools/lib/src/commands/android_analyze.dart b/packages/flutter_tools/lib/src/commands/android_analyze.dart
new file mode 100644
index 0000000..c4389ff
--- /dev/null
+++ b/packages/flutter_tools/lib/src/commands/android_analyze.dart
@@ -0,0 +1,56 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import '../base/file_system.dart';
+import '../base/logger.dart';
+import '../convert.dart';
+import '../project.dart';
+
+/// The type of analysis to perform.
+enum AndroidAnalyzeOption {
+  /// Prints out available build variants of the Android sub-project.
+  ///
+  /// An example output:
+  /// ["debug", "profile", "release"]
+  listBuildVariant,
+
+  /// Outputs app link settings of the Android sub-project into a file.
+  ///
+  /// The file path will be printed after the command is run successfully.
+  outputAppLinkSettings,
+}
+
+/// Analyze the Android sub-project of a Flutter project.
+///
+/// The [userPath] must be point to a flutter project.
+class AndroidAnalyze {
+  AndroidAnalyze({
+    required this.fileSystem,
+    required this.option,
+    required this.userPath,
+    this.buildVariant,
+    required this.logger,
+  }) : assert(option == AndroidAnalyzeOption.listBuildVariant || buildVariant != null);
+
+  final FileSystem fileSystem;
+  final AndroidAnalyzeOption option;
+  final String? buildVariant;
+  final String userPath;
+  final Logger logger;
+
+  Future<void> analyze() async {
+    final FlutterProject project = FlutterProject.fromDirectory(fileSystem.directory(userPath));
+    switch (option) {
+      case AndroidAnalyzeOption.listBuildVariant:
+        logger.printStatus(jsonEncode(await project.android.getBuildVariants()));
+      case AndroidAnalyzeOption.outputAppLinkSettings:
+        assert(buildVariant != null);
+        await project.android.outputsAppLinkSettings(variant: buildVariant!);
+        final String filePath = fileSystem.path.join(project.directory.path, 'build', 'app', 'app-link-settings-$buildVariant.json`');
+        logger.printStatus('result saved in $filePath');
+    }
+  }
+}
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/android_analyze_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/android_analyze_test.dart
new file mode 100644
index 0000000..44c9c5f
--- /dev/null
+++ b/packages/flutter_tools/test/commands.shard/hermetic/android_analyze_test.dart
@@ -0,0 +1,129 @@
+// Copyright 2014 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:args/command_runner.dart';
+import 'package:file/file.dart';
+import 'package:file/memory.dart';
+import 'package:flutter_tools/src/android/android_builder.dart';
+import 'package:flutter_tools/src/artifacts.dart';
+import 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/base/terminal.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/commands/analyze.dart';
+import 'package:flutter_tools/src/project.dart';
+import 'package:flutter_tools/src/project_validator.dart';
+import 'package:test/fake.dart';
+
+import '../../src/context.dart';
+import '../../src/test_flutter_command_runner.dart';
+
+void main() {
+
+  group('Android analyze command', () {
+    late FileSystem fileSystem;
+    late Platform platform;
+    late BufferLogger logger;
+    late FakeProcessManager processManager;
+    late Terminal terminal;
+    late AnalyzeCommand command;
+    late CommandRunner<void> runner;
+    late Directory tempDir;
+    late FakeAndroidBuilder builder;
+
+    setUpAll(() {
+      Cache.disableLocking();
+    });
+
+    setUp(() async {
+      fileSystem = MemoryFileSystem.test();
+      platform = FakePlatform();
+      logger = BufferLogger.test();
+      processManager = FakeProcessManager.empty();
+      terminal = Terminal.test();
+      command = AnalyzeCommand(
+        artifacts: Artifacts.test(),
+        fileSystem: fileSystem,
+        logger: logger,
+        platform: platform,
+        processManager: processManager,
+        terminal: terminal,
+        allProjectValidators: <ProjectValidator>[],
+        suppressAnalytics: true,
+      );
+      runner = createTestCommandRunner(command);
+      tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_tools_packages_test.');
+      tempDir.childDirectory('android').createSync();
+
+      // Setup repo roots
+      const String homePath = '/home/user/flutter';
+      Cache.flutterRoot = homePath;
+      for (final String dir in <String>['dev', 'examples', 'packages']) {
+        fileSystem.directory(homePath).childDirectory(dir).createSync(recursive: true);
+      }
+      builder = FakeAndroidBuilder();
+
+    });
+
+    testUsingContext('can list build variants', () async {
+      builder.variants = <String>['debug', 'release'];
+      await runner.run(<String>['analyze', '--android', '--list-build-variants', tempDir.path]);
+      expect(logger.statusText, contains('["debug","release"]'));
+    }, overrides: <Type, Generator>{
+      AndroidBuilder: () => builder,
+    });
+
+    testUsingContext('throw if provide multiple path', () async {
+      final Directory anotherTempDir = fileSystem.systemTempDirectory.createTempSync('another');
+      await expectLater(
+        runner.run(<String>['analyze', '--android', '--list-build-variants', tempDir.path, anotherTempDir.path]),
+        throwsA(
+          isA<Exception>().having(
+            (Exception e) => e.toString(),
+          'description',
+          contains('The Android analyze can process only one directory path'),
+          ),
+        ),
+      );
+    });
+
+    testUsingContext('can output app link settings', () async {
+      const String buildVariant = 'release';
+      await runner.run(<String>['analyze', '--android', '--output-app-link-settings', '--build-variant=$buildVariant', tempDir.path]);
+      expect(builder.outputVariant, buildVariant);
+    }, overrides: <Type, Generator>{
+      AndroidBuilder: () => builder,
+    });
+
+    testUsingContext('output app link settings throws if no build variant', () async {
+      await expectLater(
+        runner.run(<String>['analyze', '--android', '--output-app-link-settings', tempDir.path]),
+        throwsA(
+          isA<Exception>().having(
+            (Exception e) => e.toString(),
+            'description',
+            contains('"--build-variant" must be provided'),
+          ),
+        ),
+      );
+    });
+  });
+}
+
+class FakeAndroidBuilder extends Fake implements AndroidBuilder {
+  List<String> variants = const <String>[];
+  String? outputVariant;
+
+  @override
+  Future<List<String>> getBuildVariants({required FlutterProject project}) async {
+    return variants;
+  }
+
+  @override
+  Future<void> outputsAppLinkSettings(String buildVariant, {required FlutterProject project}) async {
+    outputVariant = buildVariant;
+  }
+}