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 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
21 import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
22 import static android.view.WindowInsets.Type.captionBar;
23 import static android.view.WindowInsets.Type.mandatorySystemGestures;
24 import static android.view.WindowInsets.Type.statusBars;
25 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
26 
27 import android.annotation.NonNull;
28 import android.annotation.Nullable;
29 import android.app.ActivityManager.RunningTaskInfo;
30 import android.app.WindowConfiguration.WindowingMode;
31 import android.content.Context;
32 import android.content.res.Configuration;
33 import android.content.res.Resources;
34 import android.graphics.Color;
35 import android.graphics.PixelFormat;
36 import android.graphics.Point;
37 import android.graphics.Rect;
38 import android.graphics.Region;
39 import android.os.Binder;
40 import android.os.Trace;
41 import android.view.Display;
42 import android.view.InsetsSource;
43 import android.view.InsetsState;
44 import android.view.LayoutInflater;
45 import android.view.SurfaceControl;
46 import android.view.SurfaceControlViewHost;
47 import android.view.View;
48 import android.view.WindowManager;
49 import android.view.WindowlessWindowManager;
50 import android.window.SurfaceSyncGroup;
51 import android.window.TaskConstants;
52 import android.window.WindowContainerToken;
53 import android.window.WindowContainerTransaction;
54 
55 import com.android.internal.annotations.VisibleForTesting;
56 import com.android.wm.shell.ShellTaskOrganizer;
57 import com.android.wm.shell.common.DisplayController;
58 import com.android.wm.shell.shared.DesktopModeStatus;
59 import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement;
60 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer;
61 
62 import java.util.ArrayList;
63 import java.util.Arrays;
64 import java.util.List;
65 import java.util.Objects;
66 import java.util.function.Supplier;
67 
68 /**
69  * Manages a container surface and a windowless window to show window decoration. Responsible to
70  * update window decoration window state and layout parameters on task info changes and so that
71  * window decoration is in correct state and bounds.
72  *
73  * The container surface is a child of the task display area in the same display, so that window
74  * decorations can be drawn out of the task bounds and receive input events from out of the task
75  * bounds to support drag resizing.
76  *
77  * The windowless window that hosts window decoration is positioned in front of all activities, to
78  * allow the foreground activity to draw its own background behind window decorations, such as
79  * the window captions.
80  *
81  * @param <T> The type of the root view
82  */
83 public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
84         implements AutoCloseable {
85 
86     /**
87      * The Z-order of {@link #mCaptionContainerSurface}.
88      * <p>
89      * We use {@link #mDecorationContainerSurface} to define input window for task resizing; by
90      * layering it in front of {@link #mCaptionContainerSurface}, we can allow it to handle input
91      * prior to caption view itself, treating corner inputs as resize events rather than
92      * repositioning.
93      */
94     static final int CAPTION_LAYER_Z_ORDER = -1;
95     /**
96      * The Z-order of the task input sink in {@link DragPositioningCallback}.
97      * <p>
98      * This task input sink is used to prevent undesired dispatching of motion events out of task
99      * bounds; by layering it behind {@link #mCaptionContainerSurface}, we allow captions to handle
100      * input events first.
101      */
102     static final int INPUT_SINK_Z_ORDER = -2;
103 
104     /**
105      * System-wide context. Only used to create context with overridden configurations.
106      */
107     final Context mContext;
108     final DisplayController mDisplayController;
109     final ShellTaskOrganizer mTaskOrganizer;
110     final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
111     final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
112     final Supplier<WindowContainerTransaction> mWindowContainerTransactionSupplier;
113     final SurfaceControlViewHostFactory mSurfaceControlViewHostFactory;
114     private final DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener =
115             new DisplayController.OnDisplaysChangedListener() {
116                 @Override
117                 public void onDisplayAdded(int displayId) {
118                     if (mTaskInfo.displayId != displayId) {
119                         return;
120                     }
121 
122                     mDisplayController.removeDisplayWindowListener(this);
123                     relayout(mTaskInfo);
124                 }
125             };
126 
127     RunningTaskInfo mTaskInfo;
128     int mLayoutResId;
129     final SurfaceControl mTaskSurface;
130 
131     Display mDisplay;
132     Context mDecorWindowContext;
133     SurfaceControl mDecorationContainerSurface;
134 
135     SurfaceControl mCaptionContainerSurface;
136     private WindowlessWindowManager mCaptionWindowManager;
137     private SurfaceControlViewHost mViewHost;
138     private Configuration mWindowDecorConfig;
139     TaskDragResizer mTaskDragResizer;
140     private boolean mIsCaptionVisible;
141 
142     /** The most recent set of insets applied to this window decoration. */
143     private WindowDecorationInsets mWindowDecorationInsets;
144     private final Binder mOwner = new Binder();
145     private final float[] mTmpColor = new float[3];
146 
WindowDecoration( Context context, DisplayController displayController, ShellTaskOrganizer taskOrganizer, RunningTaskInfo taskInfo, SurfaceControl taskSurface)147     WindowDecoration(
148             Context context,
149             DisplayController displayController,
150             ShellTaskOrganizer taskOrganizer,
151             RunningTaskInfo taskInfo,
152             SurfaceControl taskSurface) {
153         this(context, displayController, taskOrganizer, taskInfo, taskSurface,
154                 SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
155                 WindowContainerTransaction::new, SurfaceControl::new,
156                 new SurfaceControlViewHostFactory() {});
157     }
158 
WindowDecoration( Context context, DisplayController displayController, ShellTaskOrganizer taskOrganizer, RunningTaskInfo taskInfo, @NonNull SurfaceControl taskSurface, Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, Supplier<SurfaceControl> surfaceControlSupplier, SurfaceControlViewHostFactory surfaceControlViewHostFactory)159     WindowDecoration(
160             Context context,
161             DisplayController displayController,
162             ShellTaskOrganizer taskOrganizer,
163             RunningTaskInfo taskInfo,
164             @NonNull SurfaceControl taskSurface,
165             Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
166             Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
167             Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
168             Supplier<SurfaceControl> surfaceControlSupplier,
169             SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
170         mContext = context;
171         mDisplayController = displayController;
172         mTaskOrganizer = taskOrganizer;
173         mTaskInfo = taskInfo;
174         mTaskSurface = cloneSurfaceControl(taskSurface, surfaceControlSupplier);
175         mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier;
176         mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
177         mWindowContainerTransactionSupplier = windowContainerTransactionSupplier;
178         mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
179 
180         mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
181     }
182 
183     /**
184      * Used by {@link WindowDecoration} to trigger a new relayout because the requirements for a
185      * relayout weren't satisfied are satisfied now.
186      *
187      * @param taskInfo The previous {@link RunningTaskInfo} passed into {@link #relayout} or the
188      *                 constructor.
189      */
relayout(RunningTaskInfo taskInfo)190     abstract void relayout(RunningTaskInfo taskInfo);
191 
192     /**
193      * Used by the {@link DragPositioningCallback} associated with the implementing class to
194      * enforce drags ending in a valid position. A null result means no restriction.
195      */
196     @Nullable
calculateValidDragArea()197     abstract Rect calculateValidDragArea();
198 
relayout(RelayoutParams params, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView, RelayoutResult<T> outResult)199     void relayout(RelayoutParams params, SurfaceControl.Transaction startT,
200             SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView,
201             RelayoutResult<T> outResult) {
202         updateViewsAndSurfaces(params, startT, finishT, wct, rootView, outResult);
203         if (outResult.mRootView != null) {
204             updateViewHost(params, startT, outResult);
205         }
206     }
207 
updateViewsAndSurfaces(RelayoutParams params, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView, RelayoutResult<T> outResult)208     protected void updateViewsAndSurfaces(RelayoutParams params,
209             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
210             WindowContainerTransaction wct, T rootView, RelayoutResult<T> outResult) {
211         outResult.reset();
212         if (params.mRunningTaskInfo != null) {
213             mTaskInfo = params.mRunningTaskInfo;
214         }
215         final int oldLayoutResId = mLayoutResId;
216         mLayoutResId = params.mLayoutResId;
217 
218         if (!mTaskInfo.isVisible) {
219             releaseViews(wct);
220             finishT.hide(mTaskSurface);
221             return;
222         }
223 
224         inflateIfNeeded(params, wct, rootView, oldLayoutResId, outResult);
225         if (outResult.mRootView == null) {
226             // Didn't manage to create a root view, early out.
227             return;
228         }
229         rootView = null; // Clear it just in case we use it accidentally
230 
231         updateCaptionVisibility(outResult.mRootView, mTaskInfo.displayId);
232 
233         final Rect taskBounds = mTaskInfo.getConfiguration().windowConfiguration.getBounds();
234         outResult.mWidth = taskBounds.width();
235         outResult.mHeight = taskBounds.height();
236         outResult.mRootView.setTaskFocusState(mTaskInfo.isFocused);
237         final Resources resources = mDecorWindowContext.getResources();
238         outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
239         outResult.mCaptionWidth = params.mCaptionWidthId != Resources.ID_NULL
240                 ? loadDimensionPixelSize(resources, params.mCaptionWidthId) : taskBounds.width();
241         outResult.mCaptionX = (outResult.mWidth - outResult.mCaptionWidth) / 2;
242 
243         updateDecorationContainerSurface(startT, outResult);
244         updateCaptionContainerSurface(startT, outResult);
245         updateCaptionInsets(params, wct, outResult, taskBounds);
246         updateTaskSurface(params, startT, finishT, outResult);
247     }
248 
inflateIfNeeded(RelayoutParams params, WindowContainerTransaction wct, T rootView, int oldLayoutResId, RelayoutResult<T> outResult)249     private void inflateIfNeeded(RelayoutParams params, WindowContainerTransaction wct,
250             T rootView, int oldLayoutResId, RelayoutResult<T> outResult) {
251         if (rootView == null && params.mLayoutResId == 0) {
252             throw new IllegalArgumentException("layoutResId and rootView can't both be invalid.");
253         }
254 
255         outResult.mRootView = rootView;
256         final int oldDensityDpi = mWindowDecorConfig != null
257                 ? mWindowDecorConfig.densityDpi : DENSITY_DPI_UNDEFINED;
258         final int oldNightMode =  mWindowDecorConfig != null
259                 ? (mWindowDecorConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
260                 : Configuration.UI_MODE_NIGHT_UNDEFINED;
261         mWindowDecorConfig = params.mWindowDecorConfig != null ? params.mWindowDecorConfig
262                 : mTaskInfo.getConfiguration();
263         final int newDensityDpi = mWindowDecorConfig.densityDpi;
264         final int newNightMode =  mWindowDecorConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
265         if (oldDensityDpi != newDensityDpi
266                 || mDisplay == null
267                 || mDisplay.getDisplayId() != mTaskInfo.displayId
268                 || oldLayoutResId != mLayoutResId
269                 || oldNightMode != newNightMode
270                 || mDecorWindowContext == null) {
271             releaseViews(wct);
272 
273             if (!obtainDisplayOrRegisterListener()) {
274                 outResult.mRootView = null;
275                 return;
276             }
277             mDecorWindowContext = mContext.createConfigurationContext(mWindowDecorConfig);
278             mDecorWindowContext.setTheme(mContext.getThemeResId());
279             if (params.mLayoutResId != 0) {
280                 outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
281                         .inflate(params.mLayoutResId, null);
282             }
283         }
284 
285         if (outResult.mRootView == null) {
286             outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext)
287                     .inflate(params.mLayoutResId, null);
288         }
289     }
290 
updateDecorationContainerSurface( SurfaceControl.Transaction startT, RelayoutResult<T> outResult)291     private void updateDecorationContainerSurface(
292             SurfaceControl.Transaction startT, RelayoutResult<T> outResult) {
293         if (mDecorationContainerSurface == null) {
294             final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
295             mDecorationContainerSurface = builder
296                     .setName("Decor container of Task=" + mTaskInfo.taskId)
297                     .setContainerLayer()
298                     .setParent(mTaskSurface)
299                     .setCallsite("WindowDecoration.updateDecorationContainerSurface")
300                     .build();
301 
302             startT.setTrustedOverlay(mDecorationContainerSurface, true)
303                     .setLayer(mDecorationContainerSurface,
304                             TaskConstants.TASK_CHILD_LAYER_WINDOW_DECORATIONS);
305         }
306 
307         startT.setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight)
308                 .show(mDecorationContainerSurface);
309     }
310 
updateCaptionContainerSurface( SurfaceControl.Transaction startT, RelayoutResult<T> outResult)311     private void updateCaptionContainerSurface(
312             SurfaceControl.Transaction startT, RelayoutResult<T> outResult) {
313         if (mCaptionContainerSurface == null) {
314             final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
315             mCaptionContainerSurface = builder
316                     .setName("Caption container of Task=" + mTaskInfo.taskId)
317                     .setContainerLayer()
318                     .setParent(mDecorationContainerSurface)
319                     .setCallsite("WindowDecoration.updateCaptionContainerSurface")
320                     .build();
321         }
322 
323         startT.setWindowCrop(mCaptionContainerSurface, outResult.mCaptionWidth,
324                         outResult.mCaptionHeight)
325                 .setPosition(mCaptionContainerSurface, outResult.mCaptionX, 0 /* y */)
326                 .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
327                 .show(mCaptionContainerSurface);
328     }
329 
updateCaptionInsets(RelayoutParams params, WindowContainerTransaction wct, RelayoutResult<T> outResult, Rect taskBounds)330     private void updateCaptionInsets(RelayoutParams params, WindowContainerTransaction wct,
331             RelayoutResult<T> outResult, Rect taskBounds) {
332         if (!mIsCaptionVisible) {
333             if (mWindowDecorationInsets != null) {
334                 mWindowDecorationInsets.remove(wct);
335                 mWindowDecorationInsets = null;
336             }
337             return;
338         }
339         // Caption inset is the full width of the task with the |captionHeight| and
340         // positioned at the top of the task bounds, also in absolute coordinates.
341         // So just reuse the task bounds and adjust the bottom coordinate.
342         final Rect captionInsetsRect = new Rect(taskBounds);
343         captionInsetsRect.bottom = captionInsetsRect.top + outResult.mCaptionHeight;
344 
345         // Caption bounding rectangles: these are optional, and are used to present finer
346         // insets than traditional |Insets| to apps about where their content is occluded.
347         // These are also in absolute coordinates.
348         final Rect[] boundingRects;
349         final int numOfElements = params.mOccludingCaptionElements.size();
350         if (numOfElements == 0) {
351             boundingRects = null;
352         } else {
353             // The customizable region can at most be equal to the caption bar.
354             if (params.hasInputFeatureSpy()) {
355                 outResult.mCustomizableCaptionRegion.set(captionInsetsRect);
356             }
357             final Resources resources = mDecorWindowContext.getResources();
358             boundingRects = new Rect[numOfElements];
359             for (int i = 0; i < numOfElements; i++) {
360                 final OccludingCaptionElement element =
361                         params.mOccludingCaptionElements.get(i);
362                 final int elementWidthPx =
363                         resources.getDimensionPixelSize(element.mWidthResId);
364                 boundingRects[i] =
365                         calculateBoundingRect(element, elementWidthPx, captionInsetsRect);
366                 // Subtract the regions used by the caption elements, the rest is
367                 // customizable.
368                 if (params.hasInputFeatureSpy()) {
369                     outResult.mCustomizableCaptionRegion.op(boundingRects[i],
370                             Region.Op.DIFFERENCE);
371                 }
372             }
373         }
374 
375         final WindowDecorationInsets newInsets = new WindowDecorationInsets(
376                 mTaskInfo.token, mOwner, captionInsetsRect, boundingRects);
377         if (!newInsets.equals(mWindowDecorationInsets)) {
378             // Add or update this caption as an insets source.
379             mWindowDecorationInsets = newInsets;
380             mWindowDecorationInsets.addOrUpdate(wct);
381         }
382     }
383 
updateTaskSurface(RelayoutParams params, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, RelayoutResult<T> outResult)384     private void updateTaskSurface(RelayoutParams params, SurfaceControl.Transaction startT,
385             SurfaceControl.Transaction finishT, RelayoutResult<T> outResult) {
386         if (params.mSetTaskPositionAndCrop) {
387             final Point taskPosition = mTaskInfo.positionInParent;
388             startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
389             finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight)
390                     .setPosition(mTaskSurface, taskPosition.x, taskPosition.y);
391         }
392 
393         float shadowRadius;
394         if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
395             // Shadow is not needed for fullscreen tasks
396             shadowRadius = 0;
397         } else {
398             shadowRadius =
399                     loadDimension(mDecorWindowContext.getResources(), params.mShadowRadiusId);
400         }
401         startT.setShadowRadius(mTaskSurface, shadowRadius).show(mTaskSurface);
402         finishT.setShadowRadius(mTaskSurface, shadowRadius);
403 
404         if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
405             if (!DesktopModeStatus.isVeiledResizeEnabled()) {
406                 // When fluid resize is enabled, add a background to freeform tasks
407                 int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
408                 mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
409                 mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
410                 mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
411                 startT.setColor(mTaskSurface, mTmpColor);
412             }
413             startT.setCornerRadius(mTaskSurface, params.mCornerRadius);
414             finishT.setCornerRadius(mTaskSurface, params.mCornerRadius);
415         } else if (!DesktopModeStatus.isVeiledResizeEnabled()) {
416             startT.unsetColor(mTaskSurface);
417         }
418     }
419 
420     /**
421      * Updates a {@link SurfaceControlViewHost} to connect the window decoration surfaces with our
422      * View hierarchy.
423      *
424      * @param params parameters to use from the last relayout
425      * @param onDrawTransaction a transaction to apply in sync with #onDraw
426      * @param outResult results to use from the last relayout
427      *
428      */
updateViewHost(RelayoutParams params, SurfaceControl.Transaction onDrawTransaction, RelayoutResult<T> outResult)429     protected void updateViewHost(RelayoutParams params,
430             SurfaceControl.Transaction onDrawTransaction, RelayoutResult<T> outResult) {
431         Trace.beginSection("CaptionViewHostLayout");
432         if (mCaptionWindowManager == null) {
433             // Put caption under a container surface because ViewRootImpl sets the destination frame
434             // of windowless window layers and BLASTBufferQueue#update() doesn't support offset.
435             mCaptionWindowManager = new WindowlessWindowManager(
436                     mTaskInfo.getConfiguration(), mCaptionContainerSurface,
437                     null /* hostInputToken */);
438         }
439         mCaptionWindowManager.setConfiguration(mTaskInfo.getConfiguration());
440         final WindowManager.LayoutParams lp =
441                 new WindowManager.LayoutParams(outResult.mCaptionWidth, outResult.mCaptionHeight,
442                         TYPE_APPLICATION,
443                         WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
444         lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
445         lp.setTrustedOverlay();
446         lp.inputFeatures = params.mInputFeatures;
447         if (mViewHost == null) {
448             Trace.beginSection("CaptionViewHostLayout-new");
449             mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay,
450                     mCaptionWindowManager);
451             if (params.mApplyStartTransactionOnDraw) {
452                 if (onDrawTransaction == null) {
453                     throw new IllegalArgumentException("Trying to sync a null Transaction");
454                 }
455                 mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction);
456             }
457             mViewHost.setView(outResult.mRootView, lp);
458             Trace.endSection();
459         } else {
460             Trace.beginSection("CaptionViewHostLayout-relayout");
461             if (params.mApplyStartTransactionOnDraw) {
462                 if (onDrawTransaction == null) {
463                     throw new IllegalArgumentException("Trying to sync a null Transaction");
464                 }
465                 mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction);
466             }
467             mViewHost.relayout(lp);
468             Trace.endSection();
469         }
470         Trace.endSection(); // CaptionViewHostLayout
471     }
472 
calculateBoundingRect(@onNull OccludingCaptionElement element, int elementWidthPx, @NonNull Rect captionRect)473     private Rect calculateBoundingRect(@NonNull OccludingCaptionElement element,
474             int elementWidthPx, @NonNull Rect captionRect) {
475         switch (element.mAlignment) {
476             case START -> {
477                 return new Rect(0, 0, elementWidthPx, captionRect.height());
478             }
479             case END -> {
480                 return new Rect(captionRect.width() - elementWidthPx, 0,
481                         captionRect.width(), captionRect.height());
482             }
483         }
484         throw new IllegalArgumentException("Unexpected alignment " + element.mAlignment);
485     }
486 
487     /**
488      * Checks if task has entered/exited immersive mode and requires a change in caption visibility.
489      */
updateCaptionVisibility(View rootView, int displayId)490     private void updateCaptionVisibility(View rootView, int displayId) {
491         final InsetsState insetsState = mDisplayController.getInsetsState(displayId);
492         for (int i = 0; i < insetsState.sourceSize(); i++) {
493             final InsetsSource source = insetsState.sourceAt(i);
494             if (source.getType() != statusBars()) {
495                 continue;
496             }
497 
498             mIsCaptionVisible = source.isVisible();
499             setCaptionVisibility(rootView, mIsCaptionVisible);
500 
501             return;
502         }
503     }
504 
setTaskDragResizer(TaskDragResizer taskDragResizer)505     void setTaskDragResizer(TaskDragResizer taskDragResizer) {
506         mTaskDragResizer = taskDragResizer;
507     }
508 
setCaptionVisibility(View rootView, boolean visible)509     private void setCaptionVisibility(View rootView, boolean visible) {
510         if (rootView == null) {
511             return;
512         }
513         final int v = visible ? View.VISIBLE : View.GONE;
514         final View captionView = rootView.findViewById(getCaptionViewId());
515         captionView.setVisibility(v);
516     }
517 
getCaptionHeightId(@indowingMode int windowingMode)518     int getCaptionHeightId(@WindowingMode int windowingMode) {
519         return Resources.ID_NULL;
520     }
521 
getCaptionViewId()522     int getCaptionViewId() {
523         return Resources.ID_NULL;
524     }
525 
526     /**
527      * Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or
528      * registers {@link #mOnDisplaysChangedListener} if it doesn't.
529      *
530      * @return {@code true} if the {@link Display} instance exists; or {@code false} otherwise
531      */
obtainDisplayOrRegisterListener()532     private boolean obtainDisplayOrRegisterListener() {
533         mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
534         if (mDisplay == null) {
535             mDisplayController.addDisplayWindowListener(mOnDisplaysChangedListener);
536             return false;
537         }
538         return true;
539     }
540 
releaseViews(WindowContainerTransaction wct)541     void releaseViews(WindowContainerTransaction wct) {
542         if (mViewHost != null) {
543             mViewHost.release();
544             mViewHost = null;
545         }
546 
547         mCaptionWindowManager = null;
548 
549         final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
550         boolean released = false;
551         if (mCaptionContainerSurface != null) {
552             t.remove(mCaptionContainerSurface);
553             mCaptionContainerSurface = null;
554             released = true;
555         }
556 
557         if (mDecorationContainerSurface != null) {
558             t.remove(mDecorationContainerSurface);
559             mDecorationContainerSurface = null;
560             released = true;
561         }
562 
563         if (released) {
564             t.apply();
565         }
566 
567         if (mWindowDecorationInsets != null) {
568             mWindowDecorationInsets.remove(wct);
569             mWindowDecorationInsets = null;
570         }
571     }
572 
573     @Override
close()574     public void close() {
575         Trace.beginSection("WindowDecoration#close");
576         mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
577         final WindowContainerTransaction wct = mWindowContainerTransactionSupplier.get();
578         releaseViews(wct);
579         mTaskOrganizer.applyTransaction(wct);
580         mTaskSurface.release();
581         Trace.endSection();
582     }
583 
loadDimensionPixelSize(Resources resources, int resourceId)584     static int loadDimensionPixelSize(Resources resources, int resourceId) {
585         if (resourceId == Resources.ID_NULL) {
586             return 0;
587         }
588         return resources.getDimensionPixelSize(resourceId);
589     }
590 
loadDimension(Resources resources, int resourceId)591     static float loadDimension(Resources resources, int resourceId) {
592         if (resourceId == Resources.ID_NULL) {
593             return 0;
594         }
595         return resources.getDimension(resourceId);
596     }
597 
cloneSurfaceControl(SurfaceControl sc, Supplier<SurfaceControl> surfaceControlSupplier)598     private static SurfaceControl cloneSurfaceControl(SurfaceControl sc,
599             Supplier<SurfaceControl> surfaceControlSupplier) {
600         final SurfaceControl copy = surfaceControlSupplier.get();
601         copy.copyFrom(sc, "WindowDecoration");
602         return copy;
603     }
604 
605     /**
606      * Create a window associated with this WindowDecoration.
607      * Note that subclass must dispose of this when the task is hidden/closed.
608      *
609      * @param layoutId     layout to make the window from
610      * @param t            the transaction to apply
611      * @param xPos         x position of new window
612      * @param yPos         y position of new window
613      * @param width        width of new window
614      * @param height       height of new window
615      * @return the {@link AdditionalViewHostViewContainer} that was added.
616      */
addWindow(int layoutId, String namePrefix, SurfaceControl.Transaction t, SurfaceSyncGroup ssg, int xPos, int yPos, int width, int height)617     AdditionalViewHostViewContainer addWindow(int layoutId, String namePrefix,
618             SurfaceControl.Transaction t, SurfaceSyncGroup ssg, int xPos, int yPos,
619             int width, int height) {
620         final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
621         SurfaceControl windowSurfaceControl = builder
622                 .setName(namePrefix + " of Task=" + mTaskInfo.taskId)
623                 .setContainerLayer()
624                 .setParent(mDecorationContainerSurface)
625                 .setCallsite("WindowDecoration.addWindow")
626                 .build();
627         View v = LayoutInflater.from(mDecorWindowContext).inflate(layoutId, null);
628 
629         t.setPosition(windowSurfaceControl, xPos, yPos)
630                 .setWindowCrop(windowSurfaceControl, width, height)
631                 .show(windowSurfaceControl);
632         final WindowManager.LayoutParams lp =
633                 new WindowManager.LayoutParams(width, height, TYPE_APPLICATION,
634                         WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
635                         PixelFormat.TRANSPARENT);
636         lp.setTitle("Additional window of Task=" + mTaskInfo.taskId);
637         lp.setTrustedOverlay();
638         WindowlessWindowManager windowManager = new WindowlessWindowManager(mTaskInfo.configuration,
639                 windowSurfaceControl, null /* hostInputToken */);
640         SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory
641                 .create(mDecorWindowContext, mDisplay, windowManager);
642         ssg.add(viewHost.getSurfacePackage(), () -> viewHost.setView(v, lp));
643         return new AdditionalViewHostViewContainer(windowSurfaceControl, viewHost,
644                 mSurfaceControlTransactionSupplier);
645     }
646 
647     /**
648      * Adds caption inset source to a WCT
649      */
addCaptionInset(WindowContainerTransaction wct)650     public void addCaptionInset(WindowContainerTransaction wct) {
651         final int captionHeightId = getCaptionHeightId(mTaskInfo.getWindowingMode());
652         if (captionHeightId == Resources.ID_NULL || !mIsCaptionVisible) {
653             return;
654         }
655 
656         final int captionHeight = loadDimensionPixelSize(mContext.getResources(), captionHeightId);
657         final Rect captionInsets = new Rect(0, 0, 0, captionHeight);
658         final WindowDecorationInsets newInsets = new WindowDecorationInsets(mTaskInfo.token,
659                 mOwner, captionInsets, null /* boundingRets */);
660         if (!newInsets.equals(mWindowDecorationInsets)) {
661             mWindowDecorationInsets = newInsets;
662             mWindowDecorationInsets.addOrUpdate(wct);
663         }
664     }
665 
666     static class RelayoutParams {
667         RunningTaskInfo mRunningTaskInfo;
668         int mLayoutResId;
669         int mCaptionHeightId;
670         int mCaptionWidthId;
671         final List<OccludingCaptionElement> mOccludingCaptionElements = new ArrayList<>();
672         int mInputFeatures;
673 
674         int mShadowRadiusId;
675         int mCornerRadius;
676 
677         Configuration mWindowDecorConfig;
678 
679         boolean mApplyStartTransactionOnDraw;
680         boolean mSetTaskPositionAndCrop;
681 
reset()682         void reset() {
683             mLayoutResId = Resources.ID_NULL;
684             mCaptionHeightId = Resources.ID_NULL;
685             mCaptionWidthId = Resources.ID_NULL;
686             mOccludingCaptionElements.clear();
687             mInputFeatures = 0;
688 
689             mShadowRadiusId = Resources.ID_NULL;
690             mCornerRadius = 0;
691 
692             mApplyStartTransactionOnDraw = false;
693             mSetTaskPositionAndCrop = false;
694             mWindowDecorConfig = null;
695         }
696 
hasInputFeatureSpy()697         boolean hasInputFeatureSpy() {
698             return (mInputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_SPY) != 0;
699         }
700 
701         /**
702          * Describes elements within the caption bar that could occlude app content, and should be
703          * sent as bounding rectangles to the insets system.
704          */
705         static class OccludingCaptionElement {
706             int mWidthResId;
707             Alignment mAlignment;
708 
709             enum Alignment {
710                 START, END
711             }
712         }
713     }
714 
715     static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
716         int mCaptionHeight;
717         int mCaptionWidth;
718         int mCaptionX;
719         final Region mCustomizableCaptionRegion = Region.obtain();
720         int mWidth;
721         int mHeight;
722         T mRootView;
723 
reset()724         void reset() {
725             mWidth = 0;
726             mHeight = 0;
727             mCaptionHeight = 0;
728             mCaptionWidth = 0;
729             mCaptionX = 0;
730             mCustomizableCaptionRegion.setEmpty();
731             mRootView = null;
732         }
733     }
734 
735     @VisibleForTesting
736     public interface SurfaceControlViewHostFactory {
create(Context c, Display d, WindowlessWindowManager wmm)737         default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
738             return new SurfaceControlViewHost(c, d, wmm, "WindowDecoration");
739         }
create(Context c, Display d, WindowlessWindowManager wmm, String callsite)740         default SurfaceControlViewHost create(Context c, Display d,
741                 WindowlessWindowManager wmm, String callsite) {
742             return new SurfaceControlViewHost(c, d, wmm, callsite);
743         }
744     }
745 
746     private static class WindowDecorationInsets {
747         private static final int INDEX = 0;
748         private final WindowContainerToken mToken;
749         private final Binder mOwner;
750         private final Rect mFrame;
751         private final Rect[] mBoundingRects;
752 
WindowDecorationInsets(WindowContainerToken token, Binder owner, Rect frame, Rect[] boundingRects)753         private WindowDecorationInsets(WindowContainerToken token, Binder owner, Rect frame,
754                 Rect[] boundingRects) {
755             mToken = token;
756             mOwner = owner;
757             mFrame = frame;
758             mBoundingRects = boundingRects;
759         }
760 
addOrUpdate(WindowContainerTransaction wct)761         void addOrUpdate(WindowContainerTransaction wct) {
762             wct.addInsetsSource(mToken, mOwner, INDEX, captionBar(), mFrame, mBoundingRects);
763             wct.addInsetsSource(mToken, mOwner, INDEX, mandatorySystemGestures(), mFrame,
764                     mBoundingRects);
765         }
766 
remove(WindowContainerTransaction wct)767         void remove(WindowContainerTransaction wct) {
768             wct.removeInsetsSource(mToken, mOwner, INDEX, captionBar());
769             wct.removeInsetsSource(mToken, mOwner, INDEX, mandatorySystemGestures());
770         }
771 
772         @Override
equals(Object o)773         public boolean equals(Object o) {
774             if (this == o) return true;
775             if (!(o instanceof WindowDecoration.WindowDecorationInsets that)) return false;
776             return Objects.equals(mToken, that.mToken) && Objects.equals(mOwner,
777                     that.mOwner) && Objects.equals(mFrame, that.mFrame)
778                     && Objects.deepEquals(mBoundingRects, that.mBoundingRects);
779         }
780 
781         @Override
hashCode()782         public int hashCode() {
783             return Objects.hash(mToken, mOwner, mFrame, Arrays.hashCode(mBoundingRects));
784         }
785     }
786 }
787