Skip to content

[Android] Replay predictive back state for retained engines - #192286

Merged
auto-submit[bot] merged 6 commits into
flutter:masterfrom
xianjianlf2:fix/android-replay-framework-back-state
Nov 3, 2026
Merged

auto-submit[bot] merged 6 commits into
flutter:masterfrom
xianjianlf2:fix/android-replay-framework-back-state

Conversation

@xianjianlf2

@xianjianlf2 xianjianlf2 commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

PlatformChannel currently forwards SystemNavigator.setFrameworkHandlesBack only to the Android host that is attached at that moment. When an app retains a FlutterEngine across Activity instances, the old Activity unregisters its back callback during teardown, but the retained route does not resend its unchanged back state to the replacement Activity. System and predictive Back can then bypass Flutter.

This change keeps the most recent framework back state on the engine-scoped PlatformChannel and replays it whenever a new PlatformMessageHandler attaches. It also records the state if the framework message arrives while no host is attached.

Because replay is synchronous during PlatformPlugin construction, the plugin delegate is now assigned before registering the message handler. This ensures the replay reaches the new Activity.

Fixes #192284.

Testing:

  • Added PlatformChannelTest coverage for replacing a handler and receiving the message with no handler attached.
  • Added PlatformPluginTest coverage for constructor-time replay to the delegate.
  • Ran all tests in PlatformChannelTest and PlatformPluginTest; all passed.
  • Ran google-java-format 1.7 and git diff --check.
  • Verified before/after on a physical Pixel 10a running Android 16 / API 36 with Flutter 3.44.1. A process-scoped engine was returned from provideFlutterEngine(), the first Activity was removed, and a new Activity attached to the same PID, engine, isolate, and PopScope(canPop: false) route.
    • Before: the replacement Activity received no frameworkHandlesBack state; system Back destroyed it without invoking PopScope.
    • After: the replacement Activity synchronously received frameworkHandlesBack=true; system Back and a completed predictive-back gesture both invoked PopScope without destroying the Activity. A cancelled predictive-back gesture left the route unchanged.

Fragment follow-up (2026-10-08)

Initialize the automatic Back callback before attaching the delegate, so the cached engine's synchronous state replay is preserved. Register the callback after attachment. A regression test covers attach-time replay on Android API 33 and 34, including normal and predictive Back dispatch.

Validation for this follow-up:

  • The two new regression configurations fail against the previous Fragment implementation and pass with the fix.
  • All 22 FlutterFragmentTest cases/configurations pass in a local Robolectric harness compiling the changed Fragment and its current delegate sources. The harness uses compileSdk 36, Robolectric 4.16.1, and the Flutter 3.44.1 embedding artifact for unchanged support classes; this is not a full engine presubmit run.
  • google-java-format 1.7 and git diff --check pass.
  • No additional physical-device validation was performed for this Fragment follow-up. The Activity device results above belong to the earlier implementation.

This follow-up was assisted by Codex. Contributor review is required before requesting re-review under the project's AI contribution guidelines.

CI refresh (2026-10-30)

Merged upstream master 928815a06e in 5317a0754d to refresh the stale CI builder configuration that caused the Mac_ios new_gallery_skia_ios__transition_perf validation failure. Both CI YAML files now match that upstream revision exactly. The six Java files changed by this PR are byte-for-byte unchanged from the preceding PR head.

Validation for this refresh:

  • All 1,538 cases from the repository's dev/bots/test/ci_yaml_validation_test.dart passed in an isolated Dart harness using the current framework and engine CI files. The only test-source adjustment was replacing the shared test-helper import with package:test/test.dart; this is not a full Cocoon or engine presubmit run.
  • google-java-format 1.7 checked all six PR Java files; git diff --check passed.
  • No fresh Android unit-test or device run was performed for this upstream-only refresh; earlier Android validation is documented above.
  • Remote presubmit checks remain pending, including approval by a Google maintainer for Google testing. The all-tests-passing checkbox remains unchecked.

