358 lines
13 KiB
Java
358 lines
13 KiB
Java
/*
|
|
* 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;
|
|
}
|
|
}
|
|
}
|