Merge changes I3a966bc7,I91e6832d
* changes:
Adjust window layout for DisplayCutout
Display Cutout: Add emulation
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1d4477a..6b1632a 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10164,6 +10164,16 @@
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 905c071..7c2c12f 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1268,6 +1268,35 @@
}, 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 77c6c3e..0982a4b 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -194,6 +194,7 @@
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 3eee4da..5e7f9c6 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 0000000..edd1748
--- /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 06d7749..3538327 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.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 9a7e72a..61591bb 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.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 @@
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 @@
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 @@
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 @@
/** {@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 @@
}
}
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 @@
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 @@
}
} 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 @@
}
} 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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
// 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 @@
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 @@
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 @@
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 209ce3f..0155712 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 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 @@
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 @@
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 0941d4f..9a6da0e 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.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 @@
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 9c55707..e7e9abad 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 @@
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 @@
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 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 @@
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 @@
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];