diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java index 3dcfd00bc34c8..1ef5f0950b16f 100644 --- a/core/java/android/view/DisplayCutout.java +++ b/core/java/android/view/DisplayCutout.java @@ -153,7 +153,7 @@ public final class DisplayCutout { @Override public String toString() { return "DisplayCutout{insets=" + mSafeInsets - + " bounds=" + mBounds + + " boundingRect=" + getBoundingRect() + "}"; } @@ -279,9 +279,7 @@ public final class DisplayCutout { * @hide */ public static DisplayCutout fromBoundingPolygon(List points) { - Region bounds = Region.obtain(); Path path = new Path(); - path.reset(); for (int i = 0; i < points.size(); i++) { Point point = points.get(i); @@ -292,14 +290,24 @@ public final class DisplayCutout { } } path.close(); + return fromBounds(path); + } + /** + * Creates an instance from a bounding {@link Path}. + * + * @hide + */ + public static DisplayCutout fromBounds(Path path) { RectF clipRect = new RectF(); path.computeBounds(clipRect, false /* unused */); Region clipRegion = Region.obtain(); clipRegion.set((int) clipRect.left, (int) clipRect.top, (int) clipRect.right, (int) clipRect.bottom); + Region bounds = new Region(); bounds.setPath(path, clipRegion); + clipRegion.recycle(); return new DisplayCutout(ZERO_RECT, bounds); } @@ -329,12 +337,23 @@ public final class DisplayCutout { @Override public void writeToParcel(Parcel out, int flags) { - if (mInner == NO_CUTOUT) { + writeCutoutToParcel(mInner, out, flags); + } + + /** + * Writes a DisplayCutout to a {@link Parcel}. + * + * @see #readCutoutFromParcel(Parcel) + */ + public static void writeCutoutToParcel(DisplayCutout cutout, Parcel out, int flags) { + if (cutout == null) { + out.writeInt(-1); + } else if (cutout == NO_CUTOUT) { out.writeInt(0); } else { out.writeInt(1); - out.writeTypedObject(mInner.mSafeInsets, flags); - out.writeTypedObject(mInner.mBounds, flags); + out.writeTypedObject(cutout.mSafeInsets, flags); + out.writeTypedObject(cutout.mBounds, flags); } } @@ -345,13 +364,13 @@ public final class DisplayCutout { * Needed for AIDL out parameters. */ public void readFromParcel(Parcel in) { - mInner = readCutout(in); + mInner = readCutoutFromParcel(in); } public static final Creator CREATOR = new Creator() { @Override public ParcelableWrapper createFromParcel(Parcel in) { - return new ParcelableWrapper(readCutout(in)); + return new ParcelableWrapper(readCutoutFromParcel(in)); } @Override @@ -360,8 +379,17 @@ public final class DisplayCutout { } }; - private static DisplayCutout readCutout(Parcel in) { - if (in.readInt() == 0) { + /** + * Reads a DisplayCutout from a {@link Parcel}. + * + * @see #writeCutoutToParcel(DisplayCutout, Parcel, int) + */ + public static DisplayCutout readCutoutFromParcel(Parcel in) { + int variant = in.readInt(); + if (variant == -1) { + return null; + } + if (variant == 0) { return NO_CUTOUT; } diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index b813ddb63859e..37e9815c93c58 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -148,6 +148,13 @@ public final class DisplayInfo implements Parcelable { */ public int overscanBottom; + /** + * The {@link DisplayCutout} if present, otherwise {@code null}. + * + * @hide + */ + public DisplayCutout displayCutout; + /** * The rotation of the display relative to its natural orientation. * May be one of {@link android.view.Surface#ROTATION_0}, @@ -301,6 +308,7 @@ public final class DisplayInfo implements Parcelable { && overscanTop == other.overscanTop && overscanRight == other.overscanRight && overscanBottom == other.overscanBottom + && Objects.equal(displayCutout, other.displayCutout) && rotation == other.rotation && modeId == other.modeId && defaultModeId == other.defaultModeId @@ -342,6 +350,7 @@ public final class DisplayInfo implements Parcelable { overscanTop = other.overscanTop; overscanRight = other.overscanRight; overscanBottom = other.overscanBottom; + displayCutout = other.displayCutout; rotation = other.rotation; modeId = other.modeId; defaultModeId = other.defaultModeId; @@ -379,6 +388,7 @@ public final class DisplayInfo implements Parcelable { overscanTop = source.readInt(); overscanRight = source.readInt(); overscanBottom = source.readInt(); + displayCutout = DisplayCutout.ParcelableWrapper.readCutoutFromParcel(source); rotation = source.readInt(); modeId = source.readInt(); defaultModeId = source.readInt(); @@ -425,6 +435,7 @@ public final class DisplayInfo implements Parcelable { dest.writeInt(overscanTop); dest.writeInt(overscanRight); dest.writeInt(overscanBottom); + DisplayCutout.ParcelableWrapper.writeCutoutToParcel(displayCutout, dest, flags); dest.writeInt(rotation); dest.writeInt(modeId); dest.writeInt(defaultModeId); diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index d2685cfb5a8fd..2440e9b431168 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2769,6 +2769,13 @@ some existing device-specific resource overlays. --> @bool/config_windowIsRound + + + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d2ad99b4b744e..ac3d4020a3b05 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3200,6 +3200,7 @@ + diff --git a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java index 6aa465ce9f8c7..a1022604c27f7 100644 --- a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java +++ b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java @@ -24,6 +24,7 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.Point; +import android.graphics.PorterDuff; import android.graphics.Region; import android.os.Handler; import android.os.Looper; @@ -114,10 +115,9 @@ public class EmulatedDisplayCutout extends SystemUI { @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { + mBounds.reset(); if (insets.getDisplayCutout() != null) { insets.getDisplayCutout().getBounds().getBoundaryPath(mBounds); - } else { - mBounds.reset(); } invalidate(); return insets.consumeDisplayCutout(); @@ -126,7 +126,7 @@ public class EmulatedDisplayCutout extends SystemUI { @Override protected void onDraw(Canvas canvas) { if (!mBounds.isEmpty()) { - mPaint.setColor(Color.DKGRAY); + mPaint.setColor(Color.BLACK); mPaint.setStyle(Paint.Style.FILL); canvas.drawPath(mBounds, mPaint); diff --git a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java index b15b79fb984ef..6f7a270799bc0 100644 --- a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java +++ b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java @@ -163,6 +163,7 @@ public class RoundedCorners extends SystemUI implements Tunable { | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; lp.setTitle("RoundedOverlay"); lp.gravity = Gravity.TOP; + lp.flags2 |= WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; return lp; } diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index fddb81ba2af7f..6db3b44c17b28 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -19,6 +19,7 @@ package com.android.server.display; import android.hardware.display.DisplayViewport; import android.util.DisplayMetrics; import android.view.Display; +import android.view.DisplayCutout; import android.view.Surface; import java.util.Arrays; @@ -228,6 +229,11 @@ final class DisplayDeviceInfo { */ public int flags; + /** + * The {@link DisplayCutout} if present or {@code null} otherwise. + */ + public DisplayCutout displayCutout; + /** * The touch attachment, per {@link DisplayViewport#touch}. */ @@ -321,6 +327,7 @@ final class DisplayDeviceInfo { || appVsyncOffsetNanos != other.appVsyncOffsetNanos || presentationDeadlineNanos != other.presentationDeadlineNanos || flags != other.flags + || !Objects.equal(displayCutout, other.displayCutout) || touch != other.touch || rotation != other.rotation || type != other.type @@ -354,6 +361,7 @@ final class DisplayDeviceInfo { appVsyncOffsetNanos = other.appVsyncOffsetNanos; presentationDeadlineNanos = other.presentationDeadlineNanos; flags = other.flags; + displayCutout = other.displayCutout; touch = other.touch; rotation = other.rotation; type = other.type; @@ -380,6 +388,9 @@ final class DisplayDeviceInfo { sb.append(", ").append(xDpi).append(" x ").append(yDpi).append(" dpi"); sb.append(", appVsyncOff ").append(appVsyncOffsetNanos); sb.append(", presDeadline ").append(presentationDeadlineNanos); + if (displayCutout != null) { + sb.append(", cutout ").append(displayCutout); + } sb.append(", touch ").append(touchToString(touch)); sb.append(", rotation ").append(rotation); sb.append(", type ").append(Display.typeToString(type)); diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index eb9ff589d5f68..483b02c2bf65a 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -30,9 +30,12 @@ import android.os.Looper; import android.os.PowerManager; import android.os.SystemProperties; import android.os.Trace; +import android.text.TextUtils; +import android.util.PathParser; import android.util.Slog; import android.util.SparseArray; import android.view.Display; +import android.view.DisplayCutout; import android.view.DisplayEventReceiver; import android.view.Surface; import android.view.SurfaceControl; @@ -400,12 +403,14 @@ final class LocalDisplayAdapter extends DisplayAdapter { && SystemProperties.getBoolean(PROPERTY_EMULATOR_CIRCULAR, false))) { mInfo.flags |= DisplayDeviceInfo.FLAG_ROUND; } + mInfo.displayCutout = parseDefaultDisplayCutout(res); mInfo.type = Display.TYPE_BUILT_IN; mInfo.densityDpi = (int)(phys.density * 160 + 0.5f); mInfo.xDpi = phys.xDpi; mInfo.yDpi = phys.yDpi; mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL; } else { + mInfo.displayCutout = null; mInfo.type = Display.TYPE_HDMI; mInfo.flags |= DisplayDeviceInfo.FLAG_PRESENTATION; mInfo.name = getContext().getResources().getString( @@ -434,6 +439,15 @@ final class LocalDisplayAdapter extends DisplayAdapter { return mInfo; } + private DisplayCutout parseDefaultDisplayCutout(Resources res) { + String cutoutString = res.getString( + com.android.internal.R.string.config_mainBuiltInDisplayCutout); + if (TextUtils.isEmpty(cutoutString)) { + return null; + } + return DisplayCutout.fromBounds(PathParser.createPathFromPathData(cutoutString)); + } + @Override public Runnable requestDisplayStateLocked(final int state, final int brightness) { // Assume that the brightness is off if the display is being turned off. diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 78a540790db64..132f083c1adc3 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -145,6 +145,7 @@ final class LogicalDisplay { mInfo.overscanRight = mOverrideDisplayInfo.overscanRight; mInfo.overscanBottom = mOverrideDisplayInfo.overscanBottom; mInfo.rotation = mOverrideDisplayInfo.rotation; + mInfo.displayCutout = mOverrideDisplayInfo.displayCutout; mInfo.logicalDensityDpi = mOverrideDisplayInfo.logicalDensityDpi; mInfo.physicalXDpi = mOverrideDisplayInfo.physicalXDpi; mInfo.physicalYDpi = mOverrideDisplayInfo.physicalYDpi; @@ -280,6 +281,7 @@ final class LogicalDisplay { mBaseDisplayInfo.largestNominalAppHeight = deviceInfo.height; mBaseDisplayInfo.ownerUid = deviceInfo.ownerUid; mBaseDisplayInfo.ownerPackageName = deviceInfo.ownerPackageName; + mBaseDisplayInfo.displayCutout = deviceInfo.displayCutout; mPrimaryDisplayDeviceInfo = deviceInfo; mInfo = null; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 252eff567cfd7..df468acce74c9 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -62,6 +62,7 @@ import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_A import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; +import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates; import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_UNOCCLUDE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT; @@ -121,6 +122,7 @@ import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Matrix; +import android.graphics.Path; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; @@ -137,6 +139,7 @@ import android.util.MutableBoolean; import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.Display; +import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.InputDevice; import android.view.MagnificationSpec; @@ -158,6 +161,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.function.Consumer; import java.util.function.Predicate; @@ -208,6 +212,9 @@ class DisplayContent extends WindowContainer 0) { + mDisplayCutoutSafe.left = mRestrictedOverscan.left + c.getSafeInsetLeft(); + } + if (c.getSafeInsetTop() > 0) { + mDisplayCutoutSafe.top = mRestrictedOverscan.top + c.getSafeInsetTop(); + } + if (c.getSafeInsetRight() > 0) { + mDisplayCutoutSafe.right = mRestrictedOverscan.right - c.getSafeInsetRight(); + } + if (c.getSafeInsetBottom() > 0) { + mDisplayCutoutSafe.bottom = mRestrictedOverscan.bottom - c.getSafeInsetBottom(); + } + } } public int getInputMethodWindowVisibleHeight() { @@ -194,8 +217,7 @@ public class DisplayFrames { 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( @@ -203,8 +225,7 @@ public class DisplayFrames { 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( @@ -212,8 +233,7 @@ public class DisplayFrames { 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( @@ -221,8 +241,7 @@ public class DisplayFrames { new Point((screenWidth - widthBottom) / 2, height), new Point((screenWidth + widthBottom) / 2, height), new Point((screenWidth + widthTop) / 2, 0) - )).calculateRelativeTo(mUnrestricted); - mDisplayCutoutSafe.top = height; + )); break; } } diff --git a/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java b/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java new file mode 100644 index 0000000000000..09d7b5de1caf9 --- /dev/null +++ b/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java @@ -0,0 +1,62 @@ +/* + * 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. + */ + +package com.android.server.wm.utils; + +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 android.annotation.Dimension; +import android.graphics.Matrix; +import android.view.Surface.Rotation; + +public class CoordinateTransforms { + + private CoordinateTransforms() { + } + + /** + * Sets a matrix such that given a rotation, it transforms physical display + * coordinates to that rotation's logical coordinates. + * + * @param rotation the rotation to which the matrix should transform + * @param out the matrix to be set + */ + public static void transformPhysicalToLogicalCoordinates(@Rotation int rotation, + @Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out) { + switch (rotation) { + case ROTATION_0: + out.reset(); + break; + case ROTATION_90: + out.setRotate(270); + out.postTranslate(0, physicalWidth); + break; + case ROTATION_180: + out.setRotate(180); + out.postTranslate(physicalWidth, physicalHeight); + break; + case ROTATION_270: + out.setRotate(90); + out.postTranslate(physicalHeight, 0); + break; + default: + throw new IllegalArgumentException("Unknown rotation: " + rotation); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java index 693264cbe4ee3..2284bbbbf3acd 100644 --- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java @@ -37,13 +37,18 @@ import org.junit.runner.RunWith; import android.annotation.SuppressLint; import android.content.res.Configuration; +import android.graphics.Path; +import android.graphics.Point; +import android.graphics.Rect; import android.os.SystemClock; import android.platform.test.annotations.Presubmit; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.util.DisplayMetrics; import android.util.SparseIntArray; +import android.view.DisplayCutout; import android.view.MotionEvent; +import android.view.Surface; import java.util.Arrays; import java.util.LinkedList; @@ -53,7 +58,7 @@ import java.util.List; * Tests for the {@link DisplayContent} class. * * Build/Install/Run: - * bit FrameworksServicesTests:com.android.server.wm.DisplayContentTests + * atest com.android.server.wm.DisplayContentTests */ @SmallTest @Presubmit @@ -384,6 +389,38 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(-1, orderedDisplayIds.indexOfValue(dc.getDisplayId())); } + @Test + public void testDisplayCutout_rot0() throws Exception { + synchronized (sWm.getWindowManagerLock()) { + final DisplayContent dc = createNewDisplay(); + dc.mInitialDisplayWidth = 200; + dc.mInitialDisplayHeight = 400; + final DisplayCutout cutout = createCutout(new Rect(80, 0, 120, 10)); + + dc.mInitialDisplayCutout = cutout; + dc.setRotation(Surface.ROTATION_0); + dc.computeScreenConfiguration(new Configuration()); // recomputes dc.mDisplayInfo. + + assertEquals(cutout, dc.getDisplayInfo().displayCutout); + } + } + + @Test + public void testDisplayCutout_rot90() throws Exception { + synchronized (sWm.getWindowManagerLock()) { + final DisplayContent dc = createNewDisplay(); + dc.mInitialDisplayWidth = 200; + dc.mInitialDisplayHeight = 400; + final DisplayCutout cutout = createCutout(new Rect(80, 0, 120, 10)); + + dc.mInitialDisplayCutout = cutout; + dc.setRotation(Surface.ROTATION_90); + dc.computeScreenConfiguration(new Configuration()); // recomputes dc.mDisplayInfo. + + assertEquals(createCutout(new Rect(0, 80, 10, 120)), dc.getDisplayInfo().displayCutout); + } + } + @Test @SuppressLint("InlinedApi") public void testOrientationDefinedByKeyguard() { @@ -449,4 +486,10 @@ public class DisplayContentTests extends WindowTestsBase { y, metaState); } + + private DisplayCutout createCutout(Rect r) { + Path p = new Path(); + p.addRect(r.left, r.top, r.right, r.bottom, Path.Direction.CCW); + return DisplayCutout.fromBounds(p); + } } diff --git a/services/tests/servicestests/src/com/android/server/wm/utils/CoordinateTransformsTest.java b/services/tests/servicestests/src/com/android/server/wm/utils/CoordinateTransformsTest.java new file mode 100644 index 0000000000000..40a10e04c893a --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/wm/utils/CoordinateTransformsTest.java @@ -0,0 +1,98 @@ +/* + * 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. + */ + +package com.android.server.wm.utils; + +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 com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.*; + +import android.graphics.Matrix; +import android.graphics.Point; +import android.graphics.PointF; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ErrorCollector; + +public class CoordinateTransformsTest { + + private static final int W = 200; + private static final int H = 400; + + private final Matrix mMatrix = new Matrix(); + + @Rule + public final ErrorCollector mErrorCollector = new ErrorCollector(); + + @Before + public void setUp() throws Exception { + mMatrix.setTranslate(0xdeadbeef, 0xdeadbeef); + } + + @Test + public void transformPhysicalToLogicalCoordinates_rot0() throws Exception { + transformPhysicalToLogicalCoordinates(ROTATION_0, W, H, mMatrix); + assertThat(mMatrix, is(Matrix.IDENTITY_MATRIX)); + } + + @Test + public void transformPhysicalToLogicalCoordinates_rot90() throws Exception { + transformPhysicalToLogicalCoordinates(ROTATION_90, W, H, mMatrix); + + checkDevicePoint(0, 0).mapsToLogicalPoint(0, W); + checkDevicePoint(W, H).mapsToLogicalPoint(H, 0); + } + + @Test + public void transformPhysicalToLogicalCoordinates_rot180() throws Exception { + transformPhysicalToLogicalCoordinates(ROTATION_180, W, H, mMatrix); + + checkDevicePoint(0, 0).mapsToLogicalPoint(W, H); + checkDevicePoint(W, H).mapsToLogicalPoint(0, 0); + } + + @Test + public void transformPhysicalToLogicalCoordinates_rot270() throws Exception { + transformPhysicalToLogicalCoordinates(ROTATION_270, W, H, mMatrix); + + checkDevicePoint(0, 0).mapsToLogicalPoint(H, 0); + checkDevicePoint(W, H).mapsToLogicalPoint(0, W); + } + + private DevicePointAssertable checkDevicePoint(int x, int y) { + final Point devicePoint = new Point(x, y); + final float[] fs = new float[] {x, y}; + mMatrix.mapPoints(fs); + final PointF transformedPoint = new PointF(fs[0], fs[1]); + + return (expectedX, expectedY) -> { + mErrorCollector.checkThat("t(" + devicePoint + ")", + transformedPoint, is(new PointF(expectedX, expectedY))); + }; + } + + public interface DevicePointAssertable { + void mapsToLogicalPoint(int x, int y); + } +} \ No newline at end of file