Avoid passive clipboard read on Android (#86791)
diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart
index 6c76b1e..5679989 100644
--- a/packages/flutter/lib/src/widgets/text_selection.dart
+++ b/packages/flutter/lib/src/widgets/text_selection.dart
@@ -1654,18 +1654,26 @@
/// Check the [Clipboard] and update [value] if needed.
Future<void> update() async {
- // iOS 14 added a notification that appears when an app accesses the
- // clipboard. To avoid the notification, don't access the clipboard on iOS,
- // and instead always show the paste button, even when the clipboard is
- // empty.
- // TODO(justinmc): Use the new iOS 14 clipboard API method hasStrings that
- // won't trigger the notification.
- // https://github.com/flutter/flutter/issues/60145
switch (defaultTargetPlatform) {
+ // Android 12 introduces a toast message that appears when an app reads
+ // the content on the clipboard. To avoid unintended access, both the
+ // Flutter engine and the Flutter framework need to be updated to use the
+ // appropriate API to check whether the clipboard is empty.
+ // As a short-term workaround, always show the paste button.
+ // TODO(justinmc): Expose `hasStrings` in `Clipboard` and use that instead
+ // https://github.com/flutter/flutter/issues/74139
+ case TargetPlatform.android:
+ // Intentionally fall through.
+ // iOS 14 added a notification that appears when an app accesses the
+ // clipboard. To avoid the notification, don't access the clipboard on iOS,
+ // and instead always show the paste button, even when the clipboard is
+ // empty.
+ // TODO(justinmc): Use the new iOS 14 clipboard API method hasStrings that
+ // won't trigger the notification.
+ // https://github.com/flutter/flutter/issues/60145
case TargetPlatform.iOS:
value = ClipboardStatus.pasteable;
return;
- case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.macOS:
diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart
index f04e144..d1e5255 100644
--- a/packages/flutter/test/material/text_field_test.dart
+++ b/packages/flutter/test/material/text_field_test.dart
@@ -928,7 +928,7 @@
),
);
focusNode.requestFocus();
- await tester.pump();
+ await tester.pumpAndSettle();
await expectLater(
find.byType(TextField),
@@ -956,7 +956,7 @@
),
);
focusNode.requestFocus();
- await tester.pump();
+ await tester.pumpAndSettle();
await expectLater(
find.byType(TextField),
@@ -984,7 +984,7 @@
),
);
focusNode.requestFocus();
- await tester.pump();
+ await tester.pumpAndSettle();
await expectLater(
find.byType(TextField),
@@ -6223,10 +6223,13 @@
semantics.dispose();
- // On web (just like iOS), we don't check for pasteability because that
- // triggers a permission dialog in the browser.
+ // On web, we don't check for pasteability because that triggers a
+ // permission dialog in the browser.
// https://github.com/flutter/flutter/pull/57139#issuecomment-629048058
- }, skip: isBrowser);
+ // TODO(justinmc): Remove TargetPlatform override when Android and iOS uses
+ // `hasStrings` to check for pasteability.
+ // https://github.com/flutter/flutter/issues/74139
+ }, skip: isBrowser, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.linux }));
testWidgets('TextField throws when not descended from a Material widget', (WidgetTester tester) async {
const Widget textField = TextField();
@@ -9588,7 +9591,10 @@
// pasted.
expect(triedToReadClipboard, true);
}
- });
+ // TODO(justinmc): Eventually, all platform should call `hasStrings` instead.
+ // This entire test will become irrelevant after the fact.
+ // https://github.com/flutter/flutter/issues/74139
+ }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.linux }));
testWidgets('TextField changes mouse cursor when hovered', (WidgetTester tester) async {
await tester.pumpWidget(
diff --git a/packages/flutter/test/material/text_selection_test.dart b/packages/flutter/test/material/text_selection_test.dart
index 950be89..3c48815 100644
--- a/packages/flutter/test/material/text_selection_test.dart
+++ b/packages/flutter/test/material/text_selection_test.dart
@@ -615,7 +615,9 @@
});
});
- testWidgets('Paste only appears when clipboard has contents', (WidgetTester tester) async {
+ // TODO(justinmc): Paste should only appears when the clipboard has contents.
+ // https://github.com/flutter/flutter/issues/74139
+ testWidgets('Paste always appears regardless of clipboard content on Android', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController(
text: 'Atwater Peel Sherbrooke Bonaventure',
);
@@ -643,8 +645,10 @@
await tester.tapAt(textOffsetToPosition(tester, index));
await tester.pumpAndSettle();
- // No Paste yet, because nothing has been copied.
- expect(find.text('Paste'), findsNothing);
+ // Paste is showing even though clipboard is empty.
+ // TODO(justinmc): Paste should not appears when the clipboard is empty.
+ // https://github.com/flutter/flutter/issues/74139
+ expect(find.text('Paste'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Select all'), findsOneWidget);
diff --git a/packages/flutter/test/widgets/selectable_text_test.dart b/packages/flutter/test/widgets/selectable_text_test.dart
index f3b9175..e58f680 100644
--- a/packages/flutter/test/widgets/selectable_text_test.dart
+++ b/packages/flutter/test/widgets/selectable_text_test.dart
@@ -2487,7 +2487,7 @@
));
semanticsOwner.performAction(inputFieldId, SemanticsAction.longPress);
- await tester.pump();
+ await tester.pumpAndSettle();
expect(semantics, hasSemantics(
TestSemantics.root(
diff --git a/packages/flutter/test/widgets/text_selection_test.dart b/packages/flutter/test/widgets/text_selection_test.dart
index 48107e8..283f563 100644
--- a/packages/flutter/test/widgets/text_selection_test.dart
+++ b/packages/flutter/test/widgets/text_selection_test.dart
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart' show PointerDeviceKind, kSecondaryButton;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
@@ -765,13 +766,19 @@
TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, null);
});
- test('Clipboard API failure is gracefully recovered from', () async {
+ // TODO(justinmc): See if `testWidgets` can be reverted to `test`.
+ testWidgets('Clipboard API failure is gracefully recovered from', (WidgetTester tester) async {
final ClipboardStatusNotifier notifier = ClipboardStatusNotifier();
expect(notifier.value, ClipboardStatus.unknown);
await expectLater(notifier.update(), completes);
expect(notifier.value, ClipboardStatus.unknown);
- });
+ // TODO(justinmc): Currently on Android and iOS, ClipboardStatus.pasteable
+ // is always returned. Once both platforms properly use
+ // `hasStrings` to check whether the clipboard has any
+ // content, try to see if this override can be removed.
+ // https://github.com/flutter/flutter/issues/74139
+ }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.linux }));
});
group('when Clipboard succeeds', () {
@@ -785,7 +792,8 @@
TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, null);
});
- test('update sets value based on clipboard contents', () async {
+ // TODO(justinmc): See if `testWidgets` can be reverted to `test`.
+ testWidgets('update sets value based on clipboard contents', (WidgetTester tester) async {
final ClipboardStatusNotifier notifier = ClipboardStatusNotifier();
expect(notifier.value, ClipboardStatus.unknown);
@@ -800,7 +808,12 @@
));
await expectLater(notifier.update(), completes);
expect(notifier.value, ClipboardStatus.pasteable);
- });
+ // TODO(justinmc): Currently on Android and iOS, ClipboardStatus.pasteable
+ // is always returned. Once both platforms properly use
+ // `hasStrings` to check whether the clipboard has any
+ // content, try to see if this override can be removed.
+ // https://github.com/flutter/flutter/issues/74139
+ }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.linux }));
});
});
}