blob: 5f80d31651a8cf403db42ba7e942817ab1c58641 [file] [log] [blame]
Adrian Roosd4970af2017-11-10 15:48:01 +01001/*
2 * Copyright 2017 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
17package android.view;
18
Adrian Roos535c4202018-04-16 16:12:40 +020019import static android.util.DisplayMetrics.DENSITY_DEFAULT;
20import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
Vishnu Nair1d0fa072018-01-04 07:53:00 -080021import static android.view.DisplayCutoutProto.BOUNDS;
22import static android.view.DisplayCutoutProto.INSETS;
Adrian Roosd4970af2017-11-10 15:48:01 +010023
Adrian Roos8d13bf12018-02-21 15:17:07 +010024import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
25
Adrian Roos16693f32018-01-18 17:45:52 +010026import android.content.res.Resources;
27import android.graphics.Matrix;
Adrian Roosd07bafd2017-12-11 17:30:56 +010028import android.graphics.Path;
Adrian Roosd4970af2017-11-10 15:48:01 +010029import android.graphics.Rect;
Adrian Roosd07bafd2017-12-11 17:30:56 +010030import android.graphics.RectF;
31import android.graphics.Region;
Jorim Jaggi60640512018-06-29 01:14:31 +020032import android.graphics.Region.Op;
Adrian Roosd4970af2017-11-10 15:48:01 +010033import android.os.Parcel;
34import android.os.Parcelable;
Adrian Roos16693f32018-01-18 17:45:52 +010035import android.text.TextUtils;
36import android.util.Log;
Adrian Roos51072a82018-04-10 15:17:08 -070037import android.util.Pair;
Adrian Roos16693f32018-01-18 17:45:52 +010038import android.util.PathParser;
Vishnu Nair1d0fa072018-01-04 07:53:00 -080039import android.util.proto.ProtoOutputStream;
Adrian Roosd4970af2017-11-10 15:48:01 +010040
Adrian Roos16693f32018-01-18 17:45:52 +010041import com.android.internal.R;
Adrian Roos8d13bf12018-02-21 15:17:07 +010042import com.android.internal.annotations.GuardedBy;
Adrian Roosd4970af2017-11-10 15:48:01 +010043import com.android.internal.annotations.VisibleForTesting;
44
Adrian Roos6a4fa0e2018-03-05 19:50:16 +010045import java.util.ArrayList;
46import java.util.List;
Adrian Roosd4970af2017-11-10 15:48:01 +010047
48/**
Adrian Roos6a4fa0e2018-03-05 19:50:16 +010049 * Represents the area of the display that is not functional for displaying content.
Adrian Roosd4970af2017-11-10 15:48:01 +010050 *
51 * <p>{@code DisplayCutout} is immutable.
Adrian Roosd4970af2017-11-10 15:48:01 +010052 */
53public final class DisplayCutout {
54
Adrian Roos16693f32018-01-18 17:45:52 +010055 private static final String TAG = "DisplayCutout";
Adrian Roos6a4fa0e2018-03-05 19:50:16 +010056 private static final String BOTTOM_MARKER = "@bottom";
Adrian Roos16693f32018-01-18 17:45:52 +010057 private static final String DP_MARKER = "@dp";
Adrian Roosb8b10f82018-03-15 20:09:42 +010058 private static final String RIGHT_MARKER = "@right";
Adrian Roos16693f32018-01-18 17:45:52 +010059
Adrian Roosc84df772018-01-19 21:20:22 +010060 /**
61 * Category for overlays that allow emulating a display cutout on devices that don't have
62 * one.
63 *
64 * @see android.content.om.IOverlayManager
65 * @hide
66 */
67 public static final String EMULATION_OVERLAY_CATEGORY =
68 "com.android.internal.display_cutout_emulation";
69
Adrian Roosd07bafd2017-12-11 17:30:56 +010070 private static final Rect ZERO_RECT = new Rect();
71 private static final Region EMPTY_REGION = new Region();
Adrian Roosd4970af2017-11-10 15:48:01 +010072
73 /**
Adrian Roosd07bafd2017-12-11 17:30:56 +010074 * An instance where {@link #isEmpty()} returns {@code true}.
Adrian Roosd4970af2017-11-10 15:48:01 +010075 *
76 * @hide
77 */
Adrian Roos24264212018-02-19 16:26:15 +010078 public static final DisplayCutout NO_CUTOUT = new DisplayCutout(ZERO_RECT, EMPTY_REGION,
Adrian Roos6a4fa0e2018-03-05 19:50:16 +010079 false /* copyArguments */);
Adrian Roosd4970af2017-11-10 15:48:01 +010080
Adrian Roos8d13bf12018-02-21 15:17:07 +010081
Adrian Roos51072a82018-04-10 15:17:08 -070082 private static final Pair<Path, DisplayCutout> NULL_PAIR = new Pair<>(null, null);
Adrian Roos8d13bf12018-02-21 15:17:07 +010083 private static final Object CACHE_LOCK = new Object();
Adrian Roos51072a82018-04-10 15:17:08 -070084
Adrian Roos8d13bf12018-02-21 15:17:07 +010085 @GuardedBy("CACHE_LOCK")
86 private static String sCachedSpec;
87 @GuardedBy("CACHE_LOCK")
88 private static int sCachedDisplayWidth;
89 @GuardedBy("CACHE_LOCK")
Adrian Roos51072a82018-04-10 15:17:08 -070090 private static int sCachedDisplayHeight;
91 @GuardedBy("CACHE_LOCK")
Adrian Roos8d13bf12018-02-21 15:17:07 +010092 private static float sCachedDensity;
93 @GuardedBy("CACHE_LOCK")
Adrian Roos51072a82018-04-10 15:17:08 -070094 private static Pair<Path, DisplayCutout> sCachedCutout = NULL_PAIR;
Adrian Roos8d13bf12018-02-21 15:17:07 +010095
Adrian Roosd4970af2017-11-10 15:48:01 +010096 private final Rect mSafeInsets;
Adrian Roosd07bafd2017-12-11 17:30:56 +010097 private final Region mBounds;
Adrian Roos9bbd9662018-03-02 14:31:37 +010098
99 /**
100 * Creates a DisplayCutout instance.
101 *
102 * @param safeInsets the insets from each edge which avoid the display cutout as returned by
103 * {@link #getSafeInsetTop()} etc.
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100104 * @param boundingRects the bounding rects of the display cutouts as returned by
105 * {@link #getBoundingRects()} ()}.
Adrian Roos9bbd9662018-03-02 14:31:37 +0100106 */
107 // TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE)
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100108 public DisplayCutout(Rect safeInsets, List<Rect> boundingRects) {
Adrian Roos9bbd9662018-03-02 14:31:37 +0100109 this(safeInsets != null ? new Rect(safeInsets) : ZERO_RECT,
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100110 boundingRectsToRegion(boundingRects),
111 true /* copyArguments */);
Adrian Roos9bbd9662018-03-02 14:31:37 +0100112 }
Adrian Roosd4970af2017-11-10 15:48:01 +0100113
114 /**
115 * Creates a DisplayCutout instance.
116 *
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100117 * @param copyArguments if true, create a copy of the arguments. If false, the passed arguments
118 * are not copied and MUST remain unchanged forever.
Adrian Roosd4970af2017-11-10 15:48:01 +0100119 */
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100120 private DisplayCutout(Rect safeInsets, Region bounds, boolean copyArguments) {
121 mSafeInsets = safeInsets == null ? ZERO_RECT :
122 (copyArguments ? new Rect(safeInsets) : safeInsets);
123 mBounds = bounds == null ? Region.obtain() :
124 (copyArguments ? Region.obtain(bounds) : bounds);
Adrian Roosd4970af2017-11-10 15:48:01 +0100125 }
126
127 /**
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100128 * Returns true if the safe insets are empty (and therefore the current view does not
129 * overlap with the cutout or cutout area).
Adrian Roosd4970af2017-11-10 15:48:01 +0100130 *
Adrian Roosd07bafd2017-12-11 17:30:56 +0100131 * @hide
Adrian Roosd4970af2017-11-10 15:48:01 +0100132 */
Adrian Roosd07bafd2017-12-11 17:30:56 +0100133 public boolean isEmpty() {
134 return mSafeInsets.equals(ZERO_RECT);
Adrian Roosd4970af2017-11-10 15:48:01 +0100135 }
136
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100137 /**
138 * Returns true if there is no cutout, i.e. the bounds are empty.
139 *
140 * @hide
141 */
142 public boolean isBoundsEmpty() {
143 return mBounds.isEmpty();
144 }
145
Adrian Roos9bbd9662018-03-02 14:31:37 +0100146 /** Returns the inset from the top which avoids the display cutout in pixels. */
Adrian Roosd4970af2017-11-10 15:48:01 +0100147 public int getSafeInsetTop() {
148 return mSafeInsets.top;
149 }
150
Adrian Roos9bbd9662018-03-02 14:31:37 +0100151 /** Returns the inset from the bottom which avoids the display cutout in pixels. */
Adrian Roosd4970af2017-11-10 15:48:01 +0100152 public int getSafeInsetBottom() {
153 return mSafeInsets.bottom;
154 }
155
Adrian Roos9bbd9662018-03-02 14:31:37 +0100156 /** Returns the inset from the left which avoids the display cutout in pixels. */
Adrian Roosd4970af2017-11-10 15:48:01 +0100157 public int getSafeInsetLeft() {
158 return mSafeInsets.left;
159 }
160
Adrian Roos9bbd9662018-03-02 14:31:37 +0100161 /** Returns the inset from the right which avoids the display cutout in pixels. */
Adrian Roosd4970af2017-11-10 15:48:01 +0100162 public int getSafeInsetRight() {
163 return mSafeInsets.right;
164 }
165
166 /**
Adrian Roos9bbd9662018-03-02 14:31:37 +0100167 * Returns the safe insets in a rect in pixel units.
Adrian Roosd4970af2017-11-10 15:48:01 +0100168 *
Adrian Roosd07bafd2017-12-11 17:30:56 +0100169 * @return a rect which is set to the safe insets.
Adrian Roosd4970af2017-11-10 15:48:01 +0100170 * @hide
171 */
Adrian Roosd07bafd2017-12-11 17:30:56 +0100172 public Rect getSafeInsets() {
173 return new Rect(mSafeInsets);
Adrian Roosd4970af2017-11-10 15:48:01 +0100174 }
175
176 /**
Adrian Roosd07bafd2017-12-11 17:30:56 +0100177 * Returns the bounding region of the cutout.
Adrian Roosd4970af2017-11-10 15:48:01 +0100178 *
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100179 * <p>
180 * <strong>Note:</strong> There may be more than one cutout, in which case the returned
181 * {@code Region} will be non-contiguous and its bounding rect will be meaningless without
182 * intersecting it first.
183 *
184 * Example:
185 * <pre>
186 * // Getting the bounding rectangle of the top display cutout
187 * Region bounds = displayCutout.getBounds();
188 * bounds.op(0, 0, Integer.MAX_VALUE, displayCutout.getSafeInsetTop(), Region.Op.INTERSECT);
189 * Rect topDisplayCutout = bounds.getBoundingRect();
190 * </pre>
191 *
Adrian Roosd07bafd2017-12-11 17:30:56 +0100192 * @return the bounding region of the cutout. Coordinates are relative
Adrian Roos9bbd9662018-03-02 14:31:37 +0100193 * to the top-left corner of the content view and in pixel units.
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100194 * @hide
Adrian Roosd4970af2017-11-10 15:48:01 +0100195 */
Adrian Roosd07bafd2017-12-11 17:30:56 +0100196 public Region getBounds() {
197 return Region.obtain(mBounds);
Adrian Roosd4970af2017-11-10 15:48:01 +0100198 }
199
200 /**
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100201 * Returns a list of {@code Rect}s, each of which is the bounding rectangle for a non-functional
202 * area on the display.
Adrian Roosd4970af2017-11-10 15:48:01 +0100203 *
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100204 * There will be at most one non-functional area per short edge of the device, and none on
205 * the long edges.
206 *
207 * @return a list of bounding {@code Rect}s, one for each display cutout area.
Adrian Roosd4970af2017-11-10 15:48:01 +0100208 */
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100209 public List<Rect> getBoundingRects() {
210 List<Rect> result = new ArrayList<>();
211 Region bounds = Region.obtain();
212 // top
213 bounds.set(mBounds);
214 bounds.op(0, 0, Integer.MAX_VALUE, getSafeInsetTop(), Region.Op.INTERSECT);
215 if (!bounds.isEmpty()) {
216 result.add(bounds.getBounds());
217 }
218 // left
219 bounds.set(mBounds);
220 bounds.op(0, 0, getSafeInsetLeft(), Integer.MAX_VALUE, Region.Op.INTERSECT);
221 if (!bounds.isEmpty()) {
222 result.add(bounds.getBounds());
223 }
224 // right & bottom
225 bounds.set(mBounds);
226 bounds.op(getSafeInsetLeft() + 1, getSafeInsetTop() + 1,
227 Integer.MAX_VALUE, Integer.MAX_VALUE, Region.Op.INTERSECT);
228 if (!bounds.isEmpty()) {
229 result.add(bounds.getBounds());
230 }
231 bounds.recycle();
232 return result;
Adrian Roosd4970af2017-11-10 15:48:01 +0100233 }
234
235 @Override
236 public int hashCode() {
237 int result = mSafeInsets.hashCode();
Adrian Roosd07bafd2017-12-11 17:30:56 +0100238 result = result * 31 + mBounds.getBounds().hashCode();
Adrian Roosd4970af2017-11-10 15:48:01 +0100239 return result;
240 }
241
242 @Override
243 public boolean equals(Object o) {
244 if (o == this) {
245 return true;
246 }
247 if (o instanceof DisplayCutout) {
248 DisplayCutout c = (DisplayCutout) o;
249 return mSafeInsets.equals(c.mSafeInsets)
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100250 && mBounds.equals(c.mBounds);
Adrian Roosd4970af2017-11-10 15:48:01 +0100251 }
252 return false;
253 }
254
255 @Override
256 public String toString() {
257 return "DisplayCutout{insets=" + mSafeInsets
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100258 + " boundingRect=" + mBounds.getBounds()
Adrian Roosd4970af2017-11-10 15:48:01 +0100259 + "}";
260 }
261
262 /**
Vishnu Nair1d0fa072018-01-04 07:53:00 -0800263 * @hide
264 */
265 public void writeToProto(ProtoOutputStream proto, long fieldId) {
266 final long token = proto.start(fieldId);
267 mSafeInsets.writeToProto(proto, INSETS);
268 mBounds.getBounds().writeToProto(proto, BOUNDS);
269 proto.end(token);
270 }
271
272 /**
Adrian Roosd4970af2017-11-10 15:48:01 +0100273 * Insets the reference frame of the cutout in the given directions.
274 *
275 * @return a copy of this instance which has been inset
276 * @hide
277 */
278 public DisplayCutout inset(int insetLeft, int insetTop, int insetRight, int insetBottom) {
Adrian Roosd07bafd2017-12-11 17:30:56 +0100279 if (mBounds.isEmpty()
Adrian Roosd4970af2017-11-10 15:48:01 +0100280 || insetLeft == 0 && insetTop == 0 && insetRight == 0 && insetBottom == 0) {
281 return this;
282 }
283
284 Rect safeInsets = new Rect(mSafeInsets);
Adrian Roosd07bafd2017-12-11 17:30:56 +0100285 Region bounds = Region.obtain(mBounds);
Adrian Roosd4970af2017-11-10 15:48:01 +0100286
287 // Note: it's not really well defined what happens when the inset is negative, because we
288 // don't know if the safe inset needs to expand in general.
289 if (insetTop > 0 || safeInsets.top > 0) {
290 safeInsets.top = atLeastZero(safeInsets.top - insetTop);
291 }
292 if (insetBottom > 0 || safeInsets.bottom > 0) {
293 safeInsets.bottom = atLeastZero(safeInsets.bottom - insetBottom);
294 }
295 if (insetLeft > 0 || safeInsets.left > 0) {
296 safeInsets.left = atLeastZero(safeInsets.left - insetLeft);
297 }
298 if (insetRight > 0 || safeInsets.right > 0) {
299 safeInsets.right = atLeastZero(safeInsets.right - insetRight);
300 }
301
Adrian Roosd07bafd2017-12-11 17:30:56 +0100302 bounds.translate(-insetLeft, -insetTop);
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100303 return new DisplayCutout(safeInsets, bounds, false /* copyArguments */);
Adrian Roosd4970af2017-11-10 15:48:01 +0100304 }
305
306 /**
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100307 * Returns a copy of this instance with the safe insets replaced with the parameter.
Adrian Roos24264212018-02-19 16:26:15 +0100308 *
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100309 * @param safeInsets the new safe insets in pixels
310 * @return a copy of this instance with the safe insets replaced with the argument.
Adrian Roos24264212018-02-19 16:26:15 +0100311 *
Adrian Roos24264212018-02-19 16:26:15 +0100312 * @hide
313 */
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100314 public DisplayCutout replaceSafeInsets(Rect safeInsets) {
315 return new DisplayCutout(new Rect(safeInsets), mBounds, false /* copyArguments */);
Adrian Roosd4970af2017-11-10 15:48:01 +0100316 }
317
318 private static int atLeastZero(int value) {
319 return value < 0 ? 0 : value;
320 }
321
Adrian Roosd4970af2017-11-10 15:48:01 +0100322
323 /**
Adrian Roos24264212018-02-19 16:26:15 +0100324 * Creates an instance from a bounding rect.
Adrian Roosd4970af2017-11-10 15:48:01 +0100325 *
326 * @hide
327 */
Adrian Roos8c28c7c2018-08-20 13:43:38 +0200328 @VisibleForTesting
Adrian Roos24264212018-02-19 16:26:15 +0100329 public static DisplayCutout fromBoundingRect(int left, int top, int right, int bottom) {
Jorim Jaggi60640512018-06-29 01:14:31 +0200330 Region r = Region.obtain();
331 r.set(left, top, right, bottom);
332 return fromBounds(r);
Adrian Roos1cf585052018-01-03 18:43:27 +0100333 }
Adrian Roosd4970af2017-11-10 15:48:01 +0100334
Adrian Roos1cf585052018-01-03 18:43:27 +0100335 /**
336 * Creates an instance from a bounding {@link Path}.
337 *
338 * @hide
339 */
Jorim Jaggi60640512018-06-29 01:14:31 +0200340 public static DisplayCutout fromBounds(Region region) {
341 return new DisplayCutout(ZERO_RECT, region, false /* copyArguments */);
Adrian Roosd4970af2017-11-10 15:48:01 +0100342 }
343
344 /**
Jorim Jaggi60640512018-06-29 01:14:31 +0200345 * Creates the display cutout according to
346 * @android:string/config_mainBuiltInDisplayCutoutRectApproximation, which is the closest
347 * rectangle-base approximation of the cutout.
Adrian Roos16693f32018-01-18 17:45:52 +0100348 *
349 * @hide
350 */
Jorim Jaggi60640512018-06-29 01:14:31 +0200351 public static DisplayCutout fromResourcesRectApproximation(Resources res, int displayWidth, int displayHeight) {
352 return fromSpec(res.getString(R.string.config_mainBuiltInDisplayCutoutRectApproximation),
Adrian Roos535c4202018-04-16 16:12:40 +0200353 displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT);
Adrian Roos8d13bf12018-02-21 15:17:07 +0100354 }
355
356 /**
Adrian Roos51072a82018-04-10 15:17:08 -0700357 * Creates an instance according to @android:string/config_mainBuiltInDisplayCutout.
358 *
359 * @hide
360 */
361 public static Path pathFromResources(Resources res, int displayWidth, int displayHeight) {
Jorim Jaggi60640512018-06-29 01:14:31 +0200362 return pathAndDisplayCutoutFromSpec(
363 res.getString(R.string.config_mainBuiltInDisplayCutout),
Adrian Roos535c4202018-04-16 16:12:40 +0200364 displayWidth, displayHeight, DENSITY_DEVICE_STABLE / (float) DENSITY_DEFAULT).first;
Adrian Roos51072a82018-04-10 15:17:08 -0700365 }
366
367 /**
Adrian Roos8d13bf12018-02-21 15:17:07 +0100368 * Creates an instance according to the supplied {@link android.util.PathParser.PathData} spec.
369 *
370 * @hide
371 */
372 @VisibleForTesting(visibility = PRIVATE)
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100373 public static DisplayCutout fromSpec(String spec, int displayWidth, int displayHeight,
374 float density) {
Adrian Roos51072a82018-04-10 15:17:08 -0700375 return pathAndDisplayCutoutFromSpec(spec, displayWidth, displayHeight, density).second;
376 }
377
378 private static Pair<Path, DisplayCutout> pathAndDisplayCutoutFromSpec(String spec,
379 int displayWidth, int displayHeight, float density) {
Adrian Roos16693f32018-01-18 17:45:52 +0100380 if (TextUtils.isEmpty(spec)) {
Adrian Roos51072a82018-04-10 15:17:08 -0700381 return NULL_PAIR;
Adrian Roos16693f32018-01-18 17:45:52 +0100382 }
Adrian Roos8d13bf12018-02-21 15:17:07 +0100383 synchronized (CACHE_LOCK) {
384 if (spec.equals(sCachedSpec) && sCachedDisplayWidth == displayWidth
Adrian Roos51072a82018-04-10 15:17:08 -0700385 && sCachedDisplayHeight == displayHeight
Adrian Roos8d13bf12018-02-21 15:17:07 +0100386 && sCachedDensity == density) {
387 return sCachedCutout;
388 }
389 }
Adrian Roos16693f32018-01-18 17:45:52 +0100390 spec = spec.trim();
Adrian Roosb8b10f82018-03-15 20:09:42 +0100391 final float offsetX;
392 if (spec.endsWith(RIGHT_MARKER)) {
393 offsetX = displayWidth;
394 spec = spec.substring(0, spec.length() - RIGHT_MARKER.length()).trim();
395 } else {
396 offsetX = displayWidth / 2f;
397 }
Adrian Roos16693f32018-01-18 17:45:52 +0100398 final boolean inDp = spec.endsWith(DP_MARKER);
399 if (inDp) {
400 spec = spec.substring(0, spec.length() - DP_MARKER.length());
401 }
402
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100403 String bottomSpec = null;
404 if (spec.contains(BOTTOM_MARKER)) {
405 String[] splits = spec.split(BOTTOM_MARKER, 2);
406 spec = splits[0].trim();
407 bottomSpec = splits[1].trim();
408 }
409
410 final Path p;
Jorim Jaggi60640512018-06-29 01:14:31 +0200411 final Region r = Region.obtain();
Adrian Roos16693f32018-01-18 17:45:52 +0100412 try {
413 p = PathParser.createPathFromPathData(spec);
414 } catch (Throwable e) {
415 Log.wtf(TAG, "Could not inflate cutout: ", e);
Adrian Roos51072a82018-04-10 15:17:08 -0700416 return NULL_PAIR;
Adrian Roos16693f32018-01-18 17:45:52 +0100417 }
418
419 final Matrix m = new Matrix();
420 if (inDp) {
Adrian Roos8d13bf12018-02-21 15:17:07 +0100421 m.postScale(density, density);
Adrian Roos16693f32018-01-18 17:45:52 +0100422 }
Adrian Roosb8b10f82018-03-15 20:09:42 +0100423 m.postTranslate(offsetX, 0);
Adrian Roos16693f32018-01-18 17:45:52 +0100424 p.transform(m);
Adrian Roos8d13bf12018-02-21 15:17:07 +0100425
Adrian Roos8c28c7c2018-08-20 13:43:38 +0200426 final Rect tmpRect = new Rect();
427 toRectAndAddToRegion(p, r, tmpRect);
428 final int topInset = tmpRect.bottom;
Jorim Jaggi60640512018-06-29 01:14:31 +0200429
Adrian Roos8c28c7c2018-08-20 13:43:38 +0200430 final int bottomInset;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100431 if (bottomSpec != null) {
432 final Path bottomPath;
433 try {
434 bottomPath = PathParser.createPathFromPathData(bottomSpec);
435 } catch (Throwable e) {
436 Log.wtf(TAG, "Could not inflate bottom cutout: ", e);
Adrian Roos51072a82018-04-10 15:17:08 -0700437 return NULL_PAIR;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100438 }
439 // Keep top transform
440 m.postTranslate(0, displayHeight);
441 bottomPath.transform(m);
442 p.addPath(bottomPath);
Adrian Roos8c28c7c2018-08-20 13:43:38 +0200443 toRectAndAddToRegion(bottomPath, r, tmpRect);
444 bottomInset = displayHeight - tmpRect.top;
445 } else {
446 bottomInset = 0;
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100447 }
448
Adrian Roos8c28c7c2018-08-20 13:43:38 +0200449 // Reuse tmpRect as the inset rect we store into the DisplayCutout instance.
450 tmpRect.set(0, topInset, 0, bottomInset);
451 final DisplayCutout cutout = new DisplayCutout(tmpRect, r, false /* copyArguments */);
452
453 final Pair<Path, DisplayCutout> result = new Pair<>(p, cutout);
Adrian Roos8d13bf12018-02-21 15:17:07 +0100454 synchronized (CACHE_LOCK) {
455 sCachedSpec = spec;
456 sCachedDisplayWidth = displayWidth;
Adrian Roos51072a82018-04-10 15:17:08 -0700457 sCachedDisplayHeight = displayHeight;
Adrian Roos8d13bf12018-02-21 15:17:07 +0100458 sCachedDensity = density;
459 sCachedCutout = result;
460 }
461 return result;
Adrian Roos16693f32018-01-18 17:45:52 +0100462 }
463
Adrian Roos8c28c7c2018-08-20 13:43:38 +0200464 private static void toRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) {
Jorim Jaggi60640512018-06-29 01:14:31 +0200465 final RectF rectF = new RectF();
Jorim Jaggi60640512018-06-29 01:14:31 +0200466 p.computeBounds(rectF, false /* unused */);
Adrian Roos8c28c7c2018-08-20 13:43:38 +0200467 rectF.round(inoutRect);
468 inoutRegion.op(inoutRect, Op.UNION);
Jorim Jaggi60640512018-06-29 01:14:31 +0200469 }
470
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100471 private static Region boundingRectsToRegion(List<Rect> rects) {
472 Region result = Region.obtain();
473 if (rects != null) {
474 for (Rect r : rects) {
475 result.op(r, Region.Op.UNION);
476 }
477 }
478 return result;
479 }
480
Adrian Roos16693f32018-01-18 17:45:52 +0100481 /**
Adrian Roosd4970af2017-11-10 15:48:01 +0100482 * Helper class for passing {@link DisplayCutout} through binder.
483 *
484 * Needed, because {@code readFromParcel} cannot be used with immutable classes.
485 *
486 * @hide
487 */
488 public static final class ParcelableWrapper implements Parcelable {
489
490 private DisplayCutout mInner;
491
492 public ParcelableWrapper() {
493 this(NO_CUTOUT);
494 }
495
496 public ParcelableWrapper(DisplayCutout cutout) {
497 mInner = cutout;
498 }
499
500 @Override
501 public int describeContents() {
502 return 0;
503 }
504
505 @Override
506 public void writeToParcel(Parcel out, int flags) {
Adrian Roos1cf585052018-01-03 18:43:27 +0100507 writeCutoutToParcel(mInner, out, flags);
508 }
509
510 /**
511 * Writes a DisplayCutout to a {@link Parcel}.
512 *
513 * @see #readCutoutFromParcel(Parcel)
514 */
515 public static void writeCutoutToParcel(DisplayCutout cutout, Parcel out, int flags) {
516 if (cutout == null) {
517 out.writeInt(-1);
518 } else if (cutout == NO_CUTOUT) {
Adrian Roosd4970af2017-11-10 15:48:01 +0100519 out.writeInt(0);
520 } else {
521 out.writeInt(1);
Adrian Roos1cf585052018-01-03 18:43:27 +0100522 out.writeTypedObject(cutout.mSafeInsets, flags);
523 out.writeTypedObject(cutout.mBounds, flags);
Adrian Roosd4970af2017-11-10 15:48:01 +0100524 }
525 }
526
527 /**
528 * Similar to {@link Creator#createFromParcel(Parcel)}, but reads into an existing
529 * instance.
530 *
531 * Needed for AIDL out parameters.
532 */
533 public void readFromParcel(Parcel in) {
Adrian Roos1cf585052018-01-03 18:43:27 +0100534 mInner = readCutoutFromParcel(in);
Adrian Roosd4970af2017-11-10 15:48:01 +0100535 }
536
537 public static final Creator<ParcelableWrapper> CREATOR = new Creator<ParcelableWrapper>() {
538 @Override
539 public ParcelableWrapper createFromParcel(Parcel in) {
Adrian Roos1cf585052018-01-03 18:43:27 +0100540 return new ParcelableWrapper(readCutoutFromParcel(in));
Adrian Roosd4970af2017-11-10 15:48:01 +0100541 }
542
543 @Override
544 public ParcelableWrapper[] newArray(int size) {
545 return new ParcelableWrapper[size];
546 }
547 };
548
Adrian Roos1cf585052018-01-03 18:43:27 +0100549 /**
550 * Reads a DisplayCutout from a {@link Parcel}.
551 *
552 * @see #writeCutoutToParcel(DisplayCutout, Parcel, int)
553 */
554 public static DisplayCutout readCutoutFromParcel(Parcel in) {
555 int variant = in.readInt();
556 if (variant == -1) {
557 return null;
558 }
559 if (variant == 0) {
Adrian Roosd4970af2017-11-10 15:48:01 +0100560 return NO_CUTOUT;
561 }
562
Adrian Roosd4970af2017-11-10 15:48:01 +0100563 Rect safeInsets = in.readTypedObject(Rect.CREATOR);
Adrian Roosd07bafd2017-12-11 17:30:56 +0100564 Region bounds = in.readTypedObject(Region.CREATOR);
Adrian Roosd4970af2017-11-10 15:48:01 +0100565
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100566 return new DisplayCutout(safeInsets, bounds, false /* copyArguments */);
Adrian Roosd4970af2017-11-10 15:48:01 +0100567 }
568
569 public DisplayCutout get() {
570 return mInner;
571 }
572
573 public void set(ParcelableWrapper cutout) {
574 mInner = cutout.get();
575 }
576
577 public void set(DisplayCutout cutout) {
578 mInner = cutout;
579 }
580
581 @Override
582 public int hashCode() {
583 return mInner.hashCode();
584 }
585
586 @Override
587 public boolean equals(Object o) {
588 return o instanceof ParcelableWrapper
589 && mInner.equals(((ParcelableWrapper) o).mInner);
590 }
591
592 @Override
593 public String toString() {
594 return String.valueOf(mInner);
595 }
596 }
597}