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

Commit 4fb69c3

Browse files
Move detection of cutouts in Android engine to onApplyWindowInsets (#55992)
Stop including the translation of window insets cutouts to Flutter cutout DisplayFeatures in the window info listener method, which would not necessarily receive changes in the correct order/state when they update, e.g. when the screen orientation changes. Fixes flutter/flutter#155658 *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide] and the [C++, Objective-C, Java style guides]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I added new tests to check the change I am making or feature I am adding, or the PR is [test-exempt]. See [testing the engine] for instructions on writing and running engine tests. - [x] I updated/added relevant documentation (doc comments with `///`). - [ ] I signed the [CLA]. - [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/wiki/Tree-hygiene#overview [Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene [test-exempt]: https://github.com/flutter/flutter/wiki/Tree-hygiene#tests [Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style [testing the engine]: https://github.com/flutter/flutter/wiki/Testing-the-engine [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/wiki/Chat
1 parent 85a37c3 commit 4fb69c3

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
@@ -196,13 +196,7 @@ public void onFlutterUiNoLongerDisplayed() {
196196
}
197197
};
198198

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

207201
/**
208202
* Constructs a {@code FlutterView} programmatically, without any XML attributes.
@@ -514,6 +508,10 @@ protected void onAttachedToWindow() {
514508
this.windowInfoRepo = createWindowInfoRepo();
515509
Activity activity = ViewUtils.getActivity(getContext());
516510
if (windowInfoRepo != null && activity != null) {
511+
// Creating windowInfoListener on-demand instead of at initialization is necessary in order to
512+
// prevent it from capturing the wrong instance of `this` when spying for testing.
513+
// See https://github.com/mockito/mockito/issues/3479
514+
windowInfoListener = this::setWindowInfoListenerDisplayFeatures;
517515
windowInfoRepo.addWindowLayoutInfoListener(
518516
activity, ContextCompat.getMainExecutor(getContext()), windowInfoListener);
519517
}
@@ -526,9 +524,10 @@ protected void onAttachedToWindow() {
526524
*/
527525
@Override
528526
protected void onDetachedFromWindow() {
529-
if (windowInfoRepo != null) {
527+
if (windowInfoRepo != null && windowInfoListener != null) {
530528
windowInfoRepo.removeWindowLayoutInfoListener(windowInfoListener);
531529
}
530+
windowInfoListener = null;
532531
this.windowInfoRepo = null;
533532
super.onDetachedFromWindow();
534533
}
@@ -539,12 +538,12 @@ protected void onDetachedFromWindow() {
539538
*/
540539
@TargetApi(API_LEVELS.API_28)
541540
protected void setWindowInfoListenerDisplayFeatures(WindowLayoutInfo layoutInfo) {
542-
List<DisplayFeature> displayFeatures = layoutInfo.getDisplayFeatures();
543-
List<FlutterRenderer.DisplayFeature> result = new ArrayList<>();
541+
List<DisplayFeature> newDisplayFeatures = layoutInfo.getDisplayFeatures();
542+
List<FlutterRenderer.DisplayFeature> flutterDisplayFeatures = new ArrayList<>();
544543

545544
// Data from WindowInfoTracker display features. Fold and hinge areas are
546545
// populated here.
547-
for (DisplayFeature displayFeature : displayFeatures) {
546+
for (DisplayFeature displayFeature : newDisplayFeatures) {
548547
Log.v(
549548
TAG,
550549
"WindowInfoTracker Display Feature reported with bounds = "
@@ -567,31 +566,17 @@ protected void setWindowInfoListenerDisplayFeatures(WindowLayoutInfo layoutInfo)
567566
} else {
568567
state = DisplayFeatureState.UNKNOWN;
569568
}
570-
result.add(new FlutterRenderer.DisplayFeature(displayFeature.getBounds(), type, state));
569+
flutterDisplayFeatures.add(
570+
new FlutterRenderer.DisplayFeature(displayFeature.getBounds(), type, state));
571571
} else {
572-
result.add(
572+
flutterDisplayFeatures.add(
573573
new FlutterRenderer.DisplayFeature(
574574
displayFeature.getBounds(),
575575
DisplayFeatureType.UNKNOWN,
576576
DisplayFeatureState.UNKNOWN));
577577
}
578578
}
579-
580-
// Data from the DisplayCutout bounds. Cutouts for cameras and other sensors are
581-
// populated here. DisplayCutout was introduced in API 28.
582-
if (Build.VERSION.SDK_INT >= API_LEVELS.API_28) {
583-
WindowInsets insets = getRootWindowInsets();
584-
if (insets != null) {
585-
DisplayCutout cutout = insets.getDisplayCutout();
586-
if (cutout != null) {
587-
for (Rect bounds : cutout.getBoundingRects()) {
588-
Log.v(TAG, "DisplayCutout area reported with bounds = " + bounds.toString());
589-
result.add(new FlutterRenderer.DisplayFeature(bounds, DisplayFeatureType.CUTOUT));
590-
}
591-
}
592-
}
593-
}
594-
viewportMetrics.displayFeatures = result;
579+
viewportMetrics.setDisplayFeatures(flutterDisplayFeatures);
595580
sendViewportMetricsToFlutter();
596581
}
597582

@@ -784,6 +769,22 @@ navigationBarVisible && guessBottomKeyboardInset(insets) == 0
784769
viewportMetrics.viewInsetLeft = 0;
785770
}
786771

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

1160+
private void translateFeatureBounds(int[] displayFeatureBounds, int offset, Rect bounds) {
1161+
displayFeatureBounds[offset] = bounds.left;
1162+
displayFeatureBounds[offset + 1] = bounds.top;
1163+
displayFeatureBounds[offset + 2] = bounds.right;
1164+
displayFeatureBounds[offset + 3] = bounds.bottom;
1165+
}
1166+
11601167
/**
11611168
* Notifies Flutter that the viewport metrics, e.g. window height and width, have changed.
11621169
*
@@ -1207,20 +1214,31 @@ public void setViewportMetrics(@NonNull ViewportMetrics viewportMetrics) {
12071214
+ viewportMetrics.systemGestureInsetRight
12081215
+ "\n"
12091216
+ "Display Features: "
1210-
+ viewportMetrics.displayFeatures.size());
1211-
1212-
int[] displayFeaturesBounds = new int[viewportMetrics.displayFeatures.size() * 4];
1213-
int[] displayFeaturesType = new int[viewportMetrics.displayFeatures.size()];
1214-
int[] displayFeaturesState = new int[viewportMetrics.displayFeatures.size()];
1217+
+ viewportMetrics.displayFeatures.size()
1218+
+ "\n"
1219+
+ "Display Cutouts: "
1220+
+ viewportMetrics.displayCutouts.size());
1221+
1222+
int totalFeaturesAndCutouts =
1223+
viewportMetrics.displayFeatures.size() + viewportMetrics.displayCutouts.size();
1224+
int[] displayFeaturesBounds = new int[totalFeaturesAndCutouts * 4];
1225+
int[] displayFeaturesType = new int[totalFeaturesAndCutouts];
1226+
int[] displayFeaturesState = new int[totalFeaturesAndCutouts];
12151227
for (int i = 0; i < viewportMetrics.displayFeatures.size(); i++) {
12161228
DisplayFeature displayFeature = viewportMetrics.displayFeatures.get(i);
1217-
displayFeaturesBounds[4 * i] = displayFeature.bounds.left;
1218-
displayFeaturesBounds[4 * i + 1] = displayFeature.bounds.top;
1219-
displayFeaturesBounds[4 * i + 2] = displayFeature.bounds.right;
1220-
displayFeaturesBounds[4 * i + 3] = displayFeature.bounds.bottom;
1229+
translateFeatureBounds(displayFeaturesBounds, 4 * i, displayFeature.bounds);
12211230
displayFeaturesType[i] = displayFeature.type.encodedValue;
12221231
displayFeaturesState[i] = displayFeature.state.encodedValue;
12231232
}
1233+
int cutoutOffset = viewportMetrics.displayFeatures.size() * 4;
1234+
for (int i = 0; i < viewportMetrics.displayCutouts.size(); i++) {
1235+
DisplayFeature displayCutout = viewportMetrics.displayCutouts.get(i);
1236+
translateFeatureBounds(displayFeaturesBounds, cutoutOffset + 4 * i, displayCutout.bounds);
1237+
displayFeaturesType[viewportMetrics.displayFeatures.size() + i] =
1238+
displayCutout.type.encodedValue;
1239+
displayFeaturesState[viewportMetrics.displayFeatures.size() + i] =
1240+
displayCutout.state.encodedValue;
1241+
}
12241242

12251243
flutterJNI.setViewportMetrics(
12261244
viewportMetrics.devicePixelRatio,
@@ -1335,7 +1353,29 @@ boolean validate() {
13351353
return width > 0 && height > 0 && devicePixelRatio > 0;
13361354
}
13371355

1338-
public List<DisplayFeature> displayFeatures = new ArrayList<>();
1356+
// Features
1357+
private final List<DisplayFeature> displayFeatures = new ArrayList<>();
1358+
1359+
// Specifically display cutouts.
1360+
private final List<DisplayFeature> displayCutouts = new ArrayList<>();
1361+
1362+
public List<DisplayFeature> getDisplayFeatures() {
1363+
return displayFeatures;
1364+
}
1365+
1366+
public List<DisplayFeature> getDisplayCutouts() {
1367+
return displayCutouts;
1368+
}
1369+
1370+
public void setDisplayFeatures(List<DisplayFeature> newFeatures) {
1371+
displayFeatures.clear();
1372+
displayFeatures.addAll(newFeatures);
1373+
}
1374+
1375+
public void setDisplayCutouts(List<DisplayFeature> newCutouts) {
1376+
displayCutouts.clear();
1377+
displayCutouts.addAll(newCutouts);
1378+
}
13391379
}
13401380

13411381
/**
@@ -1358,12 +1398,6 @@ public DisplayFeature(Rect bounds, DisplayFeatureType type, DisplayFeatureState
13581398
this.type = type;
13591399
this.state = state;
13601400
}
1361-
1362-
public DisplayFeature(Rect bounds, DisplayFeatureType type) {
1363-
this.bounds = bounds;
1364-
this.type = type;
1365-
this.state = DisplayFeatureState.UNKNOWN;
1366-
}
13671401
}
13681402

13691403
/**

0 commit comments

Comments
 (0)