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 (mFrameSize == null) { 85 return this; 86 } 87 final int insetRight = mFrameSize.getWidth() - frame.right; 88 final int insetBottom = mFrameSize.getHeight() - frame.bottom; 89 if (frame.left == 0 && frame.top == 0 && insetRight == 0 && insetBottom == 0) { 90 return this; 91 } 92 if (frame.left >= mInner.getSafeInsetLeft() 93 && frame.top >= mInner.getSafeInsetTop() 94 && insetRight >= mInner.getSafeInsetRight() 95 && insetBottom >= mInner.getSafeInsetBottom()) { 96 return NO_CUTOUT; 97 } 98 if (mInner.isEmpty()) { 99 return this; 100 } 101 return inset(frame.left, frame.top, insetRight, insetBottom); 102 } 103 104 /** 105 * Calculates the safe insets relative to the given display size. 106 * 107 * @return a copy of this instance with the safe insets calculated 108 * @hide 109 */ computeSafeInsets(int width, int height)110 public WmDisplayCutout computeSafeInsets(int width, int height) { 111 return computeSafeInsets(mInner, width, height); 112 } 113 computeSafeInsets(Size displaySize, DisplayCutout cutout)114 private static Rect computeSafeInsets(Size displaySize, DisplayCutout cutout) { 115 if (displaySize.getWidth() < displaySize.getHeight()) { 116 final List<Rect> boundingRects = cutout.replaceSafeInsets( 117 new Rect(0, displaySize.getHeight() / 2, 0, displaySize.getHeight() / 2)) 118 .getBoundingRects(); 119 int topInset = findInsetForSide(displaySize, boundingRects, Gravity.TOP); 120 int bottomInset = findInsetForSide(displaySize, boundingRects, Gravity.BOTTOM); 121 return new Rect(0, topInset, 0, bottomInset); 122 } else if (displaySize.getWidth() > displaySize.getHeight()) { 123 final List<Rect> boundingRects = cutout.replaceSafeInsets( 124 new Rect(displaySize.getWidth() / 2, 0, displaySize.getWidth() / 2, 0)) 125 .getBoundingRects(); 126 int leftInset = findInsetForSide(displaySize, boundingRects, Gravity.LEFT); 127 int right = findInsetForSide(displaySize, boundingRects, Gravity.RIGHT); 128 return new Rect(leftInset, 0, right, 0); 129 } else { 130 throw new UnsupportedOperationException("not implemented: display=" + displaySize + 131 " cutout=" + cutout); 132 } 133 } 134 findInsetForSide(Size display, List<Rect> boundingRects, int gravity)135 private static int findInsetForSide(Size display, List<Rect> boundingRects, int gravity) { 136 int inset = 0; 137 final int size = boundingRects.size(); 138 for (int i = 0; i < size; i++) { 139 Rect boundingRect = boundingRects.get(i); 140 switch (gravity) { 141 case Gravity.TOP: 142 if (boundingRect.top == 0) { 143 inset = Math.max(inset, boundingRect.bottom); 144 } 145 break; 146 case Gravity.BOTTOM: 147 if (boundingRect.bottom == display.getHeight()) { 148 inset = Math.max(inset, display.getHeight() - boundingRect.top); 149 } 150 break; 151 case Gravity.LEFT: 152 if (boundingRect.left == 0) { 153 inset = Math.max(inset, boundingRect.right); 154 } 155 break; 156 case Gravity.RIGHT: 157 if (boundingRect.right == display.getWidth()) { 158 inset = Math.max(inset, display.getWidth() - boundingRect.left); 159 } 160 break; 161 default: 162 throw new IllegalArgumentException("unknown gravity: " + gravity); 163 } 164 } 165 return inset; 166 } 167 getDisplayCutout()168 public DisplayCutout getDisplayCutout() { 169 return mInner; 170 } 171 172 @Override equals(Object o)173 public boolean equals(Object o) { 174 if (!(o instanceof WmDisplayCutout)) { 175 return false; 176 } 177 WmDisplayCutout that = (WmDisplayCutout) o; 178 return Objects.equals(mInner, that.mInner) && 179 Objects.equals(mFrameSize, that.mFrameSize); 180 } 181 182 @Override hashCode()183 public int hashCode() { 184 return Objects.hash(mInner, mFrameSize); 185 } 186 187 @Override toString()188 public String toString() { 189 return "WmDisplayCutout{" + mInner + ", mFrameSize=" + mFrameSize + '}'; 190 } 191 } 192