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