• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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