update analytics in generate api docs site to use new UA4 (#136497)

Change the analytics instance that the generated api.flutter.dev and master-api.flutter.dev use.
diff --git a/dev/docs/analytics-footer.html b/dev/docs/analytics-footer.html
new file mode 100644
index 0000000..133ecf3
--- /dev/null
+++ b/dev/docs/analytics-footer.html
@@ -0,0 +1,4 @@
+<!-- Google Tag Manager (noscript) -->
+<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-ND4LWWZ"
+height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
+<!-- End Google Tag Manager (noscript) -->
diff --git a/dev/docs/analytics-header.html b/dev/docs/analytics-header.html
new file mode 100644
index 0000000..bed0852
--- /dev/null
+++ b/dev/docs/analytics-header.html
@@ -0,0 +1,7 @@
+<!-- Google Tag Manager -->
+<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
+new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
+j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
+'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f)
+})(window,document,'script','dataLayer','GTM-ND4LWWZ');</script>
+<!-- End Google Tag Manager -->
diff --git a/dev/docs/analytics.html b/dev/docs/analytics.html
deleted file mode 100644
index ee11d34..0000000
--- a/dev/docs/analytics.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<script>
-  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
-  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
-  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
-  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
-
-  ga('create', 'UA-67589403-1', 'auto');
-  ga('send', 'pageview');
-</script>
diff --git a/dev/tools/create_api_docs.dart b/dev/tools/create_api_docs.dart
index 87c556e..56f6c32 100644
--- a/dev/tools/create_api_docs.dart
+++ b/dev/tools/create_api_docs.dart
@@ -518,9 +518,9 @@
     final Version version = FlutterInformation.instance.getFlutterVersion();
 
     // Verify which version of snippets and dartdoc we're using.
-    final ProcessResult snippetsResult = Process.runSync(
-      FlutterInformation.instance.getFlutterBinaryPath().path,
+    final ProcessResult snippetsResult = processManager.runSync(
       <String>[
+        FlutterInformation.instance.getFlutterBinaryPath().path,
         'pub',
         'global',
         'list',
@@ -574,13 +574,15 @@
       '--header',
       docsRoot.childFile('styles.html').path,
       '--header',
-      docsRoot.childFile('analytics.html').path,
+      docsRoot.childFile('analytics-header.html').path,
       '--header',
       docsRoot.childFile('survey.html').path,
       '--header',
       docsRoot.childFile('snippets.html').path,
       '--header',
       docsRoot.childFile('opensearch.html').path,
+      '--footer',
+      docsRoot.childFile('analytics-footer.html').path,
       '--footer-text',
       packageRoot.childFile('footer.html').path,
       '--allow-warnings-in-packages',
@@ -643,6 +645,7 @@
       arguments: dartdocArgs,
       workingDirectory: packageRoot,
       environment: pubEnvironment,
+      processManager: processManager,
     ));
     printStream(
       process.stdout,
@@ -669,7 +672,7 @@
     }
 
     _sanityCheckDocs();
-    checkForUnresolvedDirectives(publishRoot.childDirectory('flutter').path);
+    checkForUnresolvedDirectives(publishRoot.childDirectory('flutter'));
 
     _createIndexAndCleanup();
 
@@ -690,12 +693,13 @@
     }
   }
 
