diff options
10 files changed, 455 insertions, 19 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 1d4477a1c4c1..6b1632a0a693 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10164,6 +10164,16 @@ public final class Settings { public static final String POLICY_CONTROL = "policy_control"; /** + * {@link android.view.DisplayCutout DisplayCutout} emulation mode. + * + * @hide + */ + public static final String EMULATE_DISPLAY_CUTOUT = "emulate_display_cutout"; + + /** @hide */ public static final int EMULATE_DISPLAY_CUTOUT_OFF = 0; + /** @hide */ public static final int EMULATE_DISPLAY_CUTOUT_ON = 1; + + /** * Defines global zen mode. ZEN_MODE_OFF, ZEN_MODE_IMPORTANT_INTERRUPTIONS, * or ZEN_MODE_NO_INTERRUPTIONS. * diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 905c0715ecb8..7c2c12f56e5f 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1268,6 +1268,35 @@ public interface WindowManager extends ViewManager { }, formatToHexString = true) public int flags; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + flag = true, + value = { + LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA, + }) + @interface Flags2 {} + + /** + * Window flag: allow placing the window within the area that overlaps with the + * display cutout. + * + * <p> + * The window must correctly position its contents to take the display cutout into account. + * + * @see DisplayCutout + * @hide for now + */ + public static final long FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA = 0x00000001; + + /** + * Various behavioral options/flags. Default is none. + * + * @see #FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA + * @hide for now + */ + @Flags2 public long flags2; + /** * If the window has requested hardware acceleration, but this is not * allowed in the process it is in, then still render it as if it is diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java index 77c6c3e1c193..0982a4b58c99 100644 --- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java +++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java @@ -194,6 +194,7 @@ public class SettingsBackupTest { Settings.Global.DROPBOX_RESERVE_PERCENT, Settings.Global.DROPBOX_TAG_PREFIX, Settings.Global.EMERGENCY_AFFORDANCE_NEEDED, + Settings.Global.EMULATE_DISPLAY_CUTOUT, Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, Settings.Global.ENABLE_CACHE_QUOTA_CALCULATION, Settings.Global.ENABLE_CELLULAR_ON_BOOT, diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 3eee4da7ba5f..5e7f9c6a40e5 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -337,6 +337,7 @@ <item>com.android.systemui.LatencyTester</item> <item>com.android.systemui.globalactions.GlobalActionsComponent</item> <item>com.android.systemui.RoundedCorners</item> + <item>com.android.systemui.EmulatedDisplayCutout</item> </string-array> <!-- SystemUI vender service, used in config_systemUIServiceComponents. --> diff --git a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java new file mode 100644 index 000000000000..edd1748c7380 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui; + +import android.content.Context; +import android.database.ContentObserver; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PixelFormat; +import android.graphics.Point; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.provider.Settings; +import android.view.DisplayCutout; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.view.WindowInsets; +import android.view.WindowManager; + +import java.util.ArrayList; + +/** + * Emulates a display cutout by drawing its shape in an overlay as supplied by + * {@link DisplayCutout}. + */ +public class EmulatedDisplayCutout extends SystemUI { + private View mOverlay; + private boolean mAttached; + private WindowManager mWindowManager; + + @Override + public void start() { + mWindowManager = mContext.getSystemService(WindowManager.class); + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.EMULATE_DISPLAY_CUTOUT), + false, mObserver, UserHandle.USER_ALL); + mObserver.onChange(false); + } + + private void setAttached(boolean attached) { + if (attached && !mAttached) { + if (mOverlay == null) { + mOverlay = new CutoutView(mContext); + mOverlay.setLayoutParams(getLayoutParams()); + } + mWindowManager.addView(mOverlay, mOverlay.getLayoutParams()); + mAttached = true; + } else if (!attached && mAttached) { + mWindowManager.removeView(mOverlay); + mAttached = false; + } + } + + private WindowManager.LayoutParams getLayoutParams() { + final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH + | WindowManager.LayoutParams.FLAG_SLIPPERY + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR, + PixelFormat.TRANSLUCENT); + lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS + | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; + lp.setTitle("EmulatedDisplayCutout"); + lp.gravity = Gravity.TOP; + return lp; + } + + private ContentObserver mObserver = new ContentObserver(new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange) { + boolean emulateCutout = Settings.Global.getInt( + mContext.getContentResolver(), Settings.Global.EMULATE_DISPLAY_CUTOUT, + Settings.Global.EMULATE_DISPLAY_CUTOUT_OFF) + != Settings.Global.EMULATE_DISPLAY_CUTOUT_OFF; + setAttached(emulateCutout); + } + }; + + private static class CutoutView extends View { + private Paint mPaint = new Paint(); + private Path mPath = new Path(); + private ArrayList<Point> mBoundingPolygon = new ArrayList<>(); + + CutoutView(Context context) { + super(context); + } + + @Override + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + insets.getDisplayCutout().getBoundingPolygon(mBoundingPolygon); + invalidate(); + return insets.consumeCutout(); + } + + @Override + protected void onDraw(Canvas canvas) { + if (!mBoundingPolygon.isEmpty()) { + mPaint.setColor(Color.DKGRAY); + mPaint.setStyle(Paint.Style.FILL); + + mPath.reset(); + for (int i = 0; i < mBoundingPolygon.size(); i++) { + Point point = mBoundingPolygon.get(i); + if (i == 0) { + mPath.moveTo(point.x, point.y); + } else { + mPath.lineTo(point.x, point.y); + } + } + mPath.close(); + canvas.drawPath(mPath, mPaint); + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 06d774916ad7..3538327130d4 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -37,9 +37,7 @@ import com.android.systemui.keyboard.KeyboardUI; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.media.RingtonePlayer; import com.android.systemui.pip.PipUI; -import com.android.systemui.plugins.GlobalActions; import com.android.systemui.plugins.OverlayPlugin; -import com.android.systemui.plugins.Plugin; import com.android.systemui.plugins.PluginListener; import com.android.systemui.plugins.PluginManager; import com.android.systemui.power.PowerUI; diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 9a7e72a0455a..61591bbef08d 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -48,6 +48,7 @@ import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW; +import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON; import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN; @@ -607,6 +608,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { PointerLocationView mPointerLocationView; + boolean mEmulateDisplayCutout = false; + // During layout, the layer at which the doc window is placed. int mDockLayer; // During layout, this is the layer of the status bar. @@ -965,6 +968,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { resolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.POLICY_CONTROL), false, this, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.EMULATE_DISPLAY_CUTOUT), false, this, + UserHandle.USER_ALL); updateSettings(); } @@ -2396,6 +2402,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (mImmersiveModeConfirmation != null) { mImmersiveModeConfirmation.loadSetting(mCurrentUserId); } + mEmulateDisplayCutout = Settings.Global.getInt(resolver, + Settings.Global.EMULATE_DISPLAY_CUTOUT, + Settings.Global.EMULATE_DISPLAY_CUTOUT_OFF) + != Settings.Global.EMULATE_DISPLAY_CUTOUT_OFF; } synchronized (mWindowManagerFuncs.getWindowManagerLock()) { PolicyControl.reloadFromSetting(mContext); @@ -4436,7 +4446,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { /** {@inheritDoc} */ @Override public void beginLayoutLw(DisplayFrames displayFrames, int uiMode) { - displayFrames.onBeginLayout(); + displayFrames.onBeginLayout(mEmulateDisplayCutout, mStatusBarHeight); // TODO(multi-display): This doesn't seem right...Maybe only apply to default display? mSystemGestures.screenWidth = displayFrames.mUnrestricted.width(); mSystemGestures.screenHeight = displayFrames.mUnrestricted.height(); @@ -4506,6 +4516,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } layoutScreenDecorWindows(displayFrames, pf, df, dcf); + + if (displayFrames.mDisplayCutoutSafe.top > displayFrames.mUnrestricted.top) { + // Make sure that the zone we're avoiding for the cutout is at least as tall as the + // status bar; otherwise fullscreen apps will end up cutting halfway into the status + // bar. + displayFrames.mDisplayCutoutSafe.top = Math.max(displayFrames.mDisplayCutoutSafe.top, + displayFrames.mStable.top); + } } private void layoutScreenDecorWindows(DisplayFrames displayFrames, Rect pf, Rect df, Rect dcf) { @@ -4641,11 +4659,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { final Rect dockFrame = displayFrames.mDock; mNavigationBarPosition = navigationBarPosition(displayWidth, displayHeight, rotation); + final Rect cutoutSafeUnrestricted = mTmpRect; + cutoutSafeUnrestricted.set(displayFrames.mUnrestricted); + cutoutSafeUnrestricted.intersectUnchecked(displayFrames.mDisplayCutoutSafe); + if (mNavigationBarPosition == NAV_BAR_BOTTOM) { // It's a system nav bar or a portrait screen; nav bar goes on bottom. - final int top = displayFrames.mUnrestricted.bottom + final int top = cutoutSafeUnrestricted.bottom - getNavigationBarHeight(rotation, uiMode); - mTmpNavigationFrame.set(0, top, displayWidth, displayFrames.mUnrestricted.bottom); + mTmpNavigationFrame.set(0, top, displayWidth, cutoutSafeUnrestricted.bottom); displayFrames.mStable.bottom = displayFrames.mStableFullscreen.bottom = top; if (transientNavBarShowing) { mNavigationBarController.setBarShowingLw(true); @@ -4666,9 +4688,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } else if (mNavigationBarPosition == NAV_BAR_RIGHT) { // Landscape screen; nav bar goes to the right. - final int left = displayFrames.mUnrestricted.right + final int left = cutoutSafeUnrestricted.right - getNavigationBarWidth(rotation, uiMode); - mTmpNavigationFrame.set(left, 0, displayFrames.mUnrestricted.right, displayHeight); + mTmpNavigationFrame.set(left, 0, cutoutSafeUnrestricted.right, displayHeight); displayFrames.mStable.right = displayFrames.mStableFullscreen.right = left; if (transientNavBarShowing) { mNavigationBarController.setBarShowingLw(true); @@ -4689,9 +4711,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } else if (mNavigationBarPosition == NAV_BAR_LEFT) { // Seascape screen; nav bar goes to the left. - final int right = displayFrames.mUnrestricted.left + final int right = cutoutSafeUnrestricted.left + getNavigationBarWidth(rotation, uiMode); - mTmpNavigationFrame.set(displayFrames.mUnrestricted.left, 0, right, displayHeight); + mTmpNavigationFrame.set(cutoutSafeUnrestricted.left, 0, right, displayHeight); displayFrames.mStable.left = displayFrames.mStableFullscreen.left = right; if (transientNavBarShowing) { mNavigationBarController.setBarShowingLw(true); @@ -4843,6 +4865,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { final int type = attrs.type; final int fl = PolicyControl.getWindowFlags(win, attrs); + final long fl2 = attrs.flags2; final int pfl = attrs.privateFlags; final int sim = attrs.softInputMode; final int requestedSysUiFl = PolicyControl.getSystemUiVisibility(win, null); @@ -4863,6 +4886,14 @@ public class PhoneWindowManager implements WindowManagerPolicy { final int adjust = sim & SOFT_INPUT_MASK_ADJUST; + final boolean requestedFullscreen = (fl & FLAG_FULLSCREEN) != 0 + || (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0 + || (requestedSysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0; + + final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN; + final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR; + final boolean layoutInCutout = (fl2 & FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA) != 0; + sf.set(displayFrames.mStable); if (type == TYPE_INPUT_METHOD) { @@ -4872,7 +4903,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { df.set(displayFrames.mDock); pf.set(displayFrames.mDock); // IM dock windows layout below the nav bar... - pf.bottom = df.bottom = of.bottom = displayFrames.mUnrestricted.bottom; + pf.bottom = df.bottom = of.bottom = Math.min(displayFrames.mUnrestricted.bottom, + displayFrames.mDisplayCutoutSafe.bottom); // ...with content insets above the nav bar cf.bottom = vf.bottom = displayFrames.mStable.bottom; if (mStatusBar != null && mFocusedWindow == mStatusBar && canReceiveInput(mStatusBar)) { @@ -4943,8 +4975,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - if ((fl & (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) - == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) { + if (layoutInScreen && layoutInsetDecor) { if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() + "): IN_SCREEN, INSET_DECOR"); // This is the case for a normal activity window: we want it to cover all of the @@ -5021,6 +5052,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { // moving from a window that is not hiding the status bar to one that is. cf.set(displayFrames.mRestricted); } + if (requestedFullscreen && !layoutInCutout) { + pf.intersectUnchecked(displayFrames.mDisplayCutoutSafe); + } applyStableConstraints(sysUiFl, fl, cf, displayFrames); if (adjust != SOFT_INPUT_ADJUST_NOTHING) { vf.set(displayFrames.mCurrent); @@ -5028,7 +5062,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { vf.set(cf); } } - } else if ((fl & FLAG_LAYOUT_IN_SCREEN) != 0 || (sysUiFl + } else if (layoutInScreen || (sysUiFl & (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)) != 0) { if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() @@ -5106,6 +5140,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { of.set(displayFrames.mUnrestricted); df.set(displayFrames.mUnrestricted); pf.set(displayFrames.mUnrestricted); + if (requestedFullscreen && !layoutInCutout) { + pf.intersectUnchecked(displayFrames.mDisplayCutoutSafe); + } } else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0) { of.set(displayFrames.mRestricted); df.set(displayFrames.mRestricted); @@ -5176,9 +5213,27 @@ public class PhoneWindowManager implements WindowManagerPolicy { vf.set(cf); } } + pf.intersectUnchecked(displayFrames.mDisplayCutoutSafe); } } + // Ensure that windows that did not request to be laid out in the cutout don't get laid + // out there. + if (!layoutInCutout) { + final Rect displayCutoutSafeExceptMaybeTop = mTmpRect; + displayCutoutSafeExceptMaybeTop.set(displayFrames.mDisplayCutoutSafe); + if (layoutInScreen && layoutInsetDecor) { + // At the top we have the status bar, so apps that are + // LAYOUT_IN_SCREEN | LAYOUT_INSET_DECOR already expect that there's an inset + // there and we don't need to exclude the window from that area. + displayCutoutSafeExceptMaybeTop.top = Integer.MIN_VALUE; + } + pf.intersectUnchecked(displayCutoutSafeExceptMaybeTop); + } + + // Content should never appear in the cutout. + cf.intersectUnchecked(displayFrames.mDisplayCutoutSafe); + // TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it. // Also, we don't allow windows in multi-window mode to extend out of the screen. if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && type != TYPE_SYSTEM_ERROR diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java index 209ce3fcfdb4..015571255f0d 100644 --- a/services/core/java/com/android/server/wm/DisplayFrames.java +++ b/services/core/java/com/android/server/wm/DisplayFrames.java @@ -22,12 +22,16 @@ import static android.view.Surface.ROTATION_90; import static com.android.server.wm.proto.DisplayFramesProto.STABLE_BOUNDS; import android.annotation.NonNull; +import android.graphics.Point; import android.graphics.Rect; import android.util.proto.ProtoOutputStream; import android.view.DisplayCutout; import android.view.DisplayInfo; +import com.android.internal.annotations.VisibleForTesting; + import java.io.PrintWriter; +import java.util.Arrays; /** * Container class for all the display frames that affect how we do window layout on a display. @@ -124,7 +128,7 @@ public class DisplayFrames { info.overscanLeft, info.overscanTop, info.overscanRight, info.overscanBottom); } - public void onBeginLayout() { + public void onBeginLayout(boolean emulateDisplayCutout, int statusBarHeight) { switch (mRotation) { case ROTATION_90: mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.top; @@ -165,12 +169,64 @@ public class DisplayFrames { mDisplayCutout = DisplayCutout.NO_CUTOUT; mDisplayCutoutSafe.set(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); + if (emulateDisplayCutout) { + setEmulatedDisplayCutout((int) (statusBarHeight * 0.8)); + } } public int getInputMethodWindowVisibleHeight() { return mDock.bottom - mCurrent.bottom; } + private void setEmulatedDisplayCutout(int height) { + final boolean swappedDimensions = mRotation == ROTATION_90 || mRotation == ROTATION_270; + + final int screenWidth = swappedDimensions ? mDisplayHeight : mDisplayWidth; + final int screenHeight = swappedDimensions ? mDisplayWidth : mDisplayHeight; + + final int widthTop = (int) (screenWidth * 0.3); + final int widthBottom = widthTop - height; + + switch (mRotation) { + case ROTATION_90: + mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList( + new Point(0, (screenWidth - widthTop) / 2), + new Point(height, (screenWidth - widthBottom) / 2), + new Point(height, (screenWidth + widthBottom) / 2), + new Point(0, (screenWidth + widthTop) / 2) + )).calculateRelativeTo(mUnrestricted); + mDisplayCutoutSafe.left = height; + break; + case ROTATION_180: + mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList( + new Point((screenWidth - widthTop) / 2, screenHeight), + new Point((screenWidth - widthBottom) / 2, screenHeight - height), + new Point((screenWidth + widthBottom) / 2, screenHeight - height), + new Point((screenWidth + widthTop) / 2, screenHeight) + )).calculateRelativeTo(mUnrestricted); + mDisplayCutoutSafe.bottom = screenHeight - height; + break; + case ROTATION_270: + mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList( + new Point(screenHeight, (screenWidth - widthTop) / 2), + new Point(screenHeight - height, (screenWidth - widthBottom) / 2), + new Point(screenHeight - height, (screenWidth + widthBottom) / 2), + new Point(screenHeight, (screenWidth + widthTop) / 2) + )).calculateRelativeTo(mUnrestricted); + mDisplayCutoutSafe.right = screenHeight - height; + break; + default: + mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList( + new Point((screenWidth - widthTop) / 2, 0), + new Point((screenWidth - widthBottom) / 2, height), + new Point((screenWidth + widthBottom) / 2, height), + new Point((screenWidth + widthTop) / 2, 0) + )).calculateRelativeTo(mUnrestricted); + mDisplayCutoutSafe.top = height; + break; + } + } + public void writeToProto(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); mStable.writeToProto(proto, STABLE_BOUNDS); 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 0941d4fd4fc9..9a6da0e791b5 100644 --- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java +++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerLayoutTest.java @@ -16,7 +16,12 @@ package com.android.server.policy; +import static android.view.Surface.ROTATION_180; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; +import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR; import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN; @@ -29,6 +34,7 @@ import android.graphics.PixelFormat; import android.platform.test.annotations.Presubmit; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import android.view.Surface; import android.view.WindowManager; import org.junit.Before; @@ -105,4 +111,123 @@ public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase { assertEquals(0, mAppWindow.attrs.systemUiVisibility); assertEquals(0, mAppWindow.attrs.subtreeSystemUiVisibility); } + + @Test + public void layoutWindowLw_withDisplayCutout() { + addDisplayCutout(); + + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0); + assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0); + } + + @Test + public void layoutWindowLw_withDisplayCutout_fullscreen() { + addDisplayCutout(); + + mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetByTopBottom(mAppWindow.parentFrame, STATUS_BAR_HEIGHT, 0); + assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0); + } + + @Test + public void layoutWindowLw_withDisplayCutout_fullscreenInCutout() { + addDisplayCutout(); + + mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; + mAppWindow.attrs.flags2 |= FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0); + assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0); + } + + + @Test + public void layoutWindowLw_withDisplayCutout_landscape() { + addDisplayCutout(); + setRotation(ROTATION_90); + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetBy(mAppWindow.parentFrame, DISPLAY_CUTOUT_HEIGHT, 0, 0, 0); + assertInsetBy(mAppWindow.stableFrame, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); + assertInsetBy(mAppWindow.contentFrame, + DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); + assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0); + } + + @Test + public void layoutWindowLw_withDisplayCutout_seascape() { + addDisplayCutout(); + setRotation(ROTATION_270); + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetBy(mAppWindow.parentFrame, 0, 0, DISPLAY_CUTOUT_HEIGHT, 0); + assertInsetBy(mAppWindow.stableFrame, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, 0, 0); + assertInsetBy(mAppWindow.contentFrame, + NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, DISPLAY_CUTOUT_HEIGHT, 0); + assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0); + } + + @Test + public void layoutWindowLw_withDisplayCutout_fullscreen_landscape() { + addDisplayCutout(); + setRotation(ROTATION_90); + + mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetBy(mAppWindow.parentFrame, DISPLAY_CUTOUT_HEIGHT, 0, 0, 0); + assertInsetBy(mAppWindow.stableFrame, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); + assertInsetBy(mAppWindow.contentFrame, + DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); + assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0); + } + + @Test + public void layoutWindowLw_withDisplayCutout_fullscreenInCutout_landscape() { + addDisplayCutout(); + setRotation(ROTATION_90); + + mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; + mAppWindow.attrs.flags2 |= FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; + mPolicy.addWindow(mAppWindow); + + mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mPolicy.layoutWindowLw(mAppWindow, null, mFrames); + + assertInsetBy(mAppWindow.parentFrame, 0, 0, 0, 0); + assertInsetBy(mAppWindow.stableFrame, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); + assertInsetBy(mAppWindow.contentFrame, + DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0); + assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0); + } + }
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java index 9c5570725baa..e7e9abad5bbe 100644 --- a/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java +++ b/services/tests/servicestests/src/com/android/server/policy/PhoneWindowManagerTestBase.java @@ -17,6 +17,9 @@ package com.android.server.policy; import static android.view.Surface.ROTATION_0; +import static android.view.Surface.ROTATION_180; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; @@ -54,6 +57,7 @@ public class PhoneWindowManagerTestBase { static final int STATUS_BAR_HEIGHT = 10; static final int NAV_BAR_HEIGHT = 15; + static final int DISPLAY_CUTOUT_HEIGHT = 8; TestablePhoneWindowManager mPolicy; TestContextWrapper mContext; @@ -76,10 +80,17 @@ public class PhoneWindowManagerTestBase { mPolicy = TestablePhoneWindowManager.create(mContext); + setRotation(ROTATION_0); + } + + public void setRotation(int rotation) { DisplayInfo info = new DisplayInfo(); - info.logicalWidth = DISPLAY_WIDTH; - info.logicalHeight = DISPLAY_HEIGHT; - info.rotation = ROTATION_0; + + final boolean flippedDimensions = rotation == ROTATION_90 || rotation == ROTATION_270; + info.logicalWidth = flippedDimensions ? DISPLAY_HEIGHT : DISPLAY_WIDTH; + info.logicalHeight = flippedDimensions ? DISPLAY_WIDTH : DISPLAY_HEIGHT; + info.rotation = rotation; + mFrames = new DisplayFrames(Display.DEFAULT_DISPLAY, info); } @@ -95,7 +106,7 @@ public class PhoneWindowManagerTestBase { public void addNavigationBar() { mNavigationBar = new FakeWindowState(); - mNavigationBar.attrs = new WindowManager.LayoutParams(MATCH_PARENT, STATUS_BAR_HEIGHT, + mNavigationBar.attrs = new WindowManager.LayoutParams(MATCH_PARENT, NAV_BAR_HEIGHT, TYPE_NAVIGATION_BAR, 0 /* flags */, PixelFormat.TRANSLUCENT); mNavigationBar.attrs.gravity = Gravity.BOTTOM; @@ -104,11 +115,16 @@ public class PhoneWindowManagerTestBase { mPolicy.mLastSystemUiFlags |= View.NAVIGATION_BAR_TRANSPARENT; } + public void addDisplayCutout() { + mPolicy.mEmulateDisplayCutout = true; + } + /** Asserts that {@code actual} is inset by the given amounts from the full display rect. */ public void assertInsetBy(Rect actual, int expectedInsetLeft, int expectedInsetTop, int expectedInsetRight, int expectedInsetBottom) { assertEquals(new Rect(expectedInsetLeft, expectedInsetTop, - DISPLAY_WIDTH - expectedInsetRight, DISPLAY_HEIGHT - expectedInsetBottom), actual); + mFrames.mDisplayWidth - expectedInsetRight, + mFrames.mDisplayHeight - expectedInsetBottom), actual); } /** @@ -181,6 +197,11 @@ public class PhoneWindowManagerTestBase { policy[0].mAccessibilityManager = new AccessibilityManager(context, mock(IAccessibilityManager.class), UserHandle.USER_CURRENT); policy[0].mSystemGestures = mock(SystemGesturesPointerEventListener.class); + policy[0].mNavigationBarCanMove = true; + policy[0].mPortraitRotation = ROTATION_0; + policy[0].mLandscapeRotation = ROTATION_90; + policy[0].mUpsideDownRotation = ROTATION_180; + policy[0].mSeascapeRotation = ROTATION_270; policy[0].onConfigurationChanged(); }); return policy[0]; |