From 5131181620be965765d2d50900db3f9796cbaea3 Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Tue, 3 Jul 2018 15:35:33 +0200 Subject: [PATCH] WM: Fix broken layout hint calculation Fixes issues with the layout hint calculation that would end up dispatching negative insets to applications and either causing a WTF or previous to the workaround crashing apps. The somewhat complex way of calculating insets ad-hoc is replaced with a much clearer and less error prone approach that mirrors how insets are actually calculated during regular layout. Doing that conveniently eliminates the code that was buggy in the first place (the intersect in calculateRelevantTaskInsets did not properly check for the non-interecting case). Change-Id: I05682943c7174a6c5a73862152bfbe4876f517f7 Fixes: 110834518 Test: atest PhoneWindowManagerLayoutTest --- .../android/server/policy/PhoneWindowManager.java | 63 ++++++++-------------- .../android/server/policy/WindowManagerPolicy.java | 6 +-- .../android/server/wm/WindowManagerService.java | 8 ++- .../com/android/server/wm/utils/InsetUtils.java | 29 ++++++++++ .../policy/PhoneWindowManagerLayoutTest.java | 40 +++++++++++--- 5 files changed, 92 insertions(+), 54 deletions(-) diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 5bc35e7296c3..e6195b47a586 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -290,6 +290,7 @@ import com.android.server.wm.AppTransition; import com.android.server.wm.DisplayFrames; import com.android.server.wm.WindowManagerInternal; import com.android.server.wm.WindowManagerInternal.AppTransitionListener; +import com.android.server.wm.utils.InsetUtils; import java.io.File; import java.io.FileReader; @@ -4536,16 +4537,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override // TODO: Should probably be moved into DisplayFrames. - public boolean getLayoutHintLw(WindowManager.LayoutParams attrs, Rect taskBounds, - DisplayFrames displayFrames, Rect outFrame, Rect outContentInsets, Rect outStableInsets, + public boolean getLayoutHintLw(LayoutParams attrs, Rect taskBounds, + DisplayFrames displayFrames, boolean floatingStack, Rect outFrame, + Rect outContentInsets, Rect outStableInsets, Rect outOutsets, DisplayCutout.ParcelableWrapper outDisplayCutout) { final int fl = PolicyControl.getWindowFlags(null, attrs); final int pfl = attrs.privateFlags; final int requestedSysUiVis = PolicyControl.getSystemUiVisibility(null, attrs); final int sysUiVis = requestedSysUiVis | getImpliedSysUiFlagsForLayout(attrs); final int displayRotation = displayFrames.mRotation; - final int displayWidth = displayFrames.mDisplayWidth; - final int displayHeight = displayFrames.mDisplayHeight; final boolean useOutsets = outOutsets != null && shouldUseOutsets(attrs, fl); if (useOutsets) { @@ -4569,45 +4569,40 @@ public class PhoneWindowManager implements WindowManagerPolicy { final boolean screenDecor = (pfl & PRIVATE_FLAG_IS_SCREEN_DECOR) != 0; if (layoutInScreenAndInsetDecor && !screenDecor) { - int availRight, availBottom; if (canHideNavigationBar() && (sysUiVis & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0) { outFrame.set(displayFrames.mUnrestricted); - availRight = displayFrames.mUnrestricted.right; - availBottom = displayFrames.mUnrestricted.bottom; } else { outFrame.set(displayFrames.mRestricted); - availRight = displayFrames.mRestricted.right; - availBottom = displayFrames.mRestricted.bottom; } - outStableInsets.set(displayFrames.mStable.left, displayFrames.mStable.top, - availRight - displayFrames.mStable.right, - availBottom - displayFrames.mStable.bottom); - if ((sysUiVis & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) { + final Rect sf; + if (floatingStack) { + sf = null; + } else { + sf = displayFrames.mStable; + } + + final Rect cf; + if (floatingStack) { + cf = null; + } else if ((sysUiVis & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) { if ((fl & FLAG_FULLSCREEN) != 0) { - outContentInsets.set(displayFrames.mStableFullscreen.left, - displayFrames.mStableFullscreen.top, - availRight - displayFrames.mStableFullscreen.right, - availBottom - displayFrames.mStableFullscreen.bottom); + cf = displayFrames.mStableFullscreen; } else { - outContentInsets.set(outStableInsets); + cf = displayFrames.mStable; } } else if ((fl & FLAG_FULLSCREEN) != 0 || (fl & FLAG_LAYOUT_IN_OVERSCAN) != 0) { - outContentInsets.setEmpty(); + cf = displayFrames.mOverscan; } else { - outContentInsets.set(displayFrames.mCurrent.left, displayFrames.mCurrent.top, - availRight - displayFrames.mCurrent.right, - availBottom - displayFrames.mCurrent.bottom); + cf = displayFrames.mCurrent; } if (taskBounds != null) { - calculateRelevantTaskInsets(taskBounds, outContentInsets, - displayWidth, displayHeight); - calculateRelevantTaskInsets(taskBounds, outStableInsets, - displayWidth, displayHeight); outFrame.intersect(taskBounds); } + InsetUtils.insetsBetweenFrames(outFrame, cf, outContentInsets); + InsetUtils.insetsBetweenFrames(outFrame, sf, outStableInsets); outDisplayCutout.set(displayFrames.mDisplayCutout.calculateRelativeTo(outFrame) .getDisplayCutout()); return mForceShowSystemBars; @@ -4628,22 +4623,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - /** - * For any given task bounds, the insets relevant for these bounds given the insets relevant - * for the entire display. - */ - private void calculateRelevantTaskInsets(Rect taskBounds, Rect inOutInsets, int displayWidth, - int displayHeight) { - mTmpRect.set(0, 0, displayWidth, displayHeight); - mTmpRect.inset(inOutInsets); - mTmpRect.intersect(taskBounds); - int leftInset = mTmpRect.left - taskBounds.left; - int topInset = mTmpRect.top - taskBounds.top; - int rightInset = taskBounds.right - mTmpRect.right; - int bottomInset = taskBounds.bottom - mTmpRect.bottom; - inOutInsets.set(leftInset, topInset, rightInset, bottomInset); - } - private boolean shouldUseOutsets(WindowManager.LayoutParams attrs, int fl) { return attrs.type == TYPE_WALLPAPER || (fl & (WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN)) != 0; diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 9fbe4194049b..1ebbe3ac36fa 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -71,7 +71,6 @@ import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; -import android.graphics.Point; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; @@ -1180,6 +1179,7 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { * @param taskBounds The bounds of the task this window is on or {@code null} if no task is * associated with the window. * @param displayFrames display frames. + * @param floatingStack Whether the window's stack is floating. * @param outFrame The frame of the window. * @param outContentInsets The areas covered by system windows, expressed as positive insets. * @param outStableInsets The areas covered by stable system windows irrespective of their @@ -1190,8 +1190,8 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { * See {@link #isNavBarForcedShownLw(WindowState)}. */ default boolean getLayoutHintLw(WindowManager.LayoutParams attrs, Rect taskBounds, - DisplayFrames displayFrames, Rect outFrame, Rect outContentInsets, - Rect outStableInsets, Rect outOutsets, + DisplayFrames displayFrames, boolean floatingStack, + Rect outFrame, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, DisplayCutout.ParcelableWrapper outDisplayCutout) { return false; } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 176bc2e32007..732a82829684 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -1469,14 +1469,18 @@ public class WindowManagerService extends IWindowManager.Stub displayFrames.onDisplayInfoUpdated(displayInfo, displayContent.calculateDisplayCutoutForRotation(displayInfo.rotation)); final Rect taskBounds; + final boolean floatingStack; if (atoken != null && atoken.getTask() != null) { taskBounds = mTmpRect; atoken.getTask().getBounds(mTmpRect); + floatingStack = atoken.getTask().isFloating(); } else { taskBounds = null; + floatingStack = false; } - if (mPolicy.getLayoutHintLw(win.mAttrs, taskBounds, displayFrames, outFrame, - outContentInsets, outStableInsets, outOutsets, outDisplayCutout)) { + if (mPolicy.getLayoutHintLw(win.mAttrs, taskBounds, displayFrames, floatingStack, + outFrame, outContentInsets, outStableInsets, outOutsets, + outDisplayCutout)) { res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR; } diff --git a/services/core/java/com/android/server/wm/utils/InsetUtils.java b/services/core/java/com/android/server/wm/utils/InsetUtils.java index b4a998add374..c5b103fdb40b 100644 --- a/services/core/java/com/android/server/wm/utils/InsetUtils.java +++ b/services/core/java/com/android/server/wm/utils/InsetUtils.java @@ -16,8 +16,11 @@ package com.android.server.wm.utils; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.graphics.Rect; + /** * Utility methods to handle insets represented as rects. */ @@ -35,4 +38,30 @@ public class InsetUtils { inOutInsets.right += insetsToAdd.right; inOutInsets.bottom += insetsToAdd.bottom; } + + /** + * Calculates the insets from the {@code outerFrame} to the {@code innerFrame}. + * + * Note that if a side of the outer frame is not actually on the outside, the inset for that + * side will be clamped to zero. + * + * @param outerFrame the reference frame from which the insets are calculated + * @param innerFrame the inset frame, to which the insets are calculated, + * or null to clear the insets. + * @param outInsets is set to the result of the inset calculation. + */ + public static void insetsBetweenFrames(@NonNull Rect outerFrame, @Nullable Rect innerFrame, + @NonNull Rect outInsets) { + if (innerFrame == null) { + outInsets.setEmpty(); + return; + } + final int w = outerFrame.width(); + final int h = outerFrame.height(); + outInsets.set( + Math.min(w, Math.max(0, innerFrame.left - outerFrame.left)), + Math.min(h, Math.max(0, innerFrame.top - outerFrame.top)), + Math.min(w, Math.max(0, outerFrame.right - innerFrame.right)), + Math.min(h, Math.max(0, outerFrame.bottom - innerFrame.bottom))); + } } diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java index 97a716f6bd99..cb94ec7caaca 100644 --- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java +++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java @@ -22,7 +22,6 @@ import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; -import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; @@ -309,8 +308,8 @@ public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase { final Rect stable = new Rect(); final Rect outsets = new Rect(); final DisplayCutout.ParcelableWrapper cutout = new DisplayCutout.ParcelableWrapper(); - mPolicy.getLayoutHintLw(mAppWindow.attrs, null /* taskBounds */, mFrames, frame, content, - stable, outsets, cutout); + mPolicy.getLayoutHintLw(mAppWindow.attrs, null /* taskBounds */, mFrames, + false /* floatingStack */, frame, content, stable, outsets, cutout); assertThat(frame, equalTo(mFrames.mUnrestricted)); assertThat(content, equalTo(new Rect())); @@ -331,8 +330,8 @@ public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase { final DisplayCutout.ParcelableWrapper outDisplayCutout = new DisplayCutout.ParcelableWrapper(); - mPolicy.getLayoutHintLw(mAppWindow.attrs, null, mFrames, outFrame, outContentInsets, - outStableInsets, outOutsets, outDisplayCutout); + mPolicy.getLayoutHintLw(mAppWindow.attrs, null, mFrames, false /* floatingStack */, + outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout); assertThat(outFrame, is(mFrames.mUnrestricted)); assertThat(outContentInsets, is(new Rect(0, STATUS_BAR_HEIGHT, 0, NAV_BAR_HEIGHT))); @@ -355,8 +354,35 @@ public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase { final DisplayCutout.ParcelableWrapper outDisplayCutout = new DisplayCutout.ParcelableWrapper(); - mPolicy.getLayoutHintLw(mAppWindow.attrs, taskBounds, mFrames, outFrame, outContentInsets, - outStableInsets, outOutsets, outDisplayCutout); + mPolicy.getLayoutHintLw(mAppWindow.attrs, taskBounds, mFrames, false /* floatingStack */, + outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout); + + assertThat(outFrame, is(taskBounds)); + assertThat(outContentInsets, is(new Rect())); + assertThat(outStableInsets, is(new Rect())); + assertThat(outOutsets, is(new Rect())); + assertThat(outDisplayCutout, is(new DisplayCutout.ParcelableWrapper())); + } + + @Test + public void layoutHint_appWindowInTask_outsideContentFrame() { + // Initialize DisplayFrames + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + + // Task is in the nav bar area (usually does not happen, but this is similar enough to the + // possible overlap with the IME) + final Rect taskBounds = new Rect(100, mFrames.mContent.bottom + 1, + 200, mFrames.mContent.bottom + 10); + + final Rect outFrame = new Rect(); + final Rect outContentInsets = new Rect(); + final Rect outStableInsets = new Rect(); + final Rect outOutsets = new Rect(); + final DisplayCutout.ParcelableWrapper outDisplayCutout = + new DisplayCutout.ParcelableWrapper(); + + mPolicy.getLayoutHintLw(mAppWindow.attrs, taskBounds, mFrames, true /* floatingStack */, + outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout); assertThat(outFrame, is(taskBounds)); assertThat(outContentInsets, is(new Rect())); -- 2.11.0