[flutter_releases] Flutter 2.0.2 Stable Framework Cherrypicks (#77850)

* Reject unaccepted pointers in Drag recognizer (#75943)
* Delete unzipped FlutterMacOS.framework before replacing artifact (#77316)
* Fix missing root Scaffold check in ScaffoldMessenger  (#77410)
* Roll engine cherrypicks

Co-authored-by: Michael Goderbauer <goderbauer@google.com>
Co-authored-by: Jenn Magder <magder@google.com>
Co-authored-by: Kate Lovett <katelovett@google.com>
diff --git a/bin/internal/engine.version b/bin/internal/engine.version
index 1daead4..ccb5e92 100644
--- a/bin/internal/engine.version
+++ b/bin/internal/engine.version
@@ -1 +1 @@
-40441def692f444660a11e20fac37af9050245ab
+5d8bf811b3072390933d69f3e289a4bb673636c4
diff --git a/packages/flutter/lib/src/gestures/monodrag.dart b/packages/flutter/lib/src/gestures/monodrag.dart
index 82ab77d..15eaf30 100644
--- a/packages/flutter/lib/src/gestures/monodrag.dart
+++ b/packages/flutter/lib/src/gestures/monodrag.dart
@@ -309,15 +309,16 @@
       }
     }
     if (event is PointerUpEvent || event is PointerCancelEvent) {
-      _giveUpPointer(
-        event.pointer,
-        reject: event is PointerCancelEvent || _state ==_DragState.possible,
-      );
+      _giveUpPointer(event.pointer);
     }
   }
 
+  final Set<int> _acceptedActivePointers = <int>{};
+
   @override
   void acceptGesture(int pointer) {
+    assert(!_acceptedActivePointers.contains(pointer));
+    _acceptedActivePointers.add(pointer);
     if (_state != _DragState.accepted) {
       _state = _DragState.accepted;
       final OffsetPair delta = _pendingDragOffset;
@@ -384,32 +385,36 @@
     _state = _DragState.ready;
   }
 
-  void _giveUpPointer(int pointer, {bool reject = true}) {
+  void _giveUpPointer(int pointer) {
     stopTrackingPointer(pointer);
-    if (reject)
+    // If we never accepted the pointer, we reject it since we are no longer
+    // interested in winning the gesture arena for it.
+    if (!_acceptedActivePointers.remove(pointer))
       resolvePointer(pointer, GestureDisposition.rejected);
   }
 
   void _checkDown() {
     assert(_initialButtons == kPrimaryButton);
-    final DragDownDetails details = DragDownDetails(
-      globalPosition: _initialPosition.global,
-      localPosition: _initialPosition.local,
-    );
-    if (onDown != null)
+    if (onDown != null) {
+      final DragDownDetails details = DragDownDetails(
+        globalPosition: _initialPosition.global,
+        localPosition: _initialPosition.local,
+      );
       invokeCallback<void>('onDown', () => onDown!(details));
+    }
   }
 
   void _checkStart(Duration timestamp, int pointer) {
     assert(_initialButtons == kPrimaryButton);
-    final DragStartDetails details = DragStartDetails(
-      sourceTimeStamp: timestamp,
-      globalPosition: _initialPosition.global,
-      localPosition: _initialPosition.local,
-      kind: getKindForPointer(pointer),
-    );
-    if (onStart != null)
+    if (onStart != null) {
+      final DragStartDetails details = DragStartDetails(
+        sourceTimeStamp: timestamp,
+        globalPosition: _initialPosition.global,
+        localPosition: _initialPosition.local,
+        kind: getKindForPointer(pointer),
+      );
       invokeCallback<void>('onStart', () => onStart!(details));
+    }
   }
 
   void _checkUpdate({
@@ -420,15 +425,16 @@
     Offset? localPosition,
   }) {
     assert(_initialButtons == kPrimaryButton);
-    final DragUpdateDetails details = DragUpdateDetails(
-      sourceTimeStamp: sourceTimeStamp,
-      delta: delta,
-      primaryDelta: primaryDelta,
-      globalPosition: globalPosition,
-      localPosition: localPosition,
-    );
-    if (onUpdate != null)
+    if (onUpdate != null) {
+      final DragUpdateDetails details = DragUpdateDetails(
+        sourceTimeStamp: sourceTimeStamp,
+        delta: delta,
+        primaryDelta: primaryDelta,
+        globalPosition: globalPosition,
+        localPosition: localPosition,
+      );
       invokeCallback<void>('onUpdate', () => onUpdate!(details));
+    }
   }
 
   void _checkEnd(int pointer) {
diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart
index 12046bc..957c37c 100644
--- a/packages/flutter/lib/src/material/scaffold.dart
+++ b/packages/flutter/lib/src/material/scaffold.dart
@@ -281,7 +281,7 @@
 
   void _register(ScaffoldState scaffold) {
     _scaffolds.add(scaffold);
-    if (_snackBars.isNotEmpty) {
+    if (_snackBars.isNotEmpty && _isRoot(scaffold)) {
       scaffold._updateSnackBar();
     }
   }
diff --git a/packages/flutter/test/gestures/monodrag_test.dart b/packages/flutter/test/gestures/monodrag_test.dart
new file mode 100644
index 0000000..3ca3b07
--- /dev/null
+++ b/packages/flutter/test/gestures/monodrag_test.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 'package:flutter/gestures.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'gesture_tester.dart';
+
+void main() {
+  setUp(ensureGestureBinding);
+
+  testGesture('do not crash on up event for a pending pointer after winning arena for another pointer', (GestureTester tester) {
+    // Regression test for https://github.com/flutter/flutter/issues/75061.
+
+    final VerticalDragGestureRecognizer v = VerticalDragGestureRecognizer()
+      ..onStart = (_) { };
+    final HorizontalDragGestureRecognizer h = HorizontalDragGestureRecognizer()
+      ..onStart = (_) { };
+
+    const PointerDownEvent down90 = PointerDownEvent(
+      pointer: 90,
+      position: Offset(10.0, 10.0),
+    );
+
+    const PointerUpEvent up90 = PointerUpEvent(
+      pointer: 90,
+      position: Offset(10.0, 10.0),
+    );
+
+    const PointerDownEvent down91 = PointerDownEvent(
+      pointer: 91,
+      position: Offset(20.0, 20.0),
+    );
+
+    const PointerUpEvent up91 = PointerUpEvent(
+      pointer: 91,
+      position: Offset(20.0, 20.0),
+    );
+
+    v.addPointer(down90);
+    GestureBinding.instance!.gestureArena.close(90);
+    h.addPointer(down91);
+    v.addPointer(down91);
+    GestureBinding.instance!.gestureArena.close(91);
+    tester.async.flushMicrotasks();
+
+    GestureBinding.instance!.handleEvent(up90, HitTestEntry(MockHitTestTarget()));
+    GestureBinding.instance!.handleEvent(up91, HitTestEntry(MockHitTestTarget()));
+  });
+}
+
+class MockHitTestTarget implements HitTestTarget {
+  @override
+  void handleEvent(PointerEvent event, HitTestEntry entry) { }
+}
diff --git a/packages/flutter/test/material/scaffold_test.dart b/packages/flutter/test/material/scaffold_test.dart
index 2c612d7..ab0852f 100644
--- a/packages/flutter/test/material/scaffold_test.dart
+++ b/packages/flutter/test/material/scaffold_test.dart
@@ -2197,6 +2197,77 @@
       '   MaterialApp at the top of your application widget tree.\n'
     ));
   });
+
+  testWidgets('ScaffoldMessenger checks for nesting when a new Scaffold is registered', (WidgetTester tester) async {
+    // Regression test for https://github.com/flutter/flutter/issues/77251
+    const String snackBarContent = 'SnackBar Content';
+    await tester.pumpWidget(MaterialApp(
+      home: Builder(
+        builder: (BuildContext context) => Scaffold(
+          body: Scaffold(
+            body: TextButton(
+              onPressed: () {
+                Navigator.push(
+                  context,
+                  MaterialPageRoute<void>(
+                    builder: (BuildContext context) {
+                      return Scaffold(
+                        body: Column(
+                          children: <Widget>[
+                            TextButton(
+                              onPressed: () {
+                                const SnackBar snackBar = SnackBar(
+                                  content: Text(snackBarContent),
+                                  behavior: SnackBarBehavior.floating,
+                                );
+                                ScaffoldMessenger.of(context).showSnackBar(snackBar);
+                              },
+                              child: const Text('Show SnackBar'),
+                            ),
+                            TextButton(
+                              onPressed: () {
+                                Navigator.pop(context, null);
+                              },
+                              child: const Text('Pop route'),
+                            )
+                          ],
+                        ),
+                      );
+                    },
+                  ),
+                );
+              },
+              child: const Text('Push route'),
+            ),
+          ),
+        ),
+      )
+    ));
+
+    expect(find.text(snackBarContent), findsNothing);
+    await tester.tap(find.text('Push route'));
+    await tester.pumpAndSettle();
+    expect(find.text(snackBarContent), findsNothing);
+    expect(find.text('Pop route'), findsOneWidget);
+
+    // Show SnackBar on second page
+    await tester.tap(find.text('Show SnackBar'));
+    await tester.pump();
+    expect(find.text(snackBarContent), findsOneWidget);
+    // Pop the second page, the SnackBar completes a hero animation to the next route.
+    // If we have not handled the nested Scaffolds properly, this will throw an
+    // exception as duplicate SnackBars on the first route would have a common hero tag.
+    await tester.tap(find.text('Pop route'));
+    await tester.pump();
+    // There are SnackBars two during the execution of the hero animation.
+    expect(find.text(snackBarContent), findsNWidgets(2));
+    await tester.pumpAndSettle();
+    expect(find.text(snackBarContent), findsOneWidget);
+    // Allow the SnackBar to animate out
+    await tester.pump(const Duration(seconds: 4));
+    await tester.pumpAndSettle();
+    expect(find.text(snackBarContent), findsNothing);
+  });
 }
 
 class _GeometryListener extends StatefulWidget {
diff --git a/packages/flutter_tools/lib/src/cache.dart b/packages/flutter_tools/lib/src/cache.dart
index 197bf54..11e3bf3 100644
--- a/packages/flutter_tools/lib/src/cache.dart
+++ b/packages/flutter_tools/lib/src/cache.dart
@@ -927,6 +927,7 @@
       final File frameworkZip = fileSystem.file(fileSystem.path.join(dir.path, 'FlutterMacOS.framework.zip'));
       if (frameworkZip.existsSync()) {
         final Directory framework = fileSystem.directory(fileSystem.path.join(dir.path, 'FlutterMacOS.framework'));
+        ErrorHandlingFileSystem.deleteIfExists(framework, recursive: true);
         framework.createSync();
         operatingSystemUtils.unzip(frameworkZip, framework);
       }
diff --git a/packages/flutter_tools/test/general.shard/cache_test.dart b/packages/flutter_tools/test/general.shard/cache_test.dart
index d61cf94..7422d83 100644
--- a/packages/flutter_tools/test/general.shard/cache_test.dart
+++ b/packages/flutter_tools/test/general.shard/cache_test.dart
@@ -278,6 +278,33 @@
     verify(operatingSystemUtils.chmod(argThat(hasPath(dir.path)), 'a+r,a+x'));
   });
 