This refresh was assisted by OpenAI Codex. No re-review request is included in this update.

Pre-launch Checklist

The contributor review and CLA requirements are complete. The relevant local tests are passing; the final test checkbox is left open pending presubmit CI.

Retain the latest SystemNavigator.setFrameworkHandlesBack value on the engine-scoped PlatformChannel and replay it when a new platform handler attaches. Initialize the PlatformPlugin delegate before registering the handler so synchronous replay reaches the new Activity.

Add regression coverage for handler replacement, messages received without a host, and constructor-time replay.
@github-actions github-actions Bot added platform-android Android applications specifically engine flutter/engine related. See also e: labels. team-android Owned by Android platform team labels Sep 4, 2026
@xianjianlf2
xianjianlf2 marked this pull request as ready for review September 4, 2026 08:51
@xianjianlf2
xianjianlf2 requested a review from a team as a code owner September 4, 2026 08:51
@xianjianlf2
xianjianlf2 requested review from gmackall and removed request for a team September 4, 2026 08:51

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates PlatformChannel to retain and replay the frameworkHandlesBack state when PlatformMessageHandler is attached or detached, and updates PlatformPlugin to set the message handler after initializing the delegate. Corresponding unit tests were added to verify this behavior. The review feedback suggests safely checking if arguments is an instance of Boolean before casting to prevent potential NullPointerException or ClassCastException when handling SystemNavigator.setFrameworkHandlesBack.

