v1.12.13+hotfix.2 cherry-picks (#46055)

* flutter/engine@6955b06 Cherry-picks for 1.12.13
  * flutter/engine@3e6d6bc add pointer data santizing in flutter web engine
* 09126ab Enable Android embedding v2 on the beta, dev and stable channel
* 4c95bbd Re-enable hostonly tests on non-master branches
* 066a992 Ensure that docker tag is legal
* 4484ae4 [flutter_tool] Do not continue with a no-op 'upgrade'
diff --git a/.cirrus.yml b/.cirrus.yml
index 1ae3c4f..51a58da 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -229,7 +229,6 @@
         - dart --enable-asserts ./dev/bots/test.dart
 
     - name: hostonly_devicelab_tests-0-linux
-      only_if: "$CIRRUS_PR != '' || $CIRRUS_BRANCH == 'master'" # https://github.com/flutter/flutter/issues/45453
       environment:
         # Some of the host-only devicelab tests are pretty involved and need a lot of RAM.
         CPU: 2
@@ -238,7 +237,6 @@
         - dart --enable-asserts ./dev/bots/test.dart
 
     - name: hostonly_devicelab_tests-1-linux
-      only_if: "$CIRRUS_PR != '' || $CIRRUS_BRANCH == 'master'" # https://github.com/flutter/flutter/issues/45453
       environment:
         # Some of the host-only devicelab tests are pretty involved and need a lot of RAM.
         CPU: 2
@@ -247,7 +245,6 @@
         - dart --enable-asserts ./dev/bots/test.dart
 
     - name: hostonly_devicelab_tests-2-linux
-      only_if: "$CIRRUS_PR != '' || $CIRRUS_BRANCH == 'master'" # https://github.com/flutter/flutter/issues/45453
       environment:
         # Some of the host-only devicelab tests are pretty involved and need a lot of RAM.
         CPU: 2
@@ -256,7 +253,6 @@
         - dart --enable-asserts ./dev/bots/test.dart
 
     - name: hostonly_devicelab_tests-3_last-linux
-      only_if: "$CIRRUS_PR != '' || $CIRRUS_BRANCH == 'master'" # https://github.com/flutter/flutter/issues/45453
       environment:
         # Some of the host-only devicelab tests are pretty involved and need a lot of RAM.
         CPU: 2
@@ -403,22 +399,22 @@
         - dart --enable-asserts dev\bots\test.dart
 
     - name: hostonly_devicelab_tests-0-windows
-      only_if: "changesInclude('.cirrus.yml', 'dev/**', 'bin/internal/**') || ($CIRRUS_PR == '' && $CIRRUS_BRANCH == 'master')" # https://github.com/flutter/flutter/issues/41941 https://github.com/flutter/flutter/issues/45453
+      only_if: "changesInclude('.cirrus.yml', 'dev/**', 'bin/internal/**') || $CIRRUS_PR == ''" # https://github.com/flutter/flutter/issues/41941
       script:
         - dart --enable-asserts ./dev/bots/test.dart
 
     - name: hostonly_devicelab_tests-1-windows
-      only_if: "changesInclude('.cirrus.yml', 'dev/**', 'bin/internal/**') || ($CIRRUS_PR == '' && $CIRRUS_BRANCH == 'master')" # https://github.com/flutter/flutter/issues/41941 https://github.com/flutter/flutter/issues/45453
+      only_if: "changesInclude('.cirrus.yml', 'dev/**', 'bin/internal/**') || $CIRRUS_PR == ''" # https://github.com/flutter/flutter/issues/41941
       script:
         - dart --enable-asserts ./dev/bots/test.dart
 
     - name: hostonly_devicelab_tests-2-windows
-      only_if: "changesInclude('.cirrus.yml', 'dev/**', 'bin/internal/**') || ($CIRRUS_PR == '' && $CIRRUS_BRANCH == 'master')" # https://github.com/flutter/flutter/issues/41941 https://github.com/flutter/flutter/issues/45453
+      only_if: "changesInclude('.cirrus.yml', 'dev/**', 'bin/internal/**') || $CIRRUS_PR == ''" # https://github.com/flutter/flutter/issues/41941
       script:
         - dart --enable-asserts ./dev/bots/test.dart
 
     - name: hostonly_devicelab_tests-3_last-windows
-      only_if: "changesInclude('.cirrus.yml', 'dev/**', 'bin/internal/**') || ($CIRRUS_PR == '' && $CIRRUS_BRANCH == 'master')" # https://github.com/flutter/flutter/issues/41941 https://github.com/flutter/flutter/issues/45453
+      only_if: "changesInclude('.cirrus.yml', 'dev/**', 'bin/internal/**') || $CIRRUS_PR == ''" # https://github.com/flutter/flutter/issues/41941
       script:
         - dart --enable-asserts ./dev/bots/test.dart
 
@@ -516,25 +512,25 @@
         - dart --enable-asserts ./dev/bots/test.dart
 
     - name: hostonly_devicelab_tests-0-macos
-      only_if: "changesInclude('.cirrus.yml', 'dev/**', 'bin/internal/**') || ($CIRRUS_PR == '' && $CIRRUS_BRANCH == 'master')" # https://github.com/flutter/flutter/issues/41941 https://github.com/flutter/flutter/issues/45453
+      only_if: "changesInclude('.cirrus.yml', 'dev/**', 'bin/internal/**') || $CIRRUS_PR == ''" # https://github.com/flutter/flutter/issues/41941
       script:
         - ulimit -S -n 2048 # https://github.com/flutter/flutter/issues/2976
         - dart --enable-asserts ./dev/bots/test.dart
 
     - name: hostonly_devicelab_tests-1-macos
-      only_if: "changesInclude('.cirrus.yml', 'dev/**', 'bin/internal/**') || ($CIRRUS_PR == '' && $CIRRUS_BRANCH == 'master')" # https://github.com/flutter/flutter/issues/41941 https://github.com/flutter/flutter/issues/45453
+      only_if: "changesInclude('.cirrus.yml', 'dev/**', 'bin/internal/**') || $CIRRUS_PR == ''" # https://github.com/flutter/flutter/issues/41941
       script:
         - ulimit -S -n 2048 # https://github.com/flutter/flutter/issues/2976
         - dart --enable-asserts ./dev/bots/test.dart
 
     - name: hostonly_devicelab_tests-2-macos
-      only_if: "changesInclude('.cirrus.yml', 'dev/**', 'bin/internal/**') || ($CIRRUS_PR == '' && $CIRRUS_BRANCH == 'master')" # https://github.com/flutter/flutter/issues/41941 https://github.com/flutter/flutter/issues/45453
+      only_if: "changesInclude('.cirrus.yml', 'dev/**', 'bin/internal/**') || $CIRRUS_PR == ''" # https://github.com/flutter/flutter/issues/41941
       script:
         - ulimit -S -n 2048 # https://github.com/flutter/flutter/issues/2976
         - dart --enable-asserts ./dev/bots/test.dart
 
     - name: hostonly_devicelab_tests-3_last-macos
-      only_if: "changesInclude('.cirrus.yml', 'dev/**', 'bin/internal/**') || ($CIRRUS_PR == '' && $CIRRUS_BRANCH == 'master')" # https://github.com/flutter/flutter/issues/41941 https://github.com/flutter/flutter/issues/45453
+      only_if: "changesInclude('.cirrus.yml', 'dev/**', 'bin/internal/**') || $CIRRUS_PR == ''" # https://github.com/flutter/flutter/issues/41941
       script:
         - ulimit -S -n 2048 # https://github.com/flutter/flutter/issues/2976
         - dart --enable-asserts ./dev/bots/test.dart
diff --git a/bin/internal/engine.version b/bin/internal/engine.version
index 7ad3d1a..1842d08 100644
--- a/bin/internal/engine.version
+++ b/bin/internal/engine.version
@@ -1 +1 @@
-c1e322b685a81c11c16bddd22282925b7d0272e8
+6955b06cedb2425f4363f10642c9b0e63e496af0
diff --git a/dev/ci/docker_linux/docker_build.sh b/dev/ci/docker_linux/docker_build.sh
index a533b18..3b5901d 100755
--- a/dev/ci/docker_linux/docker_build.sh
+++ b/dev/ci/docker_linux/docker_build.sh
@@ -2,6 +2,10 @@
 
 TAG="${CIRRUS_TAG:-latest}"
 
+# Convert "+" to "-" to make hotfix tags legal Docker tag names.
+# See https://docs.docker.com/engine/reference/commandline/tag/
+TAG=${TAG/+/-}
+
 # pull to make sure we are not rebuilding for nothing
 sudo docker pull "gcr.io/flutter-cirrus/build-flutter-image:$TAG"
 
diff --git a/packages/flutter_tools/lib/src/commands/upgrade.dart b/packages/flutter_tools/lib/src/commands/upgrade.dart
index ace639e..87cbc26 100644
--- a/packages/flutter_tools/lib/src/commands/upgrade.dart
+++ b/packages/flutter_tools/lib/src/commands/upgrade.dart
@@ -122,8 +122,13 @@
     }
     await resetChanges(gitTagVersion);
     await upgradeChannel(flutterVersion);
