• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.app.WindowConfiguration.windowingModeToString;
22 import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
23 import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
24 import static android.view.MotionEvent.ACTION_CANCEL;
25 import static android.view.MotionEvent.ACTION_DOWN;
26 import static android.view.MotionEvent.ACTION_UP;
27 import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION;
28 import static android.window.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS;
29 
30 import static com.android.internal.policy.SystemBarUtils.getDesktopViewAppHeaderHeightId;
31 import static com.android.wm.shell.shared.desktopmode.DesktopModeStatus.canEnterDesktopMode;
32 import static com.android.wm.shell.shared.desktopmode.DesktopModeStatus.canEnterDesktopModeOrShowAppHandle;
33 import static com.android.wm.shell.shared.desktopmode.DesktopModeStatus.isDesktopModeSupportedOnDisplay;
34 import static com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON;
35 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
36 import static com.android.wm.shell.windowdecor.DragPositioningCallbackUtility.DragEventListener;
37 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.DisabledEdge;
38 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.DisabledEdge.NONE;
39 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
40 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
41 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
42 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeHandleEdgeInset;
43 
44 import android.annotation.NonNull;
45 import android.annotation.Nullable;
46 import android.app.ActivityManager;
47 import android.app.WindowConfiguration.WindowingMode;
48 import android.app.assist.AssistContent;
49 import android.content.ComponentName;
50 import android.content.Context;
51 import android.content.Intent;
52 import android.content.res.Configuration;
53 import android.content.res.Resources;
54 import android.graphics.Bitmap;
55 import android.graphics.Insets;
56 import android.graphics.Point;
57 import android.graphics.PointF;
58 import android.graphics.Rect;
59 import android.graphics.Region;
60 import android.net.Uri;
61 import android.os.Handler;
62 import android.os.Trace;
63 import android.os.UserHandle;
64 import android.util.Size;
65 import android.view.Choreographer;
66 import android.view.InsetsState;
67 import android.view.MotionEvent;
68 import android.view.SurfaceControl;
69 import android.view.View;
70 import android.view.ViewConfiguration;
71 import android.view.WindowInsets;
72 import android.view.WindowManager;
73 import android.view.WindowManagerGlobal;
74 import android.widget.ImageButton;
75 import android.window.DesktopExperienceFlags;
76 import android.window.DesktopModeFlags;
77 import android.window.TaskSnapshot;
78 import android.window.WindowContainerTransaction;
79 
80 import com.android.internal.annotations.VisibleForTesting;
81 import com.android.window.flags.Flags;
82 import com.android.wm.shell.R;
83 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
84 import com.android.wm.shell.ShellTaskOrganizer;
85 import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
86 import com.android.wm.shell.apptoweb.AppToWebUtils;
87 import com.android.wm.shell.apptoweb.AssistContentRequester;
88 import com.android.wm.shell.apptoweb.OpenByDefaultDialog;
89 import com.android.wm.shell.common.DisplayController;
90 import com.android.wm.shell.common.DisplayLayout;
91 import com.android.wm.shell.common.MultiInstanceHelper;
92 import com.android.wm.shell.common.ShellExecutor;
93 import com.android.wm.shell.common.SyncTransactionQueue;
94 import com.android.wm.shell.desktopmode.CaptionState;
95 import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
96 import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger;
97 import com.android.wm.shell.desktopmode.DesktopModeUtils;
98 import com.android.wm.shell.desktopmode.DesktopUserRepositories;
99 import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
100 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
101 import com.android.wm.shell.shared.annotations.ShellMainThread;
102 import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy;
103 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
104 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
105 import com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer;
106 import com.android.wm.shell.splitscreen.SplitScreenController;
107 import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader;
108 import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
109 import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
110 import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
111 import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
112 import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
113 import com.android.wm.shell.windowdecor.viewholder.WindowDecorationViewHolder;
114 
115 import kotlin.Pair;
116 import kotlin.Unit;
117 import kotlin.jvm.functions.Function0;
118 import kotlin.jvm.functions.Function1;
119 
120 import kotlinx.coroutines.CoroutineScope;
121 import kotlinx.coroutines.MainCoroutineDispatcher;
122 
123 import java.util.List;
124 import java.util.function.BiConsumer;
125 import java.util.function.Consumer;
126 import java.util.function.Supplier;
127 
128 /**
129  * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
130  * {@link DesktopModeWindowDecorViewModel}.
131  *
132  * The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.
133  */
134 public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
135     private static final String TAG = "DesktopModeWindowDecoration";
136 
137     @VisibleForTesting
138     static final long CLOSE_MAXIMIZE_MENU_DELAY_MS = 150L;
139 
140     private final @ShellMainThread Handler mHandler;
141     private final @ShellMainThread ShellExecutor mMainExecutor;
142     private final @ShellMainThread MainCoroutineDispatcher mMainDispatcher;
143     private final @ShellBackgroundThread CoroutineScope mBgScope;
144     private final @ShellBackgroundThread ShellExecutor mBgExecutor;
145     private final Choreographer mChoreographer;
146     private final SyncTransactionQueue mSyncQueue;
147     private final SplitScreenController mSplitScreenController;
148     private final WindowManagerWrapper mWindowManagerWrapper;
149     private final @NonNull WindowDecorTaskResourceLoader mTaskResourceLoader;
150 
151     private WindowDecorationViewHolder mWindowDecorViewHolder;
152     private View.OnClickListener mOnCaptionButtonClickListener;
153     private View.OnTouchListener mOnCaptionTouchListener;
154     private View.OnLongClickListener mOnCaptionLongClickListener;
155     private View.OnGenericMotionListener mOnCaptionGenericMotionListener;
156     private Function0<Unit> mOnMaximizeOrRestoreClickListener;
157     private Function0<Unit> mOnImmersiveOrRestoreClickListener;
158     private Function0<Unit> mOnLeftSnapClickListener;
159     private Function0<Unit> mOnRightSnapClickListener;
160     private Consumer<DesktopModeTransitionSource> mOnToDesktopClickListener;
161     private Function0<Unit> mOnToFullscreenClickListener;
162     private Function0<Unit> mOnToSplitscreenClickListener;
163     private Function0<Unit> mOnToFloatClickListener;
164     private Function0<Unit> mOnNewWindowClickListener;
165     private Function0<Unit> mOnManageWindowsClickListener;
166     private Function0<Unit> mOnChangeAspectRatioClickListener;
167     private Function0<Unit> mOnRestartClickListener;
168     private Function0<Unit> mOnMaximizeHoverListener;
169     private DragPositioningCallback mDragPositioningCallback;
170     private DragResizeInputListener mDragResizeListener;
171     private RelayoutParams mRelayoutParams = new RelayoutParams();
172     private DisabledEdge mDisabledResizingEdge =
173             NONE;
174     private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
175             new WindowDecoration.RelayoutResult<>();
176 
177     private final Point mPositionInParent = new Point();
178     private HandleMenu mHandleMenu;
179     private boolean mMinimumInstancesFound;
180     private ManageWindowsViewContainer mManageWindowsMenu;
181 
182     private MaximizeMenu mMaximizeMenu;
183 
184     private OpenByDefaultDialog mOpenByDefaultDialog;
185 
186     private ResizeVeil mResizeVeil;
187 
188     private CapturedLink mCapturedLink;
189     private Uri mGenericLink;
190     private Uri mWebUri;
191     private Consumer<Intent> mOpenInBrowserClickListener;
192 
193     private ExclusionRegionListener mExclusionRegionListener;
194 
195     private final AppHeaderViewHolder.Factory mAppHeaderViewHolderFactory;
196     private final AppHandleViewHolder.Factory mAppHandleViewHolderFactory;
197     private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
198     private final MaximizeMenuFactory mMaximizeMenuFactory;
199     private final HandleMenuFactory mHandleMenuFactory;
200     private final AppToWebGenericLinksParser mGenericLinksParser;
201     private final AssistContentRequester mAssistContentRequester;
202     private final DesktopModeCompatPolicy mDesktopModeCompatPolicy;
203 
204     // Hover state for the maximize menu and button. The menu will remain open as long as either of
205     // these is true. See {@link #onMaximizeHoverStateChanged()}.
206     private boolean mIsAppHeaderMaximizeButtonHovered = false;
207     private boolean mIsMaximizeMenuHovered = false;
208     // Used to schedule the closing of the maximize menu when neither of the button or menu are
209     // being hovered. There's a small delay after stopping the hover, to allow a quick reentry
210     // to cancel the close.
211     private final Runnable mCloseMaximizeWindowRunnable = this::closeMaximizeMenu;
212     private final MultiInstanceHelper mMultiInstanceHelper;
213     private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
214     private final DesktopUserRepositories mDesktopUserRepositories;
215     private final DesktopModeUiEventLogger mDesktopModeUiEventLogger;
216     private boolean mIsRecentsTransitionRunning = false;
217     private boolean mIsDragging = false;
218     private Runnable mLoadAppInfoRunnable;
219     private Runnable mSetAppInfoRunnable;
220 
DesktopModeWindowDecoration( Context context, @NonNull Context userContext, DisplayController displayController, @NonNull WindowDecorTaskResourceLoader taskResourceLoader, SplitScreenController splitScreenController, DesktopUserRepositories desktopUserRepositories, ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, @ShellMainThread Handler handler, @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread MainCoroutineDispatcher mainDispatcher, @ShellBackgroundThread CoroutineScope bgScope, @ShellBackgroundThread ShellExecutor bgExecutor, Choreographer choreographer, SyncTransactionQueue syncQueue, AppHeaderViewHolder.Factory appHeaderViewHolderFactory, AppHandleViewHolder.Factory appHandleViewHolderFactory, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, AppToWebGenericLinksParser genericLinksParser, AssistContentRequester assistContentRequester, @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier, MultiInstanceHelper multiInstanceHelper, WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository, DesktopModeEventLogger desktopModeEventLogger, DesktopModeUiEventLogger desktopModeUiEventLogger, DesktopModeCompatPolicy desktopModeCompatPolicy)221     public DesktopModeWindowDecoration(
222             Context context,
223             @NonNull Context userContext,
224             DisplayController displayController,
225             @NonNull WindowDecorTaskResourceLoader taskResourceLoader,
226             SplitScreenController splitScreenController,
227             DesktopUserRepositories desktopUserRepositories,
228             ShellTaskOrganizer taskOrganizer,
229             ActivityManager.RunningTaskInfo taskInfo,
230             SurfaceControl taskSurface,
231             @ShellMainThread Handler handler,
232             @ShellMainThread ShellExecutor mainExecutor,
233             @ShellMainThread MainCoroutineDispatcher mainDispatcher,
234             @ShellBackgroundThread CoroutineScope bgScope,
235             @ShellBackgroundThread ShellExecutor bgExecutor,
236             Choreographer choreographer,
237             SyncTransactionQueue syncQueue,
238             AppHeaderViewHolder.Factory appHeaderViewHolderFactory,
239             AppHandleViewHolder.Factory appHandleViewHolderFactory,
240             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
241             AppToWebGenericLinksParser genericLinksParser,
242             AssistContentRequester assistContentRequester,
243             @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
244             MultiInstanceHelper multiInstanceHelper,
245             WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
246             DesktopModeEventLogger desktopModeEventLogger,
247             DesktopModeUiEventLogger desktopModeUiEventLogger,
248             DesktopModeCompatPolicy desktopModeCompatPolicy) {
249         this (context, userContext, displayController, taskResourceLoader, splitScreenController,
250                 desktopUserRepositories, taskOrganizer, taskInfo, taskSurface, handler,
251                 mainExecutor, mainDispatcher, bgScope, bgExecutor, choreographer, syncQueue,
252                 appHeaderViewHolderFactory, appHandleViewHolderFactory,
253                 rootTaskDisplayAreaOrganizer, genericLinksParser, assistContentRequester,
254                 SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
255                 WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper(
256                         context.getSystemService(WindowManager.class)),
257                 new SurfaceControlViewHostFactory() {},
258                 windowDecorViewHostSupplier,
259                 DefaultMaximizeMenuFactory.INSTANCE,
260                 DefaultHandleMenuFactory.INSTANCE, multiInstanceHelper,
261                 windowDecorCaptionHandleRepository, desktopModeEventLogger,
262                 desktopModeUiEventLogger, desktopModeCompatPolicy);
263     }
264 
DesktopModeWindowDecoration( Context context, @NonNull Context userContext, DisplayController displayController, @NonNull WindowDecorTaskResourceLoader taskResourceLoader, SplitScreenController splitScreenController, DesktopUserRepositories desktopUserRepositories, ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, @ShellMainThread Handler handler, @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread MainCoroutineDispatcher mainDispatcher, @ShellBackgroundThread CoroutineScope bgScope, @ShellBackgroundThread ShellExecutor bgExecutor, Choreographer choreographer, SyncTransactionQueue syncQueue, AppHeaderViewHolder.Factory appHeaderViewHolderFactory, AppHandleViewHolder.Factory appHandleViewHolderFactory, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, AppToWebGenericLinksParser genericLinksParser, AssistContentRequester assistContentRequester, Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, Supplier<SurfaceControl> surfaceControlSupplier, WindowManagerWrapper windowManagerWrapper, SurfaceControlViewHostFactory surfaceControlViewHostFactory, @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier, MaximizeMenuFactory maximizeMenuFactory, HandleMenuFactory handleMenuFactory, MultiInstanceHelper multiInstanceHelper, WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository, DesktopModeEventLogger desktopModeEventLogger, DesktopModeUiEventLogger desktopModeUiEventLogger, DesktopModeCompatPolicy desktopModeCompatPolicy)265     DesktopModeWindowDecoration(
266             Context context,
267             @NonNull Context userContext,
268             DisplayController displayController,
269             @NonNull WindowDecorTaskResourceLoader taskResourceLoader,
270             SplitScreenController splitScreenController,
271             DesktopUserRepositories desktopUserRepositories,
272             ShellTaskOrganizer taskOrganizer,
273             ActivityManager.RunningTaskInfo taskInfo,
274             SurfaceControl taskSurface,
275             @ShellMainThread Handler handler,
276             @ShellMainThread ShellExecutor mainExecutor,
277             @ShellMainThread MainCoroutineDispatcher mainDispatcher,
278             @ShellBackgroundThread CoroutineScope bgScope,
279             @ShellBackgroundThread ShellExecutor bgExecutor,
280             Choreographer choreographer,
281             SyncTransactionQueue syncQueue,
282             AppHeaderViewHolder.Factory appHeaderViewHolderFactory,
283             AppHandleViewHolder.Factory appHandleViewHolderFactory,
284             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
285             AppToWebGenericLinksParser genericLinksParser,
286             AssistContentRequester assistContentRequester,
287             Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
288             Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
289             Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
290             Supplier<SurfaceControl> surfaceControlSupplier,
291             WindowManagerWrapper windowManagerWrapper,
292             SurfaceControlViewHostFactory surfaceControlViewHostFactory,
293             @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
294             MaximizeMenuFactory maximizeMenuFactory,
295             HandleMenuFactory handleMenuFactory,
296             MultiInstanceHelper multiInstanceHelper,
297             WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
298             DesktopModeEventLogger desktopModeEventLogger,
299             DesktopModeUiEventLogger desktopModeUiEventLogger,
300             DesktopModeCompatPolicy desktopModeCompatPolicy) {
301         super(context, userContext, displayController, taskOrganizer, taskInfo,
302                 taskSurface, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
303                 windowContainerTransactionSupplier, surfaceControlSupplier,
304                 surfaceControlViewHostFactory, windowDecorViewHostSupplier, desktopModeEventLogger);
305         mSplitScreenController = splitScreenController;
306         mHandler = handler;
307         mMainExecutor = mainExecutor;
308         mMainDispatcher = mainDispatcher;
309         mBgScope = bgScope;
310         mBgExecutor = bgExecutor;
311         mChoreographer = choreographer;
312         mSyncQueue = syncQueue;
313         mAppHeaderViewHolderFactory = appHeaderViewHolderFactory;
314         mAppHandleViewHolderFactory = appHandleViewHolderFactory;
315         mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
316         mGenericLinksParser = genericLinksParser;
317         mAssistContentRequester = assistContentRequester;
318         mMaximizeMenuFactory = maximizeMenuFactory;
319         mHandleMenuFactory = handleMenuFactory;
320         mMultiInstanceHelper = multiInstanceHelper;
321         mWindowManagerWrapper = windowManagerWrapper;
322         mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository;
323         mDesktopUserRepositories = desktopUserRepositories;
324         mTaskResourceLoader = taskResourceLoader;
325         mTaskResourceLoader.onWindowDecorCreated(taskInfo);
326         mDesktopModeCompatPolicy = desktopModeCompatPolicy;
327         mDesktopModeUiEventLogger = desktopModeUiEventLogger;
328     }
329 
330     /**
331      * Register a listener to be called back when one of the tasks' maximize/restore action is
332      * triggered.
333      * TODO(b/346441962): hook this up to double-tap and the header's maximize button, instead of
334      *  having the ViewModel deal with parsing motion events.
335      */
setOnMaximizeOrRestoreClickListener(Function0<Unit> listener)336     void setOnMaximizeOrRestoreClickListener(Function0<Unit> listener) {
337         mOnMaximizeOrRestoreClickListener = listener;
338     }
339 
340     /**
341      * Registers a listener to be called back when one of the tasks' immersive/restore action is
342      * triggered.
343      */
setOnImmersiveOrRestoreClickListener(Function0<Unit> listener)344     void setOnImmersiveOrRestoreClickListener(Function0<Unit> listener) {
345         mOnImmersiveOrRestoreClickListener = listener;
346     }
347 
348     /** Registers a listener to be called when the decoration's snap-left action is triggered.*/
setOnLeftSnapClickListener(Function0<Unit> listener)349     void setOnLeftSnapClickListener(Function0<Unit> listener) {
350         mOnLeftSnapClickListener = listener;
351     }
352 
353     /** Registers a listener to be called when the decoration's snap-right action is triggered. */
setOnRightSnapClickListener(Function0<Unit> listener)354     void setOnRightSnapClickListener(Function0<Unit> listener) {
355         mOnRightSnapClickListener = listener;
356     }
357 
358     /** Registers a listener to be called when the decoration's to-desktop action is triggered. */
setOnToDesktopClickListener(Consumer<DesktopModeTransitionSource> listener)359     void setOnToDesktopClickListener(Consumer<DesktopModeTransitionSource> listener) {
360         mOnToDesktopClickListener = listener;
361     }
362 
363     /**
364      * Registers a listener to be called when the decoration's to-fullscreen action is triggered.
365      */
setOnToFullscreenClickListener(Function0<Unit> listener)366     void setOnToFullscreenClickListener(Function0<Unit> listener) {
367         mOnToFullscreenClickListener = listener;
368     }
369 
370     /** Registers a listener to be called when the decoration's to-split action is triggered. */
setOnToSplitScreenClickListener(Function0<Unit> listener)371     void setOnToSplitScreenClickListener(Function0<Unit> listener) {
372         mOnToSplitscreenClickListener = listener;
373     }
374 
375     /** Registers a listener to be called when the decoration's to-split action is triggered. */
setOnToFloatClickListener(Function0<Unit> listener)376     void setOnToFloatClickListener(Function0<Unit> listener) {
377         mOnToFloatClickListener = listener;
378     }
379 
380     /**
381      * Adds a drag resize observer that gets notified on the task being drag resized.
382      *
383      * @param dragResizeListener The observing object to be added.
384      */
addDragResizeListener(DragEventListener dragResizeListener)385     public void addDragResizeListener(DragEventListener dragResizeListener) {
386         mTaskDragResizer.addDragEventListener(dragResizeListener);
387     }
388 
389     /**
390      * Removes an already existing drag resize observer.
391      *
392      * @param dragResizeListener observer to be removed.
393      */
removeDragResizeListener(DragEventListener dragResizeListener)394     public void removeDragResizeListener(DragEventListener dragResizeListener) {
395         mTaskDragResizer.removeDragEventListener(dragResizeListener);
396     }
397 
398     /** Registers a listener to be called when the decoration's new window action is triggered. */
setOnNewWindowClickListener(Function0<Unit> listener)399     void setOnNewWindowClickListener(Function0<Unit> listener) {
400         mOnNewWindowClickListener = listener;
401     }
402 
403     /**
404      * Registers a listener to be called when the decoration's manage windows action is
405      * triggered.
406      */
setManageWindowsClickListener(Function0<Unit> listener)407     void setManageWindowsClickListener(Function0<Unit> listener) {
408         mOnManageWindowsClickListener = listener;
409     }
410 
411     /** Registers a listener to be called when the aspect ratio action is triggered. */
setOnChangeAspectRatioClickListener(Function0<Unit> listener)412     void setOnChangeAspectRatioClickListener(Function0<Unit> listener) {
413         mOnChangeAspectRatioClickListener = listener;
414     }
415 
416     /** Registers a listener to be called when the aspect ratio action is triggered. */
setOnRestartClickListener(Function0<Unit> listener)417     void setOnRestartClickListener(Function0<Unit> listener) {
418         mOnRestartClickListener = listener;
419     }
420 
421     /** Registers a listener to be called when the maximize header button is hovered. */
setOnMaximizeHoverListener(Function0<Unit> listener)422     void setOnMaximizeHoverListener(Function0<Unit> listener) {
423         mOnMaximizeHoverListener = listener;
424     }
425 
setCaptionListeners( View.OnClickListener onCaptionButtonClickListener, View.OnTouchListener onCaptionTouchListener, View.OnLongClickListener onLongClickListener, View.OnGenericMotionListener onGenericMotionListener)426     void setCaptionListeners(
427             View.OnClickListener onCaptionButtonClickListener,
428             View.OnTouchListener onCaptionTouchListener,
429             View.OnLongClickListener onLongClickListener,
430             View.OnGenericMotionListener onGenericMotionListener) {
431         mOnCaptionButtonClickListener = onCaptionButtonClickListener;
432         mOnCaptionTouchListener = onCaptionTouchListener;
433         mOnCaptionLongClickListener = onLongClickListener;
434         mOnCaptionGenericMotionListener = onGenericMotionListener;
435     }
436 
setExclusionRegionListener(ExclusionRegionListener exclusionRegionListener)437     void setExclusionRegionListener(ExclusionRegionListener exclusionRegionListener) {
438         mExclusionRegionListener = exclusionRegionListener;
439     }
440 
setDragPositioningCallback(DragPositioningCallback dragPositioningCallback)441     void setDragPositioningCallback(DragPositioningCallback dragPositioningCallback) {
442         mDragPositioningCallback = dragPositioningCallback;
443     }
444 
setOpenInBrowserClickListener(Consumer<Intent> listener)445     void setOpenInBrowserClickListener(Consumer<Intent> listener) {
446         mOpenInBrowserClickListener = listener;
447     }
448 
449     @Override
onExclusionRegionChanged(@onNull Region exclusionRegion)450     void onExclusionRegionChanged(@NonNull Region exclusionRegion) {
451         if (Flags.appHandleNoRelayoutOnExclusionChange() && isAppHandle(mWindowDecorViewHolder)) {
452             // Avoid unnecessary relayouts for app handle. See b/383672263
453             return;
454         }
455         relayout(mTaskInfo, mHasGlobalFocus, exclusionRegion);
456     }
457 
458     @Override
relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus, @NonNull Region displayExclusionRegion)459     void relayout(ActivityManager.RunningTaskInfo taskInfo, boolean hasGlobalFocus,
460             @NonNull Region displayExclusionRegion) {
461         final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
462         // The visibility, crop and position of the task should only be set when a task is
463         // fluid resizing. In all other cases, it is expected that the transition handler sets
464         // those task properties to allow the handler time to animate with full control of the task
465         // leash. In general, allowing the window decoration to set any of these is likely to cause
466         // incorrect frames and flickering because relayouts from TaskListener#onTaskInfoChanged
467         // aren't synchronized with shell transition callbacks, so if they come too early it
468         // might show/hide or crop the task at a bad time.
469         // Fluid resizing is exempt from this because it intentionally doesn't use shell
470         // transitions to resize the task, so onTaskInfoChanged relayouts is the only way to make
471         // sure the crop is set correctly.
472         final boolean shouldSetTaskVisibilityPositionAndCrop =
473                 !DesktopModeStatus.isVeiledResizeEnabled()
474                         && mTaskDragResizer.isResizingOrAnimating();
475         // For headers only (i.e. in freeform): use |applyStartTransactionOnDraw| so that the
476         // transaction (that applies task crop) is synced with the buffer transaction (that draws
477         // the View). Both will be shown on screen at the same, whereas applying them independently
478         // causes flickering. See b/270202228.
479         final boolean applyTransactionOnDraw = taskInfo.isFreeform();
480         relayout(taskInfo, t, t, applyTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop,
481                 hasGlobalFocus, displayExclusionRegion);
482         if (!applyTransactionOnDraw) {
483             t.apply();
484         }
485     }
486 
487     /**
488      * Disables resizing for the given edge.
489      *
490      * @param disabledResizingEdge edge to disable.
491      * @param shouldDelayUpdate whether the update should be executed immediately or delayed.
492      */
updateDisabledResizingEdge( DragResizeWindowGeometry.DisabledEdge disabledResizingEdge, boolean shouldDelayUpdate)493     public void updateDisabledResizingEdge(
494             DragResizeWindowGeometry.DisabledEdge disabledResizingEdge, boolean shouldDelayUpdate) {
495         mDisabledResizingEdge = disabledResizingEdge;
496         final boolean inFullImmersive = mDesktopUserRepositories.getCurrent()
497                 .isTaskInFullImmersiveState(mTaskInfo.taskId);
498         if (shouldDelayUpdate) {
499             return;
500         }
501         updateDragResizeListenerIfNeeded(mDecorationContainerSurface, inFullImmersive);
502     }
503 
504 
relayout(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop, boolean hasGlobalFocus, @NonNull Region displayExclusionRegion)505     void relayout(ActivityManager.RunningTaskInfo taskInfo,
506             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
507             boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop,
508             boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) {
509         Trace.beginSection("DesktopModeWindowDecoration#relayout");
510 
511         if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_APP_TO_WEB.isTrue()) {
512             setCapturedLink(taskInfo.capturedLink, taskInfo.capturedLinkTimestamp);
513         }
514 
515         if (isHandleMenuActive()) {
516             mHandleMenu.relayout(
517                     startT,
518                     mResult.mCaptionX,
519                     // Add top padding to the caption Y so that the menu is shown over what is the
520                     // actual contents of the caption, ignoring padding. This is currently relevant
521                     // to the Header in desktop immersive.
522                     mResult.mCaptionY + mResult.mCaptionTopPadding);
523         }
524 
525         if (isOpenByDefaultDialogActive()) {
526             mOpenByDefaultDialog.relayout(taskInfo);
527         }
528 
529         final boolean inFullImmersive = mDesktopUserRepositories.getProfile(taskInfo.userId)
530                 .isTaskInFullImmersiveState(taskInfo.taskId);
531         updateRelayoutParams(mRelayoutParams, mContext, taskInfo, mSplitScreenController,
532                 applyStartTransactionOnDraw, shouldSetTaskVisibilityPositionAndCrop,
533                 mIsStatusBarVisible, mIsKeyguardVisibleAndOccluded, inFullImmersive,
534                 mIsDragging, mDisplayController.getInsetsState(taskInfo.displayId), hasGlobalFocus,
535                 displayExclusionRegion,
536                 /* shouldIgnoreCornerRadius= */ mIsRecentsTransitionRunning
537                         && DesktopModeFlags
538                         .ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue(),
539                 mDesktopModeCompatPolicy.shouldExcludeCaptionFromAppBounds(taskInfo));
540 
541         final WindowDecorLinearLayout oldRootView = mResult.mRootView;
542         final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
543         final WindowContainerTransaction wct = new WindowContainerTransaction();
544 
545         relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
546         // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
547 
548         Trace.beginSection("DesktopModeWindowDecoration#relayout-applyWCT");
549         mBgExecutor.execute(() -> mTaskOrganizer.applyTransaction(wct));
550         Trace.endSection();
551 
552         if (mResult.mRootView == null) {
553             // This means something blocks the window decor from showing, e.g. the task is hidden.
554             // Nothing is set up in this case including the decoration surface.
555             if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
556                 notifyNoCaptionHandle();
557             }
558             mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
559             disposeStatusBarInputLayer();
560             Trace.endSection(); // DesktopModeWindowDecoration#relayout
561             return;
562         }
563 
564         if (DesktopModeFlags.SKIP_DECOR_VIEW_RELAYOUT_WHEN_CLOSING_BUGFIX.isTrue()
565                 ? (oldRootView != mResult.mRootView && taskInfo.isVisibleRequested)
566                 : oldRootView != mResult.mRootView) {
567             disposeStatusBarInputLayer();
568             mWindowDecorViewHolder = createViewHolder();
569             // Load these only when first creating the view.
570             loadTaskNameAndIconInBackground((name, icon) -> {
571                 final AppHeaderViewHolder appHeader = asAppHeader(mWindowDecorViewHolder);
572                 if (appHeader != null) {
573                     appHeader.setAppName(name);
574                     appHeader.setAppIcon(icon);
575                     if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
576                         notifyCaptionStateChanged();
577                     }
578                 }
579             });
580         }
581 
582         if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
583             notifyCaptionStateChanged();
584         }
585 
586         Trace.beginSection("DesktopModeWindowDecoration#relayout-bindData");
587         if (isAppHandle(mWindowDecorViewHolder)) {
588             updateAppHandleViewHolder();
589         } else {
590             updateAppHeaderViewHolder(inFullImmersive, hasGlobalFocus);
591         }
592         Trace.endSection();
593 
594         if (!hasGlobalFocus) {
595             closeHandleMenu();
596             closeManageWindowsMenu();
597             closeMaximizeMenu();
598             notifyNoCaptionHandle();
599         }
600         updateDragResizeListenerIfNeeded(oldDecorationSurface, inFullImmersive);
601         updateMaximizeMenu(startT, inFullImmersive);
602         Trace.endSection(); // DesktopModeWindowDecoration#relayout
603     }
604 
605     /**
606      * Loads the task's name and icon in a background thread and posts the results back in the
607      * main thread.
608      */
loadTaskNameAndIconInBackground(BiConsumer<CharSequence, Bitmap> onResult)609     private void loadTaskNameAndIconInBackground(BiConsumer<CharSequence, Bitmap> onResult) {
610         if (mWindowDecorViewHolder == null) return;
611         if (asAppHeader(mWindowDecorViewHolder) == null) {
612             // Only needed when drawing a header.
613             return;
614         }
615         if (mLoadAppInfoRunnable != null) {
616             mBgExecutor.removeCallbacks(mLoadAppInfoRunnable);
617         }
618         if (mSetAppInfoRunnable != null) {
619             mMainExecutor.removeCallbacks(mSetAppInfoRunnable);
620         }
621         mLoadAppInfoRunnable = () -> {
622             final CharSequence name = mTaskResourceLoader.getName(mTaskInfo);
623             final Bitmap icon = mTaskResourceLoader.getHeaderIcon(mTaskInfo);
624             mSetAppInfoRunnable = () -> {
625                 onResult.accept(name, icon);
626             };
627             mMainExecutor.execute(mSetAppInfoRunnable);
628         };
629         mBgExecutor.execute(mLoadAppInfoRunnable);
630     }
631 
showInputLayer()632     private boolean showInputLayer() {
633         if (!DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue()) {
634             return isCaptionVisible();
635         }
636         // Don't show the input layer during the recents transition, otherwise it could become
637         // touchable while in overview, during quick-switch or even for a short moment after going
638         // Home.
639         return isCaptionVisible() && !mIsRecentsTransitionRunning;
640     }
641 
isCaptionVisible()642     private boolean isCaptionVisible() {
643         return mTaskInfo.isVisible && mIsCaptionVisible;
644     }
645 
setCapturedLink(Uri capturedLink, long timeStamp)646     private void setCapturedLink(Uri capturedLink, long timeStamp) {
647         if (capturedLink == null
648                 || (mCapturedLink != null && mCapturedLink.mTimeStamp == timeStamp)) {
649             return;
650         }
651         mCapturedLink = new CapturedLink(capturedLink, timeStamp);
652     }
653 
654     @Nullable
getBrowserLink()655     private Intent getBrowserLink() {
656         final Uri browserLink;
657         if (mWebUri != null) {
658             browserLink = mWebUri;
659         } else if (isCapturedLinkAvailable()) {
660             browserLink = mCapturedLink.mUri;
661         } else {
662             browserLink = mGenericLink;
663         }
664 
665         if (browserLink == null) return null;
666         return AppToWebUtils.getBrowserIntent(browserLink, mContext.getPackageManager(),
667                 mUserContext.getUserId());
668 
669     }
670 
671     @Nullable
getAppLink()672     private Intent getAppLink() {
673         return mWebUri == null ? null
674                 : AppToWebUtils.getAppIntent(mWebUri, mContext.getPackageManager(),
675                         mUserContext.getUserId());
676     }
677 
isBrowserApp()678     private boolean isBrowserApp() {
679         final ComponentName baseActivity = mTaskInfo.baseActivity;
680         return baseActivity != null && AppToWebUtils.isBrowserApp(mContext,
681                 baseActivity.getPackageName(), mUserContext.getUserId());
682     }
683 
getUser()684     UserHandle getUser() {
685         return mUserContext.getUser();
686     }
687 
updateDragResizeListenerIfNeeded(@ullable SurfaceControl containerSurface, boolean inFullImmersive)688     private void updateDragResizeListenerIfNeeded(@Nullable SurfaceControl containerSurface,
689             boolean inFullImmersive) {
690         final boolean taskPositionChanged = !mTaskInfo.positionInParent.equals(mPositionInParent);
691         if (!isDragResizable(mTaskInfo, inFullImmersive)) {
692             if (taskPositionChanged) {
693                 // We still want to track caption bar's exclusion region on a non-resizeable task.
694                 updateExclusionRegion(inFullImmersive);
695             }
696             closeDragResizeListener();
697             return;
698         }
699         updateDragResizeListener(containerSurface,
700                 (geometryChanged) -> {
701                     if (geometryChanged || taskPositionChanged) {
702                         updateExclusionRegion(inFullImmersive);
703                     }
704                 });
705     }
706 
updateDragResizeListener(@ullable SurfaceControl containerSurface, Consumer<Boolean> onUpdateFinished)707     private void updateDragResizeListener(@Nullable SurfaceControl containerSurface,
708             Consumer<Boolean> onUpdateFinished) {
709         final boolean containerSurfaceChanged = containerSurface != mDecorationContainerSurface;
710         final boolean isFirstDragResizeListener = mDragResizeListener == null;
711         final boolean shouldCreateListener = containerSurfaceChanged || isFirstDragResizeListener;
712         if (containerSurfaceChanged) {
713             closeDragResizeListener();
714         }
715         if (shouldCreateListener) {
716             final ShellExecutor bgExecutor =
717                     DesktopModeFlags.ENABLE_DRAG_RESIZE_SET_UP_IN_BG_THREAD.isTrue()
718                             ? mBgExecutor : mMainExecutor;
719             mDragResizeListener = new DragResizeInputListener(
720                     mContext,
721                     WindowManagerGlobal.getWindowSession(),
722                     mMainExecutor,
723                     bgExecutor,
724                     mTaskInfo,
725                     mHandler,
726                     mChoreographer,
727                     mDisplay.getDisplayId(),
728                     mDecorationContainerSurface,
729                     mDragPositioningCallback,
730                     mSurfaceControlBuilderSupplier,
731                     mSurfaceControlTransactionSupplier,
732                     mDisplayController,
733                     mDesktopModeEventLogger);
734         }
735         final DragResizeInputListener newListener = mDragResizeListener;
736         final int touchSlop = ViewConfiguration.get(mResult.mRootView.getContext())
737                 .getScaledTouchSlop();
738         final Resources res = mResult.mRootView.getResources();
739         final DragResizeWindowGeometry newGeometry = new DragResizeWindowGeometry(
740                 DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()
741                         ? mResult.mCornerRadius : mRelayoutParams.mCornerRadius,
742                 new Size(mResult.mWidth, mResult.mHeight),
743                 getResizeEdgeHandleSize(res), getResizeHandleEdgeInset(res),
744                 getFineResizeCornerSize(res), getLargeResizeCornerSize(res),
745                 mDisabledResizingEdge);
746         newListener.addInitializedCallback(() -> {
747             onUpdateFinished.accept(newListener.setGeometry(newGeometry, touchSlop));
748         });
749     }
750 
isDragResizable(ActivityManager.RunningTaskInfo taskInfo, boolean inFullImmersive)751     private static boolean isDragResizable(ActivityManager.RunningTaskInfo taskInfo,
752             boolean inFullImmersive) {
753         if (inFullImmersive) {
754             // Task cannot be resized in full immersive.
755             return false;
756         }
757         if (DesktopModeFlags.ENABLE_WINDOWING_SCALED_RESIZING.isTrue()) {
758             return taskInfo.isFreeform();
759         }
760         return taskInfo.isFreeform() && taskInfo.isResizeable;
761     }
762 
notifyCaptionStateChanged()763     private void notifyCaptionStateChanged() {
764         if (!canEnterDesktopMode(mContext) || !isEducationEnabled()) {
765             return;
766         }
767         if (!isCaptionVisible()) {
768             notifyNoCaptionHandle();
769         } else if (isAppHandle(mWindowDecorViewHolder)) {
770             // App handle is visible since `mWindowDecorViewHolder` is of type
771             // [AppHandleViewHolder].
772             final CaptionState captionState = new CaptionState.AppHandle(mTaskInfo,
773                     isHandleMenuActive(), getCurrentAppHandleBounds(), isCapturedLinkAvailable());
774             mWindowDecorCaptionHandleRepository.notifyCaptionChanged(captionState);
775         } else {
776             // App header is visible since `mWindowDecorViewHolder` is of type
777             // [AppHeaderViewHolder].
778             final AppHeaderViewHolder appHeader = asAppHeader(mWindowDecorViewHolder);
779             if (appHeader != null) {
780                 appHeader.runOnAppChipGlobalLayout(
781                         () -> {
782                             notifyAppHeaderStateChanged();
783                             return Unit.INSTANCE;
784                         });
785             }
786         }
787     }
788 
isCapturedLinkAvailable()789     private boolean isCapturedLinkAvailable() {
790         return mCapturedLink != null && !mCapturedLink.mUsed;
791     }
792 
onCapturedLinkUsed()793     private void onCapturedLinkUsed() {
794         if (mCapturedLink != null) {
795             mCapturedLink.setUsed();
796         }
797     }
798 
notifyNoCaptionHandle()799     private void notifyNoCaptionHandle() {
800         if (!canEnterDesktopMode(mContext) || !isEducationEnabled()) {
801             return;
802         }
803         mWindowDecorCaptionHandleRepository.notifyCaptionChanged(
804                 CaptionState.NoCaption.INSTANCE);
805     }
806 
getCurrentAppHandleBounds()807     private Rect getCurrentAppHandleBounds() {
808         return new Rect(
809                 mResult.mCaptionX,
810                 /* top= */0,
811                 mResult.mCaptionX + mResult.mCaptionWidth,
812                 mResult.mCaptionHeight);
813     }
814 
notifyAppHeaderStateChanged()815     private void notifyAppHeaderStateChanged() {
816         final AppHeaderViewHolder appHeader = asAppHeader(mWindowDecorViewHolder);
817         if (appHeader == null) {
818             return;
819         }
820         final Rect appChipPositionInWindow = appHeader.getAppChipLocationInWindow();
821         final Rect taskBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
822         final Rect appChipGlobalPosition = new Rect(
823                 taskBounds.left + appChipPositionInWindow.left,
824                 taskBounds.top + appChipPositionInWindow.top,
825                 taskBounds.left + appChipPositionInWindow.right,
826                 taskBounds.top + appChipPositionInWindow.bottom);
827         final CaptionState captionState = new CaptionState.AppHeader(
828                 mTaskInfo,
829                 isHandleMenuActive(),
830                 appChipGlobalPosition,
831                 isCapturedLinkAvailable());
832 
833         mWindowDecorCaptionHandleRepository.notifyCaptionChanged(captionState);
834     }
835 
updateMaximizeMenu(SurfaceControl.Transaction startT, boolean inFullImmersive)836     private void updateMaximizeMenu(SurfaceControl.Transaction startT, boolean inFullImmersive) {
837         if (!isDragResizable(mTaskInfo, inFullImmersive) || !isMaximizeMenuActive()) {
838             return;
839         }
840         if (!mTaskInfo.isVisible()) {
841             closeMaximizeMenu();
842         } else {
843             mMaximizeMenu.positionMenu(startT);
844         }
845     }
846 
determineHandlePosition()847     private Point determineHandlePosition() {
848         final Point position = new Point(mResult.mCaptionX, 0);
849         if (mSplitScreenController.getSplitPosition(mTaskInfo.taskId)
850                 == SPLIT_POSITION_BOTTOM_OR_RIGHT
851         ) {
852             if (mSplitScreenController.isLeftRightSplit()) {
853                 // If this is the right split task, add left stage's width.
854                 final Rect leftStageBounds = new Rect();
855                 mSplitScreenController.getStageBounds(leftStageBounds, new Rect());
856                 position.x += leftStageBounds.width();
857             } else {
858                 final Rect bottomStageBounds = new Rect();
859                 mSplitScreenController.getRefStageBounds(new Rect(), bottomStageBounds);
860                 position.y += bottomStageBounds.top;
861             }
862         }
863         return position;
864     }
865 
866     /**
867      * Dispose of the view used to forward inputs in status bar region. Intended to be
868      * used any time handle is no longer visible.
869      */
disposeStatusBarInputLayer()870     void disposeStatusBarInputLayer() {
871         if (!isAppHandle(mWindowDecorViewHolder)
872                 || !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
873             return;
874         }
875         asAppHandle(mWindowDecorViewHolder).disposeStatusBarInputLayer();
876     }
877 
878     /** Update the view holder for app handle. */
updateAppHandleViewHolder()879     private void updateAppHandleViewHolder() {
880         if (!isAppHandle(mWindowDecorViewHolder)) return;
881         asAppHandle(mWindowDecorViewHolder).bindData(new AppHandleViewHolder.HandleData(
882                 mTaskInfo, determineHandlePosition(), mResult.mCaptionWidth,
883                 mResult.mCaptionHeight, /* showInputLayer= */ showInputLayer(),
884                 /* isCaptionVisible= */ isCaptionVisible()
885         ));
886     }
887 
888     /** Update the view holder for app header. */
updateAppHeaderViewHolder(boolean inFullImmersive, boolean hasGlobalFocus)889     private void updateAppHeaderViewHolder(boolean inFullImmersive, boolean hasGlobalFocus) {
890         if (!isAppHeader(mWindowDecorViewHolder)) return;
891         asAppHeader(mWindowDecorViewHolder).bindData(new AppHeaderViewHolder.HeaderData(
892                 mTaskInfo,
893                 DesktopModeUtils.isTaskMaximized(mTaskInfo, mDisplayController),
894                 inFullImmersive,
895                 hasGlobalFocus,
896                 /* maximizeHoverEnabled= */ canOpenMaximizeMenu(
897                     /* animatingTaskResizeOrReposition= */ false),
898                 isCaptionVisible()
899         ));
900     }
901 
createViewHolder()902     private WindowDecorationViewHolder createViewHolder() {
903         if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_handle) {
904             return mAppHandleViewHolderFactory.create(
905                     mResult.mRootView,
906                     mOnCaptionTouchListener,
907                     mOnCaptionButtonClickListener,
908                     mWindowManagerWrapper,
909                     mHandler,
910                     mDesktopModeUiEventLogger
911             );
912         } else if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_header) {
913             return mAppHeaderViewHolderFactory.create(
914                     mResult.mRootView,
915                     mOnCaptionTouchListener,
916                     mOnCaptionButtonClickListener,
917                     mOnCaptionLongClickListener,
918                     mOnCaptionGenericMotionListener,
919                     mOnLeftSnapClickListener,
920                     mOnRightSnapClickListener,
921                     mOnMaximizeOrRestoreClickListener,
922                     mOnMaximizeHoverListener,
923                     mDesktopModeUiEventLogger);
924         }
925         throw new IllegalArgumentException("Unexpected layout resource id");
926     }
927 
isAppHandle(WindowDecorationViewHolder viewHolder)928     private boolean isAppHandle(WindowDecorationViewHolder viewHolder) {
929         return viewHolder instanceof AppHandleViewHolder;
930     }
931 
isAppHeader(WindowDecorationViewHolder viewHolder)932     private boolean isAppHeader(WindowDecorationViewHolder viewHolder) {
933         return viewHolder instanceof AppHeaderViewHolder;
934     }
935 
936     @Nullable
asAppHandle(WindowDecorationViewHolder viewHolder)937     private AppHandleViewHolder asAppHandle(WindowDecorationViewHolder viewHolder) {
938         if (viewHolder instanceof AppHandleViewHolder) {
939             return (AppHandleViewHolder) viewHolder;
940         }
941         return null;
942     }
943 
944     @Nullable
asAppHeader(WindowDecorationViewHolder viewHolder)945     private AppHeaderViewHolder asAppHeader(WindowDecorationViewHolder viewHolder) {
946         if (viewHolder instanceof AppHeaderViewHolder) {
947             return (AppHeaderViewHolder) viewHolder;
948         }
949         return null;
950     }
951 
952     @VisibleForTesting
updateRelayoutParams( RelayoutParams relayoutParams, Context context, ActivityManager.RunningTaskInfo taskInfo, SplitScreenController splitScreenController, boolean applyStartTransactionOnDraw, boolean shouldSetTaskVisibilityPositionAndCrop, boolean isStatusBarVisible, boolean isKeyguardVisibleAndOccluded, boolean inFullImmersiveMode, boolean isDragging, @NonNull InsetsState displayInsetsState, boolean hasGlobalFocus, @NonNull Region displayExclusionRegion, boolean shouldIgnoreCornerRadius, boolean shouldExcludeCaptionFromAppBounds)953     static void updateRelayoutParams(
954             RelayoutParams relayoutParams,
955             Context context,
956             ActivityManager.RunningTaskInfo taskInfo,
957             SplitScreenController splitScreenController,
958             boolean applyStartTransactionOnDraw,
959             boolean shouldSetTaskVisibilityPositionAndCrop,
960             boolean isStatusBarVisible,
961             boolean isKeyguardVisibleAndOccluded,
962             boolean inFullImmersiveMode,
963             boolean isDragging,
964             @NonNull InsetsState displayInsetsState,
965             boolean hasGlobalFocus,
966             @NonNull Region displayExclusionRegion,
967             boolean shouldIgnoreCornerRadius,
968             boolean shouldExcludeCaptionFromAppBounds) {
969         final int captionLayoutId = getDesktopModeWindowDecorLayoutId(taskInfo.getWindowingMode());
970         final boolean isAppHeader =
971                 captionLayoutId == R.layout.desktop_mode_app_header;
972         final boolean isAppHandle = captionLayoutId == R.layout.desktop_mode_app_handle;
973         relayoutParams.reset();
974         relayoutParams.mRunningTaskInfo = taskInfo;
975         relayoutParams.mLayoutResId = captionLayoutId;
976         relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode());
977         relayoutParams.mCaptionWidthId = getCaptionWidthId(relayoutParams.mLayoutResId);
978         relayoutParams.mHasGlobalFocus = hasGlobalFocus;
979         relayoutParams.mDisplayExclusionRegion.set(displayExclusionRegion);
980         // Allow the handle view to be delayed since the handle is just a small addition to the
981         // window, whereas the header cannot be delayed because it is expected to be visible from
982         // the first frame.
983         relayoutParams.mAsyncViewHost = isAppHandle;
984 
985         boolean showCaption;
986         if (DesktopModeFlags.ENABLE_DESKTOP_IMMERSIVE_DRAG_BUGFIX.isTrue() && isDragging) {
987             // If the task is being dragged, the caption should not be hidden so that it continues
988             // receiving input
989             showCaption = true;
990         } else if (DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()) {
991             if (inFullImmersiveMode) {
992                 showCaption = (isStatusBarVisible && !isKeyguardVisibleAndOccluded);
993             } else {
994                 showCaption = taskInfo.isFreeform()
995                         || (isStatusBarVisible && !isKeyguardVisibleAndOccluded);
996             }
997         } else {
998             // Caption should always be visible in freeform mode. When not in freeform,
999             // align with the status bar except when showing over keyguard (where it should not
1000             // shown).
1001             //  TODO(b/356405803): Investigate how it's possible for the status bar visibility to
1002             //   be false while a freeform window is open if the status bar is always
1003             //   forcibly-shown. It may be that the InsetsState (from which |mIsStatusBarVisible|
1004             //   is set) still contains an invisible insets source in immersive cases even if the
1005             //   status bar is shown?
1006             showCaption = taskInfo.isFreeform()
1007                     || (isStatusBarVisible && !isKeyguardVisibleAndOccluded);
1008         }
1009         relayoutParams.mIsCaptionVisible = showCaption;
1010         final boolean isBottomSplit = !splitScreenController.isLeftRightSplit()
1011                 && splitScreenController.getSplitPosition(taskInfo.taskId)
1012                 == SPLIT_POSITION_BOTTOM_OR_RIGHT;
1013         relayoutParams.mIsInsetSource = (isAppHeader && !inFullImmersiveMode) || isBottomSplit;
1014         if (isAppHeader) {
1015             if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) {
1016                 // The app is requesting to customize the caption bar, which means input on
1017                 // customizable/exclusion regions must go to the app instead of to the system.
1018                 // This may be accomplished with spy windows or custom touchable regions:
1019                 if (DesktopModeFlags.ENABLE_ACCESSIBLE_CUSTOM_HEADERS.isTrue()) {
1020                     // Set the touchable region of the caption to only the areas where input should
1021                     // be handled by the system (i.e. non custom-excluded areas). The region will
1022                     // be calculated based on occluding caption elements and exclusion areas
1023                     // reported by the app.
1024                     relayoutParams.mLimitTouchRegionToSystemAreas = true;
1025                 } else {
1026                     // Allow input to fall through to the windows below so that the app can respond
1027                     // to input events on their custom content.
1028                     relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY;
1029                 }
1030             } else {
1031                 if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isTrue()) {
1032                     if (shouldExcludeCaptionFromAppBounds) {
1033                         relayoutParams.mShouldSetAppBounds = true;
1034                     } else {
1035                         // Force-consume the caption bar insets when the app tries to hide the
1036                         // caption. This improves app compatibility of immersive apps.
1037                         relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING;
1038                     }
1039                 }
1040             }
1041             if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isTrue()) {
1042                 if (shouldExcludeCaptionFromAppBounds) {
1043                     relayoutParams.mShouldSetAppBounds = true;
1044                 } else {
1045                     // Always force-consume the caption bar insets for maximum app compatibility,
1046                     // including non-immersive apps that just don't handle caption insets properly.
1047                     relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
1048                 }
1049             }
1050             if (DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()
1051                     && inFullImmersiveMode) {
1052                 final Insets systemBarInsets = displayInsetsState.calculateInsets(
1053                         taskInfo.getConfiguration().windowConfiguration.getBounds(),
1054                         WindowInsets.Type.systemBars() & ~WindowInsets.Type.captionBar(),
1055                         false /* ignoreVisibility */);
1056                 relayoutParams.mCaptionTopPadding = systemBarInsets.top;
1057             }
1058             // Report occluding elements as bounding rects to the insets system so that apps can
1059             // draw in the empty space in the center:
1060             //   First, the "app chip" section of the caption bar (+ some extra margins).
1061             final RelayoutParams.OccludingCaptionElement appChipElement =
1062                     new RelayoutParams.OccludingCaptionElement();
1063             appChipElement.mWidthResId = R.dimen.desktop_mode_customizable_caption_margin_start;
1064             appChipElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.START;
1065             relayoutParams.mOccludingCaptionElements.add(appChipElement);
1066             //   Then, the right-aligned section (drag space, maximize and close buttons).
1067             final RelayoutParams.OccludingCaptionElement controlsElement =
1068                     new RelayoutParams.OccludingCaptionElement();
1069             controlsElement.mWidthResId = R.dimen.desktop_mode_customizable_caption_margin_end;
1070             if (DesktopModeFlags.ENABLE_MINIMIZE_BUTTON.isTrue()) {
1071                 controlsElement.mWidthResId =
1072                       R.dimen.desktop_mode_customizable_caption_with_minimize_button_margin_end;
1073             }
1074             controlsElement.mAlignment = RelayoutParams.OccludingCaptionElement.Alignment.END;
1075             relayoutParams.mOccludingCaptionElements.add(controlsElement);
1076         } else if (isAppHandle && !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
1077             // The focused decor (fullscreen/split) does not need to handle input because input in
1078             // the App Handle is handled by the InputMonitor in DesktopModeWindowDecorViewModel.
1079             // Note: This does not apply with the above flag enabled as the status bar input layer
1080             // will forward events to the handle directly.
1081             relayoutParams.mInputFeatures
1082                     |= WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
1083         }
1084         if (isAppHeader
1085                 && DesktopModeStatus.useWindowShadow(/* isFocusedWindow= */ hasGlobalFocus)) {
1086             if (DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()) {
1087                 relayoutParams.mShadowRadiusId = hasGlobalFocus
1088                         ? R.dimen.freeform_decor_shadow_focused_thickness
1089                         : R.dimen.freeform_decor_shadow_unfocused_thickness;
1090             } else {
1091                 relayoutParams.mShadowRadius = hasGlobalFocus
1092                         ? context.getResources().getDimensionPixelSize(
1093                         R.dimen.freeform_decor_shadow_focused_thickness)
1094                         : context.getResources().getDimensionPixelSize(
1095                                 R.dimen.freeform_decor_shadow_unfocused_thickness);
1096             }
1097         } else {
1098             if (DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()) {
1099                 relayoutParams.mShadowRadiusId = Resources.ID_NULL;
1100             } else {
1101                 relayoutParams.mShadowRadius = INVALID_SHADOW_RADIUS;
1102             }
1103         }
1104         relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
1105         relayoutParams.mSetTaskVisibilityPositionAndCrop = shouldSetTaskVisibilityPositionAndCrop;
1106 
1107         // The configuration used to layout the window decoration. A copy is made instead of using
1108         // the original reference so that the configuration isn't mutated on config changes and
1109         // diff checks can be made in WindowDecoration#relayout using the pre/post-relayout
1110         // configuration. See b/301119301.
1111         // TODO(b/301119301): consider moving the config data needed for diffs to relayout params
1112         // instead of using a whole Configuration as a parameter.
1113         final Configuration windowDecorConfig = new Configuration();
1114         if (DesktopModeFlags.ENABLE_APP_HEADER_WITH_TASK_DENSITY.isTrue() && isAppHeader) {
1115             // Should match the density of the task. The task may have had its density overridden
1116             // to be different that SysUI's.
1117             windowDecorConfig.setTo(taskInfo.configuration);
1118         } else if (DesktopModeStatus.useDesktopOverrideDensity()) {
1119             // The task has had its density overridden, but keep using the system's density to
1120             // layout the header.
1121             windowDecorConfig.setTo(context.getResources().getConfiguration());
1122         } else {
1123             windowDecorConfig.setTo(taskInfo.configuration);
1124         }
1125         relayoutParams.mWindowDecorConfig = windowDecorConfig;
1126 
1127         if (DesktopModeStatus.useRoundedCorners()) {
1128             if (DesktopExperienceFlags.ENABLE_DYNAMIC_RADIUS_COMPUTATION_BUGFIX.isTrue()) {
1129                 relayoutParams.mCornerRadiusId = shouldIgnoreCornerRadius ? Resources.ID_NULL :
1130                         getCornerRadiusId(relayoutParams.mLayoutResId);
1131             } else {
1132                 relayoutParams.mCornerRadius = shouldIgnoreCornerRadius ? INVALID_CORNER_RADIUS :
1133                         getCornerRadius(context, relayoutParams.mLayoutResId);
1134             }
1135         }
1136         // Set opaque background for all freeform tasks to prevent freeform tasks below
1137         // from being visible if freeform task window above is translucent.
1138         // Otherwise if fluid resize is enabled, add a background to freeform tasks.
1139         relayoutParams.mShouldSetBackground = DesktopModeStatus.shouldSetBackground(taskInfo);
1140     }
1141 
1142     @Deprecated
getCornerRadius(@onNull Context context, int layoutResId)1143     private static int getCornerRadius(@NonNull Context context, int layoutResId) {
1144         if (layoutResId == R.layout.desktop_mode_app_header) {
1145             return loadDimensionPixelSize(context.getResources(),
1146                     com.android.wm.shell.shared.R.dimen
1147                             .desktop_windowing_freeform_rounded_corner_radius);
1148         }
1149         return INVALID_CORNER_RADIUS;
1150     }
1151 
getCornerRadiusId(int layoutResId)1152     private static int getCornerRadiusId(int layoutResId) {
1153         if (layoutResId == R.layout.desktop_mode_app_header) {
1154             return com.android.wm.shell.shared.R.dimen
1155                     .desktop_windowing_freeform_rounded_corner_radius;
1156         }
1157         return Resources.ID_NULL;
1158     }
1159 
1160     /**
1161      * If task has focused window decor, return the caption id of the fullscreen caption size
1162      * resource. Otherwise, return ID_NULL and caption width be set to task width.
1163      */
getCaptionWidthId(int layoutResId)1164     private static int getCaptionWidthId(int layoutResId) {
1165         if (layoutResId == R.layout.desktop_mode_app_handle) {
1166             return R.dimen.desktop_mode_fullscreen_decor_caption_width;
1167         }
1168         return Resources.ID_NULL;
1169     }
1170 
calculateMaximizeMenuPosition(int menuWidth, int menuHeight)1171     private Point calculateMaximizeMenuPosition(int menuWidth, int menuHeight) {
1172         final Point position = new Point();
1173         final Resources resources = mContext.getResources();
1174         final DisplayLayout displayLayout =
1175                 mDisplayController.getDisplayLayout(mTaskInfo.displayId);
1176         if (displayLayout == null) return position;
1177 
1178         final int displayWidth = displayLayout.width();
1179         final int displayHeight = displayLayout.height();
1180         final int captionHeight = getCaptionHeight(mTaskInfo.getWindowingMode());
1181 
1182         final ImageButton maximizeWindowButton =
1183                 mResult.mRootView.findViewById(R.id.maximize_window);
1184         final int[] maximizeButtonLocation = new int[2];
1185         maximizeWindowButton.getLocationInWindow(maximizeButtonLocation);
1186 
1187         int menuLeft = (mPositionInParent.x + maximizeButtonLocation[0] - (menuWidth
1188                 - maximizeWindowButton.getWidth()) / 2);
1189         int menuTop = (mPositionInParent.y + captionHeight);
1190         final int menuRight = menuLeft + menuWidth;
1191         final int menuBottom = menuTop + menuHeight;
1192 
1193         // If the menu is out of screen bounds, shift it as needed
1194         if (menuLeft < 0) {
1195             menuLeft = 0;
1196         } else if (menuRight > displayWidth) {
1197             menuLeft = (displayWidth - menuWidth);
1198         }
1199         if (menuBottom > displayHeight) {
1200             menuTop = (displayHeight - menuHeight);
1201         }
1202 
1203         return new Point(menuLeft, menuTop);
1204     }
1205 
isHandleMenuActive()1206     boolean isHandleMenuActive() {
1207         return mHandleMenu != null;
1208     }
1209 
isOpenByDefaultDialogActive()1210     boolean isOpenByDefaultDialogActive() {
1211         return mOpenByDefaultDialog != null;
1212     }
1213 
createOpenByDefaultDialog()1214     void createOpenByDefaultDialog() {
1215         mOpenByDefaultDialog = new OpenByDefaultDialog(
1216                 mContext,
1217                 mTaskInfo,
1218                 mTaskSurface,
1219                 mDisplayController,
1220                 mTaskResourceLoader,
1221                 mSurfaceControlTransactionSupplier,
1222                 mMainDispatcher,
1223                 mBgScope,
1224                 new OpenByDefaultDialog.DialogLifecycleListener() {
1225                     @Override
1226                     public void onDialogCreated() {
1227                         closeHandleMenu();
1228                     }
1229 
1230                     @Override
1231                     public void onDialogDismissed() {
1232                         mOpenByDefaultDialog = null;
1233                     }
1234                 }
1235         );
1236     }
1237 
shouldResizeListenerHandleEvent(@onNull MotionEvent e, @NonNull Point offset)1238     boolean shouldResizeListenerHandleEvent(@NonNull MotionEvent e, @NonNull Point offset) {
1239         return mDragResizeListener != null && mDragResizeListener.shouldHandleEvent(e, offset);
1240     }
1241 
isHandlingDragResize()1242     boolean isHandlingDragResize() {
1243         return mDragResizeListener != null && mDragResizeListener.isHandlingDragResize();
1244     }
1245 
closeDragResizeListener()1246     private void closeDragResizeListener() {
1247         if (mDragResizeListener == null) {
1248             return;
1249         }
1250         mDragResizeListener.close();
1251         mDragResizeListener = null;
1252     }
1253 
1254     /**
1255      * Create the resize veil for this task. Note the veil's visibility is View.GONE by default
1256      * until a resize event calls showResizeVeil below.
1257      */
createResizeVeilIfNeeded()1258     private void createResizeVeilIfNeeded() {
1259         if (mResizeVeil != null) return;
1260         mResizeVeil = new ResizeVeil(mContext, mDisplayController, mTaskResourceLoader,
1261                 mMainDispatcher, mBgScope, mTaskSurface,
1262                 mSurfaceControlTransactionSupplier, mTaskInfo);
1263     }
1264 
1265     /**
1266      * Show the resize veil.
1267      */
showResizeVeil(Rect taskBounds)1268     public void showResizeVeil(Rect taskBounds) {
1269         createResizeVeilIfNeeded();
1270         mResizeVeil.showVeil(mTaskSurface, taskBounds, mTaskInfo);
1271     }
1272 
1273     /**
1274      * Show the resize veil.
1275      */
showResizeVeil(SurfaceControl.Transaction tx, Rect taskBounds)1276     public void showResizeVeil(SurfaceControl.Transaction tx, Rect taskBounds) {
1277         createResizeVeilIfNeeded();
1278         mResizeVeil.showVeil(tx, mTaskSurface, taskBounds, mTaskInfo, false /* fadeIn */);
1279     }
1280 
1281     /**
1282      * Set new bounds for the resize veil
1283      */
updateResizeVeil(Rect newBounds)1284     public void updateResizeVeil(Rect newBounds) {
1285         mResizeVeil.updateResizeVeil(newBounds);
1286     }
1287 
1288     /**
1289      * Set new bounds for the resize veil
1290      */
updateResizeVeil(SurfaceControl.Transaction tx, Rect newBounds)1291     public void updateResizeVeil(SurfaceControl.Transaction tx, Rect newBounds) {
1292         mResizeVeil.updateResizeVeil(tx, newBounds);
1293     }
1294 
1295     /**
1296      * Fade the resize veil out.
1297      */
hideResizeVeil()1298     public void hideResizeVeil() {
1299         mResizeVeil.hideVeil();
1300     }
1301 
disposeResizeVeil()1302     private void disposeResizeVeil() {
1303         if (mResizeVeil == null) return;
1304         mResizeVeil.dispose();
1305         mResizeVeil = null;
1306     }
1307 
1308     /**
1309      * Determine valid drag area for this task based on elements in the app chip.
1310      */
1311     @Override
1312     @NonNull
calculateValidDragArea()1313     Rect calculateValidDragArea() {
1314         final int appTextWidth = ((AppHeaderViewHolder)
1315                 mWindowDecorViewHolder).getAppNameTextWidth();
1316         final int leftButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
1317                 R.dimen.desktop_mode_app_details_width_minus_text) + appTextWidth;
1318         final int requiredEmptySpace = loadDimensionPixelSize(mContext.getResources(),
1319                 R.dimen.freeform_required_visible_empty_space_in_header);
1320         final int rightButtonsWidth = loadDimensionPixelSize(mContext.getResources(),
1321                 R.dimen.desktop_mode_right_edge_buttons_width);
1322         final int taskWidth = mTaskInfo.configuration.windowConfiguration.getBounds().width();
1323         final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
1324         final int displayWidth = layout.width();
1325         final Rect stableBounds = new Rect();
1326         layout.getStableBounds(stableBounds);
1327         return new Rect(
1328                 determineMinX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
1329                         taskWidth),
1330                 stableBounds.top,
1331                 determineMaxX(leftButtonsWidth, rightButtonsWidth, requiredEmptySpace,
1332                         taskWidth, displayWidth),
1333                 determineMaxY(requiredEmptySpace, stableBounds));
1334     }
1335 
1336 
1337     /**
1338      * Determine the lowest x coordinate of a freeform task. Used for restricting drag inputs.
1339      */
determineMinX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace, int taskWidth)1340     private int determineMinX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
1341             int taskWidth) {
1342         // Do not let apps with < 48dp empty header space go off the left edge at all.
1343         if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
1344             return 0;
1345         }
1346         return -taskWidth + requiredEmptySpace + rightButtonsWidth;
1347     }
1348 
1349     /**
1350      * Determine the highest x coordinate of a freeform task. Used for restricting drag inputs.
1351      */
determineMaxX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace, int taskWidth, int displayWidth)1352     private int determineMaxX(int leftButtonsWidth, int rightButtonsWidth, int requiredEmptySpace,
1353             int taskWidth, int displayWidth) {
1354         // Do not let apps with < 48dp empty header space go off the right edge at all.
1355         if (leftButtonsWidth + rightButtonsWidth + requiredEmptySpace > taskWidth) {
1356             return displayWidth - taskWidth;
1357         }
1358         return displayWidth - requiredEmptySpace - leftButtonsWidth;
1359     }
1360 
1361     /**
1362      * Determine the highest y coordinate of a freeform task. Used for restricting drag inputs.fmdra
1363      */
determineMaxY(int requiredEmptySpace, Rect stableBounds)1364     private int determineMaxY(int requiredEmptySpace, Rect stableBounds) {
1365         return stableBounds.bottom - requiredEmptySpace;
1366     }
1367 
1368 
1369     /**
1370      * Create and display maximize menu window
1371      */
createMaximizeMenu()1372     void createMaximizeMenu() {
1373         mMaximizeMenu = mMaximizeMenuFactory.create(mSyncQueue, mRootTaskDisplayAreaOrganizer,
1374                 mDisplayController, mTaskInfo, mContext,
1375                 (width, height) -> calculateMaximizeMenuPosition(width, height),
1376                 mSurfaceControlTransactionSupplier, mDesktopModeUiEventLogger);
1377 
1378         mMaximizeMenu.show(
1379                 /* isTaskInImmersiveMode= */
1380                 DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()
1381                         && mDesktopUserRepositories.getProfile(mTaskInfo.userId)
1382                             .isTaskInFullImmersiveState(mTaskInfo.taskId),
1383                 /* showImmersiveOption= */
1384                 DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()
1385                         && TaskInfoKt.getRequestingImmersive(mTaskInfo),
1386                 /* showSnapOptions= */ mTaskInfo.isResizeable,
1387                 mOnMaximizeOrRestoreClickListener,
1388                 mOnImmersiveOrRestoreClickListener,
1389                 mOnLeftSnapClickListener,
1390                 mOnRightSnapClickListener,
1391                 hovered -> {
1392                     mIsMaximizeMenuHovered = hovered;
1393                     onMaximizeHoverStateChanged();
1394                     return null;
1395                 },
1396                 () -> {
1397                     closeMaximizeMenu();
1398                     return null;
1399                 }
1400         );
1401     }
1402 
1403     /** Set whether the app header's maximize button is hovered. */
setAppHeaderMaximizeButtonHovered(boolean hovered)1404     void setAppHeaderMaximizeButtonHovered(boolean hovered) {
1405         mIsAppHeaderMaximizeButtonHovered = hovered;
1406         onMaximizeHoverStateChanged();
1407     }
1408 
1409     /**
1410      * Called when either one of the maximize button in the app header or the maximize menu has
1411      * changed its hover state.
1412      */
onMaximizeHoverStateChanged()1413     void onMaximizeHoverStateChanged() {
1414         if (!mIsMaximizeMenuHovered && !mIsAppHeaderMaximizeButtonHovered) {
1415             // Neither is hovered, close the menu.
1416             if (isMaximizeMenuActive()) {
1417                 mHandler.postDelayed(mCloseMaximizeWindowRunnable, CLOSE_MAXIMIZE_MENU_DELAY_MS);
1418             }
1419             return;
1420         }
1421         // At least one of the two is hovered, cancel the close if needed.
1422         mHandler.removeCallbacks(mCloseMaximizeWindowRunnable);
1423     }
1424 
1425     /**
1426      * Close the maximize menu window
1427      */
closeMaximizeMenu()1428     void closeMaximizeMenu() {
1429         if (!isMaximizeMenuActive()) return;
1430         mMaximizeMenu.close(() -> {
1431             // Request the accessibility service to refocus on the maximize button after closing
1432             // the menu.
1433             final AppHeaderViewHolder appHeader = asAppHeader(mWindowDecorViewHolder);
1434             if (appHeader != null) {
1435                 appHeader.requestAccessibilityFocus();
1436             }
1437             return Unit.INSTANCE;
1438         });
1439         mMaximizeMenu = null;
1440     }
1441 
isMaximizeMenuActive()1442     boolean isMaximizeMenuActive() {
1443         return mMaximizeMenu != null;
1444     }
1445 
1446     /**
1447      * Updates app info and creates and displays handle menu window.
1448      */
createHandleMenu(boolean minimumInstancesFound)1449     void createHandleMenu(boolean minimumInstancesFound) {
1450         // Requests assist content. When content is received, calls {@link #onAssistContentReceived}
1451         // which sets app info and creates the handle menu.
1452         mMinimumInstancesFound = minimumInstancesFound;
1453         mAssistContentRequester.requestAssistContent(
1454                 mTaskInfo.taskId, this::onAssistContentReceived);
1455     }
1456 
1457     /**
1458      * Called when assist content is received. updates the saved links and creates the handle menu.
1459      */
1460     @VisibleForTesting
onAssistContentReceived(@ullable AssistContent assistContent)1461     void onAssistContentReceived(@Nullable AssistContent assistContent) {
1462         mWebUri = assistContent == null ? null : AppToWebUtils.getSessionWebUri(assistContent);
1463         updateGenericLink();
1464         final boolean supportsMultiInstance = mMultiInstanceHelper
1465                 .supportsMultiInstanceSplit(mTaskInfo.baseActivity, mTaskInfo.userId)
1466                 && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES.isTrue();
1467         final boolean shouldShowManageWindowsButton = supportsMultiInstance
1468                 && mMinimumInstancesFound;
1469         final boolean shouldShowChangeAspectRatioButton = HandleMenu.Companion
1470                 .shouldShowChangeAspectRatioButton(mTaskInfo);
1471         final boolean shouldShowRestartButton = HandleMenu.Companion
1472                 .shouldShowRestartButton(mTaskInfo);
1473         final boolean inDesktopImmersive = mDesktopUserRepositories.getProfile(mTaskInfo.userId)
1474                 .isTaskInFullImmersiveState(mTaskInfo.taskId);
1475         final boolean isBrowserApp = isBrowserApp();
1476         mHandleMenu = mHandleMenuFactory.create(
1477                 mMainDispatcher,
1478                 mBgScope,
1479                 this,
1480                 mWindowManagerWrapper,
1481                 mTaskResourceLoader,
1482                 mRelayoutParams.mLayoutResId,
1483                 mSplitScreenController,
1484                 canEnterDesktopModeOrShowAppHandle(mContext),
1485                 supportsMultiInstance,
1486                 shouldShowManageWindowsButton,
1487                 shouldShowChangeAspectRatioButton,
1488                 isDesktopModeSupportedOnDisplay(mContext, mDisplay),
1489                 shouldShowRestartButton,
1490                 isBrowserApp,
1491                 isBrowserApp ? getAppLink() : getBrowserLink(),
1492                 mDesktopModeUiEventLogger,
1493                 mResult.mCaptionWidth,
1494                 mResult.mCaptionHeight,
1495                 mResult.mCaptionX,
1496                 // Add top padding to the caption Y so that the menu is shown over what is the
1497                 // actual contents of the caption, ignoring padding. This is currently relevant
1498                 // to the Header in desktop immersive.
1499                 mResult.mCaptionY + mResult.mCaptionTopPadding
1500         );
1501         mWindowDecorViewHolder.onHandleMenuOpened();
1502         mHandleMenu.show(
1503                 /* onToDesktopClickListener= */ () -> {
1504                     mOnToDesktopClickListener.accept(APP_HANDLE_MENU_BUTTON);
1505                     return Unit.INSTANCE;
1506                 },
1507                 /* onToFullscreenClickListener= */ mOnToFullscreenClickListener,
1508                 /* onToSplitScreenClickListener= */ mOnToSplitscreenClickListener,
1509                 /* onToFloatClickListener= */ mOnToFloatClickListener,
1510                 /* onNewWindowClickListener= */ mOnNewWindowClickListener,
1511                 /* onManageWindowsClickListener= */ mOnManageWindowsClickListener,
1512                 /* onAspectRatioSettingsClickListener= */ mOnChangeAspectRatioClickListener,
1513                 /* openInBrowserClickListener= */ (intent) -> {
1514                     mOpenInBrowserClickListener.accept(intent);
1515                     onCapturedLinkUsed();
1516                     if (Flags.enableDesktopWindowingAppToWebEducationIntegration()) {
1517                         mWindowDecorCaptionHandleRepository.onAppToWebUsage();
1518                     }
1519                     return Unit.INSTANCE;
1520                 },
1521                 /* onOpenByDefaultClickListener= */ () -> {
1522                     if (!isOpenByDefaultDialogActive()) {
1523                         createOpenByDefaultDialog();
1524                     }
1525                     return Unit.INSTANCE;
1526                 },
1527                 /* onRestartClickListener= */ mOnRestartClickListener,
1528                 /* onCloseMenuClickListener= */ () -> {
1529                     closeHandleMenu();
1530                     return Unit.INSTANCE;
1531                 },
1532                 /* onOutsideTouchListener= */ () -> {
1533                     closeHandleMenu();
1534                     return Unit.INSTANCE;
1535                 },
1536                 /* forceShowSystemBars= */ inDesktopImmersive
1537         );
1538         if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
1539             notifyCaptionStateChanged();
1540         }
1541         mMinimumInstancesFound = false;
1542     }
1543 
createManageWindowsMenu(@onNull List<Pair<Integer, TaskSnapshot>> snapshotList, @NonNull Function1<Integer, Unit> onIconClickListener )1544     void createManageWindowsMenu(@NonNull List<Pair<Integer, TaskSnapshot>> snapshotList,
1545             @NonNull Function1<Integer, Unit> onIconClickListener
1546     ) {
1547         if (mTaskInfo.isFreeform()) {
1548             // The menu uses display-wide coordinates for positioning, so make position the sum
1549             // of task position and caption position.
1550             final Rect taskBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
1551             mManageWindowsMenu = new DesktopHeaderManageWindowsMenu(
1552                     mTaskInfo,
1553                     /* x= */ taskBounds.left + mResult.mCaptionX,
1554                     /* y= */ taskBounds.top + mResult.mCaptionY + mResult.mCaptionTopPadding,
1555                     mDisplayController,
1556                     mRootTaskDisplayAreaOrganizer,
1557                     mContext,
1558                     mDesktopUserRepositories,
1559                     mSurfaceControlBuilderSupplier,
1560                     mSurfaceControlTransactionSupplier,
1561                     snapshotList,
1562                     onIconClickListener,
1563                     /* onOutsideClickListener= */ () -> {
1564                         closeManageWindowsMenu();
1565                         return Unit.INSTANCE;
1566                     }
1567                     );
1568         } else {
1569             mManageWindowsMenu = new DesktopHandleManageWindowsMenu(
1570                     mTaskInfo,
1571                     mSplitScreenController,
1572                     getCaptionX(),
1573                     mResult.mCaptionWidth,
1574                     mWindowManagerWrapper,
1575                     mContext,
1576                     snapshotList,
1577                     onIconClickListener,
1578                     /* onOutsideClickListener= */ () -> {
1579                         closeManageWindowsMenu();
1580                         return Unit.INSTANCE;
1581                     }
1582                     );
1583         }
1584     }
1585 
closeManageWindowsMenu()1586     void closeManageWindowsMenu() {
1587         if (mManageWindowsMenu != null) {
1588             mManageWindowsMenu.animateClose();
1589         }
1590         mManageWindowsMenu = null;
1591     }
1592 
updateGenericLink()1593     private void updateGenericLink() {
1594         final ComponentName baseActivity = mTaskInfo.baseActivity;
1595         if (baseActivity == null) {
1596             return;
1597         }
1598 
1599         final String genericLink =
1600                 mGenericLinksParser.getGenericLink(baseActivity.getPackageName());
1601         mGenericLink = genericLink == null ? null : Uri.parse(genericLink);
1602     }
1603 
1604     /**
1605      * Close the handle menu window.
1606      */
closeHandleMenu()1607     void closeHandleMenu() {
1608         if (!isHandleMenuActive()) return;
1609         mWindowDecorViewHolder.onHandleMenuClosed();
1610         mHandleMenu.close();
1611         mHandleMenu = null;
1612         if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
1613             notifyCaptionStateChanged();
1614         }
1615     }
1616 
1617     @Override
releaseViews(WindowContainerTransaction wct)1618     void releaseViews(WindowContainerTransaction wct) {
1619         closeHandleMenu();
1620         closeManageWindowsMenu();
1621         closeMaximizeMenu();
1622         super.releaseViews(wct);
1623     }
1624 
1625     /**
1626      * Close an open handle menu if input is outside of menu coordinates
1627      *
1628      * @param ev the tapped point to compare against
1629      */
closeHandleMenuIfNeeded(MotionEvent ev)1630     void closeHandleMenuIfNeeded(MotionEvent ev) {
1631         if (!isHandleMenuActive()) return;
1632 
1633         PointF inputPoint = offsetCaptionLocation(ev);
1634 
1635         // If this is called before open_menu_button's onClick, we don't want to close
1636         // the menu since it will just reopen in onClick.
1637         final boolean pointInOpenMenuButton = pointInView(
1638                 mResult.mRootView.findViewById(R.id.open_menu_button),
1639                 inputPoint.x,
1640                 inputPoint.y);
1641 
1642         if (!mHandleMenu.isValidMenuInput(inputPoint) && !pointInOpenMenuButton) {
1643             closeHandleMenu();
1644         }
1645     }
1646 
isFocused()1647     boolean isFocused() {
1648         return mHasGlobalFocus;
1649     }
1650 
1651     /**
1652      * Offset the coordinates of a {@link MotionEvent} to be in the same coordinate space as caption
1653      *
1654      * @param ev the {@link MotionEvent} to offset
1655      * @return the point of the input in local space
1656      */
offsetCaptionLocation(MotionEvent ev)1657     private PointF offsetCaptionLocation(MotionEvent ev) {
1658         final PointF result = new PointF(ev.getX(), ev.getY());
1659         final ActivityManager.RunningTaskInfo taskInfo =
1660                 mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId);
1661         if (taskInfo == null) return result;
1662         final Point positionInParent = taskInfo.positionInParent;
1663         result.offset(-positionInParent.x, -positionInParent.y);
1664         return result;
1665     }
1666 
1667     /**
1668      * Checks if motion event occurs in the caption handle area of a focused caption (the caption on
1669      * a task in fullscreen or in multi-windowing mode). This should be used in cases where
1670      * onTouchListener will not work (i.e. when caption is in status bar area).
1671      *
1672      * @param ev       the {@link MotionEvent} to check
1673      * @return {@code true} if event is inside caption handle view, {@code false} if not
1674      */
checkTouchEventInFocusedCaptionHandle(MotionEvent ev)1675     boolean checkTouchEventInFocusedCaptionHandle(MotionEvent ev) {
1676         if (isHandleMenuActive() || !isAppHandle(mWindowDecorViewHolder)
1677                 || DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
1678             return false;
1679         }
1680         // The status bar input layer can only receive input in handle coordinates to begin with,
1681         // so checking coordinates is unnecessary as input is always within handle bounds.
1682         if (isAppHandle(mWindowDecorViewHolder)
1683                 && DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()
1684                 && isCaptionVisible()) {
1685             return true;
1686         }
1687 
1688         return checkTouchEventInCaption(ev);
1689     }
1690 
1691     /**
1692      * Checks if touch event occurs in caption.
1693      *
1694      * @param ev       the {@link MotionEvent} to check
1695      * @return {@code true} if event is inside caption view, {@code false} if not
1696      */
checkTouchEventInCaption(MotionEvent ev)1697     boolean checkTouchEventInCaption(MotionEvent ev) {
1698         final PointF inputPoint = offsetCaptionLocation(ev);
1699         return inputPoint.x >= mResult.mCaptionX
1700                 && inputPoint.x <= mResult.mCaptionX + mResult.mCaptionWidth
1701                 && inputPoint.y >= 0
1702                 && inputPoint.y <= mResult.mCaptionHeight;
1703     }
1704 
1705     /**
1706      * Checks whether the touch event falls inside the customizable caption region.
1707      */
checkTouchEventInCustomizableRegion(MotionEvent ev)1708     boolean checkTouchEventInCustomizableRegion(MotionEvent ev) {
1709         return mResult.mCustomizableCaptionRegion.contains((int) ev.getRawX(), (int) ev.getRawY());
1710     }
1711 
1712     /**
1713      * Check a passed MotionEvent if it has occurred on any button related to this decor.
1714      * Note this should only be called when a regular onClick is not possible
1715      * (i.e. the button was clicked through status bar layer)
1716      *
1717      * @param ev the MotionEvent to compare
1718      */
checkTouchEvent(MotionEvent ev)1719     void checkTouchEvent(MotionEvent ev) {
1720         if (mResult.mRootView == null || DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) return;
1721         final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
1722         final View handle = caption.findViewById(R.id.caption_handle);
1723         final boolean inHandle = !isHandleMenuActive()
1724                 && checkTouchEventInFocusedCaptionHandle(ev);
1725         final int action = ev.getActionMasked();
1726         if (action == ACTION_UP && inHandle) {
1727             handle.performClick();
1728         }
1729         if (isHandleMenuActive()) {
1730             // If the whole handle menu can be touched directly, rely on FLAG_WATCH_OUTSIDE_TOUCH.
1731             // This is for the case that some of the handle menu is underneath the status bar.
1732             if (isAppHandle(mWindowDecorViewHolder)
1733                     && !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
1734                 mHandleMenu.checkMotionEvent(ev);
1735                 closeHandleMenuIfNeeded(ev);
1736             }
1737         }
1738     }
1739 
1740     /**
1741      * Updates hover and pressed status of views in this decoration. Should only be called
1742      * when status cannot be updated normally (i.e. the button is hovered through status
1743      * bar layer).
1744      * @param ev the MotionEvent to compare against.
1745      */
updateHoverAndPressStatus(MotionEvent ev)1746     void updateHoverAndPressStatus(MotionEvent ev) {
1747         if (mResult.mRootView == null || DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) return;
1748         final View handle = mResult.mRootView.findViewById(R.id.caption_handle);
1749         final boolean inHandle = !isHandleMenuActive()
1750                 && checkTouchEventInFocusedCaptionHandle(ev);
1751         final int action = ev.getActionMasked();
1752         // The comparison against ACTION_UP is needed for the cancel drag to desktop case.
1753         handle.setHovered(inHandle && action != ACTION_UP);
1754         // We want handle to remain pressed if the pointer moves outside of it during a drag.
1755         handle.setPressed((inHandle && action == ACTION_DOWN)
1756                 || (handle.isPressed() && action != ACTION_UP && action != ACTION_CANCEL));
1757         if (isHandleMenuActive()) {
1758             mHandleMenu.checkMotionEvent(ev);
1759         }
1760     }
1761 
1762     /**
1763      * Indicates that an app handle drag has been interrupted, this can happen e.g. if we receive an
1764      * unknown transition during the drag-to-desktop transition.
1765      */
handleDragInterrupted()1766     void handleDragInterrupted() {
1767         if (mResult.mRootView == null) return;
1768         final View handle = mResult.mRootView.findViewById(R.id.caption_handle);
1769         handle.setHovered(false);
1770         handle.setPressed(false);
1771     }
1772 
pointInView(View v, float x, float y)1773     private boolean pointInView(View v, float x, float y) {
1774         return v != null && v.getLeft() <= x && v.getRight() >= x
1775                 && v.getTop() <= y && v.getBottom() >= y;
1776     }
1777 
1778     /** Returns true if at least one education flag is enabled. */
isEducationEnabled()1779     private boolean isEducationEnabled() {
1780         return Flags.enableDesktopWindowingAppHandleEducation()
1781                 || Flags.enableDesktopWindowingAppToWebEducationIntegration();
1782     }
1783 
1784     @Override
close()1785     public void close() {
1786         if (mLoadAppInfoRunnable != null) {
1787             mBgExecutor.removeCallbacks(mLoadAppInfoRunnable);
1788         }
1789         if (mSetAppInfoRunnable != null) {
1790             mMainExecutor.removeCallbacks(mSetAppInfoRunnable);
1791         }
1792         mTaskResourceLoader.onWindowDecorClosed(mTaskInfo);
1793         closeDragResizeListener();
1794         closeHandleMenu();
1795         closeManageWindowsMenu();
1796         mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
1797         disposeResizeVeil();
1798         disposeStatusBarInputLayer();
1799         if (mWindowDecorViewHolder != null) {
1800             mWindowDecorViewHolder.close();
1801             mWindowDecorViewHolder = null;
1802         }
1803         if (canEnterDesktopMode(mContext) && isEducationEnabled()) {
1804             notifyNoCaptionHandle();
1805         }
1806         super.close();
1807     }
1808 
getDesktopModeWindowDecorLayoutId(@indowingMode int windowingMode)1809     private static int getDesktopModeWindowDecorLayoutId(@WindowingMode int windowingMode) {
1810         return windowingMode == WINDOWING_MODE_FREEFORM
1811                 ? R.layout.desktop_mode_app_header
1812                 : R.layout.desktop_mode_app_handle;
1813     }
1814 
updatePositionInParent()1815     private void updatePositionInParent() {
1816         mPositionInParent.set(mTaskInfo.positionInParent);
1817     }
1818 
updateExclusionRegion(boolean inFullImmersive)1819     private void updateExclusionRegion(boolean inFullImmersive) {
1820         // An outdated position in parent is one reason for this to be called; update it here.
1821         updatePositionInParent();
1822         mExclusionRegionListener
1823                 .onExclusionRegionChanged(mTaskInfo.taskId,
1824                         getGlobalExclusionRegion(inFullImmersive));
1825     }
1826 
1827     /**
1828      * Create a new exclusion region from the corner rects (if resizeable) and caption bounds
1829      * of this task.
1830      */
getGlobalExclusionRegion(boolean inFullImmersive)1831     private Region getGlobalExclusionRegion(boolean inFullImmersive) {
1832         Region exclusionRegion;
1833         if (mDragResizeListener != null
1834                 && isDragResizable(mTaskInfo, inFullImmersive)) {
1835             exclusionRegion = mDragResizeListener.getCornersRegion();
1836         } else {
1837             exclusionRegion = new Region();
1838         }
1839         if (inFullImmersive) {
1840             // Task can't be moved in full immersive, so skip excluding the caption region.
1841             return exclusionRegion;
1842         }
1843         exclusionRegion.union(new Rect(0, 0, mResult.mWidth,
1844                 getCaptionHeight(mTaskInfo.getWindowingMode())));
1845         exclusionRegion.translate(mPositionInParent.x, mPositionInParent.y);
1846         return exclusionRegion;
1847     }
1848 
getCaptionX()1849     int getCaptionX() {
1850         return mResult.mCaptionX;
1851     }
1852 
1853     @Override
getCaptionHeightId(@indowingMode int windowingMode)1854     int getCaptionHeightId(@WindowingMode int windowingMode) {
1855         return getCaptionHeightIdStatic(windowingMode);
1856     }
1857 
getCaptionHeightIdStatic(@indowingMode int windowingMode)1858     private static int getCaptionHeightIdStatic(@WindowingMode int windowingMode) {
1859         return windowingMode == WINDOWING_MODE_FULLSCREEN
1860                 ? com.android.internal.R.dimen.status_bar_height_default
1861                 : getDesktopViewAppHeaderHeightId();
1862     }
1863 
getCaptionHeight(@indowingMode int windowingMode)1864     private int getCaptionHeight(@WindowingMode int windowingMode) {
1865         return loadDimensionPixelSize(mContext.getResources(), getCaptionHeightId(windowingMode));
1866     }
1867 
1868     @Override
getCaptionViewId()1869     int getCaptionViewId() {
1870         return R.id.desktop_mode_caption;
1871     }
1872 
setAnimatingTaskResizeOrReposition(boolean animatingTaskResizeOrReposition)1873     void setAnimatingTaskResizeOrReposition(boolean animatingTaskResizeOrReposition) {
1874         if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_handle) return;
1875         final boolean inFullImmersive =
1876                 mDesktopUserRepositories.getProfile(mTaskInfo.userId)
1877                         .isTaskInFullImmersiveState(mTaskInfo.taskId);
1878         asAppHeader(mWindowDecorViewHolder).bindData(new AppHeaderViewHolder.HeaderData(
1879                 mTaskInfo,
1880                 DesktopModeUtils.isTaskMaximized(mTaskInfo, mDisplayController),
1881                 inFullImmersive,
1882                 isFocused(),
1883                 /* maximizeHoverEnabled= */ canOpenMaximizeMenu(animatingTaskResizeOrReposition),
1884                 isCaptionVisible()));
1885     }
1886 
1887     /**
1888      * Declares whether a Recents transition is currently active.
1889      *
1890      * <p> When a Recents transition is active we allow that transition to take ownership of the
1891      * corner radius of its task surfaces, so each window decoration should stop updating the corner
1892      * radius of its task surface during that time.
1893      */
setIsRecentsTransitionRunning(boolean isRecentsTransitionRunning)1894     void setIsRecentsTransitionRunning(boolean isRecentsTransitionRunning) {
1895         mIsRecentsTransitionRunning = isRecentsTransitionRunning;
1896     }
1897 
1898     /**
1899      * Declares whether the window decoration is being dragged.
1900      */
setIsDragging(boolean isDragging)1901     void setIsDragging(boolean isDragging) {
1902         mIsDragging = isDragging;
1903     }
1904 
1905     /**
1906      * Called when there is a {@link MotionEvent#ACTION_HOVER_EXIT} on the maximize window button.
1907      */
onMaximizeButtonHoverExit()1908     void onMaximizeButtonHoverExit() {
1909         asAppHeader(mWindowDecorViewHolder).onMaximizeWindowHoverExit();
1910     }
1911 
1912     /**
1913      * Called when there is a {@link MotionEvent#ACTION_HOVER_ENTER} on the maximize window button.
1914      */
onMaximizeButtonHoverEnter()1915     void onMaximizeButtonHoverEnter() {
1916         asAppHeader(mWindowDecorViewHolder).onMaximizeWindowHoverEnter();
1917     }
1918 
canOpenMaximizeMenu(boolean animatingTaskResizeOrReposition)1919     private boolean canOpenMaximizeMenu(boolean animatingTaskResizeOrReposition) {
1920         if (!DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()) {
1921             return !animatingTaskResizeOrReposition;
1922         }
1923         final boolean inImmersiveAndRequesting =
1924                 mDesktopUserRepositories.getProfile(mTaskInfo.userId)
1925                         .isTaskInFullImmersiveState(mTaskInfo.taskId)
1926                     && TaskInfoKt.getRequestingImmersive(mTaskInfo);
1927         return !animatingTaskResizeOrReposition && !inImmersiveAndRequesting;
1928     }
1929 
1930     @Override
toString()1931     public String toString() {
1932         return "{"
1933                 + "mPositionInParent=" + mPositionInParent + ", "
1934                 + "taskId=" + mTaskInfo.taskId + ", "
1935                 + "windowingMode=" + windowingModeToString(mTaskInfo.getWindowingMode()) + ", "
1936                 + "isFocused=" + isFocused()
1937                 + "}";
1938     }
1939 
1940     static class Factory {
1941 
create( Context context, @NonNull Context userContext, DisplayController displayController, @NonNull WindowDecorTaskResourceLoader appResourceProvider, SplitScreenController splitScreenController, DesktopUserRepositories desktopUserRepositories, ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, @ShellMainThread Handler handler, @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread MainCoroutineDispatcher mainDispatcher, @ShellBackgroundThread CoroutineScope bgScope, @ShellBackgroundThread ShellExecutor bgExecutor, Choreographer choreographer, SyncTransactionQueue syncQueue, AppHeaderViewHolder.Factory appHeaderViewHolderFactory, AppHandleViewHolder.Factory appHandleViewHolderFactory, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, AppToWebGenericLinksParser genericLinksParser, AssistContentRequester assistContentRequester, @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier, MultiInstanceHelper multiInstanceHelper, WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository, DesktopModeEventLogger desktopModeEventLogger, DesktopModeUiEventLogger desktopModeUiEventLogger, DesktopModeCompatPolicy desktopModeCompatPolicy)1942         DesktopModeWindowDecoration create(
1943                 Context context,
1944                 @NonNull Context userContext,
1945                 DisplayController displayController,
1946                 @NonNull WindowDecorTaskResourceLoader appResourceProvider,
1947                 SplitScreenController splitScreenController,
1948                 DesktopUserRepositories desktopUserRepositories,
1949                 ShellTaskOrganizer taskOrganizer,
1950                 ActivityManager.RunningTaskInfo taskInfo,
1951                 SurfaceControl taskSurface,
1952                 @ShellMainThread Handler handler,
1953                 @ShellMainThread ShellExecutor mainExecutor,
1954                 @ShellMainThread MainCoroutineDispatcher mainDispatcher,
1955                 @ShellBackgroundThread CoroutineScope bgScope,
1956                 @ShellBackgroundThread ShellExecutor bgExecutor,
1957                 Choreographer choreographer,
1958                 SyncTransactionQueue syncQueue,
1959                 AppHeaderViewHolder.Factory appHeaderViewHolderFactory,
1960                 AppHandleViewHolder.Factory appHandleViewHolderFactory,
1961                 RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
1962                 AppToWebGenericLinksParser genericLinksParser,
1963                 AssistContentRequester assistContentRequester,
1964                 @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost>
1965                         windowDecorViewHostSupplier,
1966                 MultiInstanceHelper multiInstanceHelper,
1967                 WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
1968                 DesktopModeEventLogger desktopModeEventLogger,
1969                 DesktopModeUiEventLogger desktopModeUiEventLogger,
1970                 DesktopModeCompatPolicy desktopModeCompatPolicy) {
1971             return new DesktopModeWindowDecoration(
1972                     context,
1973                     userContext,
1974                     displayController,
1975                     appResourceProvider,
1976                     splitScreenController,
1977                     desktopUserRepositories,
1978                     taskOrganizer,
1979                     taskInfo,
1980                     taskSurface,
1981                     handler,
1982                     mainExecutor,
1983                     mainDispatcher,
1984                     bgScope,
1985                     bgExecutor,
1986                     choreographer,
1987                     syncQueue,
1988                     appHeaderViewHolderFactory,
1989                     appHandleViewHolderFactory,
1990                     rootTaskDisplayAreaOrganizer,
1991                     genericLinksParser,
1992                     assistContentRequester,
1993                     windowDecorViewHostSupplier,
1994                     multiInstanceHelper,
1995                     windowDecorCaptionHandleRepository,
1996                     desktopModeEventLogger,
1997                     desktopModeUiEventLogger,
1998                     desktopModeCompatPolicy);
1999         }
2000     }
2001 
2002     @VisibleForTesting
2003     static class CapturedLink {
2004         private final long mTimeStamp;
2005         private final Uri mUri;
2006         private boolean mUsed;
2007 
CapturedLink(@onNull Uri uri, long timeStamp)2008         CapturedLink(@NonNull Uri uri, long timeStamp) {
2009             mUri = uri;
2010             mTimeStamp = timeStamp;
2011         }
2012 
setUsed()2013         private void setUsed() {
2014             mUsed = true;
2015         }
2016     }
2017 
2018     interface ExclusionRegionListener {
2019         /** Inform the implementing class of this task's change in region resize handles */
onExclusionRegionChanged(int taskId, Region region)2020         void onExclusionRegionChanged(int taskId, Region region);
2021 
2022         /**
2023          * Inform the implementing class that this task no longer needs an exclusion region,
2024          * likely due to it closing.
2025          */
onExclusionRegionDismissed(int taskId)2026         void onExclusionRegionDismissed(int taskId);
2027     }
2028 }
2029