Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 8c48577

Browse files
[CP-stable]Move detection of cutouts in Android engine to onApplyWindowInsets (#56379)
This pull request is created by [automatic cherry pick workflow](https://github.com/flutter/flutter/blob/main/docs/releases/Flutter-Cherrypick-Process.md#automatically-creates-a-cherry-pick-request) Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request. ### Issue Link: What is the link to the issue this cherry-pick is addressing? flutter/flutter#158192 ### Changelog Description: Explain this cherry pick in one line that is accessible to most Flutter developers. See [best practices](https://github.com/flutter/flutter/blob/main/docs/releases/Hotfix-Documentation-Best-Practices.md) for examples #55992. - Positions of display cutouts on Android may not update - as returned by `MediaQuery` and used by `SafeArea` - upon screen orientation change. ### Impact Description: What is the impact (ex. visual jank on Samsung phones, app crash, cannot ship an iOS app)? Does it impact development (ex. flutter doctor crashes when Android Studio is installed), or the shipping production app (the app crashes on launch) The positions of display cutouts as reported by `MediaQuery` would not properly update on screen orientation. ### Workaround: Is there a workaround for this issue? N/A ### Risk: What is the risk level of this cherry-pick? ### Test Coverage: Are you confident that your fix is well-tested by automated tests? ### Validation Steps: What are the steps to validate that this fix works? Run the code sample provided by flutter/flutter#155658 on Android and change the screen orientation.
1 parent cb4b5ff commit 8c48577

File tree

4 files changed

+251
-75
lines changed

4 files changed

+251
-75
lines changed

shell/platform/android/io/flutter/embedding/android/FlutterView.java

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -194,13 +194,7 @@ public void onFlutterUiNoLongerDisplayed() {
194194
}
195195
};
196196

197-
private final Consumer<WindowLayoutInfo> windowInfoListener =
198-
new Consumer<WindowLayoutInfo>() {
199-
@Override
200-
public void accept(WindowLayoutInfo layoutInfo) {
201-
setWindowInfoListenerDisplayFeatures(layoutInfo);
202-
}
203-
};
197+
private Consumer<WindowLayoutInfo> windowInfoListener;
204198

205199
/**
206200
* Constructs a {@code FlutterView} programmatically, without any XML attributes.
@@ -512,6 +506,10 @@ protected void onAttachedToWindow() {
512506
this.windowInfoRepo = createWindowInfoRepo();
513507
Activity activity = ViewUtils.getActivity(getContext());
514508
if (windowInfoRepo != null && activity != null) {
509+
// Creating windowInfoListener on-demand instead of at initialization is necessary in order to
510+
// prevent it from capturing the wrong instance of `this` when spying for testing.
511+
// See https://github.com/mockito/mockito/issues/3479
512+
windowInfoListener = this::setWindowInfoListenerDisplayFeatures;
515513
windowInfoRepo.addWindowLayoutInfoListener(
516514
activity, ContextCompat.getMainExecutor(getContext()), windowInfoListener);
517515
}
@@ -524,9 +522,10 @@ protected void onAttachedToWindow() {
524522
*/
525523
@Override
526524
protected void onDetachedFromWindow() {
527-
if (windowInfoRepo != null) {
525+
if (windowInfoRepo != null && windowInfoListener != null) {
528526
windowInfoRepo.removeWindowLayoutInfoListener(windowInfoListener);
529527
}
528+
windowInfoListener = null;
530529
this.windowInfoRepo = null;
531530
super.onDetachedFromWindow();
532531
}
@@ -537,12 +536,12 @@ protected void onDetachedFromWindow() {
537536
*/
538537
@TargetApi(API_LEVELS.API_28)
539538
protected void setWindowInfoListenerDisplayFeatures(WindowLayoutInfo layoutInfo) {
540-
List<DisplayFeature> displayFeatures = layoutInfo.getDisplayFeatures();
541-
List<FlutterRenderer.DisplayFeature> result = new ArrayList<>();
539+
List<DisplayFeature> newDisplayFeatures = layoutInfo.getDisplayFeatures();
540+
List<FlutterRenderer.DisplayFeature> flutterDisplayFeatures = new ArrayList<>();
542541

543542
// Data from WindowInfoTracker display features. Fold and hinge areas are
544543
// populated here.
545-
for (DisplayFeature displayFeature : displayFeatures) {
544+
for (DisplayFeature displayFeature : newDisplayFeatures) {
546545
Log.v(
547546
TAG,
548547
"WindowInfoTracker Display Feature reported with bounds = "
@@ -565,31 +564,17 @@ protected void setWindowInfoListenerDisplayFeatures(WindowLayoutInfo layoutInfo)
565564
} else {
566565
state = DisplayFeatureState.UNKNOWN;
567566
}
568-
result.add(new FlutterRenderer.DisplayFeature(displayFeature.getBounds(), type, state));
567+
flutterDisplayFeatures.add(
568+
new FlutterRenderer.DisplayFeature(displayFeature.getBounds(), type, state));
569569
} else {
570-
result.add(
570+
flutterDisplayFeatures.add(
571571
new FlutterRenderer.DisplayFeature(
572572
displayFeature.getBounds(),
573573
DisplayFeatureType.UNKNOWN,
574574
DisplayFeatureState.UNKNOWN));
575575
}
576576
}
577-
578-
// Data from the DisplayCutout bounds. Cutouts for cameras and other sensors are
579-
// populated here. DisplayCutout was introduced in API 28.
580-
if (Build.VERSION.SDK_INT >= API_LEVELS.API_28) {
581-
WindowInsets insets = getRootWindowInsets();
582-
if (insets != null) {
583-
DisplayCutout cutout = insets.getDisplayCutout();
584-
if (cutout != null) {
585-
for (Rect bounds : cutout.getBoundingRects()) {
586-
Log.v(TAG, "DisplayCutout area reported with bounds = " + bounds.toString());
587-
result.add(new FlutterRenderer.DisplayFeature(bounds, DisplayFeatureType.CUTOUT));
588-
}
589-
}
590-
}
591-
}
592-
viewportMetrics.displayFeatures = result;
577+
viewportMetrics.setDisplayFeatures(flutterDisplayFeatures);
593578
sendViewportMetricsToFlutter();
594579
}
595580

@@ -782,6 +767,22 @@ navigationBarVisible && guessBottomKeyboardInset(insets) == 0
782767
viewportMetrics.viewInsetLeft = 0;
783768
}
784769

770+
// Data from the DisplayCutout bounds. Cutouts for cameras and other sensors are
771+
// populated here. DisplayCutout was introduced in API 28.
772+
List<FlutterRenderer.DisplayFeature> displayCutouts = new ArrayList<>();
773+
if (Build.VERSION.SDK_INT >= API_LEVELS.API_28) {
774+
DisplayCutout cutout = insets.getDisplayCutout();
775+
if (cutout != null) {
776+
for (Rect bounds : cutout.getBoundingRects()) {
777+
Log.v(TAG, "DisplayCutout area reported with bounds = " + bounds.toString());
778+
displayCutouts.add(
779+
new FlutterRenderer.DisplayFeature(
780+
bounds, DisplayFeatureType.CUTOUT, DisplayFeatureState.UNKNOWN));
781+
}
782+
}
783+
}
784+
viewportMetrics.setDisplayCutouts(displayCutouts);
785+
785786
// The caption bar inset is a new addition, and the APIs called to query it utilize a list of
786787
// bounding Rects instead of an Insets object, which is a newer API method, as compared to the
787788
// existing Insets-based method calls above.

shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,6 +1137,13 @@ public void stopRenderingToSurface() {
11371137
}
11381138
}
11391139

1140+
private void translateFeatureBounds(int[] displayFeatureBounds, int offset, Rect bounds) {
1141+
displayFeatureBounds[offset] = bounds.left;
1142+
displayFeatureBounds[offset + 1] = bounds.top;
1143+
displayFeatureBounds[offset + 2] = bounds.right;
1144+
displayFeatureBounds[offset + 3] = bounds.bottom;
1145+
}
1146+
11401147
/**
11411148
* Notifies Flutter that the viewport metrics, e.g. window height and width, have changed.
11421149
*
@@ -1187,20 +1194,31 @@ public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) {
11871194
+ viewportMetrics.systemGestureInsetRight
11881195
+ "\n"
11891196
+ "Display Features: "
1190-
+ viewportMetrics.displayFeatures.size());
1191-
1192-
int[] displayFeaturesBounds = new int[viewportMetrics.displayFeatures.size() * 4];
1193-
int[] displayFeaturesType = new int[viewportMetrics.displayFeatures.size()];
1194-
int[] displayFeaturesState = new int[viewportMetrics.displayFeatures.size()];
1197+
+ viewportMetrics.displayFeatures.size()
1198+
+ "\n"
1199+
+ "Display Cutouts: "
1200+
+ viewportMetrics.displayCutouts.size());
1201+
1202+
int totalFeaturesAndCutouts =
1203+
viewportMetrics.displayFeatures.size() + viewportMetrics.displayCutouts.size();
1204+
int[] displayFeaturesBounds = new int[totalFeaturesAndCutouts * 4];
1205+
int[] displayFeaturesType = new int[totalFeaturesAndCutouts];
1206+
int[] displayFeaturesState = new int[totalFeaturesAndCutouts];
11951207
for (int i = 0; i < viewportMetrics.displayFeatures.size(); i++) {
11961208
DisplayFeature displayFeature = viewportMetrics.displayFeatures.get(i);
1197-
displayFeaturesBounds[4 * i] = displayFeature.bounds.left;
1198-
displayFeaturesBounds[4 * i + 1] = displayFeature.bounds.top;
1199-
displayFeaturesBounds[4 * i + 2] = displayFeature.bounds.right;
1200-
displayFeaturesBounds[4 * i + 3] = displayFeature.bounds.bottom;
1209+
translateFeatureBounds(displayFeaturesBounds, 4 * i, displayFeature.bounds);
12011210
displayFeaturesType[i] = displayFeature.type.encodedValue;
12021211
displayFeaturesState[i] = displayFeature.state.encodedValue;
12031212
}
1213+
int cutoutOffset = viewportMetrics.displayFeatures.size() * 4;
1214+
for (int i = 0; i < viewportMetrics.displayCutouts.size(); i++) {
1215+
DisplayFeature displayCutout = viewportMetrics.displayCutouts.get(i);
1216+
translateFeatureBounds(displayFeaturesBounds, cutoutOffset + 4 * i, displayCutout.bounds);
1217+
displayFeaturesType[viewportMetrics.displayFeatures.size() + i] =
1218+
displayCutout.type.encodedValue;
1219+
displayFeaturesState[viewportMetrics.displayFeatures.size() + i] =
1220+
displayCutout.state.encodedValue;
1221+
}
12041222

12051223
flutterJNI.setViewportMetrics(
12061224
viewportMetrics.devicePixelRatio,
@@ -1314,7 +1332,29 @@ boolean validate() {
13141332
return width > 0 && height > 0 && devicePixelRatio > 0;
13151333
}
13161334

1317-
public List<DisplayFeature> displayFeatures = new ArrayList<>();
1335+
// Features
1336+
private final List<DisplayFeature> displayFeatures = new ArrayList<>();
1337+
1338+
// Specifically display cutouts.
1339+
private final List<DisplayFeature> displayCutouts = new ArrayList<>();
1340+
1341+
public List<DisplayFeature> getDisplayFeatures() {
1342+
return displayFeatures;
1343+
}
1344+
1345+
public List<DisplayFeature> getDisplayCutouts() {
1346+
return displayCutouts;
1347+
}
1348+
1349+
public void setDisplayFeatures(List<DisplayFeature> newFeatures) {
1350+
displayFeatures.clear();
1351+
displayFeatures.addAll(newFeatures);
1352+
}
1353+
1354+
public void setDisplayCutouts(List<DisplayFeature> newCutouts) {
1355+
displayCutouts.clear();
1356+
displayCutouts.addAll(newCutouts);
1357+
}
13181358
}
13191359

13201360
/**
@@ -1337,12 +1377,6 @@ public DisplayFeature(Rect bounds, DisplayFeatureType type, DisplayFeatureState
13371377
this.type = type;
13381378
this.state = state;
13391379
}
1340-
1341-
public DisplayFeature(Rect bounds, DisplayFeatureType type) {
1342-
this.bounds = bounds;
1343-
this.type = type;
1344-
this.state = DisplayFeatureState.UNKNOWN;
1345-
}
13461380
}
13471381

13481382
/**

0 commit comments

Comments
 (0)