diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 4469fdbb12ecb..8e2efe3f2b383 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -103,6 +103,14 @@ public final class Display { private int mCachedAppWidthCompat; private int mCachedAppHeightCompat; + /** + * Indicates that the application is started in a different rotation than the real display, so + * the display information may be adjusted. That ensures the methods {@link #getRotation}, + * {@link #getRealSize}, {@link #getRealMetrics}, and {@link #getCutout} are consistent with how + * the application window is laid out. + */ + private boolean mMayAdjustByFixedRotation; + /** * The default Display id, which is the id of the primary display assuming there is one. */ @@ -804,7 +812,9 @@ public final class Display { public int getRotation() { synchronized (this) { updateDisplayInfoLocked(); - return mDisplayInfo.rotation; + return mMayAdjustByFixedRotation + ? getDisplayAdjustments().getRotation(mDisplayInfo.rotation) + : mDisplayInfo.rotation; } } @@ -828,7 +838,9 @@ public final class Display { public DisplayCutout getCutout() { synchronized (this) { updateDisplayInfoLocked(); - return mDisplayInfo.displayCutout; + return mMayAdjustByFixedRotation + ? getDisplayAdjustments().getDisplayCutout(mDisplayInfo.displayCutout) + : mDisplayInfo.displayCutout; } } @@ -1140,6 +1152,9 @@ public final class Display { updateDisplayInfoLocked(); outSize.x = mDisplayInfo.logicalWidth; outSize.y = mDisplayInfo.logicalHeight; + if (mMayAdjustByFixedRotation) { + getDisplayAdjustments().adjustSize(outSize, mDisplayInfo.rotation); + } } } @@ -1159,6 +1174,9 @@ public final class Display { updateDisplayInfoLocked(); mDisplayInfo.getLogicalMetrics(outMetrics, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + if (mMayAdjustByFixedRotation) { + getDisplayAdjustments().adjustMetrics(outMetrics, mDisplayInfo.rotation); + } } } @@ -1225,6 +1243,11 @@ public final class Display { } } } + + // TODO(b/147213487): Replace the condition with per-resources state. + mMayAdjustByFixedRotation = mIsValid && mResources != null + && mResources.getConfiguration().windowConfiguration.getRotation() + != mDisplayInfo.rotation; } private void updateCachedAppSizeIfNeededLocked() { @@ -1243,9 +1266,12 @@ public final class Display { public String toString() { synchronized (this) { updateDisplayInfoLocked(); - mDisplayInfo.getAppMetrics(mTempMetrics, getDisplayAdjustments()); + final DisplayAdjustments adjustments = getDisplayAdjustments(); + mDisplayInfo.getAppMetrics(mTempMetrics, adjustments); return "Display id " + mDisplayId + ": " + mDisplayInfo - + ", " + mTempMetrics + ", isValid=" + mIsValid; + + (mMayAdjustByFixedRotation + ? (", " + adjustments.getFixedRotationAdjustments() + ", ") : ", ") + + mTempMetrics + ", isValid=" + mIsValid; } } diff --git a/core/java/android/view/DisplayAdjustments.java b/core/java/android/view/DisplayAdjustments.java index 27c2d5c5cdc3a..c726bee9f402d 100644 --- a/core/java/android/view/DisplayAdjustments.java +++ b/core/java/android/view/DisplayAdjustments.java @@ -21,6 +21,10 @@ import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; +import android.graphics.Point; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.DisplayMetrics; import java.util.Objects; @@ -30,6 +34,7 @@ public class DisplayAdjustments { private volatile CompatibilityInfo mCompatInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; private final Configuration mConfiguration = new Configuration(Configuration.EMPTY); + private FixedRotationAdjustments mFixedRotationAdjustments; @UnsupportedAppUsage public DisplayAdjustments() { @@ -44,6 +49,7 @@ public class DisplayAdjustments { public DisplayAdjustments(@NonNull DisplayAdjustments daj) { setCompatibilityInfo(daj.mCompatInfo); mConfiguration.setTo(daj.getConfiguration()); + mFixedRotationAdjustments = daj.mFixedRotationAdjustments; } @UnsupportedAppUsage @@ -84,11 +90,78 @@ public class DisplayAdjustments { return mConfiguration; } + public void setFixedRotationAdjustments(FixedRotationAdjustments fixedRotationAdjustments) { + mFixedRotationAdjustments = fixedRotationAdjustments; + } + + public FixedRotationAdjustments getFixedRotationAdjustments() { + return mFixedRotationAdjustments; + } + + /** Returns {@code false} if the width and height of display should swap. */ + private boolean noFlip(@Surface.Rotation int realRotation) { + final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments; + if (rotationAdjustments == null) { + return true; + } + // Check if the delta is rotated by 90 degrees. + return (realRotation - rotationAdjustments.mRotation + 4) % 2 == 0; + } + + /** Adjusts the given size if possible. */ + public void adjustSize(@NonNull Point size, @Surface.Rotation int realRotation) { + if (noFlip(realRotation)) { + return; + } + final int w = size.x; + size.x = size.y; + size.y = w; + } + + /** Adjusts the given metrics if possible. */ + public void adjustMetrics(@NonNull DisplayMetrics metrics, @Surface.Rotation int realRotation) { + if (noFlip(realRotation)) { + return; + } + int w = metrics.widthPixels; + metrics.widthPixels = metrics.heightPixels; + metrics.heightPixels = w; + + w = metrics.noncompatWidthPixels; + metrics.noncompatWidthPixels = metrics.noncompatHeightPixels; + metrics.noncompatHeightPixels = w; + + float x = metrics.xdpi; + metrics.xdpi = metrics.ydpi; + metrics.ydpi = x; + + x = metrics.noncompatXdpi; + metrics.noncompatXdpi = metrics.noncompatYdpi; + metrics.noncompatYdpi = x; + } + + /** Returns the adjusted cutout if available. Otherwise the original cutout is returned. */ + @Nullable + public DisplayCutout getDisplayCutout(@Nullable DisplayCutout realCutout) { + final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments; + return rotationAdjustments != null && rotationAdjustments.mRotatedDisplayCutout != null + ? rotationAdjustments.mRotatedDisplayCutout + : realCutout; + } + + /** Returns the adjusted rotation if available. Otherwise the original rotation is returned. */ + @Surface.Rotation + public int getRotation(@Surface.Rotation int realRotation) { + final FixedRotationAdjustments rotationAdjustments = mFixedRotationAdjustments; + return rotationAdjustments != null ? rotationAdjustments.mRotation : realRotation; + } + @Override public int hashCode() { int hash = 17; hash = hash * 31 + Objects.hashCode(mCompatInfo); hash = hash * 31 + Objects.hashCode(mConfiguration); + hash = hash * 31 + Objects.hashCode(mFixedRotationAdjustments); return hash; } @@ -98,7 +171,82 @@ public class DisplayAdjustments { return false; } DisplayAdjustments daj = (DisplayAdjustments)o; - return Objects.equals(daj.mCompatInfo, mCompatInfo) && - Objects.equals(daj.mConfiguration, mConfiguration); + return Objects.equals(daj.mCompatInfo, mCompatInfo) + && Objects.equals(daj.mConfiguration, mConfiguration) + && Objects.equals(daj.mFixedRotationAdjustments, mFixedRotationAdjustments); + } + + /** + * An application can be launched in different rotation than the real display. This class + * provides the information to adjust the values returned by {@link #Display}. + * @hide + */ + public static class FixedRotationAdjustments implements Parcelable { + /** The application-based rotation. */ + @Surface.Rotation + final int mRotation; + + /** Non-null if the device has cutout. */ + @Nullable + final DisplayCutout mRotatedDisplayCutout; + + public FixedRotationAdjustments(@Surface.Rotation int rotation, DisplayCutout cutout) { + mRotation = rotation; + mRotatedDisplayCutout = cutout; + } + + @Override + public int hashCode() { + int hash = 17; + hash = hash * 31 + mRotation; + hash = hash * 31 + Objects.hashCode(mRotatedDisplayCutout); + return hash; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof FixedRotationAdjustments)) { + return false; + } + final FixedRotationAdjustments other = (FixedRotationAdjustments) o; + return mRotation == other.mRotation + && Objects.equals(mRotatedDisplayCutout, other.mRotatedDisplayCutout); + } + + @Override + public String toString() { + return "FixedRotationAdjustments{rotation=" + Surface.rotationToString(mRotation) + + " cutout=" + mRotatedDisplayCutout + "}"; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mRotation); + dest.writeTypedObject( + new DisplayCutout.ParcelableWrapper(mRotatedDisplayCutout), flags); + } + + private FixedRotationAdjustments(Parcel in) { + mRotation = in.readInt(); + final DisplayCutout.ParcelableWrapper cutoutWrapper = + in.readTypedObject(DisplayCutout.ParcelableWrapper.CREATOR); + mRotatedDisplayCutout = cutoutWrapper != null ? cutoutWrapper.get() : null; + } + + public static final Creator CREATOR = + new Creator() { + public FixedRotationAdjustments createFromParcel(Parcel in) { + return new FixedRotationAdjustments(in); + } + + public FixedRotationAdjustments[] newArray(int size) { + return new FixedRotationAdjustments[size]; + } + }; } } diff --git a/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java b/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java index afbf8db3cd2d9..2fc42e91a8cc8 100644 --- a/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java +++ b/core/tests/coretests/src/android/view/DisplayAdjustmentsTests.java @@ -19,6 +19,9 @@ package android.view; import static org.junit.Assert.assertEquals; import android.content.res.Configuration; +import android.graphics.Point; +import android.util.DisplayMetrics; +import android.view.DisplayAdjustments.FixedRotationAdjustments; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -67,4 +70,38 @@ public class DisplayAdjustmentsTests { assertEquals(configuration, newAdjustments.getConfiguration()); } + + @Test + public void testFixedRotationAdjustments() { + final DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments(); + final int realRotation = Surface.ROTATION_0; + final int fixedRotation = Surface.ROTATION_90; + + mDisplayAdjustments.setFixedRotationAdjustments( + new FixedRotationAdjustments(fixedRotation, null /* cutout */)); + + final int w = 1000; + final int h = 2000; + final Point size = new Point(w, h); + mDisplayAdjustments.adjustSize(size, realRotation); + + assertEquals(fixedRotation, mDisplayAdjustments.getRotation(realRotation)); + assertEquals(new Point(h, w), size); + + final DisplayMetrics metrics = new DisplayMetrics(); + metrics.xdpi = metrics.noncompatXdpi = w; + metrics.widthPixels = metrics.noncompatWidthPixels = w; + metrics.ydpi = metrics.noncompatYdpi = h; + metrics.heightPixels = metrics.noncompatHeightPixels = h; + + final DisplayMetrics flippedMetrics = new DisplayMetrics(); + flippedMetrics.xdpi = flippedMetrics.noncompatXdpi = h; + flippedMetrics.widthPixels = flippedMetrics.noncompatWidthPixels = h; + flippedMetrics.ydpi = flippedMetrics.noncompatYdpi = w; + flippedMetrics.heightPixels = flippedMetrics.noncompatHeightPixels = w; + + mDisplayAdjustments.adjustMetrics(metrics, realRotation); + + assertEquals(flippedMetrics, metrics); + } }