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