the display's native orientation. -->
<string translatable="false" name="config_mainBuiltInDisplayCutout"></string>
+ <!-- Whether the display cutout region of the main built-in display should be forced to
+ black in software (to avoid aliasing or emulate a cutout that is not physically existent).
+ -->
+ <bool name="config_fillMainBuiltInDisplayCutout">false</bool>
+
<!-- Ultrasound support for Mic/speaker path -->
<!-- Whether the default microphone audio source supports near-ultrasound frequencies
(range of 18 - 21 kHz). -->
<java-symbol type="string" name="global_action_logout" />
<java-symbol type="string" name="config_mainBuiltInDisplayCutout" />
+ <java-symbol type="bool" name="config_fillMainBuiltInDisplayCutout" />
<java-symbol type="drawable" name="ic_logout" />
<java-symbol type="array" name="config_autoBrightnessDisplayValuesNits" />
import android.view.WindowInsets;
import android.view.WindowManager;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
+
import java.util.Collections;
import java.util.List;
* Emulates a display cutout by drawing its shape in an overlay as supplied by
* {@link DisplayCutout}.
*/
-public class EmulatedDisplayCutout extends SystemUI {
+public class EmulatedDisplayCutout extends SystemUI implements ConfigurationListener {
private View mOverlay;
private boolean mAttached;
private WindowManager mWindowManager;
@Override
public void start() {
+ Dependency.get(ConfigurationController.class).addCallback(this);
+
mWindowManager = mContext.getSystemService(WindowManager.class);
- mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.EMULATE_DISPLAY_CUTOUT),
- false, mObserver, UserHandle.USER_ALL);
- mObserver.onChange(false);
+ updateAttached();
+ }
+
+ @Override
+ public void onOverlayChanged() {
+ updateAttached();
+ }
+
+ private void updateAttached() {
+ boolean shouldAttach = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout);
+ setAttached(shouldAttach);
}
private void setAttached(boolean attached) {
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 final Paint mPaint = new Paint();
private final Path mBounds = new Path();
--- /dev/null
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_RRO_THEME := DisplayCutoutEmulation
+LOCAL_CERTIFICATE := platform
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := DisplayCutoutEmulationOverlay
+
+include $(BUILD_RRO_PACKAGE)
--- /dev/null
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.internal.display.cutout.emulation"
+ android:versionCode="1"
+ android:versionName="1.0">
+ <overlay android:targetPackage="android" android:priority="1"/>
+
+ <application android:label="@string/display_cutout_emulation_overlay" android:hasCode="false"/>
+</manifest>
--- /dev/null
+<!--
+ ~ Copyright (C) 2018 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.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- The bounding path of the cutout region of the main built-in display.
+ Must either be empty if there is no cutout region, or a string that is parsable by
+ {@link android.util.PathParser}. -->
+ <string translatable="false" name="config_mainBuiltInDisplayCutout">
+ M 687.0,0
+ l -66,50
+ l 0,50
+ l 66,50
+ l 66,0
+ l 66,-50
+ l 0,-50
+ l -66,-50
+ z
+ </string>
+
+ <!-- Whether the display cutout region of the main built-in display should be forced to
+ black in software (to avoid aliasing or emulate a cutout that is not physically existent).
+ -->
+ <bool name="config_fillMainBuiltInDisplayCutout">true</bool>
+
+ <!-- Height of the status bar -->
+ <dimen name="status_bar_height">150px</dimen>
+
+</resources>
+
+
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * 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.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <string name="display_cutout_emulation_overlay">Display Cutout Emulation</string>
+
+</resources>
+
package com.android.server.display;
+import android.app.ActivityThread;
import android.content.res.Resources;
import com.android.server.LocalServices;
import com.android.server.lights.Light;
| DisplayDeviceInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS;
}
- final Resources res = getContext().getResources();
+ final Resources res = getOverlayContext().getResources();
if (mBuiltInDisplayId == SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN) {
mInfo.name = res.getString(
com.android.internal.R.string.display_manager_built_in_display_name);
}
}
+ /** Supplies a context whose Resources apply runtime-overlays */
+ Context getOverlayContext() {
+ return ActivityThread.currentActivityThread().getSystemUiContext();
+ }
+
/**
* Keeps track of a display configuration.
*/
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.
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();
}
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);
/** {@inheritDoc} */
@Override
public void beginLayoutLw(DisplayFrames displayFrames, int uiMode) {
- displayFrames.onBeginLayout(mEmulateDisplayCutout, mStatusBarHeight);
+ displayFrames.onBeginLayout();
// TODO(multi-display): This doesn't seem right...Maybe only apply to default display?
mSystemGestures.screenWidth = displayFrames.mUnrestricted.width();
mSystemGestures.screenHeight = displayFrames.mUnrestricted.height();
/** During layout, the current screen borders along which input method windows are placed. */
public final Rect mDock = new Rect();
- /** The display cutout used for layout (after rotation and emulation) */
+ /** The display cutout used for layout (after rotation) */
@NonNull public DisplayCutout mDisplayCutout = DisplayCutout.NO_CUTOUT;
/** The cutout as supplied by display info */
? info.displayCutout : DisplayCutout.NO_CUTOUT;
}
- public void onBeginLayout(boolean emulateDisplayCutout, int statusBarHeight) {
+ public void onBeginLayout() {
switch (mRotation) {
case ROTATION_90:
mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.top;
mStable.set(mUnrestricted);
mStableFullscreen.set(mUnrestricted);
mCurrent.set(mUnrestricted);
- mDisplayCutout = mDisplayInfoCutout;
- if (emulateDisplayCutout) {
- setEmulatedDisplayCutout((int) (statusBarHeight * 0.8));
- }
- mDisplayCutout = mDisplayCutout.calculateRelativeTo(mOverscan);
+ mDisplayCutout = mDisplayInfoCutout.calculateRelativeTo(mOverscan);
mDisplayCutoutSafe.set(Integer.MIN_VALUE, Integer.MIN_VALUE,
Integer.MAX_VALUE, Integer.MAX_VALUE);
if (!mDisplayCutout.isEmpty()) {
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)
- ));
- 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)
- ));
- 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)
- ));
- 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)
- ));
- break;
- }
- }
-
public void writeToProto(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
mStable.writeToProto(proto, STABLE_BOUNDS);
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates;
+
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import android.content.ContextWrapper;
import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.IBinder;
import android.support.test.InstrumentationRegistry;
import android.testing.TestableResources;
import android.view.Display;
+import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.View;
FakeWindowState mStatusBar;
FakeWindowState mNavigationBar;
+ private boolean mHasDisplayCutout;
+ private int mRotation = ROTATION_0;
+ private final Matrix mTmpMatrix = new Matrix();
@Before
public void setUpBase() throws Exception {
mPolicy = TestablePhoneWindowManager.create(mContext);
- setRotation(ROTATION_0);
+ updateDisplayFrames();
}
public void setRotation(int rotation) {
+ mRotation = rotation;
+ updateDisplayFrames();
+ }
+
+ private void updateDisplayFrames() {
DisplayInfo info = new DisplayInfo();
- final boolean flippedDimensions = rotation == ROTATION_90 || rotation == ROTATION_270;
+ final boolean flippedDimensions = mRotation == ROTATION_90 || mRotation == ROTATION_270;
info.logicalWidth = flippedDimensions ? DISPLAY_HEIGHT : DISPLAY_WIDTH;
info.logicalHeight = flippedDimensions ? DISPLAY_WIDTH : DISPLAY_HEIGHT;
- info.rotation = rotation;
+ info.rotation = mRotation;
+ if (mHasDisplayCutout) {
+ Path p = new Path();
+ p.addRect(DISPLAY_WIDTH / 4, 0, DISPLAY_WIDTH * 3 / 4, DISPLAY_CUTOUT_HEIGHT,
+ Path.Direction.CCW);
+ transformPhysicalToLogicalCoordinates(
+ mRotation, DISPLAY_WIDTH, DISPLAY_HEIGHT, mTmpMatrix);
+ p.transform(mTmpMatrix);
+ info.displayCutout = DisplayCutout.fromBounds(p);
+ } else {
+ info.displayCutout = null;
+ }
mFrames = new DisplayFrames(Display.DEFAULT_DISPLAY, info);
}
}
public void addDisplayCutout() {
- mPolicy.mEmulateDisplayCutout = true;
+ mHasDisplayCutout = true;
+ updateDisplayFrames();
}
/** Asserts that {@code actual} is inset by the given amounts from the full display rect. */