| /* |
| * Copyright (C) 2022 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 android.view; |
| |
| import static android.view.Surface.ROTATION_0; |
| |
| import android.annotation.Nullable; |
| import android.annotation.TestApi; |
| import android.content.res.Resources; |
| import android.content.res.TypedArray; |
| import android.graphics.Matrix; |
| import android.graphics.Path; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.util.DisplayUtils; |
| import android.util.PathParser; |
| import android.util.RotationUtils; |
| |
| import androidx.annotation.NonNull; |
| |
| import com.android.internal.R; |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| import java.util.Objects; |
| |
| /** |
| * A class representing the shape of a display. It provides a {@link Path} of the display shape of |
| * the display shape. |
| * |
| * {@link DisplayShape} is immutable. |
| */ |
| public final class DisplayShape implements Parcelable { |
| |
| /** @hide */ |
| public static final DisplayShape NONE = new DisplayShape("" /* displayShapeSpec */, |
| 0 /* displayWidth */, 0 /* displayHeight */, 0 /* physicalPixelDisplaySizeRatio */, |
| 0 /* rotation */); |
| |
| /** @hide */ |
| @VisibleForTesting |
| public final String mDisplayShapeSpec; |
| private final float mPhysicalPixelDisplaySizeRatio; |
| private final int mDisplayWidth; |
| private final int mDisplayHeight; |
| private final int mRotation; |
| private final int mOffsetX; |
| private final int mOffsetY; |
| private final float mScale; |
| |
| private DisplayShape(@NonNull String displayShapeSpec, int displayWidth, int displayHeight, |
| float physicalPixelDisplaySizeRatio, int rotation) { |
| this(displayShapeSpec, displayWidth, displayHeight, physicalPixelDisplaySizeRatio, |
| rotation, 0, 0, 1f); |
| } |
| |
| private DisplayShape(@NonNull String displayShapeSpec, int displayWidth, int displayHeight, |
| float physicalPixelDisplaySizeRatio, int rotation, int offsetX, int offsetY, |
| float scale) { |
| mDisplayShapeSpec = displayShapeSpec; |
| mDisplayWidth = displayWidth; |
| mDisplayHeight = displayHeight; |
| mPhysicalPixelDisplaySizeRatio = physicalPixelDisplaySizeRatio; |
| mRotation = rotation; |
| mOffsetX = offsetX; |
| mOffsetY = offsetY; |
| mScale = scale; |
| } |
| |
| /** |
| * @hide |
| */ |
| @NonNull |
| public static DisplayShape fromResources( |
| @NonNull Resources res, @NonNull String displayUniqueId, int physicalDisplayWidth, |
| int physicalDisplayHeight, int displayWidth, int displayHeight) { |
| final boolean isScreenRound = RoundedCorners.getBuiltInDisplayIsRound(res, displayUniqueId); |
| final String spec = getSpecString(res, displayUniqueId); |
| if (spec == null || spec.isEmpty()) { |
| return createDefaultDisplayShape(displayWidth, displayHeight, isScreenRound); |
| } |
| final float physicalPixelDisplaySizeRatio = DisplayUtils.getPhysicalPixelDisplaySizeRatio( |
| physicalDisplayWidth, physicalDisplayHeight, displayWidth, displayHeight); |
| return fromSpecString(spec, physicalPixelDisplaySizeRatio, displayWidth, displayHeight); |
| } |
| |
| /** |
| * @hide |
| */ |
| @NonNull |
| public static DisplayShape createDefaultDisplayShape( |
| int displayWidth, int displayHeight, boolean isScreenRound) { |
| return fromSpecString(createDefaultSpecString(displayWidth, displayHeight, isScreenRound), |
| 1f, displayWidth, displayHeight); |
| } |
| |
| /** |
| * @hide |
| */ |
| @TestApi |
| @NonNull |
| public static DisplayShape fromSpecString(@NonNull String spec, |
| float physicalPixelDisplaySizeRatio, int displayWidth, int displayHeight) { |
| return Cache.getDisplayShape(spec, physicalPixelDisplaySizeRatio, displayWidth, |
| displayHeight); |
| } |
| |
| private static String createDefaultSpecString(int displayWidth, int displayHeight, |
| boolean isCircular) { |
| final String spec; |
| if (isCircular) { |
| final float xRadius = displayWidth / 2f; |
| final float yRadius = displayHeight / 2f; |
| // Draw a circular display shape. |
| spec = "M0," + yRadius |
| // Draw upper half circle with arcTo command. |
| + " A" + xRadius + "," + yRadius + " 0 1,1 " + displayWidth + "," + yRadius |
| // Draw lower half circle with arcTo command. |
| + " A" + xRadius + "," + yRadius + " 0 1,1 0," + yRadius + " Z"; |
| } else { |
| // Draw a rectangular display shape. |
| spec = "M0,0" |
| // Draw top edge. |
| + " L" + displayWidth + ",0" |
| // Draw right edge. |
| + " L" + displayWidth + "," + displayHeight |
| // Draw bottom edge. |
| + " L0," + displayHeight |
| // Draw left edge by close command which draws a line from current position to |
| // the initial points (0,0). |
| + " Z"; |
| } |
| return spec; |
| } |
| |
| /** |
| * Gets the display shape svg spec string of a display which is determined by the given display |
| * unique id. |
| * |
| * Loads the default config {@link R.string#config_mainDisplayShape} if |
| * {@link R.array#config_displayUniqueIdArray} is not set. |
| * |
| * @hide |
| */ |
| public static String getSpecString(Resources res, String displayUniqueId) { |
| final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); |
| final TypedArray array = res.obtainTypedArray(R.array.config_displayShapeArray); |
| final String spec; |
| if (index >= 0 && index < array.length()) { |
| spec = array.getString(index); |
| } else { |
| spec = res.getString(R.string.config_mainDisplayShape); |
| } |
| array.recycle(); |
| return spec; |
| } |
| |
| /** |
| * @hide |
| */ |
| public DisplayShape setRotation(int rotation) { |
| return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight, |
| mPhysicalPixelDisplaySizeRatio, rotation, mOffsetX, mOffsetY, mScale); |
| } |
| |
| /** |
| * @hide |
| */ |
| public DisplayShape setOffset(int offsetX, int offsetY) { |
| return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight, |
| mPhysicalPixelDisplaySizeRatio, mRotation, offsetX, offsetY, mScale); |
| } |
| |
| /** |
| * @hide |
| */ |
| public DisplayShape setScale(float scale) { |
| return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight, |
| mPhysicalPixelDisplaySizeRatio, mRotation, mOffsetX, mOffsetY, scale); |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight, |
| mPhysicalPixelDisplaySizeRatio, mRotation, mOffsetX, mOffsetY, mScale); |
| } |
| |
| @Override |
| public boolean equals(@Nullable Object o) { |
| if (o == this) { |
| return true; |
| } |
| if (o instanceof DisplayShape) { |
| DisplayShape ds = (DisplayShape) o; |
| return Objects.equals(mDisplayShapeSpec, ds.mDisplayShapeSpec) |
| && mDisplayWidth == ds.mDisplayWidth && mDisplayHeight == ds.mDisplayHeight |
| && mPhysicalPixelDisplaySizeRatio == ds.mPhysicalPixelDisplaySizeRatio |
| && mRotation == ds.mRotation && mOffsetX == ds.mOffsetX |
| && mOffsetY == ds.mOffsetY && mScale == ds.mScale; |
| } |
| return false; |
| } |
| |
| @Override |
| public String toString() { |
| return "DisplayShape{" |
| + " spec=" + mDisplayShapeSpec.hashCode() |
| + " displayWidth=" + mDisplayWidth |
| + " displayHeight=" + mDisplayHeight |
| + " physicalPixelDisplaySizeRatio=" + mPhysicalPixelDisplaySizeRatio |
| + " rotation=" + mRotation |
| + " offsetX=" + mOffsetX |
| + " offsetY=" + mOffsetY |
| + " scale=" + mScale + "}"; |
| } |
| |
| /** |
| * Returns a {@link Path} of the display shape. |
| * |
| * @return a {@link Path} of the display shape. |
| */ |
| @NonNull |
| public Path getPath() { |
| return Cache.getPath(this); |
| } |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| @Override |
| public void writeToParcel(@NonNull Parcel dest, int flags) { |
| dest.writeString8(mDisplayShapeSpec); |
| dest.writeInt(mDisplayWidth); |
| dest.writeInt(mDisplayHeight); |
| dest.writeFloat(mPhysicalPixelDisplaySizeRatio); |
| dest.writeInt(mRotation); |
| dest.writeInt(mOffsetX); |
| dest.writeInt(mOffsetY); |
| dest.writeFloat(mScale); |
| } |
| |
| public static final @NonNull Creator<DisplayShape> CREATOR = new Creator<DisplayShape>() { |
| @Override |
| public DisplayShape createFromParcel(Parcel in) { |
| final String spec = in.readString8(); |
| final int displayWidth = in.readInt(); |
| final int displayHeight = in.readInt(); |
| final float ratio = in.readFloat(); |
| final int rotation = in.readInt(); |
| final int offsetX = in.readInt(); |
| final int offsetY = in.readInt(); |
| final float scale = in.readFloat(); |
| return new DisplayShape(spec, displayWidth, displayHeight, ratio, rotation, offsetX, |
| offsetY, scale); |
| } |
| |
| @Override |
| public DisplayShape[] newArray(int size) { |
| return new DisplayShape[size]; |
| } |
| }; |
| |
| private static final class Cache { |
| private static final Object CACHE_LOCK = new Object(); |
| |
| @GuardedBy("CACHE_LOCK") |
| private static String sCachedSpec; |
| @GuardedBy("CACHE_LOCK") |
| private static int sCachedDisplayWidth; |
| @GuardedBy("CACHE_LOCK") |
| private static int sCachedDisplayHeight; |
| @GuardedBy("CACHE_LOCK") |
| private static float sCachedPhysicalPixelDisplaySizeRatio; |
| @GuardedBy("CACHE_LOCK") |
| private static DisplayShape sCachedDisplayShape; |
| |
| @GuardedBy("CACHE_LOCK") |
| private static DisplayShape sCacheForPath; |
| @GuardedBy("CACHE_LOCK") |
| private static Path sCachedPath; |
| |
| static DisplayShape getDisplayShape(String spec, float physicalPixelDisplaySizeRatio, |
| int displayWidth, int displayHeight) { |
| synchronized (CACHE_LOCK) { |
| if (spec.equals(sCachedSpec) |
| && sCachedDisplayWidth == displayWidth |
| && sCachedDisplayHeight == displayHeight |
| && sCachedPhysicalPixelDisplaySizeRatio == physicalPixelDisplaySizeRatio) { |
| return sCachedDisplayShape; |
| } |
| } |
| |
| final DisplayShape shape = new DisplayShape(spec, displayWidth, displayHeight, |
| physicalPixelDisplaySizeRatio, ROTATION_0); |
| |
| synchronized (CACHE_LOCK) { |
| sCachedSpec = spec; |
| sCachedDisplayWidth = displayWidth; |
| sCachedDisplayHeight = displayHeight; |
| sCachedPhysicalPixelDisplaySizeRatio = physicalPixelDisplaySizeRatio; |
| sCachedDisplayShape = shape; |
| } |
| return shape; |
| } |
| |
| static Path getPath(@NonNull DisplayShape shape) { |
| synchronized (CACHE_LOCK) { |
| if (shape.equals(sCacheForPath)) { |
| return sCachedPath; |
| } |
| } |
| |
| final Path path = PathParser.createPathFromPathData(shape.mDisplayShapeSpec); |
| |
| if (!path.isEmpty()) { |
| final Matrix matrix = new Matrix(); |
| if (shape.mRotation != ROTATION_0) { |
| RotationUtils.transformPhysicalToLogicalCoordinates( |
| shape.mRotation, shape.mDisplayWidth, shape.mDisplayHeight, matrix); |
| } |
| if (shape.mPhysicalPixelDisplaySizeRatio != 1f) { |
| matrix.preScale(shape.mPhysicalPixelDisplaySizeRatio, |
| shape.mPhysicalPixelDisplaySizeRatio); |
| } |
| if (shape.mOffsetX != 0 || shape.mOffsetY != 0) { |
| matrix.postTranslate(shape.mOffsetX, shape.mOffsetY); |
| } |
| if (shape.mScale != 1f) { |
| matrix.postScale(shape.mScale, shape.mScale); |
| } |
| path.transform(matrix); |
| } |
| |
| synchronized (CACHE_LOCK) { |
| sCacheForPath = shape; |
| sCachedPath = path; |
| } |
| return path; |
| } |
| } |
| } |