Unverified Commit 221705b6 authored by Wyte Krongapiradee's avatar Wyte Krongapiradee Committed by GitHub

Avoid passive clipboard read on Android (#86791)

parent 7707f423
...@@ -1654,6 +1654,16 @@ class ClipboardStatusNotifier extends ValueNotifier<ClipboardStatus> with Widget ...@@ -1654,6 +1654,16 @@ class ClipboardStatusNotifier extends ValueNotifier<ClipboardStatus> with Widget
/// Check the [Clipboard] and update [value] if needed. /// Check the [Clipboard] and update [value] if needed.
Future<void> update() async { Future<void> update() async {
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 // iOS 14 added a notification that appears when an app accesses the
// clipboard. To avoid the notification, don't access the clipboard on iOS, // clipboard. To avoid the notification, don't access the clipboard on iOS,
// and instead always show the paste button, even when the clipboard is // and instead always show the paste button, even when the clipboard is
...@@ -1661,11 +1671,9 @@ class ClipboardStatusNotifier extends ValueNotifier<ClipboardStatus> with Widget ...@@ -1661,11 +1671,9 @@ class ClipboardStatusNotifier extends ValueNotifier<ClipboardStatus> with Widget
// TODO(justinmc): Use the new iOS 14 clipboard API method hasStrings that // TODO(justinmc): Use the new iOS 14 clipboard API method hasStrings that
// won't trigger the notification. // won't trigger the notification.
// https://github.com/flutter/flutter/issues/60145 // https://github.com/flutter/flutter/issues/60145
switch (defaultTargetPlatform) {
case TargetPlatform.iOS: case TargetPlatform.iOS:
value = ClipboardStatus.pasteable; value = ClipboardStatus.pasteable;
return; return;
case TargetPlatform.android:
case TargetPlatform.fuchsia: case TargetPlatform.fuchsia:
case TargetPlatform.linux: case TargetPlatform.linux:
case TargetPlatform.macOS: case TargetPlatform.macOS:
......
...@@ -928,7 +928,7 @@ void main() { ...@@ -928,7 +928,7 @@ void main() {
), ),
); );
focusNode.requestFocus(); focusNode.requestFocus();
await tester.pump(); await tester.pumpAndSettle();
await expectLater( await expectLater(
find.byType(TextField), find.byType(TextField),
...@@ -956,7 +956,7 @@ void main() { ...@@ -956,7 +956,7 @@ void main() {
), ),
); );
focusNode.requestFocus(); focusNode.requestFocus();
await tester.pump(); await tester.pumpAndSettle();
await expectLater( await expectLater(
find.byType(TextField), find.byType(TextField),
...@@ -984,7 +984,7 @@ void main() { ...@@ -984,7 +984,7 @@ void main() {
), ),
); );
focusNode.requestFocus(); focusNode.requestFocus();
await tester.pump(); await tester.pumpAndSettle();
await expectLater( await expectLater(
find.byType(TextField), find.byType(TextField),
...@@ -6223,10 +6223,13 @@ void main() { ...@@ -6223,10 +6223,13 @@ void main() {
semantics.dispose(); semantics.dispose();
// On web (just like iOS), we don't check for pasteability because that // On web, we don't check for pasteability because that triggers a
// triggers a permission dialog in the browser. // permission dialog in the browser.
// https://github.com/flutter/flutter/pull/57139#issuecomment-629048058 // 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 { testWidgets('TextField throws when not descended from a Material widget', (WidgetTester tester) async {
const Widget textField = TextField(); const Widget textField = TextField();
...@@ -9588,7 +9591,10 @@ void main() { ...@@ -9588,7 +9591,10 @@ void main() {
// pasted. // pasted.
expect(triedToReadClipboard, true); 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 { testWidgets('TextField changes mouse cursor when hovered', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
......
...@@ -615,7 +615,9 @@ void main() { ...@@ -615,7 +615,9 @@ void main() {
}); });
}); });
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( final TextEditingController controller = TextEditingController(
text: 'Atwater Peel Sherbrooke Bonaventure', text: 'Atwater Peel Sherbrooke Bonaventure',
); );
...@@ -643,8 +645,10 @@ void main() { ...@@ -643,8 +645,10 @@ void main() {
await tester.tapAt(textOffsetToPosition(tester, index)); await tester.tapAt(textOffsetToPosition(tester, index));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// No Paste yet, because nothing has been copied. // Paste is showing even though clipboard is empty.
expect(find.text('Paste'), findsNothing); // 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('Copy'), findsOneWidget);
expect(find.text('Cut'), findsOneWidget); expect(find.text('Cut'), findsOneWidget);
expect(find.text('Select all'), findsOneWidget); expect(find.text('Select all'), findsOneWidget);
......
...@@ -2487,7 +2487,7 @@ void main() { ...@@ -2487,7 +2487,7 @@ void main() {
)); ));
semanticsOwner.performAction(inputFieldId, SemanticsAction.longPress); semanticsOwner.performAction(inputFieldId, SemanticsAction.longPress);
await tester.pump(); await tester.pumpAndSettle();
expect(semantics, hasSemantics( expect(semantics, hasSemantics(
TestSemantics.root( TestSemantics.root(
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart' show PointerDeviceKind, kSecondaryButton; import 'package:flutter/gestures.dart' show PointerDeviceKind, kSecondaryButton;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
...@@ -765,13 +766,19 @@ void main() { ...@@ -765,13 +766,19 @@ void main() {
TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, null); 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(); final ClipboardStatusNotifier notifier = ClipboardStatusNotifier();
expect(notifier.value, ClipboardStatus.unknown); expect(notifier.value, ClipboardStatus.unknown);
await expectLater(notifier.update(), completes); await expectLater(notifier.update(), completes);
expect(notifier.value, ClipboardStatus.unknown); 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', () { group('when Clipboard succeeds', () {
...@@ -785,7 +792,8 @@ void main() { ...@@ -785,7 +792,8 @@ void main() {
TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, null); 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(); final ClipboardStatusNotifier notifier = ClipboardStatusNotifier();
expect(notifier.value, ClipboardStatus.unknown); expect(notifier.value, ClipboardStatus.unknown);
...@@ -800,7 +808,12 @@ void main() { ...@@ -800,7 +808,12 @@ void main() {
)); ));
await expectLater(notifier.update(), completes); await expectLater(notifier.update(), completes);
expect(notifier.value, ClipboardStatus.pasteable); 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 }));
}); });
}); });
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment