Add a platform view test to android_hardware_smoke_test#188069
Conversation
Expand test coverage to address more of flutter#182123 This adds a test with a platform view. In order to get both the flutter pixels and the native pixels fully composited, we have to initiate the screenshot from the test runner, not the app. This has a few consequences for the architecture and data flow of the tests. Details in the changes to README.md. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
There was a problem hiding this comment.
Code Review
This pull request introduces a platform view smoke test (platformViewTest) to the Android hardware smoke test suite, implementing a system-level screenshot strategy to capture and crop both Flutter and native Android views. Key changes include adding AndroidPlatformView and its Kotlin implementation, introducing PixelExactLocalFileComparator to compare raw pixels on-device, and updating the host-driven test runner to capture screenshots via ADB. Feedback suggests wrapping bitmap operations in a try-finally block to prevent native memory leaks, using View.of(context) for the device pixel ratio, specifying offset and length when converting ByteData to Uint8List, and removing a redundant Uint8List.fromList conversion.
| // Crop the full-screen screenshot to the exact widget bounds. | ||
| val cropped = Bitmap.createBitmap(screenshot, x, y, width, height) | ||
| if (cropped != screenshot) { | ||
| screenshot.recycle() | ||
| } | ||
|
|
||
| val stream = ByteArrayOutputStream() | ||
| try { | ||
| cropped.compress(Bitmap.CompressFormat.PNG, 100, stream) | ||
| } finally { | ||
| cropped.recycle() | ||
| } | ||
| val croppedBytes = stream.toByteArray() |
There was a problem hiding this comment.
If Bitmap.createBitmap throws an exception (for example, due to memory pressure or unexpected state), the original screenshot bitmap will not be recycled, leading to a potential native memory leak of a large full-screen screenshot. Wrapping the creation and compression in a try-finally block ensures that both bitmaps are safely recycled under all success and failure conditions.
| // Crop the full-screen screenshot to the exact widget bounds. | |
| val cropped = Bitmap.createBitmap(screenshot, x, y, width, height) | |
| if (cropped != screenshot) { | |
| screenshot.recycle() | |
| } | |
| val stream = ByteArrayOutputStream() | |
| try { | |
| cropped.compress(Bitmap.CompressFormat.PNG, 100, stream) | |
| } finally { | |
| cropped.recycle() | |
| } | |
| val croppedBytes = stream.toByteArray() | |
| var cropped: Bitmap? = null | |
| val croppedBytes = try { | |
| cropped = Bitmap.createBitmap(screenshot, x, y, width, height) | |
| val stream = ByteArrayOutputStream() | |
| cropped.compress(Bitmap.CompressFormat.PNG, 100, stream) | |
| stream.toByteArray() | |
| } finally { | |
| if (cropped != null && cropped != screenshot) { | |
| screenshot.recycle() | |
| cropped.recycle() | |
| } else { | |
| screenshot.recycle() | |
| } | |
| } |
| final Offset position = renderObject.localToGlobal(Offset.zero); | ||
| final Size size = renderObject.size; | ||
| // We can assume one window for these tests since they are android-only. | ||
| final double devicePixelRatio = ui.PlatformDispatcher.instance.views.first.devicePixelRatio; |
There was a problem hiding this comment.
Instead of accessing the global ui.PlatformDispatcher.instance.views.first directly, it is safer and more idiomatic to use View.of(context) to retrieve the devicePixelRatio associated with the current context's view.
| final double devicePixelRatio = ui.PlatformDispatcher.instance.views.first.devicePixelRatio; | |
| final double devicePixelRatio = View.of(context).devicePixelRatio; |
| final Uint8List list1 = bytes1.buffer.asUint8List(); | ||
| final Uint8List list2 = bytes2.buffer.asUint8List(); |
There was a problem hiding this comment.
Using bytes.buffer.asUint8List() without specifying the offset and length can lead to incorrect behavior if the ByteData is a view of a larger buffer (i.e., has a non-zero offsetInBytes). It is safer and more robust to pass offsetInBytes and lengthInBytes to asUint8List.
| final Uint8List list1 = bytes1.buffer.asUint8List(); | |
| final Uint8List list2 = bytes2.buffer.asUint8List(); | |
| final Uint8List list1 = bytes1.buffer.asUint8List(bytes1.offsetInBytes, bytes1.lengthInBytes); | |
| final Uint8List list2 = bytes2.buffer.asUint8List(bytes2.offsetInBytes, bytes2.lengthInBytes); |
| ); | ||
| } | ||
| final img.Image cropped = img.copyCrop(decoded, x: x, y: y, width: w, height: h); | ||
| imageBytes = Uint8List.fromList(img.encodePng(cropped)); |
There was a problem hiding this comment.
In the image package version 4.x, img.encodePng already returns a Uint8List. Wrapping it in Uint8List.fromList is redundant and performs an unnecessary memory allocation and copy of the entire image byte array.
| imageBytes = Uint8List.fromList(img.encodePng(cropped)); | |
| imageBytes = img.encodePng(cropped); |
|
auto label is removed for flutter/flutter/188069, Failed to enqueue flutter/flutter/188069 with HTTP 400: Pull request Required status check "Merge Queue Guard" is expected.. |
flutter/flutter@3a0420c...b10d0f1 2026-06-17 mr-peipei@web.de Skip platform-specific plugin registration if no platforms enabled (flutter/flutter#186304) 2026-06-17 engine-flutter-autoroll@skia.org Roll Packages from 8286d39 to 6ce00a8 (1 revision) (flutter/flutter#188109) 2026-06-17 engine-flutter-autoroll@skia.org Roll Skia from 79f93fd5f36e to 5d19002eb73e (1 revision) (flutter/flutter#188108) 2026-06-17 simon@journeyapps.com Import `dart:_js_interop_wasm` in addition to `dart:_wasm` to convert between `JSAny` and `WasmExternRef?` (flutter/flutter#186974) 2026-06-17 engine-flutter-autoroll@skia.org Roll Dart SDK from f811ecae9ca0 to e39bde5b1bfc (2 revisions) (flutter/flutter#188107) 2026-06-17 engine-flutter-autoroll@skia.org Roll Skia from 026f6a6be2b9 to 79f93fd5f36e (1 revision) (flutter/flutter#188105) 2026-06-17 engine-flutter-autoroll@skia.org Roll Dart SDK from 462bf0a1d489 to f811ecae9ca0 (1 revision) (flutter/flutter#188099) 2026-06-17 engine-flutter-autoroll@skia.org Roll Fuchsia Linux SDK from VeLhhlDcod09NR4Hb... to or21OEdGtairm6nl9... (flutter/flutter#188098) 2026-06-17 engine-flutter-autoroll@skia.org Roll Skia from 2ffd155313f5 to 026f6a6be2b9 (10 revisions) (flutter/flutter#188097) 2026-06-17 engine-flutter-autoroll@skia.org Roll Dart SDK from 824b4b48b6d4 to 462bf0a1d489 (1 revision) (flutter/flutter#188093) 2026-06-17 jason-simmons@users.noreply.github.com Manual Dart roll from f6c31f4c3a63 to 824b4b48b6d4 (flutter/flutter#188023) 2026-06-17 awolff@google.com Add a platform view test to android_hardware_smoke_test (flutter/flutter#188069) 2026-06-17 44747303+theprantadutta@users.noreply.github.com [flutter_tools] Format empty app template with latest dart format (flutter/flutter#187443) 2026-06-16 49699333+dependabot[bot]@users.noreply.github.com Bump the all-github-actions group across 1 directory with 3 updates (flutter/flutter#188086) 2026-06-16 engine-flutter-autoroll@skia.org Roll Skia from d7196b0b4939 to 2ffd155313f5 (9 revisions) (flutter/flutter#188081) 2026-06-16 43089218+chika3742@users.noreply.github.com Prevent downgrading `project.pbxproj` when greater version number (flutter/flutter#186250) 2026-06-16 magder@google.com Only allow dependabot to autoupdate GitHub-owned actions (flutter/flutter#187936) 2026-06-16 matt.boetger@gmail.com Fall back to source AndroidManifest.xml if AAPT fails or returns garbage (flutter/flutter#187197) 2026-06-16 137456488+flutter-pub-roller-bot@users.noreply.github.com Roll pub packages (flutter/flutter#187769) 2026-06-16 jason-simmons@users.noreply.github.com [Impeller] Move queue submission into a callback that is invoked by FenceWaiterVK::AddFence only if it can accept the fence (flutter/flutter#187761) 2026-06-16 jhy03261997@gmail.com Reland [a11y] Map some framework semantics roles to android classes. (flutter/flutter#188037) 2026-06-16 1961493+harryterkelsen@users.noreply.github.com refactor(web): Unify Image on Skwasm and CanvasKit (flutter/flutter#187873) 2026-06-16 30870216+gaaclarke@users.noreply.github.com Adds arm64 variant of impeller devicelab tests for windows. (flutter/flutter#188053) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC bmparr@google.com,stuartmorgan@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Expand test coverage to address more of #182123
This adds a test with a platform view. In order to get both the flutter pixels and the native pixels fully composited, we have to initiate the screenshot from the test runner, not the app. This has a few consequences for the architecture and data flow of the tests. Details in the changes to README.md.
PR #187913 (for issue #182123) added
image: anyto android_hardware_smoke_test'spubspec.yaml, but didn't update thepubspec.lockfile in the workspace root. This lockfile doesn't currently contain theimagepackage, so it broke CI and was reverted in #188051A package missing from the lockfile seems to cause CI fail in this misleading way. To fix it, we just need to add
imageto the lockfile.Pre-launch Checklist
///).Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the
gemini-code-assistbot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.