-  /// Runs a sanity check by running a test.
-  void _sanityCheckDocs([Platform platform = const LocalPlatform()]) {
+  /// A subset of all generated doc files for [_sanityCheckDocs].
+  @visibleForTesting
+  List<File> get canaries {
     final Directory flutterDirectory = publishRoot.childDirectory('flutter');
     final Directory widgetsDirectory = flutterDirectory.childDirectory('widgets');
 
-    final List<File> canaries = <File>[
+    return <File>[
       publishRoot.childDirectory('assets').childFile('overrides.css'),
       flutterDirectory.childDirectory('dart-io').childFile('File-class.html'),
       flutterDirectory.childDirectory('dart-ui').childFile('Canvas-class.html'),
@@ -710,12 +714,19 @@
       widgetsDirectory.childFile('Widget-class.html'),
       widgetsDirectory.childFile('Listener-class.html'),
     ];
+  }
+
+  /// Runs a sanity check by running a test.
+  void _sanityCheckDocs([Platform platform = const LocalPlatform()]) {
     for (final File canary in canaries) {
       if (!canary.existsSync()) {
         throw Exception('Missing "${canary.path}", which probably means the documentation failed to build correctly.');
       }
     }
     // Make sure at least one example of each kind includes source code.
+    final Directory widgetsDirectory = publishRoot
+        .childDirectory('flutter')
+        .childDirectory('widgets');
 
     // Check a "sample" example, any one will do.
     _sanityCheckExample(
diff --git a/dev/tools/dartdoc_checker.dart b/dev/tools/dartdoc_checker.dart
index 75fbd2b..cbc47f8 100644
--- a/dev/tools/dartdoc_checker.dart
+++ b/dev/tools/dartdoc_checker.dart
@@ -4,9 +4,31 @@
 
 import 'dart:io';
 
+import 'package:meta/meta.dart';
 import 'package:path/path.dart' as path;
 
-/// Scans the dartdoc HTML output in the provided `htmlOutputPath` for
+/// Makes sure that the path we were given contains some of the expected
+/// libraries.
+@visibleForTesting
+const List<String> dartdocDirectiveCanaryLibraries = <String>[
+  'animation',
+  'cupertino',
+  'material',
+  'widgets',
+  'rendering',
+  'flutter_driver',
+];
+
+/// Makes sure that the path we were given contains some of the expected
+/// HTML files.
+@visibleForTesting
+const List<String> dartdocDirectiveCanaryFiles = <String>[
+  'Widget-class.html',
+  'Material-class.html',
+  'Canvas-class.html',
+];
+
+/// Scans the dartdoc HTML output in the provided `dartDocDir` for
 /// unresolved dartdoc directives (`{@foo x y}`).
 ///
 /// Dartdoc usually replaces those directives with other content. However,
@@ -22,27 +44,14 @@
 /// ```
 /// void foo({@required int bar});
 /// ```
-void checkForUnresolvedDirectives(String htmlOutputPath) {
-  final Directory dartDocDir = Directory(htmlOutputPath);
+void checkForUnresolvedDirectives(Directory dartDocDir) {
   if (!dartDocDir.existsSync()) {
     throw Exception('Directory with dartdoc output (${dartDocDir.path}) does not exist.');
   }
 
-  // Makes sure that the path we were given contains some of the expected
-  // libraries and HTML files.
-  final List<String> canaryLibraries = <String>[
-    'animation',
-    'cupertino',
-    'material',
-    'widgets',
-    'rendering',
-    'flutter_driver',
-  ];
-  final List<String> canaryFiles = <String>[
-    'Widget-class.html',
-    'Material-class.html',
-    'Canvas-class.html',
-  ];
+  // Make a copy since this will be mutated
+  final List<String> canaryLibraries = dartdocDirectiveCanaryLibraries.toList();
+  final List<String> canaryFiles = dartdocDirectiveCanaryFiles.toList();
 
   print('Scanning for unresolved dartdoc directives...');
 
@@ -112,5 +121,5 @@
   if (!Directory(args.single).existsSync()) {
     throw Exception('The dartdoc HTML output directory ${args.single} does not exist.');
   }
-  checkForUnresolvedDirectives(args.single);
+  checkForUnresolvedDirectives(Directory(args.single));
 }
diff --git a/dev/tools/test/create_api_docs_test.dart b/dev/tools/test/create_api_docs_test.dart
index 6d6b1e8..811885e 100644
--- a/dev/tools/test/create_api_docs_test.dart
+++ b/dev/tools/test/create_api_docs_test.dart
@@ -10,6 +10,7 @@
 
 import '../../../packages/flutter_tools/test/src/fake_process_manager.dart';
 import '../create_api_docs.dart' as apidocs;
+import '../dartdoc_checker.dart';
 
 void main() {
   group('FlutterInformation', () {
@@ -223,6 +224,235 @@
       expect(info['engineRealm'], equals('realm'));
     });
   });
+
+  group('DartDocGenerator', () {
+    late apidocs.DartdocGenerator generator;
+    late MemoryFileSystem fs;
+    late FakeProcessManager processManager;
+    late Directory publishRoot;
+
+    setUp(() {
+      fs = MemoryFileSystem.test();
+      publishRoot = fs.directory('/path/to/publish');
+      processManager = FakeProcessManager.empty();
+      generator = apidocs.DartdocGenerator(
+        packageRoot: fs.directory('/path/to/package'),
+        publishRoot: publishRoot,
+        docsRoot: fs.directory('/path/to/docs'),
+        filesystem: fs,
+        processManager: processManager,
+      );
+      final Directory repoRoot = fs.directory('/flutter');
+      repoRoot.childDirectory('packages').createSync(recursive: true);
+      apidocs.FlutterInformation.instance = apidocs.FlutterInformation(
+        filesystem: fs,
+        processManager: processManager,
+        platform: FakePlatform(environment: <String, String>{
+          'FLUTTER_ROOT': repoRoot.path,
+        }),
+      );
+    });
+
+    test('.generateDartDoc() invokes dartdoc with the correct command line arguments', () async {
+      processManager.addCommands(<FakeCommand>[
+        const FakeCommand(command: <String>['/flutter/bin/flutter', 'pub', 'get']),
+        const FakeCommand(
+          command: <String>['/flutter/bin/flutter', '--version', '--machine'],
+          stdout: testVersionInfo,
+        ),
+        const FakeCommand(
+          command: <Pattern>['git', 'status', '-b', '--porcelain'],
+          stdout: '## $branchName',
+        ),
+        const FakeCommand(
+          command: <String>['git', 'rev-parse', 'HEAD'],
+        ),
+        const FakeCommand(
+          command: <String>['/flutter/bin/flutter', 'pub', 'global', 'list'],
+        ),
+        FakeCommand(
+          command: <Pattern>[
+            '/flutter/bin/flutter',
+            'pub',
+            'global',
+            'run',
+            '--enable-asserts',
+            'dartdoc',
+            '--output',
+            '/path/to/publish/flutter',
+            '--allow-tools',
+            '--json',
+            '--validate-links',
+            '--link-to-source-excludes',
+            '/flutter/bin/cache',
+            '--link-to-source-root',
+            '/flutter',
+            '--link-to-source-uri-template',
+            'https://github.com/flutter/flutter/blob/master/%f%#L%l%',
+            '--inject-html',
+            '--use-base-href',
+            '--header',
+            '/path/to/docs/styles.html',
+            '--header',
+            '/path/to/docs/analytics-header.html',
+            '--header',
+            '/path/to/docs/survey.html',
+            '--header',
+            '/path/to/docs/snippets.html',
+            '--header',
+            '/path/to/docs/opensearch.html',
+            '--footer',
+            '/path/to/docs/analytics-footer.html',
+            '--footer-text',
+            '/path/to/package/footer.html',
+            '--allow-warnings-in-packages',
+            // match package names
+            RegExp(r'^(\w+,)+(\w+)$'),
+            '--exclude-packages',
+            RegExp(r'^(\w+,)+(\w+)$'),
+            '--exclude',
+            // match dart package URIs
+            RegExp(r'^([\w\/:.]+,)+([\w\/:.]+)$'),
+            '--favicon',
+            '/path/to/docs/favicon.ico',
+            '--package-order',
+            'flutter,Dart,${apidocs.kPlatformIntegrationPackageName},flutter_test,flutter_driver',
+            '--auto-include-dependencies',
+          ],
+        ),
+      ]);
+
+      // This will throw while sanity checking generated files, which is tested independently
+      await expectLater(
+        () => generator.generateDartdoc(),
+        throwsA(
+          isA<Exception>().having(
+            (Exception e) => e.toString(),
+            'message',
+            contains(RegExp(r'Missing .* which probably means the documentation failed to build correctly.')),
+          ),
+        ),
+      );
+
+      expect(processManager, hasNoRemainingExpectations);
+    });
+
+    test('sanity checks spot check generated files', () async {
+      processManager.addCommands(<FakeCommand>[
+        const FakeCommand(command: <String>['/flutter/bin/flutter', 'pub', 'get']),
+        const FakeCommand(
+          command: <String>['/flutter/bin/flutter', '--version', '--machine'],
+          stdout: testVersionInfo,
+        ),
+        const FakeCommand(
+          command: <Pattern>['git', 'status', '-b', '--porcelain'],
+          stdout: '## $branchName',
+        ),
+        const FakeCommand(
+          command: <String>['git', 'rev-parse', 'HEAD'],
+        ),
+        const FakeCommand(
+          command: <String>['/flutter/bin/flutter', 'pub', 'global', 'list'],
+        ),
+        FakeCommand(
+          command: <Pattern>[
+            '/flutter/bin/flutter',
+            'pub',
+            'global',
+            'run',
+            '--enable-asserts',
+            'dartdoc',
+            '--output',
+            '/path/to/publish/flutter',
+            '--allow-tools',
+            '--json',
+            '--validate-links',
+            '--link-to-source-excludes',
+            '/flutter/bin/cache',
+            '--link-to-source-root',
+            '/flutter',
+            '--link-to-source-uri-template',
+            'https://github.com/flutter/flutter/blob/master/%f%#L%l%',
+            '--inject-html',
+            '--use-base-href',
+            '--header',
+            '/path/to/docs/styles.html',
+            '--header',
+            '/path/to/docs/analytics-header.html',
+            '--header',
+            '/path/to/docs/survey.html',
+            '--header',
+            '/path/to/docs/snippets.html',
+            '--header',
+            '/path/to/docs/opensearch.html',
+            '--footer',
+            '/path/to/docs/analytics-footer.html',
+            '--footer-text',
+            '/path/to/package/footer.html',
+            '--allow-warnings-in-packages',
+            // match package names
+            RegExp(r'^(\w+,)+(\w+)$'),
+            '--exclude-packages',
+            RegExp(r'^(\w+,)+(\w+)$'),
+            '--exclude',
+            // match dart package URIs
+            RegExp(r'^([\w\/:.]+,)+([\w\/:.]+)$'),
+            '--favicon',
+            '/path/to/docs/favicon.ico',
+            '--package-order',
+            'flutter,Dart,${apidocs.kPlatformIntegrationPackageName},flutter_test,flutter_driver',
+            '--auto-include-dependencies',
+          ],
+          onRun: () {
+            for (final File canary in generator.canaries) {
+              canary.createSync(recursive: true);
+            }
+            for (final String path in dartdocDirectiveCanaryFiles) {
+              publishRoot.childDirectory('flutter').childFile(path).createSync(recursive: true);
+            }
+            for (final String path in dartdocDirectiveCanaryLibraries) {
+              publishRoot.childDirectory('flutter').childDirectory(path).createSync(recursive: true);
+            }
+            publishRoot.childDirectory('flutter').childFile('index.html').createSync();
+
+            final Directory widgetsDir = publishRoot
+                .childDirectory('flutter')
+                .childDirectory('widgets')
+                ..createSync(recursive: true);
+            widgetsDir.childFile('showGeneralDialog.html').writeAsStringSync('''
+<pre id="longSnippet1">
+  <code class="language-dart">
+    import &#39;package:flutter&#47;material.dart&#39;;
+  </code>
+</pre>
+''',
+            );
+            expect(publishRoot.childDirectory('flutter').existsSync(), isTrue);
+            (widgetsDir
+              .childDirectory('ModalRoute')
+              ..createSync(recursive: true))
+              .childFile('barrierColor.html')
+              .writeAsStringSync('''
+<pre id="sample-code">
+  <code class="language-dart">
+    class FooClass {
+      Color get barrierColor => FooColor();
+    }
+  </code>
+</pre>
+''');
+            const String queryParams = 'split=1&run=true&sample_id=widgets.Listener.123&sample_channel=master&channel=master';
+            widgetsDir.childFile('Listener-class.html').writeAsStringSync('''
+<iframe class="snippet-dartpad" src="https://dartpad.dev/embed-flutter.html?$queryParams">
+</iframe>
+''');
+          }
+        ),
+      ]);
+
+      await generator.generateDartdoc();
+    });
+  });
 }
 
 const String branchName = 'stable';