/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wm; import static android.view.SurfaceControl.HIDDEN; import android.graphics.Rect; import android.view.SurfaceControl; import java.util.function.Supplier; /** * Manages a set of {@link SurfaceControl}s to draw a black letterbox between an * outer rect and an inner rect. */ public class Letterbox { private static final Rect EMPTY_RECT = new Rect(); private final Supplier mFactory; private final Rect mOuter = new Rect(); private final Rect mInner = new Rect(); private final LetterboxSurface mTop = new LetterboxSurface("top"); private final LetterboxSurface mLeft = new LetterboxSurface("left"); private final LetterboxSurface mBottom = new LetterboxSurface("bottom"); private final LetterboxSurface mRight = new LetterboxSurface("right"); /** * Constructs a Letterbox. * * @param surfaceControlFactory a factory for creating the managed {@link SurfaceControl}s */ public Letterbox(Supplier surfaceControlFactory) { mFactory = surfaceControlFactory; } /** * Lays out the letterbox, such that the area between the outer and inner * frames will be covered by black color surfaces. * * The caller must use {@link #applySurfaceChanges} to apply the new layout to the surface. * * @param outer the outer frame of the letterbox (this frame will be black, except the area * that intersects with the {code inner} frame). * @param inner the inner frame of the letterbox (this frame will be clear) */ public void layout(Rect outer, Rect inner) { mOuter.set(outer); mInner.set(inner); mTop.layout(outer.left, outer.top, inner.right, inner.top); mLeft.layout(outer.left, inner.top, inner.left, outer.bottom); mBottom.layout(inner.left, inner.bottom, outer.right, outer.bottom); mRight.layout(inner.right, outer.top, outer.right, inner.bottom); } /** * Gets the insets between the outer and inner rects. */ public Rect getInsets() { return new Rect( mLeft.getWidth(), mTop.getHeight(), mRight.getWidth(), mBottom.getHeight()); } /** * Returns true if any part of the letterbox overlaps with the given {@code rect}. */ public boolean isOverlappingWith(Rect rect) { return mTop.isOverlappingWith(rect) || mLeft.isOverlappingWith(rect) || mBottom.isOverlappingWith(rect) || mRight.isOverlappingWith(rect); } /** * Hides the letterbox. * * The caller must use {@link #applySurfaceChanges} to apply the new layout to the surface. */ public void hide() { layout(EMPTY_RECT, EMPTY_RECT); } /** * Destroys the managed {@link SurfaceControl}s. */ public void destroy() { mOuter.setEmpty(); mInner.setEmpty(); mTop.destroy(); mLeft.destroy(); mBottom.destroy(); mRight.destroy(); } /** Returns whether a call to {@link #applySurfaceChanges} would change the surface. */ public boolean needsApplySurfaceChanges() { return mTop.needsApplySurfaceChanges() || mLeft.needsApplySurfaceChanges() || mBottom.needsApplySurfaceChanges() || mRight.needsApplySurfaceChanges(); } public void applySurfaceChanges(SurfaceControl.Transaction t) { mTop.applySurfaceChanges(t); mLeft.applySurfaceChanges(t); mBottom.applySurfaceChanges(t); mRight.applySurfaceChanges(t); } private class LetterboxSurface { private final String mType; private SurfaceControl mSurface; private final Rect mSurfaceFrame = new Rect(); private final Rect mLayoutFrame = new Rect(); public LetterboxSurface(String type) { mType = type; } public void layout(int left, int top, int right, int bottom) { if (mLayoutFrame.left == left && mLayoutFrame.top == top && mLayoutFrame.right == right && mLayoutFrame.bottom == bottom) { // Nothing changed. return; } mLayoutFrame.set(left, top, right, bottom); } private void createSurface() { mSurface = mFactory.get().setName("Letterbox - " + mType) .setFlags(HIDDEN).setColorLayer(true).build(); mSurface.setLayer(-1); mSurface.setColor(new float[]{0, 0, 0}); } public void destroy() { if (mSurface != null) { mSurface.destroy(); mSurface = null; } } public int getWidth() { return Math.max(0, mLayoutFrame.width()); } public int getHeight() { return Math.max(0, mLayoutFrame.height()); } public boolean isOverlappingWith(Rect rect) { if (getWidth() <= 0 || getHeight() <= 0) { return false; } return Rect.intersects(rect, mLayoutFrame); } public void applySurfaceChanges(SurfaceControl.Transaction t) { if (mSurfaceFrame.equals(mLayoutFrame)) { // Nothing changed. return; } mSurfaceFrame.set(mLayoutFrame); if (!mSurfaceFrame.isEmpty()) { if (mSurface == null) { createSurface(); } t.setPosition(mSurface, mSurfaceFrame.left, mSurfaceFrame.top); t.setSize(mSurface, mSurfaceFrame.width(), mSurfaceFrame.height()); t.show(mSurface); } else if (mSurface != null) { t.hide(mSurface); } } public boolean needsApplySurfaceChanges() { return !mSurfaceFrame.equals(mLayoutFrame); } } }