-    await attemptFastForward();
-    await flutterUpgradeContinue();
+    final bool alreadyUpToDate = await attemptFastForward(flutterVersion);
+    if (alreadyUpToDate) {
+      // If the upgrade was a no op, then do not continue with the second half.
+      printTrace('Flutter is already up to date on channel ${flutterVersion.channel}');
+    } else {
+      await flutterUpgradeContinue();
+    }
   }
 
   Future<void> flutterUpgradeContinue() async {
@@ -234,7 +239,10 @@
   ///
   /// If there haven't been any hot fixes or local changes, this is equivalent
   /// to a fast-forward.
-  Future<void> attemptFastForward() async {
+  ///
+  /// If the fast forward lands us on the same channel and revision, then
+  /// returns true, otherwise returns false.
+  Future<bool> attemptFastForward(FlutterVersion oldFlutterVersion) async {
     final int code = await processUtils.stream(
       <String>['git', 'pull', '--ff'],
       workingDirectory: Cache.flutterRoot,
@@ -243,6 +251,17 @@
     if (code != 0) {
       throwToolExit(null, exitCode: code);
     }
+
+    // Check if the upgrade did anything.
+    bool alreadyUpToDate = false;
+    try {
+      final FlutterVersion newFlutterVersion = FlutterVersion();
+      alreadyUpToDate = newFlutterVersion.channel == oldFlutterVersion.channel &&
+        newFlutterVersion.frameworkRevision == oldFlutterVersion.frameworkRevision;
+    } catch (e) {
+      printTrace('Failed to determine FlutterVersion after upgrade fast-forward: $e');
+    }
+    return alreadyUpToDate;
   }
 
   /// Update the engine repository and precache all artifacts.
diff --git a/packages/flutter_tools/lib/src/features.dart b/packages/flutter_tools/lib/src/features.dart
index de23ec3..fb0e089 100644
--- a/packages/flutter_tools/lib/src/features.dart
+++ b/packages/flutter_tools/lib/src/features.dart
@@ -141,10 +141,22 @@
   name: 'flutter create generates projects using the Android embedding V2',
   environmentOverride: 'ENABLE_ANDROID_EMBEDDING_V2',
   configSetting: 'enable-android-embedding-v2',
+  beta: FeatureChannelSetting(
+    available: true,
+    enabledByDefault: true,
+  ),
+  dev: FeatureChannelSetting(
+    available: true,
+    enabledByDefault: true,
+  ),
   master: FeatureChannelSetting(
     available: true,
     enabledByDefault: true,
   ),
+  stable: FeatureChannelSetting(
+    available: true,
+    enabledByDefault: true,
+  ),
 );
 
 /// The [Feature] for using the incremental compiler instead of build runner.
diff --git a/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart b/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart
index d77d984..487b7b4 100644
--- a/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart
+++ b/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart
@@ -121,6 +121,30 @@
       Platform: () => fakePlatform,
     });
 
