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.Point; 22 import android.graphics.Rect; 23 import android.os.Binder; 24 import android.os.Process; 25 import android.view.InputChannel; 26 import android.view.InputEventReceiver; 27 import android.view.InputWindowHandle; 28 import android.view.SurfaceControl; 29 import android.view.WindowManager; 30 31 import com.android.server.UiThread; 32 33 import java.util.function.Supplier; 34 35 /** 36 * Manages a set of {@link SurfaceControl}s to draw a black letterbox between an 37 * outer rect and an inner rect. 38 */ 39 public class Letterbox { 40 41 private static final Rect EMPTY_RECT = new Rect(); 42 private static final Point ZERO_POINT = new Point(0, 0); 43 44 private final Supplier<SurfaceControl.Builder> mFactory; 45 private final Rect mOuter = new Rect(); 46 private final Rect mInner = new Rect(); 47 private final LetterboxSurface mTop = new LetterboxSurface("top"); 48 private final LetterboxSurface mLeft = new LetterboxSurface("left"); 49 private final LetterboxSurface mBottom = new LetterboxSurface("bottom"); 50 private final LetterboxSurface mRight = new LetterboxSurface("right"); 51 private final LetterboxSurface[] mSurfaces = { mLeft, mTop, mRight, mBottom }; 52 53 /** 54 * Constructs a Letterbox. 55 * 56 * @param surfaceControlFactory a factory for creating the managed {@link SurfaceControl}s 57 */ Letterbox(Supplier<SurfaceControl.Builder> surfaceControlFactory)58 public Letterbox(Supplier<SurfaceControl.Builder> surfaceControlFactory) { 59 mFactory = surfaceControlFactory; 60 } 61 62 /** 63 * Lays out the letterbox, such that the area between the outer and inner 64 * frames will be covered by black color surfaces. 65 * 66 * The caller must use {@link #applySurfaceChanges} to apply the new layout to the surface. 67 * @param outer the outer frame of the letterbox (this frame will be black, except the area 68 * that intersects with the {code inner} frame), in global coordinates 69 * @param inner the inner frame of the letterbox (this frame will be clear), in global 70 * coordinates 71 * @param surfaceOrigin the origin of the surface factory in global coordinates 72 */ layout(Rect outer, Rect inner, Point surfaceOrigin)73 public void layout(Rect outer, Rect inner, Point surfaceOrigin) { 74 mOuter.set(outer); 75 mInner.set(inner); 76 77 mTop.layout(outer.left, outer.top, inner.right, inner.top, surfaceOrigin); 78 mLeft.layout(outer.left, inner.top, inner.left, outer.bottom, surfaceOrigin); 79 mBottom.layout(inner.left, inner.bottom, outer.right, outer.bottom, surfaceOrigin); 80 mRight.layout(inner.right, outer.top, outer.right, inner.bottom, surfaceOrigin); 81 } 82 83 84 /** 85 * Gets the insets between the outer and inner rects. 86 */ getInsets()87 public Rect getInsets() { 88 return new Rect( 89 mLeft.getWidth(), 90 mTop.getHeight(), 91 mRight.getWidth(), 92 mBottom.getHeight()); 93 } 94 95 /** @return The frame that used to place the content. */ getInnerFrame()96 Rect getInnerFrame() { 97 return mInner; 98 } 99 100 /** 101 * Returns true if any part of the letterbox overlaps with the given {@code rect}. 102 */ isOverlappingWith(Rect rect)103 public boolean isOverlappingWith(Rect rect) { 104 for (LetterboxSurface surface : mSurfaces) { 105 if (surface.isOverlappingWith(rect)) { 106 return true; 107 } 108 } 109 return false; 110 } 111 112 /** 113 * Hides the letterbox. 114 * 115 * The caller must use {@link #applySurfaceChanges} to apply the new layout to the surface. 116 */ hide()117 public void hide() { 118 layout(EMPTY_RECT, EMPTY_RECT, ZERO_POINT); 119 } 120 121 /** 122 * Destroys the managed {@link SurfaceControl}s. 123 */ destroy()124 public void destroy() { 125 mOuter.setEmpty(); 126 mInner.setEmpty(); 127 128 for (LetterboxSurface surface : mSurfaces) { 129 surface.remove(); 130 } 131 } 132 133 /** Returns whether a call to {@link #applySurfaceChanges} would change the surface. */ needsApplySurfaceChanges()134 public boolean needsApplySurfaceChanges() { 135 for (LetterboxSurface surface : mSurfaces) { 136 if (surface.needsApplySurfaceChanges()) { 137 return true; 138 } 139 } 140 return false; 141 } 142 applySurfaceChanges(SurfaceControl.Transaction t)143 public void applySurfaceChanges(SurfaceControl.Transaction t) { 144 for (LetterboxSurface surface : mSurfaces) { 145 surface.applySurfaceChanges(t); 146 } 147 } 148 149 /** Enables touches to slide into other neighboring surfaces. */ attachInput(WindowState win)150 void attachInput(WindowState win) { 151 for (LetterboxSurface surface : mSurfaces) { 152 surface.attachInput(win); 153 } 154 } 155 onMovedToDisplay(int displayId)156 void onMovedToDisplay(int displayId) { 157 for (LetterboxSurface surface : mSurfaces) { 158 if (surface.mInputInterceptor != null) { 159 surface.mInputInterceptor.mWindowHandle.displayId = displayId; 160 } 161 } 162 } 163 164 private static class InputInterceptor { 165 final InputChannel mServerChannel; 166 final InputChannel mClientChannel; 167 final InputWindowHandle mWindowHandle; 168 final InputEventReceiver mInputEventReceiver; 169 final WindowManagerService mWmService; 170 final Binder mToken = new Binder(); 171 InputInterceptor(String namePrefix, WindowState win)172 InputInterceptor(String namePrefix, WindowState win) { 173 mWmService = win.mWmService; 174 final String name = namePrefix + (win.mAppToken != null ? win.mAppToken : win); 175 final InputChannel[] channels = InputChannel.openInputChannelPair(name); 176 mServerChannel = channels[0]; 177 mClientChannel = channels[1]; 178 mInputEventReceiver = new SimpleInputReceiver(mClientChannel); 179 180 mWmService.mInputManager.registerInputChannel(mServerChannel, mToken); 181 182 mWindowHandle = new InputWindowHandle(null /* inputApplicationHandle */, 183 null /* clientWindow */, win.getDisplayId()); 184 mWindowHandle.name = name; 185 mWindowHandle.token = mToken; 186 mWindowHandle.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 187 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 188 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH 189 | WindowManager.LayoutParams.FLAG_SLIPPERY; 190 mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER; 191 mWindowHandle.dispatchingTimeoutNanos = 192 WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; 193 mWindowHandle.visible = true; 194 mWindowHandle.ownerPid = Process.myPid(); 195 mWindowHandle.ownerUid = Process.myUid(); 196 mWindowHandle.scaleFactor = 1.0f; 197 } 198 updateTouchableRegion(Rect frame)199 void updateTouchableRegion(Rect frame) { 200 if (frame.isEmpty()) { 201 // Use null token to indicate the surface doesn't need to receive input event (see 202 // the usage of Layer.hasInput in SurfaceFlinger), so InputDispatcher won't keep the 203 // unnecessary records. 204 mWindowHandle.token = null; 205 return; 206 } 207 mWindowHandle.token = mToken; 208 mWindowHandle.touchableRegion.set(frame); 209 mWindowHandle.touchableRegion.translate(-frame.left, -frame.top); 210 } 211 dispose()212 void dispose() { 213 mWmService.mInputManager.unregisterInputChannel(mServerChannel); 214 mInputEventReceiver.dispose(); 215 mServerChannel.dispose(); 216 mClientChannel.dispose(); 217 } 218 219 private static class SimpleInputReceiver extends InputEventReceiver { SimpleInputReceiver(InputChannel inputChannel)220 SimpleInputReceiver(InputChannel inputChannel) { 221 super(inputChannel, UiThread.getHandler().getLooper()); 222 } 223 } 224 } 225 226 private class LetterboxSurface { 227 228 private final String mType; 229 private SurfaceControl mSurface; 230 231 private final Rect mSurfaceFrameRelative = new Rect(); 232 private final Rect mLayoutFrameGlobal = new Rect(); 233 private final Rect mLayoutFrameRelative = new Rect(); 234 235 private InputInterceptor mInputInterceptor; 236 LetterboxSurface(String type)237 public LetterboxSurface(String type) { 238 mType = type; 239 } 240 layout(int left, int top, int right, int bottom, Point surfaceOrigin)241 public void layout(int left, int top, int right, int bottom, Point surfaceOrigin) { 242 mLayoutFrameGlobal.set(left, top, right, bottom); 243 mLayoutFrameRelative.set(mLayoutFrameGlobal); 244 mLayoutFrameRelative.offset(-surfaceOrigin.x, -surfaceOrigin.y); 245 } 246 createSurface()247 private void createSurface() { 248 mSurface = mFactory.get().setName("Letterbox - " + mType) 249 .setFlags(HIDDEN).setColorLayer().build(); 250 mSurface.setLayer(-1); 251 mSurface.setColor(new float[]{0, 0, 0}); 252 mSurface.setColorSpaceAgnostic(true); 253 } 254 attachInput(WindowState win)255 void attachInput(WindowState win) { 256 if (mInputInterceptor != null) { 257 mInputInterceptor.dispose(); 258 } 259 mInputInterceptor = new InputInterceptor("Letterbox_" + mType + "_", win); 260 } 261 remove()262 public void remove() { 263 if (mSurface != null) { 264 new SurfaceControl.Transaction().remove(mSurface).apply(); 265 mSurface = null; 266 } 267 if (mInputInterceptor != null) { 268 mInputInterceptor.dispose(); 269 mInputInterceptor = null; 270 } 271 } 272 getWidth()273 public int getWidth() { 274 return Math.max(0, mLayoutFrameGlobal.width()); 275 } 276 getHeight()277 public int getHeight() { 278 return Math.max(0, mLayoutFrameGlobal.height()); 279 } 280 281 /** 282 * Returns if the given {@code rect} overlaps with this letterbox piece. 283 * @param rect the area to check for overlap in global coordinates 284 */ isOverlappingWith(Rect rect)285 public boolean isOverlappingWith(Rect rect) { 286 if (mLayoutFrameGlobal.isEmpty()) { 287 return false; 288 } 289 return Rect.intersects(rect, mLayoutFrameGlobal); 290 } 291 applySurfaceChanges(SurfaceControl.Transaction t)292 public void applySurfaceChanges(SurfaceControl.Transaction t) { 293 if (mSurfaceFrameRelative.equals(mLayoutFrameRelative)) { 294 // Nothing changed. 295 return; 296 } 297 mSurfaceFrameRelative.set(mLayoutFrameRelative); 298 if (!mSurfaceFrameRelative.isEmpty()) { 299 if (mSurface == null) { 300 createSurface(); 301 } 302 t.setPosition(mSurface, mSurfaceFrameRelative.left, mSurfaceFrameRelative.top); 303 t.setWindowCrop(mSurface, mSurfaceFrameRelative.width(), 304 mSurfaceFrameRelative.height()); 305 t.show(mSurface); 306 } else if (mSurface != null) { 307 t.hide(mSurface); 308 } 309 if (mSurface != null && mInputInterceptor != null) { 310 mInputInterceptor.updateTouchableRegion(mSurfaceFrameRelative); 311 t.setInputWindowInfo(mSurface, mInputInterceptor.mWindowHandle); 312 } 313 } 314 needsApplySurfaceChanges()315 public boolean needsApplySurfaceChanges() { 316 return !mSurfaceFrameRelative.equals(mLayoutFrameRelative); 317 } 318 } 319 } 320