1 /*
2  * Copyright (C) 2022 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.wm.shell.windowdecor;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
20 
21 import android.app.ActivityManager.RunningTaskInfo;
22 import android.content.Context;
23 import android.content.res.Configuration;
24 import android.content.res.Resources;
25 import android.graphics.Color;
26 import android.graphics.PixelFormat;
27 import android.graphics.Point;
28 import android.graphics.Rect;
29 import android.os.Binder;
30 import android.view.Display;
31 import android.view.LayoutInflater;
32 import android.view.SurfaceControl;
33 import android.view.SurfaceControlViewHost;
34 import android.view.View;
35 import android.view.ViewRootImpl;
36 import android.view.WindowInsets;
37 import android.view.WindowManager;
38 import android.view.WindowlessWindowManager;
39 import android.window.SurfaceSyncGroup;
40 import android.window.TaskConstants;
41 import android.window.WindowContainerTransaction;
42 
43 import com.android.wm.shell.ShellTaskOrganizer;
44 import com.android.wm.shell.common.DisplayController;
45 
46 import java.util.function.Supplier;
47 
48 /**
49  * Manages a container surface and a windowless window to show window decoration. Responsible to
50  * update window decoration window state and layout parameters on task info changes and so that
51  * window decoration is in correct state and bounds.
52  *
53  * The container surface is a child of the task display area in the same display, so that window
54  * decorations can be drawn out of the task bounds and receive input events from out of the task
55  * bounds to support drag resizing.
56  *
57  * The windowless window that hosts window decoration is positioned in front of all activities, to
58  * allow the foreground activity to draw its own background behind window decorations, such as
59  * the window captions.
60  *
61  * @param <T> The type of the root view
62  */
63 public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
64         implements AutoCloseable {
65 
66     /**
67      * The Z-order of {@link #mCaptionContainerSurface}.
68      * <p>
69      * We use {@link #mDecorationContainerSurface} to define input window for task resizing; by
70      * layering it in front of {@link #mCaptionContainerSurface}, we can allow it to handle input
71      * prior to caption view itself, treating corner inputs as resize events rather than
72      * repositioning.
73      */
74     static final int CAPTION_LAYER_Z_ORDER = -1;
75     /**
76      * The Z-order of the task input sink in {@link DragPositioningCallback}.
77      * <p>
78      * This task input sink is used to prevent undesired dispatching of motion events out of task
79      * bounds; by layering it behind {@link #mCaptionContainerSurface}, we allow captions to handle
80      * input events first.
81      */
82     static final int INPUT_SINK_Z_ORDER = -2;
83 
84     /**
85      * System-wide context. Only used to create context with overridden configurations.
86      */
87     final Context mContext;
88     final DisplayController mDisplayController;
89     final ShellTaskOrganizer mTaskOrganizer;
90     final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
91     final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
92     final Supplier<WindowContainerTransaction> mWindowContainerTransactionSupplier;
93     final SurfaceControlViewHostFactory mSurfaceControlViewHostFactory;
94     private final DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener =
95             new DisplayController.OnDisplaysChangedListener() {
96                 @Override
97                 public void onDisplayAdded(int displayId) {
98                     if (mTaskInfo.displayId != displayId) {
99                         return;
100                     }
101 
102                     mDisplayController.removeDisplayWindowListener(this);
103                     relayout(mTaskInfo);
104                 }
105             };
106 
107     RunningTaskInfo mTaskInfo;
108     int mLayoutResId;
109     final SurfaceControl mTaskSurface;
110 
111     Display mDisplay;
112     Context mDecorWindowContext;
113     SurfaceControl mDecorationContainerSurface;
114 
115     SurfaceControl mCaptionContainerSurface;
116     private WindowlessWindowManager mCaptionWindowManager;
117     private SurfaceControlViewHost mViewHost;
118 
119     private final Binder mOwner = new Binder();
120     private final Rect mCaptionInsetsRect = new Rect();
121     private final float[] mTmpColor = new float[3];
122 
WindowDecoration( Context context, DisplayController displayController, ShellTaskOrganizer taskOrganizer, RunningTaskInfo taskInfo, SurfaceControl taskSurface)123     WindowDecoration(
124             Context context,
125             DisplayController displayController,
126             ShellTaskOrganizer taskOrganizer,
127             RunningTaskInfo taskInfo,
128             SurfaceControl taskSurface) {
129         this(context, displayController, taskOrganizer, taskInfo, taskSurface,
130                 SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
131                 WindowContainerTransaction::new, new SurfaceControlViewHostFactory() {});
132     }
133 
WindowDecoration( Context context, DisplayController displayController, ShellTaskOrganizer taskOrganizer, RunningTaskInfo taskInfo, SurfaceControl taskSurface, Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, SurfaceControlViewHostFactory surfaceControlViewHostFactory)134     WindowDecoration(
135             Context context,
136             DisplayController displayController,
137             ShellTaskOrganizer taskOrganizer,
138             RunningTaskInfo taskInfo,
139             SurfaceControl taskSurface,
140             Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
141             Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
142             Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
143             SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
144         mContext = context;
145         mDisplayController = displayController;
146         mTaskOrganizer = taskOrganizer;
147         mTaskInfo = taskInfo;
148         mTaskSurface = taskSurface;
149         mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier;
150         mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
151         mWindowContainerTransactionSupplier = windowContainerTransactionSupplier;
152         mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
153 
154         mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
155         mDecorWindowContext = mContext.createConfigurationContext(
156                 getConfigurationWithOverrides(mTaskInfo));
157     }
158 
159     /**
160      * Get {@link Configuration} from supplied {@link RunningTaskInfo}.
161      *
162      * Allows values to be overridden before returning the configuration.
163      */
getConfigurationWithOverrides(RunningTaskInfo taskInfo)164     protected Configuration getConfigurationWithOverrides(RunningTaskInfo taskInfo) {
165         return taskInfo.getConfiguration();
166     }
167 
168     /**
169      * Used by {@link WindowDecoration} to trigger a new relayout because the requirements for a
170      * relayout weren't satisfied are satisfied now.
171      *
172      * @param taskInfo The previous {@link RunningTaskInfo} passed into {@link #relayout} or the
173      *                 constructor.
174      */
relayout(RunningTaskInfo taskInfo)175     abstract void relayout(RunningTaskInfo taskInfo);
176 
relayout(RelayoutParams params, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView, RelayoutResult<T> outResult)177     void relayout(RelayoutParams params, SurfaceControl.Transaction startT,
178             SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView,
179             RelayoutResult<T> outResult) {
180         outResult.reset();
181 
182         final Configuration oldTaskConfig = mTaskInfo.getConfiguration();
183         if (params.mRunningTaskInfo != null) {
184             mTaskInfo = params.mRunningTaskInfo;
185         }
186         final int oldLayoutResId = mLayoutResId;
187         mLayoutResId = params.mLayoutResId;
188 
189         if (!mTaskInfo.isVisible) {
190             releaseViews();
191             finishT.hide(mTaskSurface);
192             return;
193         }
194 
195         if (rootView == null && params.mLayoutResId == 0) {
196             throw new IllegalArgumentException("layoutResId and rootView can't both be invalid.");
197         }
198 
199         outResult.mRootView = rootView;
200         rootView = null; // Clear it just in case we use it accidentally
201         final Configuration taskConfig = getConfigurationWithOverrides(mTaskInfo);
202         if (oldTaskConfig.densityDpi != taskConfig.densityDpi
203                 || mDisplay == null
204                 || mDisplay.getDisplayId() != mTaskInfo.displayId
205                 || oldLayoutResId != mLayoutResId) {
206             releaseViews();
207 
208             if (!obtainDisplayOrRegisterListener()) {
209                 outResult.mRootView = null;
210                 return;
211             }
212             mDecorWindowContext = mContext.createConfigurationContext(taskConfig);
213             if (params.mLayoutResId != 0) {
214                 outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
215                         .inflate(params.mLayoutResId, null);
216             }
217         }
218 
219         if (outResult.mRootView == null) {
220             outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
221                     .inflate(params.mLayoutResId, null);
222         }
223 
224         final Resources resources = mDecorWindowContext.getResources();
225         final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
226         outResult.mWidth = taskBounds.width();
227         outResult.mHeight = taskBounds.height();
228 
229         // DecorationContainerSurface
230         if (mDecorationContainerSurface == null) {
231             final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
232             mDecorationContainerSurface = builder
233                     .setName("Decor container of Task=" + mTaskInfo.taskId)
234                     .setContainerLayer()
235                     .setParent(mTaskSurface)
236                     .build();
237 
238             startT.setTrustedOverlay(mDecorationContainerSurface, true)
239                     .setLayer(mDecorationContainerSurface,
240                             TaskConstants.TASK_CHILD_LAYER_WINDOW_DECORATIONS);
241         }
242 
243         startT.setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight)
244                 .show(mDecorationContainerSurface);
245 
246         // CaptionContainerSurface, CaptionWindowManager
247         if (mCaptionContainerSurface == null) {
248             final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
249             mCaptionContainerSurface = builder
250                     .setName("Caption container of Task=" + mTaskInfo.taskId)
251                     .setContainerLayer()
252                     .setParent(mDecorationContainerSurface)
253                     .build();
254         }
255 
256         final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
257         final int captionWidth = taskBounds.width();
258 
259         startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
260                 .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
261                 .show(mCaptionContainerSurface);
262 
263         if (ViewRootImpl.CAPTION_ON_SHELL) {
264             outResult.mRootView.setTaskFocusState(mTaskInfo.isFocused);
265 
266             // Caption insets
267             mCaptionInsetsRect.set(taskBounds);
268             mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
269             wct.addInsetsSource(mTaskInfo.token,
270                     mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
271             wct.addInsetsSource(mTaskInfo.token,
272                     mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures(),
273                     mCaptionInsetsRect);
274         } else {
275             startT.hide(mCaptionContainerSurface);
276         }
277 
278         // Task surface itself
279         float shadowRadius = loadDimension(resources, params.mShadowRadiusId);
280         int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
281         mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
282         mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
283         mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
284         final Point taskPosition = mTaskInfo.positionInParent;
285         startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight)
286                 .setShadowRadius(mTaskSurface, shadowRadius)
287                 .setColor(mTaskSurface, mTmpColor)
288                 .show(mTaskSurface);
289         finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
290                 .setShadowRadius(mTaskSurface, shadowRadius)
291                 .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
292         if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
293             startT.setCornerRadius(mTaskSurface, params.mCornerRadius);
294             finishT.setCornerRadius(mTaskSurface, params.mCornerRadius);
295         }
296 
297         if (mCaptionWindowManager == null) {
298             // Put caption under a container surface because ViewRootImpl sets the destination frame
299             // of windowless window layers and BLASTBufferQueue#update() doesn't support offset.
300             mCaptionWindowManager = new WindowlessWindowManager(
301                     mTaskInfo.getConfiguration(), mCaptionContainerSurface,
302                     null /* hostInputToken */);
303         }
304 
305         // Caption view
306         mCaptionWindowManager.setConfiguration(taskConfig);
307         final WindowManager.LayoutParams lp =
308                 new WindowManager.LayoutParams(captionWidth, captionHeight,
309                         WindowManager.LayoutParams.TYPE_APPLICATION,
310                         WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
311         lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
312         lp.setTrustedOverlay();
313         if (mViewHost == null) {
314             mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay,
315                     mCaptionWindowManager);
316             if (params.mApplyStartTransactionOnDraw) {
317                 mViewHost.getRootSurfaceControl().applyTransactionOnDraw(startT);
318             }
319             mViewHost.setView(outResult.mRootView, lp);
320         } else {
321             if (params.mApplyStartTransactionOnDraw) {
322                 mViewHost.getRootSurfaceControl().applyTransactionOnDraw(startT);
323             }
324             mViewHost.relayout(lp);
325         }
326     }
327 
getCaptionHeightId()328     int getCaptionHeightId() {
329         return Resources.ID_NULL;
330     }
331 
332     /**
333      * Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or
334      * registers {@link #mOnDisplaysChangedListener} if it doesn't.
335      *
336      * @return {@code true} if the {@link Display} instance exists; or {@code false} otherwise
337      */
obtainDisplayOrRegisterListener()338     private boolean obtainDisplayOrRegisterListener() {
339         mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
340         if (mDisplay == null) {
341             mDisplayController.addDisplayWindowListener(mOnDisplaysChangedListener);
342             return false;
343         }
344         return true;
345     }
346 
releaseViews()347     void releaseViews() {
348         if (mViewHost != null) {
349             mViewHost.release();
350             mViewHost = null;
351         }
352 
353         mCaptionWindowManager = null;
354 
355         final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
356         boolean released = false;
357         if (mCaptionContainerSurface != null) {
358             t.remove(mCaptionContainerSurface);
359             mCaptionContainerSurface = null;
360             released = true;
361         }
362 
363         if (mDecorationContainerSurface != null) {
364             t.remove(mDecorationContainerSurface);
365             mDecorationContainerSurface = null;
366             released = true;
367         }
368 
369         if (released) {
370             t.apply();
371         }
372 
373         final WindowContainerTransaction wct = mWindowContainerTransactionSupplier.get();
374         wct.removeInsetsSource(mTaskInfo.token,
375                 mOwner, 0 /* index */, WindowInsets.Type.captionBar());
376         wct.removeInsetsSource(mTaskInfo.token,
377                 mOwner, 0 /* index */, WindowInsets.Type.mandatorySystemGestures());
378         mTaskOrganizer.applyTransaction(wct);
379     }
380 
381     @Override
close()382     public void close() {
383         mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
384         releaseViews();
385     }
386 
loadDimensionPixelSize(Resources resources, int resourceId)387     static int loadDimensionPixelSize(Resources resources, int resourceId) {
388         if (resourceId == Resources.ID_NULL) {
389             return 0;
390         }
391         return resources.getDimensionPixelSize(resourceId);
392     }
393 
loadDimension(Resources resources, int resourceId)394     static float loadDimension(Resources resources, int resourceId) {
395         if (resourceId == Resources.ID_NULL) {
396             return 0;
397         }
398         return resources.getDimension(resourceId);
399     }
400 
401     /**
402      * Create a window associated with this WindowDecoration.
403      * Note that subclass must dispose of this when the task is hidden/closed.
404      *
405      * @param layoutId     layout to make the window from
406      * @param t            the transaction to apply
407      * @param xPos         x position of new window
408      * @param yPos         y position of new window
409      * @param width        width of new window
410      * @param height       height of new window
411      * @param shadowRadius radius of the shadow of the new window
412      * @param cornerRadius radius of the corners of the new window
413      * @return the {@link AdditionalWindow} that was added.
414      */
addWindow(int layoutId, String namePrefix, SurfaceControl.Transaction t, SurfaceSyncGroup ssg, int xPos, int yPos, int width, int height, int shadowRadius, int cornerRadius)415     AdditionalWindow addWindow(int layoutId, String namePrefix, SurfaceControl.Transaction t,
416             SurfaceSyncGroup ssg, int xPos, int yPos, int width, int height, int shadowRadius,
417             int cornerRadius) {
418         final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
419         SurfaceControl windowSurfaceControl = builder
420                 .setName(namePrefix + " of Task=" + mTaskInfo.taskId)
421                 .setContainerLayer()
422                 .setParent(mDecorationContainerSurface)
423                 .build();
424         View v = LayoutInflater.from(mDecorWindowContext).inflate(layoutId, null);
425 
426         t.setPosition(windowSurfaceControl, xPos, yPos)
427                 .setWindowCrop(windowSurfaceControl, width, height)
428                 .setShadowRadius(windowSurfaceControl, shadowRadius)
429                 .setCornerRadius(windowSurfaceControl, cornerRadius)
430                 .show(windowSurfaceControl);
431         final WindowManager.LayoutParams lp =
432                 new WindowManager.LayoutParams(width, height,
433                         WindowManager.LayoutParams.TYPE_APPLICATION,
434                         WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
435         lp.setTitle("Additional window of Task=" + mTaskInfo.taskId);
436         lp.setTrustedOverlay();
437         WindowlessWindowManager windowManager = new WindowlessWindowManager(mTaskInfo.configuration,
438                 windowSurfaceControl, null /* hostInputToken */);
439         SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory
440                 .create(mDecorWindowContext, mDisplay, windowManager);
441         ssg.add(viewHost.getSurfacePackage(), () -> viewHost.setView(v, lp));
442         return new AdditionalWindow(windowSurfaceControl, viewHost,
443                 mSurfaceControlTransactionSupplier);
444     }
445 
446     /**
447      * Adds caption inset source to a WCT
448      */
addCaptionInset(WindowContainerTransaction wct)449     public void addCaptionInset(WindowContainerTransaction wct) {
450         final int captionHeightId = getCaptionHeightId();
451         if (!ViewRootImpl.CAPTION_ON_SHELL || captionHeightId == Resources.ID_NULL) {
452             return;
453         }
454 
455         final int captionHeight = loadDimensionPixelSize(mContext.getResources(), captionHeightId);
456         final Rect captionInsets = new Rect(0, 0, 0, captionHeight);
457         wct.addInsetsSource(mTaskInfo.token, mOwner, 0 /* index */, WindowInsets.Type.captionBar(),
458                 captionInsets);
459     }
460 
461     static class RelayoutParams {
462         RunningTaskInfo mRunningTaskInfo;
463         int mLayoutResId;
464         int mCaptionHeightId;
465         int mCaptionWidthId;
466         int mShadowRadiusId;
467 
468         int mCornerRadius;
469 
470         int mCaptionX;
471         int mCaptionY;
472 
473         boolean mApplyStartTransactionOnDraw;
474 
reset()475         void reset() {
476             mLayoutResId = Resources.ID_NULL;
477             mCaptionHeightId = Resources.ID_NULL;
478             mCaptionWidthId = Resources.ID_NULL;
479             mShadowRadiusId = Resources.ID_NULL;
480 
481             mCornerRadius = 0;
482 
483             mCaptionX = 0;
484             mCaptionY = 0;
485 
486             mApplyStartTransactionOnDraw = false;
487         }
488     }
489 
490     static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
491         int mWidth;
492         int mHeight;
493         T mRootView;
494 
reset()495         void reset() {
496             mWidth = 0;
497             mHeight = 0;
498             mRootView = null;
499         }
500     }
501 
502     interface SurfaceControlViewHostFactory {
create(Context c, Display d, WindowlessWindowManager wmm)503         default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
504             return new SurfaceControlViewHost(c, d, wmm, "WindowDecoration");
505         }
506     }
507 
508     /**
509      * Subclass for additional windows associated with this WindowDecoration
510      */
511     static class AdditionalWindow {
512         SurfaceControl mWindowSurface;
513         SurfaceControlViewHost mWindowViewHost;
514         Supplier<SurfaceControl.Transaction> mTransactionSupplier;
515 
AdditionalWindow(SurfaceControl surfaceControl, SurfaceControlViewHost surfaceControlViewHost, Supplier<SurfaceControl.Transaction> transactionSupplier)516         AdditionalWindow(SurfaceControl surfaceControl,
517                 SurfaceControlViewHost surfaceControlViewHost,
518                 Supplier<SurfaceControl.Transaction> transactionSupplier) {
519             mWindowSurface = surfaceControl;
520             mWindowViewHost = surfaceControlViewHost;
521             mTransactionSupplier = transactionSupplier;
522         }
523 
releaseView()524         void releaseView() {
525             WindowlessWindowManager windowManager = mWindowViewHost.getWindowlessWM();
526 
527             if (mWindowViewHost != null) {
528                 mWindowViewHost.release();
529                 mWindowViewHost = null;
530             }
531             windowManager = null;
532             final SurfaceControl.Transaction t = mTransactionSupplier.get();
533             boolean released = false;
534             if (mWindowSurface != null) {
535                 t.remove(mWindowSurface);
536                 mWindowSurface = null;
537                 released = true;
538             }
539             if (released) {
540                 t.apply();
541             }
542         }
543     }
544 }
545