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; 18 19 import static android.view.SurfaceControl.HIDDEN; 20 21 import android.graphics.Rect; 22 import android.view.SurfaceControl; 23 24 import java.util.function.Supplier; 25 26 /** 27 * Manages a set of {@link SurfaceControl}s to draw a black letterbox between an 28 * outer rect and an inner rect. 29 */ 30 public class Letterbox { 31 32 private static final Rect EMPTY_RECT = new Rect(); 33 34 private final Supplier<SurfaceControl.Builder> mFactory; 35 private final Rect mOuter = new Rect(); 36 private final Rect mInner = new Rect(); 37 private final LetterboxSurface mTop = new LetterboxSurface("top"); 38 private final LetterboxSurface mLeft = new LetterboxSurface("left"); 39 private final LetterboxSurface mBottom = new LetterboxSurface("bottom"); 40 private final LetterboxSurface mRight = new LetterboxSurface("right"); 41 42 /** 43 * Constructs a Letterbox. 44 * 45 * @param surfaceControlFactory a factory for creating the managed {@link SurfaceControl}s 46 */ 47 public Letterbox(Supplier<SurfaceControl.Builder> surfaceControlFactory) { 48 mFactory = surfaceControlFactory; 49 } 50 51 /** 52 * Lays out the letterbox, such that the area between the outer and inner 53 * frames will be covered by black color surfaces. 54 * 55 * The caller must use {@link #applySurfaceChanges} to apply the new layout to the surface. 56 * 57 * @param outer the outer frame of the letterbox (this frame will be black, except the area 58 * that intersects with the {code inner} frame). 59 * @param inner the inner frame of the letterbox (this frame will be clear) 60 */ 61 public void layout(Rect outer, Rect inner) { 62 mOuter.set(outer); 63 mInner.set(inner); 64 65 mTop.layout(outer.left, outer.top, inner.right, inner.top); 66 mLeft.layout(outer.left, inner.top, inner.left, outer.bottom); 67 mBottom.layout(inner.left, inner.bottom, outer.right, outer.bottom); 68 mRight.layout(inner.right, outer.top, outer.right, inner.bottom); 69 } 70 71 72 /** 73 * Gets the insets between the outer and inner rects. 74 */ 75 public Rect getInsets() { 76 return new Rect( 77 mLeft.getWidth(), 78 mTop.getHeight(), 79 mRight.getWidth(), 80 mBottom.getHeight()); 81 } 82 83 /** 84 * Returns true if any part of the letterbox overlaps with the given {@code rect}. 85 */ 86 public boolean isOverlappingWith(Rect rect) { 87 return mTop.isOverlappingWith(rect) || mLeft.isOverlappingWith(rect) 88 || mBottom.isOverlappingWith(rect) || mRight.isOverlappingWith(rect); 89 } 90 91 /** 92 * Hides the letterbox. 93 * 94 * The caller must use {@link #applySurfaceChanges} to apply the new layout to the surface. 95 */ 96 public void hide() { 97 layout(EMPTY_RECT, EMPTY_RECT); 98 } 99 100 /** 101 * Destroys the managed {@link SurfaceControl}s. 102 */ 103 public void destroy() { 104 mOuter.setEmpty(); 105 mInner.setEmpty(); 106 107 mTop.destroy(); 108 mLeft.destroy(); 109 mBottom.destroy(); 110 mRight.destroy(); 111 } 112 113 /** Returns whether a call to {@link #applySurfaceChanges} would change the surface. */ 114 public boolean needsApplySurfaceChanges() { 115 return mTop.needsApplySurfaceChanges() 116 || mLeft.needsApplySurfaceChanges() 117 || mBottom.needsApplySurfaceChanges() 118 || mRight.needsApplySurfaceChanges(); 119 } 120 121 public void applySurfaceChanges(SurfaceControl.Transaction t) { 122 mTop.applySurfaceChanges(t); 123 mLeft.applySurfaceChanges(t); 124 mBottom.applySurfaceChanges(t); 125 mRight.applySurfaceChanges(t); 126 } 127 128 private class LetterboxSurface { 129 130 private final String mType; 131 private SurfaceControl mSurface; 132 133 private final Rect mSurfaceFrame = new Rect(); 134 private final Rect mLayoutFrame = new Rect(); 135 136 public LetterboxSurface(String type) { 137 mType = type; 138 } 139 140 public void layout(int left, int top, int right, int bottom) { 141 if (mLayoutFrame.left == left && mLayoutFrame.top == top 142 && mLayoutFrame.right == right && mLayoutFrame.bottom == bottom) { 143 // Nothing changed. 144 return; 145 } 146 mLayoutFrame.set(left, top, right, bottom); 147 } 148 149 private void createSurface() { 150 mSurface = mFactory.get().setName("Letterbox - " + mType) 151 .setFlags(HIDDEN).setColorLayer(true).build(); 152 mSurface.setLayer(-1); 153 mSurface.setColor(new float[]{0, 0, 0}); 154 } 155 156 public void destroy() { 157 if (mSurface != null) { 158 mSurface.destroy(); 159 mSurface = null; 160 } 161 } 162 163 public int getWidth() { 164 return Math.max(0, mLayoutFrame.width()); 165 } 166 167 public int getHeight() { 168 return Math.max(0, mLayoutFrame.height()); 169 } 170 171 public boolean isOverlappingWith(Rect rect) { 172 if (getWidth() <= 0 || getHeight() <= 0) { 173 return false; 174 } 175 return Rect.intersects(rect, mLayoutFrame); 176 } 177 178 public void applySurfaceChanges(SurfaceControl.Transaction t) { 179 if (mSurfaceFrame.equals(mLayoutFrame)) { 180 // Nothing changed. 181 return; 182 } 183 mSurfaceFrame.set(mLayoutFrame); 184 if (!mSurfaceFrame.isEmpty()) { 185 if (mSurface == null) { 186 createSurface(); 187 } 188 t.setPosition(mSurface, mSurfaceFrame.left, mSurfaceFrame.top); 189 t.setSize(mSurface, mSurfaceFrame.width(), mSurfaceFrame.height()); 190 t.show(mSurface); 191 } else if (mSurface != null) { 192 t.hide(mSurface); 193 } 194 } 195 196 public boolean needsApplySurfaceChanges() { 197 return !mSurfaceFrame.equals(mLayoutFrame); 198 } 199 } 200 } 201