blob: 66a9c6c01ca40117389bfeeaa488b6b6e9cbc20e [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
Vishnu Nair1d0fa072018-01-04 07:53:00 -080019import static android.view.DisplayCutoutProto.BOUNDS;
20import static android.view.DisplayCutoutProto.INSETS;
Adrian Roosd4970af2017-11-10 15:48:01 +010021
Adrian Roos8d13bf12018-02-21 15:17:07 +010022import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
23
Adrian Roos16693f32018-01-18 17:45:52 +010024import android.content.res.Resources;
25import android.graphics.Matrix;
Adrian Roosd07bafd2017-12-11 17:30:56 +010026import android.graphics.Path;
Adrian Roosd4970af2017-11-10 15:48:01 +010027import android.graphics.Rect;
Adrian Roosd07bafd2017-12-11 17:30:56 +010028import android.graphics.RectF;
29import android.graphics.Region;
Adrian Roosd4970af2017-11-10 15:48:01 +010030import android.os.Parcel;
31import android.os.Parcelable;
Adrian Roos16693f32018-01-18 17:45:52 +010032import android.text.TextUtils;
33import android.util.Log;
34import android.util.PathParser;
Vishnu Nair1d0fa072018-01-04 07:53:00 -080035import android.util.proto.ProtoOutputStream;
Adrian Roosd4970af2017-11-10 15:48:01 +010036
Adrian Roos16693f32018-01-18 17:45:52 +010037import com.android.internal.R;
Adrian Roos8d13bf12018-02-21 15:17:07 +010038import com.android.internal.annotations.GuardedBy;
Adrian Roosd4970af2017-11-10 15:48:01 +010039import com.android.internal.annotations.VisibleForTesting;
40
Adrian Roos6a4fa0e2018-03-05 19:50:16 +010041import java.util.ArrayList;
42import java.util.List;
Adrian Roosd4970af2017-11-10 15:48:01 +010043
44/**
Adrian Roos6a4fa0e2018-03-05 19:50:16 +010045 * Represents the area of the display that is not functional for displaying content.
Adrian Roosd4970af2017-11-10 15:48:01 +010046 *
47 * <p>{@code DisplayCutout} is immutable.
Adrian Roosd4970af2017-11-10 15:48:01 +010048 */
49public final class DisplayCutout {
50
Adrian Roos16693f32018-01-18 17:45:52 +010051 private static final String TAG = "DisplayCutout";
Adrian Roos6a4fa0e2018-03-05 19:50:16 +010052 private static final String BOTTOM_MARKER = "@bottom";
Adrian Roos16693f32018-01-18 17:45:52 +010053 private static final String DP_MARKER = "@dp";
Adrian Roosb8b10f82018-03-15 20:09:42 +010054 private static final String RIGHT_MARKER = "@right";
Adrian Roos16693f32018-01-18 17:45:52 +010055
Adrian Roosc84df772018-01-19 21:20:22 +010056 /**
57 * Category for overlays that allow emulating a display cutout on devices that don't have
58 * one.
59 *
60 * @see android.content.om.IOverlayManager
61 * @hide
62 */
63 public static final String EMULATION_OVERLAY_CATEGORY =
64 "com.android.internal.display_cutout_emulation";
65
Adrian Roosd07bafd2017-12-11 17:30:56 +010066 private static final Rect ZERO_RECT = new Rect();
67 private static final Region EMPTY_REGION = new Region();
Adrian Roosd4970af2017-11-10 15:48:01 +010068
69 /**
Adrian Roosd07bafd2017-12-11 17:30:56 +010070 * An instance where {@link #isEmpty()} returns {@code true}.
Adrian Roosd4970af2017-11-10 15:48:01 +010071 *
72 * @hide
73 */
Adrian Roos24264212018-02-19 16:26:15 +010074 public static final DisplayCutout NO_CUTOUT = new DisplayCutout(ZERO_RECT, EMPTY_REGION,
Adrian Roos6a4fa0e2018-03-05 19:50:16 +010075 false /* copyArguments */);
Adrian Roosd4970af2017-11-10 15:48:01 +010076
Adrian Roos8d13bf12018-02-21 15:17:07 +010077
78 private static final Object CACHE_LOCK = new Object();
79 @GuardedBy("CACHE_LOCK")
80 private static String sCachedSpec;
81 @GuardedBy("CACHE_LOCK")
82 private static int sCachedDisplayWidth;
83 @GuardedBy("CACHE_LOCK")
84 private static float sCachedDensity;
85 @GuardedBy("CACHE_LOCK")
86 private static DisplayCutout sCachedCutout;
87
Adrian Roosd4970af2017-11-10 15:48:01 +010088 private final Rect mSafeInsets;
Adrian Roosd07bafd2017-12-11 17:30:56 +010089 private final Region mBounds;
Adrian Roos9bbd9662018-03-02 14:31:37 +010090
91 /**
92 * Creates a DisplayCutout instance.
93 *
94 * @param safeInsets the insets from each edge which avoid the display cutout as returned by
95 * {@link #getSafeInsetTop()} etc.
Adrian Roos6a4fa0e2018-03-05 19:50:16 +010096 * @param boundingRects the bounding rects of the display cutouts as returned by
97 * {@link #getBoundingRects()} ()}.
Adrian Roos9bbd9662018-03-02 14:31:37 +010098 */
99 // TODO(b/73953958): @VisibleForTesting(visibility = PRIVATE)
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100100 public DisplayCutout(Rect safeInsets, List<Rect> boundingRects) {
Adrian Roos9bbd9662018-03-02 14:31:37 +0100101 this(safeInsets != null ? new Rect(safeInsets) : ZERO_RECT,
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100102 boundingRectsToRegion(boundingRects),
103 true /* copyArguments */);
Adrian Roos9bbd9662018-03-02 14:31:37 +0100104 }
Adrian Roosd4970af2017-11-10 15:48:01 +0100105
106 /**
107 * Creates a DisplayCutout instance.
108 *
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100109 * @param copyArguments if true, create a copy of the arguments. If false, the passed arguments
110 * are not copied and MUST remain unchanged forever.
Adrian Roosd4970af2017-11-10 15:48:01 +0100111 */
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100112 private DisplayCutout(Rect safeInsets, Region bounds, boolean copyArguments) {
113 mSafeInsets = safeInsets == null ? ZERO_RECT :
114 (copyArguments ? new Rect(safeInsets) : safeInsets);
115 mBounds = bounds == null ? Region.obtain() :
116 (copyArguments ? Region.obtain(bounds) : bounds);
Adrian Roosd4970af2017-11-10 15:48:01 +0100117 }
118
119 /**
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100120 * Returns true if the safe insets are empty (and therefore the current view does not
121 * overlap with the cutout or cutout area).
Adrian Roosd4970af2017-11-10 15:48:01 +0100122 *
Adrian Roosd07bafd2017-12-11 17:30:56 +0100123 * @hide
Adrian Roosd4970af2017-11-10 15:48:01 +0100124 */
Adrian Roosd07bafd2017-12-11 17:30:56 +0100125 public boolean isEmpty() {
126 return mSafeInsets.equals(ZERO_RECT);
Adrian Roosd4970af2017-11-10 15:48:01 +0100127 }
128
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100129 /**
130 * Returns true if there is no cutout, i.e. the bounds are empty.
131 *
132 * @hide
133 */
134 public boolean isBoundsEmpty() {
135 return mBounds.isEmpty();
136 }
137
Adrian Roos9bbd9662018-03-02 14:31:37 +0100138 /** Returns the inset from the top which avoids the display cutout in pixels. */
Adrian Roosd4970af2017-11-10 15:48:01 +0100139 public int getSafeInsetTop() {
140 return mSafeInsets.top;
141 }
142
Adrian Roos9bbd9662018-03-02 14:31:37 +0100143 /** Returns the inset from the bottom which avoids the display cutout in pixels. */
Adrian Roosd4970af2017-11-10 15:48:01 +0100144 public int getSafeInsetBottom() {
145 return mSafeInsets.bottom;
146 }
147
Adrian Roos9bbd9662018-03-02 14:31:37 +0100148 /** Returns the inset from the left which avoids the display cutout in pixels. */
Adrian Roosd4970af2017-11-10 15:48:01 +0100149 public int getSafeInsetLeft() {
150 return mSafeInsets.left;
151 }
152
Adrian Roos9bbd9662018-03-02 14:31:37 +0100153 /** Returns the inset from the right which avoids the display cutout in pixels. */
Adrian Roosd4970af2017-11-10 15:48:01 +0100154 public int getSafeInsetRight() {
155 return mSafeInsets.right;
156 }
157
158 /**
Adrian Roos9bbd9662018-03-02 14:31:37 +0100159 * Returns the safe insets in a rect in pixel units.
Adrian Roosd4970af2017-11-10 15:48:01 +0100160 *
Adrian Roosd07bafd2017-12-11 17:30:56 +0100161 * @return a rect which is set to the safe insets.
Adrian Roosd4970af2017-11-10 15:48:01 +0100162 * @hide
163 */
Adrian Roosd07bafd2017-12-11 17:30:56 +0100164 public Rect getSafeInsets() {
165 return new Rect(mSafeInsets);
Adrian Roosd4970af2017-11-10 15:48:01 +0100166 }
167
168 /**
Adrian Roosd07bafd2017-12-11 17:30:56 +0100169 * Returns the bounding region of the cutout.
Adrian Roosd4970af2017-11-10 15:48:01 +0100170 *
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100171 * <p>
172 * <strong>Note:</strong> There may be more than one cutout, in which case the returned
173 * {@code Region} will be non-contiguous and its bounding rect will be meaningless without
174 * intersecting it first.
175 *
176 * Example:
177 * <pre>
178 * // Getting the bounding rectangle of the top display cutout
179 * Region bounds = displayCutout.getBounds();
180 * bounds.op(0, 0, Integer.MAX_VALUE, displayCutout.getSafeInsetTop(), Region.Op.INTERSECT);
181 * Rect topDisplayCutout = bounds.getBoundingRect();
182 * </pre>
183 *
Adrian Roosd07bafd2017-12-11 17:30:56 +0100184 * @return the bounding region of the cutout. Coordinates are relative
Adrian Roos9bbd9662018-03-02 14:31:37 +0100185 * to the top-left corner of the content view and in pixel units.
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100186 * @hide
Adrian Roosd4970af2017-11-10 15:48:01 +0100187 */
Adrian Roosd07bafd2017-12-11 17:30:56 +0100188 public Region getBounds() {
189 return Region.obtain(mBounds);
Adrian Roosd4970af2017-11-10 15:48:01 +0100190 }
191
192 /**
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100193 * Returns a list of {@code Rect}s, each of which is the bounding rectangle for a non-functional
194 * area on the display.
Adrian Roosd4970af2017-11-10 15:48:01 +0100195 *
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100196 * There will be at most one non-functional area per short edge of the device, and none on
197 * the long edges.
198 *
199 * @return a list of bounding {@code Rect}s, one for each display cutout area.
Adrian Roosd4970af2017-11-10 15:48:01 +0100200 */
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100201 public List<Rect> getBoundingRects() {
202 List<Rect> result = new ArrayList<>();
203 Region bounds = Region.obtain();
204 // top
205 bounds.set(mBounds);
206 bounds.op(0, 0, Integer.MAX_VALUE, getSafeInsetTop(), Region.Op.INTERSECT);
207 if (!bounds.isEmpty()) {
208 result.add(bounds.getBounds());
209 }
210 // left
211 bounds.set(mBounds);
212 bounds.op(0, 0, getSafeInsetLeft(), Integer.MAX_VALUE, Region.Op.INTERSECT);
213 if (!bounds.isEmpty()) {
214 result.add(bounds.getBounds());
215 }
216 // right & bottom
217 bounds.set(mBounds);
218 bounds.op(getSafeInsetLeft() + 1, getSafeInsetTop() + 1,
219 Integer.MAX_VALUE, Integer.MAX_VALUE, Region.Op.INTERSECT);
220 if (!bounds.isEmpty()) {
221 result.add(bounds.getBounds());
222 }
223 bounds.recycle();
224 return result;
Adrian Roosd4970af2017-11-10 15:48:01 +0100225 }
226
227 @Override
228 public int hashCode() {
229 int result = mSafeInsets.hashCode();
Adrian Roosd07bafd2017-12-11 17:30:56 +0100230 result = result * 31 + mBounds.getBounds().hashCode();
Adrian Roosd4970af2017-11-10 15:48:01 +0100231 return result;
232 }
233
234 @Override
235 public boolean equals(Object o) {
236 if (o == this) {
237 return true;
238 }
239 if (o instanceof DisplayCutout) {
240 DisplayCutout c = (DisplayCutout) o;
241 return mSafeInsets.equals(c.mSafeInsets)
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100242 && mBounds.equals(c.mBounds);
Adrian Roosd4970af2017-11-10 15:48:01 +0100243 }
244 return false;
245 }
246
247 @Override
248 public String toString() {
249 return "DisplayCutout{insets=" + mSafeInsets
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100250 + " boundingRect=" + mBounds.getBounds()
Adrian Roosd4970af2017-11-10 15:48:01 +0100251 + "}";
252 }
253
254 /**
Vishnu Nair1d0fa072018-01-04 07:53:00 -0800255 * @hide
256 */
257 public void writeToProto(ProtoOutputStream proto, long fieldId) {
258 final long token = proto.start(fieldId);
259 mSafeInsets.writeToProto(proto, INSETS);
260 mBounds.getBounds().writeToProto(proto, BOUNDS);
261 proto.end(token);
262 }
263
264 /**
Adrian Roosd4970af2017-11-10 15:48:01 +0100265 * Insets the reference frame of the cutout in the given directions.
266 *
267 * @return a copy of this instance which has been inset
268 * @hide
269 */
270 public DisplayCutout inset(int insetLeft, int insetTop, int insetRight, int insetBottom) {
Adrian Roosd07bafd2017-12-11 17:30:56 +0100271 if (mBounds.isEmpty()
Adrian Roosd4970af2017-11-10 15:48:01 +0100272 || insetLeft == 0 && insetTop == 0 && insetRight == 0 && insetBottom == 0) {
273 return this;
274 }
275
276 Rect safeInsets = new Rect(mSafeInsets);
Adrian Roosd07bafd2017-12-11 17:30:56 +0100277 Region bounds = Region.obtain(mBounds);
Adrian Roosd4970af2017-11-10 15:48:01 +0100278
279 // Note: it's not really well defined what happens when the inset is negative, because we
280 // don't know if the safe inset needs to expand in general.
281 if (insetTop > 0 || safeInsets.top > 0) {
282 safeInsets.top = atLeastZero(safeInsets.top - insetTop);
283 }
284 if (insetBottom > 0 || safeInsets.bottom > 0) {
285 safeInsets.bottom = atLeastZero(safeInsets.bottom - insetBottom);
286 }
287 if (insetLeft > 0 || safeInsets.left > 0) {
288 safeInsets.left = atLeastZero(safeInsets.left - insetLeft);
289 }
290 if (insetRight > 0 || safeInsets.right > 0) {
291 safeInsets.right = atLeastZero(safeInsets.right - insetRight);
292 }
293
Adrian Roosd07bafd2017-12-11 17:30:56 +0100294 bounds.translate(-insetLeft, -insetTop);
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100295 return new DisplayCutout(safeInsets, bounds, false /* copyArguments */);
Adrian Roosd4970af2017-11-10 15:48:01 +0100296 }
297
298 /**
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100299 * Returns a copy of this instance with the safe insets replaced with the parameter.
Adrian Roos24264212018-02-19 16:26:15 +0100300 *
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100301 * @param safeInsets the new safe insets in pixels
302 * @return a copy of this instance with the safe insets replaced with the argument.
Adrian Roos24264212018-02-19 16:26:15 +0100303 *
Adrian Roos24264212018-02-19 16:26:15 +0100304 * @hide
305 */
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100306 public DisplayCutout replaceSafeInsets(Rect safeInsets) {
307 return new DisplayCutout(new Rect(safeInsets), mBounds, false /* copyArguments */);
Adrian Roosd4970af2017-11-10 15:48:01 +0100308 }
309
310 private static int atLeastZero(int value) {
311 return value < 0 ? 0 : value;
312 }
313
Adrian Roosd4970af2017-11-10 15:48:01 +0100314
315 /**
Adrian Roos24264212018-02-19 16:26:15 +0100316 * Creates an instance from a bounding rect.
Adrian Roosd4970af2017-11-10 15:48:01 +0100317 *
318 * @hide
319 */
Adrian Roos24264212018-02-19 16:26:15 +0100320 public static DisplayCutout fromBoundingRect(int left, int top, int right, int bottom) {
Adrian Roosd07bafd2017-12-11 17:30:56 +0100321 Path path = new Path();
Adrian Roosd07bafd2017-12-11 17:30:56 +0100322 path.reset();
Adrian Roos24264212018-02-19 16:26:15 +0100323 path.moveTo(left, top);
324 path.lineTo(left, bottom);
325 path.lineTo(right, bottom);
326 path.lineTo(right, top);
Adrian Roosd07bafd2017-12-11 17:30:56 +0100327 path.close();
Adrian Roos1cf585052018-01-03 18:43:27 +0100328 return fromBounds(path);
329 }
Adrian Roosd4970af2017-11-10 15:48:01 +0100330
Adrian Roos1cf585052018-01-03 18:43:27 +0100331 /**
332 * Creates an instance from a bounding {@link Path}.
333 *
334 * @hide
335 */
336 public static DisplayCutout fromBounds(Path path) {
Adrian Roosd07bafd2017-12-11 17:30:56 +0100337 RectF clipRect = new RectF();
338 path.computeBounds(clipRect, false /* unused */);
339 Region clipRegion = Region.obtain();
340 clipRegion.set((int) clipRect.left, (int) clipRect.top,
341 (int) clipRect.right, (int) clipRect.bottom);
342
Adrian Roos1cf585052018-01-03 18:43:27 +0100343 Region bounds = new Region();
Adrian Roosd07bafd2017-12-11 17:30:56 +0100344 bounds.setPath(path, clipRegion);
Adrian Roos1cf585052018-01-03 18:43:27 +0100345 clipRegion.recycle();
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100346 return new DisplayCutout(ZERO_RECT, bounds, false /* copyArguments */);
Adrian Roosd4970af2017-11-10 15:48:01 +0100347 }
348
349 /**
Adrian Roos16693f32018-01-18 17:45:52 +0100350 * Creates an instance according to @android:string/config_mainBuiltInDisplayCutout.
351 *
352 * @hide
353 */
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100354 public static DisplayCutout fromResources(Resources res, int displayWidth, int displayHeight) {
Adrian Roos8d13bf12018-02-21 15:17:07 +0100355 return fromSpec(res.getString(R.string.config_mainBuiltInDisplayCutout),
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100356 displayWidth, displayHeight, res.getDisplayMetrics().density);
Adrian Roos8d13bf12018-02-21 15:17:07 +0100357 }
358
359 /**
360 * Creates an instance according to the supplied {@link android.util.PathParser.PathData} spec.
361 *
362 * @hide
363 */
364 @VisibleForTesting(visibility = PRIVATE)
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100365 public static DisplayCutout fromSpec(String spec, int displayWidth, int displayHeight,
366 float density) {
Adrian Roos16693f32018-01-18 17:45:52 +0100367 if (TextUtils.isEmpty(spec)) {
368 return null;
369 }
Adrian Roos8d13bf12018-02-21 15:17:07 +0100370 synchronized (CACHE_LOCK) {
371 if (spec.equals(sCachedSpec) && sCachedDisplayWidth == displayWidth
372 && sCachedDensity == density) {
373 return sCachedCutout;
374 }
375 }
Adrian Roos16693f32018-01-18 17:45:52 +0100376 spec = spec.trim();
Adrian Roosb8b10f82018-03-15 20:09:42 +0100377 final float offsetX;
378 if (spec.endsWith(RIGHT_MARKER)) {
379 offsetX = displayWidth;
380 spec = spec.substring(0, spec.length() - RIGHT_MARKER.length()).trim();
381 } else {
382 offsetX = displayWidth / 2f;
383 }
Adrian Roos16693f32018-01-18 17:45:52 +0100384 final boolean inDp = spec.endsWith(DP_MARKER);
385 if (inDp) {
386 spec = spec.substring(0, spec.length() - DP_MARKER.length());
387 }
388
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100389 String bottomSpec = null;
390 if (spec.contains(BOTTOM_MARKER)) {
391 String[] splits = spec.split(BOTTOM_MARKER, 2);
392 spec = splits[0].trim();
393 bottomSpec = splits[1].trim();
394 }
395
396 final Path p;
Adrian Roos16693f32018-01-18 17:45:52 +0100397 try {
398 p = PathParser.createPathFromPathData(spec);
399 } catch (Throwable e) {
400 Log.wtf(TAG, "Could not inflate cutout: ", e);
401 return null;
402 }
403
404 final Matrix m = new Matrix();
405 if (inDp) {
Adrian Roos8d13bf12018-02-21 15:17:07 +0100406 m.postScale(density, density);
Adrian Roos16693f32018-01-18 17:45:52 +0100407 }
Adrian Roosb8b10f82018-03-15 20:09:42 +0100408 m.postTranslate(offsetX, 0);
Adrian Roos16693f32018-01-18 17:45:52 +0100409 p.transform(m);
Adrian Roos8d13bf12018-02-21 15:17:07 +0100410
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100411 if (bottomSpec != null) {
412 final Path bottomPath;
413 try {
414 bottomPath = PathParser.createPathFromPathData(bottomSpec);
415 } catch (Throwable e) {
416 Log.wtf(TAG, "Could not inflate bottom cutout: ", e);
417 return null;
418 }
419 // Keep top transform
420 m.postTranslate(0, displayHeight);
421 bottomPath.transform(m);
422 p.addPath(bottomPath);
423 }
424
Adrian Roos8d13bf12018-02-21 15:17:07 +0100425 final DisplayCutout result = fromBounds(p);
426 synchronized (CACHE_LOCK) {
427 sCachedSpec = spec;
428 sCachedDisplayWidth = displayWidth;
429 sCachedDensity = density;
430 sCachedCutout = result;
431 }
432 return result;
Adrian Roos16693f32018-01-18 17:45:52 +0100433 }
434
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100435 private static Region boundingRectsToRegion(List<Rect> rects) {
436 Region result = Region.obtain();
437 if (rects != null) {
438 for (Rect r : rects) {
439 result.op(r, Region.Op.UNION);
440 }
441 }
442 return result;
443 }
444
Adrian Roos16693f32018-01-18 17:45:52 +0100445 /**
Adrian Roosd4970af2017-11-10 15:48:01 +0100446 * Helper class for passing {@link DisplayCutout} through binder.
447 *
448 * Needed, because {@code readFromParcel} cannot be used with immutable classes.
449 *
450 * @hide
451 */
452 public static final class ParcelableWrapper implements Parcelable {
453
454 private DisplayCutout mInner;
455
456 public ParcelableWrapper() {
457 this(NO_CUTOUT);
458 }
459
460 public ParcelableWrapper(DisplayCutout cutout) {
461 mInner = cutout;
462 }
463
464 @Override
465 public int describeContents() {
466 return 0;
467 }
468
469 @Override
470 public void writeToParcel(Parcel out, int flags) {
Adrian Roos1cf585052018-01-03 18:43:27 +0100471 writeCutoutToParcel(mInner, out, flags);
472 }
473
474 /**
475 * Writes a DisplayCutout to a {@link Parcel}.
476 *
477 * @see #readCutoutFromParcel(Parcel)
478 */
479 public static void writeCutoutToParcel(DisplayCutout cutout, Parcel out, int flags) {
480 if (cutout == null) {
481 out.writeInt(-1);
482 } else if (cutout == NO_CUTOUT) {
Adrian Roosd4970af2017-11-10 15:48:01 +0100483 out.writeInt(0);
484 } else {
485 out.writeInt(1);
Adrian Roos1cf585052018-01-03 18:43:27 +0100486 out.writeTypedObject(cutout.mSafeInsets, flags);
487 out.writeTypedObject(cutout.mBounds, flags);
Adrian Roosd4970af2017-11-10 15:48:01 +0100488 }
489 }
490
491 /**
492 * Similar to {@link Creator#createFromParcel(Parcel)}, but reads into an existing
493 * instance.
494 *
495 * Needed for AIDL out parameters.
496 */
497 public void readFromParcel(Parcel in) {
Adrian Roos1cf585052018-01-03 18:43:27 +0100498 mInner = readCutoutFromParcel(in);
Adrian Roosd4970af2017-11-10 15:48:01 +0100499 }
500
501 public static final Creator<ParcelableWrapper> CREATOR = new Creator<ParcelableWrapper>() {
502 @Override
503 public ParcelableWrapper createFromParcel(Parcel in) {
Adrian Roos1cf585052018-01-03 18:43:27 +0100504 return new ParcelableWrapper(readCutoutFromParcel(in));
Adrian Roosd4970af2017-11-10 15:48:01 +0100505 }
506
507 @Override
508 public ParcelableWrapper[] newArray(int size) {
509 return new ParcelableWrapper[size];
510 }
511 };
512
Adrian Roos1cf585052018-01-03 18:43:27 +0100513 /**
514 * Reads a DisplayCutout from a {@link Parcel}.
515 *
516 * @see #writeCutoutToParcel(DisplayCutout, Parcel, int)
517 */
518 public static DisplayCutout readCutoutFromParcel(Parcel in) {
519 int variant = in.readInt();
520 if (variant == -1) {
521 return null;
522 }
523 if (variant == 0) {
Adrian Roosd4970af2017-11-10 15:48:01 +0100524 return NO_CUTOUT;
525 }
526
Adrian Roosd4970af2017-11-10 15:48:01 +0100527 Rect safeInsets = in.readTypedObject(Rect.CREATOR);
Adrian Roosd07bafd2017-12-11 17:30:56 +0100528 Region bounds = in.readTypedObject(Region.CREATOR);
Adrian Roosd4970af2017-11-10 15:48:01 +0100529
Adrian Roos6a4fa0e2018-03-05 19:50:16 +0100530 return new DisplayCutout(safeInsets, bounds, false /* copyArguments */);
Adrian Roosd4970af2017-11-10 15:48:01 +0100531 }
532
533 public DisplayCutout get() {
534 return mInner;
535 }
536
537 public void set(ParcelableWrapper cutout) {
538 mInner = cutout.get();
539 }
540
541 public void set(DisplayCutout cutout) {
542 mInner = cutout;
543 }
544
545 @Override
546 public int hashCode() {
547 return mInner.hashCode();
548 }
549
550 @Override
551 public boolean equals(Object o) {
552 return o instanceof ParcelableWrapper
553 && mInner.equals(((ParcelableWrapper) o).mInner);
554 }
555
556 @Override
557 public String toString() {
558 return String.valueOf(mInner);
559 }
560 }
561}