1 /* 2 * Copyright (C) 2018 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 com.android.server.wm.utils; 18 19 import android.graphics.Rect; 20 import android.util.Size; 21 import android.view.DisplayCutout; 22 import android.view.Gravity; 23 24 import java.util.List; 25 import java.util.Objects; 26 27 /** 28 * Wrapper for DisplayCutout that also tracks the display size and using this allows (re)calculating 29 * safe insets. 30 */ 31 public class WmDisplayCutout { 32 33 public static final WmDisplayCutout NO_CUTOUT = new WmDisplayCutout(DisplayCutout.NO_CUTOUT, 34 null); 35 36 private final DisplayCutout mInner; 37 private final Size mFrameSize; 38 WmDisplayCutout(DisplayCutout inner, Size frameSize)39 public WmDisplayCutout(DisplayCutout inner, Size frameSize) { 40 mInner = inner; 41 mFrameSize = frameSize; 42 } 43 computeSafeInsets(DisplayCutout inner, int displayWidth, int displayHeight)44 public static WmDisplayCutout computeSafeInsets(DisplayCutout inner, 45 int displayWidth, int displayHeight) { 46 if (inner == DisplayCutout.NO_CUTOUT || inner.isBoundsEmpty()) { 47 return NO_CUTOUT; 48 } 49 50 final Size displaySize = new Size(displayWidth, displayHeight); 51 final Rect safeInsets = computeSafeInsets(displaySize, inner); 52 return new WmDisplayCutout(inner.replaceSafeInsets(safeInsets), displaySize); 53 } 54 55 /** 56 * Insets the reference frame of the cutout in the given directions. 57 * 58 * @return a copy of this instance which has been inset 59 * @hide 60 */ inset(int insetLeft, int insetTop, int insetRight, int insetBottom)61 public WmDisplayCutout inset(int insetLeft, int insetTop, int insetRight, int insetBottom) { 62 DisplayCutout newInner = mInner.inset(insetLeft, insetTop, insetRight, insetBottom); 63 64 if (mInner == newInner) { 65 return this; 66 } 67 68 Size frame = mFrameSize == null ? null : new Size( 69 mFrameSize.getWidth() - insetLeft - insetRight, 70 mFrameSize.getHeight() - insetTop - insetBottom); 71 72 return new WmDisplayCutout(newInner, frame); 73 } 74 75 /** 76 * Recalculates the cutout relative to the given reference frame. 77 * 78 * The safe insets must already have been computed, e.g. with {@link #computeSafeInsets}. 79 * 80 * @return a copy of this instance with the safe insets recalculated 81 * @hide 82 */ calculateRelativeTo(Rect frame)83 public WmDisplayCutout calculateRelativeTo(Rect frame) { 84 if (mInner.isEmpty()) { 85 return this; 86 } 87 return inset(frame.left, frame.top, 88 mFrameSize.getWidth() - frame.right, mFrameSize.getHeight() - frame.bottom); 89 } 90 91 /** 92 * Calculates the safe insets relative to the given display size. 93 * 94 * @return a copy of this instance with the safe insets calculated 95 * @hide 96 */ computeSafeInsets(int width, int height)97 public WmDisplayCutout computeSafeInsets(int width, int height) { 98 return computeSafeInsets(mInner, width, height); 99 } 100 computeSafeInsets(Size displaySize, DisplayCutout cutout)101 private static Rect computeSafeInsets(Size displaySize, DisplayCutout cutout) { 102 if (displaySize.getWidth() < displaySize.getHeight()) { 103 final List<Rect> boundingRects = cutout.replaceSafeInsets( 104 new Rect(0, displaySize.getHeight() / 2, 0, displaySize.getHeight() / 2)) 105 .getBoundingRects(); 106 int topInset = findInsetForSide(displaySize, boundingRects, Gravity.TOP); 107 int bottomInset = findInsetForSide(displaySize, boundingRects, Gravity.BOTTOM); 108 return new Rect(0, topInset, 0, bottomInset); 109 } else if (displaySize.getWidth() > displaySize.getHeight()) { 110 final List<Rect> boundingRects = cutout.replaceSafeInsets( 111 new Rect(displaySize.getWidth() / 2, 0, displaySize.getWidth() / 2, 0)) 112 .getBoundingRects(); 113 int leftInset = findInsetForSide(displaySize, boundingRects, Gravity.LEFT); 114 int right = findInsetForSide(displaySize, boundingRects, Gravity.RIGHT); 115 return new Rect(leftInset, 0, right, 0); 116 } else { 117 throw new UnsupportedOperationException("not implemented: display=" + displaySize + 118 " cutout=" + cutout); 119 } 120 } 121 findInsetForSide(Size display, List<Rect> boundingRects, int gravity)122 private static int findInsetForSide(Size display, List<Rect> boundingRects, int gravity) { 123 int inset = 0; 124 final int size = boundingRects.size(); 125 for (int i = 0; i < size; i++) { 126 Rect boundingRect = boundingRects.get(i); 127 switch (gravity) { 128 case Gravity.TOP: 129 if (boundingRect.top == 0) { 130 inset = Math.max(inset, boundingRect.bottom); 131 } 132 break; 133 case Gravity.BOTTOM: 134 if (boundingRect.bottom == display.getHeight()) { 135 inset = Math.max(inset, display.getHeight() - boundingRect.top); 136 } 137 break; 138 case Gravity.LEFT: 139 if (boundingRect.left == 0) { 140 inset = Math.max(inset, boundingRect.right); 141 } 142 break; 143 case Gravity.RIGHT: 144 if (boundingRect.right == display.getWidth()) { 145 inset = Math.max(inset, display.getWidth() - boundingRect.left); 146 } 147 break; 148 default: 149 throw new IllegalArgumentException("unknown gravity: " + gravity); 150 } 151 } 152 return inset; 153 } 154 getDisplayCutout()155 public DisplayCutout getDisplayCutout() { 156 return mInner; 157 } 158 159 @Override equals(Object o)160 public boolean equals(Object o) { 161 if (!(o instanceof WmDisplayCutout)) { 162 return false; 163 } 164 WmDisplayCutout that = (WmDisplayCutout) o; 165 return Objects.equals(mInner, that.mInner) && 166 Objects.equals(mFrameSize, that.mFrameSize); 167 } 168 169 @Override hashCode()170 public int hashCode() { 171 return Objects.hash(mInner, mFrameSize); 172 } 173 } 174