Comment on lines +144 to 147
frameworkHandlesBack = (boolean) arguments;
if (platformMessageHandler != null) {
platformMessageHandler.setFrameworkHandlesBack(frameworkHandlesBack);
result.success(null);
break;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Directly casting arguments to boolean can throw a NullPointerException if arguments is null (due to unboxing), or a ClassCastException if it is of an unexpected type. To ensure robust defensive programming, we should safely check if arguments is an instance of Boolean before casting.

Suggested change
frameworkHandlesBack = (boolean) arguments;
if (platformMessageHandler != null) {
platformMessageHandler.setFrameworkHandlesBack(frameworkHandlesBack);
result.success(null);
break;
}
if (arguments instanceof Boolean) {
frameworkHandlesBack = (Boolean) arguments;
} else {
frameworkHandlesBack = false;
}
if (platformMessageHandler != null) {
platformMessageHandler.setFrameworkHandlesBack(frameworkHandlesBack);
}

@xianjianlf2

Copy link
Copy Markdown
Contributor Author

Additional physical-device validation on Flutter 3.47.2 stable (d3b14c8769, engine a804b26164), using a Samsung SM-A376B running Android 16 / API 36:

  • Official 3.47.2: System Back failed 3/3 times and completed predictive Back failed 3/3 times after attaching the retained engine to a replacement Activity. The Activity closed and PopScope received no callback.
  • With this PR: 3/3 complete test cycles passed. The replacement Activity received the retained state; System Back and completed predictive Back reached PopScope with didPop=false; cancelled predictive Back left the Activity in the foreground without invoking the callback.
  • The PID and FlutterEngine identity remained unchanged across Activity replacement in every run.

Full verification details: #192284 (comment)

@gmackall gmackall added the CICD Run CI/CD label Sep 9, 2026
@flutter-dashboard flutter-dashboard Bot removed the CICD Run CI/CD label Sep 9, 2026
@mboetger mboetger added the CICD Run CI/CD label Sep 15, 2026
@flutter-dashboard flutter-dashboard Bot removed the CICD Run CI/CD label Oct 6, 2026

@gmackall gmackall left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix and the device validation. This works for FlutterActivity, but I think FlutterFragment (and so FlutterFragmentActivity, which always sets shouldAutomaticallyHandleOnBackPressed(true)) is still broken.

public void onAttach(@NonNull Context context) {
super.onAttach(context);
delegate = delegateFactory.createDelegate(this);
delegate.onAttach(context);
if (getArguments().getBoolean(ARG_SHOULD_AUTOMATICALLY_HANDLE_ON_BACK_PRESSED, false)) {
requireActivity().getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
// When Android handles a back gesture, it pops an Activity or goes back
// to the home screen. When Flutter handles a back gesture, it pops a
// route inside of the Flutter part of the app. By default, Android
// handles back gestures, so this callback is disabled. If, for example,
// the Flutter app has routes for which it wants to handle the back
// gesture, then it will enable this callback using
// setFrameworkHandlesBack.
onBackPressedCallback.setEnabled(false);
}
context.registerComponentCallbacks(this);
}

FlutterFragment.onAttach calls delegate.onAttach(context) first. With this PR that constructs PlatformPlugin, which replays setFrameworkHandlesBack(true) and calls onBackPressedCallback.setEnabled(true). Then onAttach calls onBackPressedCallback.setEnabled(false), which overwrites the replayed value. So a FlutterFragmentActivity.withCachedEngine(...) replacement host still sends Back to the system unless there's a savedInstanceState bundle.

Could you set the default before attaching, so the replay wins? Replacing this block with something like:

boolean autoBack = getArguments().getBoolean(ARG_SHOULD_AUTOMATICALLY_HANDLE_ON_BACK_PRESSED, false);
if (autoBack) {
  // When Android handles a back gesture, it pops an Activity or goes back
  // ... (keep the existing comment)
  onBackPressedCallback.setEnabled(false);
}
delegate.onAttach(context);
if (autoBack) {
  requireActivity().getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
}

@jesswrd jesswrd added the CICD Run CI/CD label Oct 28, 2026
Sync with 928815a to remove stale builder definitions. The six PR Java files remain unchanged. Validated the upstream CI YAML test cases in an isolated Dart harness (1,538 passing), Java formatting, and diff whitespace.
@flutter-dashboard flutter-dashboard Bot removed the CICD Run CI/CD label Oct 29, 2026
@gmackall gmackall added the CICD Run CI/CD label Nov 2, 2026
@gmackall
gmackall requested review from a team and mboetger and removed request for a team November 2, 2026 21:36
@gmackall gmackall added the autosubmit Merge PR when tree becomes green via auto submit App label Nov 2, 2026
@auto-submit
auto-submit Bot added this pull request to the merge queue Nov 3, 2026
Merged via the queue into flutter:master with commit a041edd Nov 3, 2026
25 of 26 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Nov 3, 2026
auto-submit Bot pushed a commit to flutter/packages that referenced this pull request Nov 3, 2026
Roll Flutter from fcfb4f203eae to 934720261be0 (38 revisions)

flutter/flutter@fcfb4f2...9347202

2026-11-03 [email protected] Roll Skia from 5bceaa033999 to c733ad3e37cb (5 revisions) (flutter/flutter#195872)
2026-11-03 [email protected] Roll Skia from 34d2bf479385 to 5bceaa033999 (9 revisions) (flutter/flutter#195864)
2026-11-03 [email protected] fix: prefer pointer events over semantics tap for mouse clicks on web (flutter/flutter#191529)
2026-11-03 [email protected] [VPAT] Expose a semantics text field value while it is not being edited (flutter/flutter#195445)
2026-11-03 [email protected] [Impeller] Make a GL context current before constructing an AiksContext without a surface (flutter/flutter#195629)
2026-11-03 [email protected] [flutter_tools] Eliminate globals.dart from project.dart (flutter/flutter#194066)
2026-11-03 [email protected] [tool] Complete context runner elimination and migrate AppContext usages to ToolContext (flutter/flutter#190797)
2026-11-03 [email protected] [web] Unify font collections between CanvasKit and Skwasm (flutter/flutter#195539)
2026-11-03 [email protected] [Android] Replay predictive back state for retained engines (flutter/flutter#192286)
2026-11-03 [email protected] [vulkan]: Adds depth/stencil synchronization for subpasses (flutter/flutter#195689)
2026-11-03 [email protected] [web] Honor requested upscaling in instantiateImageCodecWithSize and ImageDescriptor.instantiateCodec (flutter/flutter#195836)
2026-11-03 [email protected] Roll Skia from 866b61ca92c9 to 34d2bf479385 (9 revisions) (flutter/flutter#195848)
2026-11-03 [email protected] Roll Fuchsia Test Scripts from xl1LWBuppzu-BnOzV... to B6zarykZp6PKWRueg... (flutter/flutter#195847)
2026-11-03 [email protected] Remove unused ffi (flutter/flutter#195710)
2026-11-02 [email protected] Revert: [Android] Let the raster thread use every core of a chip without little cores (flutter/flutter#195838)
2026-11-02 [email protected] Run 10 impeller iOS benchmarks as one devicelab task (flutter/flutter#195661)
2026-11-02 [email protected] Use named uniforms instead of setFloat in StretchEffect (flutter/flutter#195372)
2026-11-02 [email protected] [flutter_tools] Report when the browser disconnects during a web test run (flutter/flutter#192198)
2026-11-02 [email protected] fix: Resolve memory leak in image_list example (flutter/flutter#189526)
2026-11-02 [email protected] Only run the common local engine configurations in presubmit (flutter/flutter#195688)
2026-11-02 [email protected] Update gcloud_version to version [email protected] (flutter/flutter#195769)
2026-11-02 [email protected] [Impeller] Order the Vulkan copy-texture-to-texture layout transition after the copy (flutter/flutter#195814)
2026-11-02 [email protected] [Impeller] Order the Vulkan ResizeTexture layout transition after its blit (flutter/flutter#195721)
2026-11-02 [email protected] [web] Fix CanvasKit fragment shader uniforms being freed or mutated after recording (flutter/flutter#195718)
2026-11-02 [email protected] Roll Fuchsia Linux SDK from MqypI6RNyVZ_FLUE-... to T_-6-6X1lDMghhAvX... (flutter/flutter#195809)
2026-11-02 [email protected] [Android] Log why Impeller falls back to OpenGLES (flutter/flutter#194764)
2026-11-02 [email protected] Roll Skia from 3f35fb6e591b to 866b61ca92c9 (11 revisions) (flutter/flutter#195802)
2026-11-02 [email protected] Roll Skia from 3f35fb6e591b to 866b61ca92c9 (11 revisions) (flutter/flutter#195802)
2026-11-02 [email protected] Only run the common local engine configurations in presubmit (flutter/flutter#195688)
2026-11-02 [email protected] Balance tool_integration_tests subshards by expected duration (flutter/flutter#195628)
2026-11-02 [email protected] [web_paragraph] Map code units without a cluster to a cluster (flutter/flutter#195384)
2026-11-02 [email protected] [web] Hide synthetic autofill inputs from assistive technology (flutter/flutter#195618)
2026-11-02 [email protected] Roll Packages from 3742a4e to 3f34a6f (8 revisions) (flutter/flutter#195795)
2026-11-02 [email protected] [web] Unskip web tests that pass on CanvasKit and Skwasm: platform_view_test, route_notification_messages_test (flutter/flutter#195605)
2026-11-02 [email protected] Roll Skia from e5ad3868b8db to 3f35fb6e591b (1 revision) (flutter/flutter#195789)
2026-11-02 [email protected] Roll Skia from f5215e74aa16 to e5ad3868b8db (3 revisions) (flutter/flutter#195779)
2026-11-02 [email protected] Roll Skia from e43478a2f9f8 to f5215e74aa16 (1 revision) (flutter/flutter#195776)
2026-11-01 [email protected] Roll Fuchsia Linux SDK from kW0A0xxNkqf38ljm4... to MqypI6RNyVZ_FLUE-... (flutter/flutter#195768)

If this roll has caused a breakage, revert this CL and set the roller
to dry run mode using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC [email protected],[email protected] 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
...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD engine flutter/engine related. See also e: labels. platform-android Android applications specifically team-android Owned by Android platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Android] Predictive back state is lost when a retained FlutterEngine attaches to a new Activity

4 participants