+    testUsingContext('Doesn\'t continue on known tag, dev branch, no force, already up-to-date', () async {
+      fakeCommandRunner.alreadyUpToDate = true;
+      final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
+        false,
+        false,
+        gitTagVersion,
+        flutterVersion,
+      );
+      expect(await result, null);
+      verifyNever(processManager.start(
+        <String>[
+          fs.path.join('bin', 'flutter'),
+          'upgrade',
+          '--continue',
+          '--no-version-check',
+        ],
+        environment: anyNamed('environment'),
+        workingDirectory: anyNamed('workingDirectory'),
+      ));
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => processManager,
+      Platform: () => fakePlatform,
+    });
+
     testUsingContext('verifyUpstreamConfigured', () async {
       when(processManager.run(
         <String>['git', 'rev-parse', '@{u}'],
@@ -288,6 +312,8 @@
 class FakeUpgradeCommandRunner extends UpgradeCommandRunner {
   bool willHaveUncomittedChanges = false;
 
+  bool alreadyUpToDate = false;
+
   @override
   Future<void> verifyUpstreamConfigured() async {}
 
@@ -301,7 +327,7 @@
   Future<void> upgradeChannel(FlutterVersion flutterVersion) async {}
 
   @override
-  Future<void> attemptFastForward() async {}
+  Future<bool> attemptFastForward(FlutterVersion flutterVersion) async => alreadyUpToDate;
 
   @override
   Future<void> precacheArtifacts() async {}
diff --git a/packages/flutter_tools/test/general.shard/features_test.dart b/packages/flutter_tools/test/general.shard/features_test.dart
index ba14ebc..fb04874 100644
--- a/packages/flutter_tools/test/general.shard/features_test.dart
+++ b/packages/flutter_tools/test/general.shard/features_test.dart
@@ -432,6 +432,36 @@
 
       expect(featureFlags.isWindowsEnabled, false);
     }));
+
+    group('isAndroidEmbeddingV2Enabled', () {
+      test('is enabled on beta', () => testbed.run(() {
+        when(mockFlutterVerion.channel).thenReturn('beta');
+        when<bool>(mockFlutterConfig.getValue('enable-android-embedding-v2') as bool).thenReturn(true);
+
+        expect(featureFlags.isAndroidEmbeddingV2Enabled, true);
+      }));
+
+      test('is enabled on dev', () => testbed.run(() {
+        when(mockFlutterVerion.channel).thenReturn('dev');
+        when<bool>(mockFlutterConfig.getValue('enable-android-embedding-v2') as bool).thenReturn(true);
+
+        expect(featureFlags.isAndroidEmbeddingV2Enabled, true);
+      }));
+
+      test('is enabled on master', () => testbed.run(() {
+        when(mockFlutterVerion.channel).thenReturn('master');
+        when<bool>(mockFlutterConfig.getValue('enable-android-embedding-v2') as bool).thenReturn(true);
+
+        expect(featureFlags.isAndroidEmbeddingV2Enabled, true);
+      }));
+
+      test('is enabled on stable', () => testbed.run(() {
+        when(mockFlutterVerion.channel).thenReturn('stable');
+        when<bool>(mockFlutterConfig.getValue('enable-android-embedding-v2') as bool).thenReturn(true);
+
+        expect(featureFlags.isAndroidEmbeddingV2Enabled, true);
+      }));
+    });
   });
 }