+  testWithoutContext('EngineCachedArtifact removes unzipped FlutterMacOS.framework before replacing', () async {
+    final OperatingSystemUtils operatingSystemUtils = MockOperatingSystemUtils();
+    final MockCache cache = MockCache();
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    final Directory artifactDir = fileSystem.systemTempDirectory.createTempSync('flutter_cache_test_artifact.');
+    final Directory downloadDir = fileSystem.systemTempDirectory.createTempSync('flutter_cache_test_download.');
+
+    when(cache.getArtifactDirectory(any)).thenReturn(artifactDir);
+    when(cache.getDownloadDir()).thenReturn(downloadDir);
+    final Directory binDir = artifactDir.childDirectory('bin_dir')..createSync();
+    binDir.childFile('FlutterMacOS.framework.zip').createSync();
+    final Directory unzippedFramework = binDir.childDirectory('FlutterMacOS.framework');
+    final File staleFile = unzippedFramework.childFile('stale_file')..createSync(recursive: true);
+    artifactDir.childFile('unused_url_path').createSync();
+
+    final FakeCachedArtifact artifact = FakeCachedArtifact(
+      cache: cache,
+      binaryDirs: <List<String>>[
+        <String>['bin_dir', 'unused_url_path'],
+      ],
+      requiredArtifacts: DevelopmentArtifact.universal,
+    );
+    await artifact.updateInner(MockArtifactUpdater(), fileSystem, operatingSystemUtils);
+    expect(unzippedFramework, exists);
+    expect(staleFile, isNot(exists));
+  });
+
   testWithoutContext('IosUsbArtifacts verifies executables for libimobiledevice in isUpToDateInner', () async {
     final FileSystem fileSystem = MemoryFileSystem.test();
     final Cache cache = Cache.test(fileSystem: fileSystem, processManager: FakeProcessManager.any());