1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.view; 18 19 import static android.view.Surface.ROTATION_0; 20 21 import android.annotation.Nullable; 22 import android.annotation.TestApi; 23 import android.content.res.Resources; 24 import android.content.res.TypedArray; 25 import android.graphics.Matrix; 26 import android.graphics.Path; 27 import android.os.Parcel; 28 import android.os.Parcelable; 29 import android.util.DisplayUtils; 30 import android.util.PathParser; 31 import android.util.RotationUtils; 32 33 import androidx.annotation.NonNull; 34 35 import com.android.internal.R; 36 import com.android.internal.annotations.GuardedBy; 37 import com.android.internal.annotations.VisibleForTesting; 38 39 import java.util.Objects; 40 41 /** 42 * A class representing the shape of a display. It provides a {@link Path} of the display shape of 43 * the display shape. 44 * 45 * {@link DisplayShape} is immutable. 46 */ 47 public final class DisplayShape implements Parcelable { 48 49 /** @hide */ 50 public static final DisplayShape NONE = new DisplayShape("" /* displayShapeSpec */, 51 0 /* displayWidth */, 0 /* displayHeight */, 0 /* physicalPixelDisplaySizeRatio */, 52 0 /* rotation */); 53 54 /** @hide */ 55 @VisibleForTesting 56 public final String mDisplayShapeSpec; 57 private final float mPhysicalPixelDisplaySizeRatio; 58 private final int mDisplayWidth; 59 private final int mDisplayHeight; 60 private final int mRotation; 61 private final int mOffsetX; 62 private final int mOffsetY; 63 private final float mScale; 64 DisplayShape(@onNull String displayShapeSpec, int displayWidth, int displayHeight, float physicalPixelDisplaySizeRatio, int rotation)65 private DisplayShape(@NonNull String displayShapeSpec, int displayWidth, int displayHeight, 66 float physicalPixelDisplaySizeRatio, int rotation) { 67 this(displayShapeSpec, displayWidth, displayHeight, physicalPixelDisplaySizeRatio, 68 rotation, 0, 0, 1f); 69 } 70 DisplayShape(@onNull String displayShapeSpec, int displayWidth, int displayHeight, float physicalPixelDisplaySizeRatio, int rotation, int offsetX, int offsetY, float scale)71 private DisplayShape(@NonNull String displayShapeSpec, int displayWidth, int displayHeight, 72 float physicalPixelDisplaySizeRatio, int rotation, int offsetX, int offsetY, 73 float scale) { 74 mDisplayShapeSpec = displayShapeSpec; 75 mDisplayWidth = displayWidth; 76 mDisplayHeight = displayHeight; 77 mPhysicalPixelDisplaySizeRatio = physicalPixelDisplaySizeRatio; 78 mRotation = rotation; 79 mOffsetX = offsetX; 80 mOffsetY = offsetY; 81 mScale = scale; 82 } 83 84 /** 85 * @hide 86 */ 87 @NonNull fromResources( @onNull Resources res, @NonNull String displayUniqueId, int physicalDisplayWidth, int physicalDisplayHeight, int displayWidth, int displayHeight)88 public static DisplayShape fromResources( 89 @NonNull Resources res, @NonNull String displayUniqueId, int physicalDisplayWidth, 90 int physicalDisplayHeight, int displayWidth, int displayHeight) { 91 final boolean isScreenRound = RoundedCorners.getBuiltInDisplayIsRound(res, displayUniqueId); 92 final String spec = getSpecString(res, displayUniqueId); 93 if (spec == null || spec.isEmpty()) { 94 return createDefaultDisplayShape(displayWidth, displayHeight, isScreenRound); 95 } 96 final float physicalPixelDisplaySizeRatio = DisplayUtils.getPhysicalPixelDisplaySizeRatio( 97 physicalDisplayWidth, physicalDisplayHeight, displayWidth, displayHeight); 98 return fromSpecString(spec, physicalPixelDisplaySizeRatio, displayWidth, displayHeight); 99 } 100 101 /** 102 * @hide 103 */ 104 @NonNull createDefaultDisplayShape( int displayWidth, int displayHeight, boolean isScreenRound)105 public static DisplayShape createDefaultDisplayShape( 106 int displayWidth, int displayHeight, boolean isScreenRound) { 107 return fromSpecString(createDefaultSpecString(displayWidth, displayHeight, isScreenRound), 108 1f, displayWidth, displayHeight); 109 } 110 111 /** 112 * @hide 113 */ 114 @TestApi 115 @NonNull fromSpecString(@onNull String spec, float physicalPixelDisplaySizeRatio, int displayWidth, int displayHeight)116 public static DisplayShape fromSpecString(@NonNull String spec, 117 float physicalPixelDisplaySizeRatio, int displayWidth, int displayHeight) { 118 return Cache.getDisplayShape(spec, physicalPixelDisplaySizeRatio, displayWidth, 119 displayHeight); 120 } 121 createDefaultSpecString(int displayWidth, int displayHeight, boolean isCircular)122 private static String createDefaultSpecString(int displayWidth, int displayHeight, 123 boolean isCircular) { 124 final String spec; 125 if (isCircular) { 126 final float xRadius = displayWidth / 2f; 127 final float yRadius = displayHeight / 2f; 128 // Draw a circular display shape. 129 spec = "M0," + yRadius 130 // Draw upper half circle with arcTo command. 131 + " A" + xRadius + "," + yRadius + " 0 1,1 " + displayWidth + "," + yRadius 132 // Draw lower half circle with arcTo command. 133 + " A" + xRadius + "," + yRadius + " 0 1,1 0," + yRadius + " Z"; 134 } else { 135 // Draw a rectangular display shape. 136 spec = "M0,0" 137 // Draw top edge. 138 + " L" + displayWidth + ",0" 139 // Draw right edge. 140 + " L" + displayWidth + "," + displayHeight 141 // Draw bottom edge. 142 + " L0," + displayHeight 143 // Draw left edge by close command which draws a line from current position to 144 // the initial points (0,0). 145 + " Z"; 146 } 147 return spec; 148 } 149 150 /** 151 * Gets the display shape svg spec string of a display which is determined by the given display 152 * unique id. 153 * 154 * Loads the default config {@link R.string#config_mainDisplayShape} if 155 * {@link R.array#config_displayUniqueIdArray} is not set. 156 * 157 * @hide 158 */ getSpecString(Resources res, String displayUniqueId)159 public static String getSpecString(Resources res, String displayUniqueId) { 160 final int index = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId); 161 final TypedArray array = res.obtainTypedArray(R.array.config_displayShapeArray); 162 final String spec; 163 if (index >= 0 && index < array.length()) { 164 spec = array.getString(index); 165 } else { 166 spec = res.getString(R.string.config_mainDisplayShape); 167 } 168 array.recycle(); 169 return spec; 170 } 171 172 /** 173 * @hide 174 */ setRotation(int rotation)175 public DisplayShape setRotation(int rotation) { 176 return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight, 177 mPhysicalPixelDisplaySizeRatio, rotation, mOffsetX, mOffsetY, mScale); 178 } 179 180 /** 181 * @hide 182 */ setOffset(int offsetX, int offsetY)183 public DisplayShape setOffset(int offsetX, int offsetY) { 184 return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight, 185 mPhysicalPixelDisplaySizeRatio, mRotation, offsetX, offsetY, mScale); 186 } 187 188 /** 189 * @hide 190 */ setScale(float scale)191 public DisplayShape setScale(float scale) { 192 return new DisplayShape(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight, 193 mPhysicalPixelDisplaySizeRatio, mRotation, mOffsetX, mOffsetY, scale); 194 } 195 196 @Override hashCode()197 public int hashCode() { 198 return Objects.hash(mDisplayShapeSpec, mDisplayWidth, mDisplayHeight, 199 mPhysicalPixelDisplaySizeRatio, mRotation, mOffsetX, mOffsetY, mScale); 200 } 201 202 @Override equals(@ullable Object o)203 public boolean equals(@Nullable Object o) { 204 if (o == this) { 205 return true; 206 } 207 if (o instanceof DisplayShape) { 208 DisplayShape ds = (DisplayShape) o; 209 return Objects.equals(mDisplayShapeSpec, ds.mDisplayShapeSpec) 210 && mDisplayWidth == ds.mDisplayWidth && mDisplayHeight == ds.mDisplayHeight 211 && mPhysicalPixelDisplaySizeRatio == ds.mPhysicalPixelDisplaySizeRatio 212 && mRotation == ds.mRotation && mOffsetX == ds.mOffsetX 213 && mOffsetY == ds.mOffsetY && mScale == ds.mScale; 214 } 215 return false; 216 } 217 218 @Override toString()219 public String toString() { 220 return "DisplayShape{" 221 + " spec=" + mDisplayShapeSpec.hashCode() 222 + " displayWidth=" + mDisplayWidth 223 + " displayHeight=" + mDisplayHeight 224 + " physicalPixelDisplaySizeRatio=" + mPhysicalPixelDisplaySizeRatio 225 + " rotation=" + mRotation 226 + " offsetX=" + mOffsetX 227 + " offsetY=" + mOffsetY 228 + " scale=" + mScale + "}"; 229 } 230 231 /** 232 * Returns a {@link Path} of the display shape. 233 * 234 * @return a {@link Path} of the display shape. 235 */ 236 @NonNull getPath()237 public Path getPath() { 238 return Cache.getPath(this); 239 } 240 241 @Override describeContents()242 public int describeContents() { 243 return 0; 244 } 245 246 @Override writeToParcel(@onNull Parcel dest, int flags)247 public void writeToParcel(@NonNull Parcel dest, int flags) { 248 dest.writeString8(mDisplayShapeSpec); 249 dest.writeInt(mDisplayWidth); 250 dest.writeInt(mDisplayHeight); 251 dest.writeFloat(mPhysicalPixelDisplaySizeRatio); 252 dest.writeInt(mRotation); 253 dest.writeInt(mOffsetX); 254 dest.writeInt(mOffsetY); 255 dest.writeFloat(mScale); 256 } 257 258 public static final @NonNull Creator<DisplayShape> CREATOR = new Creator<DisplayShape>() { 259 @Override 260 public DisplayShape createFromParcel(Parcel in) { 261 final String spec = in.readString8(); 262 final int displayWidth = in.readInt(); 263 final int displayHeight = in.readInt(); 264 final float ratio = in.readFloat(); 265 final int rotation = in.readInt(); 266 final int offsetX = in.readInt(); 267 final int offsetY = in.readInt(); 268 final float scale = in.readFloat(); 269 return new DisplayShape(spec, displayWidth, displayHeight, ratio, rotation, offsetX, 270 offsetY, scale); 271 } 272 273 @Override 274 public DisplayShape[] newArray(int size) { 275 return new DisplayShape[size]; 276 } 277 }; 278 279 private static final class Cache { 280 private static final Object CACHE_LOCK = new Object(); 281 282 @GuardedBy("CACHE_LOCK") 283 private static String sCachedSpec; 284 @GuardedBy("CACHE_LOCK") 285 private static int sCachedDisplayWidth; 286 @GuardedBy("CACHE_LOCK") 287 private static int sCachedDisplayHeight; 288 @GuardedBy("CACHE_LOCK") 289 private static float sCachedPhysicalPixelDisplaySizeRatio; 290 @GuardedBy("CACHE_LOCK") 291 private static DisplayShape sCachedDisplayShape; 292 293 @GuardedBy("CACHE_LOCK") 294 private static DisplayShape sCacheForPath; 295 @GuardedBy("CACHE_LOCK") 296 private static Path sCachedPath; 297 getDisplayShape(String spec, float physicalPixelDisplaySizeRatio, int displayWidth, int displayHeight)298 static DisplayShape getDisplayShape(String spec, float physicalPixelDisplaySizeRatio, 299 int displayWidth, int displayHeight) { 300 synchronized (CACHE_LOCK) { 301 if (spec.equals(sCachedSpec) 302 && sCachedDisplayWidth == displayWidth 303 && sCachedDisplayHeight == displayHeight 304 && sCachedPhysicalPixelDisplaySizeRatio == physicalPixelDisplaySizeRatio) { 305 return sCachedDisplayShape; 306 } 307 } 308 309 final DisplayShape shape = new DisplayShape(spec, displayWidth, displayHeight, 310 physicalPixelDisplaySizeRatio, ROTATION_0); 311 312 synchronized (CACHE_LOCK) { 313 sCachedSpec = spec; 314 sCachedDisplayWidth = displayWidth; 315 sCachedDisplayHeight = displayHeight; 316 sCachedPhysicalPixelDisplaySizeRatio = physicalPixelDisplaySizeRatio; 317 sCachedDisplayShape = shape; 318 } 319 return shape; 320 } 321 getPath(@onNull DisplayShape shape)322 static Path getPath(@NonNull DisplayShape shape) { 323 synchronized (CACHE_LOCK) { 324 if (shape.equals(sCacheForPath)) { 325 return sCachedPath; 326 } 327 } 328 329 final Path path = PathParser.createPathFromPathData(shape.mDisplayShapeSpec); 330 331 if (!path.isEmpty()) { 332 final Matrix matrix = new Matrix(); 333 if (shape.mRotation != ROTATION_0) { 334 RotationUtils.transformPhysicalToLogicalCoordinates( 335 shape.mRotation, shape.mDisplayWidth, shape.mDisplayHeight, matrix); 336 } 337 if (shape.mPhysicalPixelDisplaySizeRatio != 1f) { 338 matrix.preScale(shape.mPhysicalPixelDisplaySizeRatio, 339 shape.mPhysicalPixelDisplaySizeRatio); 340 } 341 if (shape.mOffsetX != 0 || shape.mOffsetY != 0) { 342 matrix.postTranslate(shape.mOffsetX, shape.mOffsetY); 343 } 344 if (shape.mScale != 1f) { 345 matrix.postScale(shape.mScale, shape.mScale); 346 } 347 path.transform(matrix); 348 } 349 350 synchronized (CACHE_LOCK) { 351 sCacheForPath = shape; 352 sCachedPath = path; 353 } 354 return path; 355 } 356 } 357 } 358