• 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.ActivityTaskManager.INVALID_TASK_ID;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
21 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
22 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
23 import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
24 import static android.view.MotionEvent.ACTION_CANCEL;
25 import static android.view.MotionEvent.ACTION_HOVER_ENTER;
26 import static android.view.MotionEvent.ACTION_HOVER_EXIT;
27 import static android.view.MotionEvent.ACTION_MOVE;
28 import static android.view.MotionEvent.ACTION_UP;
29 import static android.view.WindowInsets.Type.statusBars;
30 
31 import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU;
32 import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;
33 import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod;
34 import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason;
35 import static com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger;
36 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
37 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
38 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
39 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
40 import static com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer.MANAGE_WINDOWS_MINIMUM_INSTANCES;
41 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
42 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
43 import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
44 
45 import android.annotation.NonNull;
46 import android.app.ActivityManager;
47 import android.app.ActivityManager.RunningTaskInfo;
48 import android.app.ActivityTaskManager;
49 import android.app.IActivityManager;
50 import android.app.IActivityTaskManager;
51 import android.content.Context;
52 import android.content.Intent;
53 import android.graphics.Point;
54 import android.graphics.PointF;
55 import android.graphics.Rect;
56 import android.graphics.Region;
57 import android.hardware.input.InputManager;
58 import android.os.Handler;
59 import android.os.IBinder;
60 import android.os.Looper;
61 import android.os.RemoteException;
62 import android.os.UserHandle;
63 import android.util.Log;
64 import android.util.SparseArray;
65 import android.view.Choreographer;
66 import android.view.GestureDetector;
67 import android.view.ISystemGestureExclusionListener;
68 import android.view.IWindowManager;
69 import android.view.InputChannel;
70 import android.view.InputEvent;
71 import android.view.InputEventReceiver;
72 import android.view.InputMonitor;
73 import android.view.InsetsState;
74 import android.view.MotionEvent;
75 import android.view.SurfaceControl;
76 import android.view.SurfaceControl.Transaction;
77 import android.view.View;
78 import android.view.ViewConfiguration;
79 import android.view.ViewRootImpl;
80 import android.window.DesktopModeFlags;
81 import android.window.TaskSnapshot;
82 import android.window.WindowContainerToken;
83 import android.window.WindowContainerTransaction;
84 
85 import androidx.annotation.Nullable;
86 import androidx.annotation.OptIn;
87 
88 import com.android.internal.annotations.VisibleForTesting;
89 import com.android.internal.jank.Cuj;
90 import com.android.internal.jank.InteractionJankMonitor;
91 import com.android.internal.protolog.ProtoLog;
92 import com.android.internal.util.LatencyTracker;
93 import com.android.window.flags.Flags;
94 import com.android.wm.shell.R;
95 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
96 import com.android.wm.shell.ShellTaskOrganizer;
97 import com.android.wm.shell.apptoweb.AppToWebGenericLinksParser;
98 import com.android.wm.shell.apptoweb.AssistContentRequester;
99 import com.android.wm.shell.common.ComponentUtils;
100 import com.android.wm.shell.common.DisplayChangeController;
101 import com.android.wm.shell.common.DisplayController;
102 import com.android.wm.shell.common.DisplayInsetsController;
103 import com.android.wm.shell.common.DisplayLayout;
104 import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController;
105 import com.android.wm.shell.common.MultiInstanceHelper;
106 import com.android.wm.shell.common.ShellExecutor;
107 import com.android.wm.shell.common.SyncTransactionQueue;
108 import com.android.wm.shell.compatui.CompatUIController;
109 import com.android.wm.shell.compatui.api.CompatUIHandler;
110 import com.android.wm.shell.compatui.impl.CompatUIRequests;
111 import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
112 import com.android.wm.shell.desktopmode.DesktopImmersiveController;
113 import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
114 import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger;
115 import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum;
116 import com.android.wm.shell.desktopmode.DesktopModeUtils;
117 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
118 import com.android.wm.shell.desktopmode.DesktopRepository;
119 import com.android.wm.shell.desktopmode.DesktopTasksController;
120 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
121 import com.android.wm.shell.desktopmode.DesktopTasksLimiter;
122 import com.android.wm.shell.desktopmode.DesktopUserRepositories;
123 import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
124 import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction;
125 import com.android.wm.shell.desktopmode.common.ToggleTaskSizeUtilsKt;
126 import com.android.wm.shell.desktopmode.education.AppHandleEducationController;
127 import com.android.wm.shell.desktopmode.education.AppToWebEducationController;
128 import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer;
129 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
130 import com.android.wm.shell.recents.RecentsTransitionHandler;
131 import com.android.wm.shell.recents.RecentsTransitionStateListener;
132 import com.android.wm.shell.shared.FocusTransitionListener;
133 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
134 import com.android.wm.shell.shared.annotations.ShellMainThread;
135 import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
136 import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy;
137 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
138 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
139 import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
140 import com.android.wm.shell.splitscreen.SplitScreenController;
141 import com.android.wm.shell.sysui.KeyguardChangeListener;
142 import com.android.wm.shell.sysui.ShellCommandHandler;
143 import com.android.wm.shell.sysui.ShellController;
144 import com.android.wm.shell.sysui.ShellInit;
145 import com.android.wm.shell.transition.FocusTransitionObserver;
146 import com.android.wm.shell.transition.Transitions;
147 import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
148 import com.android.wm.shell.windowdecor.common.AppHandleAndHeaderVisibilityHelper;
149 import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader;
150 import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHost;
151 import com.android.wm.shell.windowdecor.common.viewhost.WindowDecorViewHostSupplier;
152 import com.android.wm.shell.windowdecor.extension.InsetsStateKt;
153 import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
154 import com.android.wm.shell.windowdecor.tiling.DesktopTilingDecorViewModel;
155 import com.android.wm.shell.windowdecor.tiling.SnapEventHandler;
156 import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
157 import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
158 
159 import kotlin.Pair;
160 import kotlin.Unit;
161 import kotlin.jvm.functions.Function1;
162 
163 import kotlinx.coroutines.CoroutineScope;
164 import kotlinx.coroutines.ExperimentalCoroutinesApi;
165 import kotlinx.coroutines.MainCoroutineDispatcher;
166 
167 import org.jetbrains.annotations.NotNull;
168 
169 import java.io.PrintWriter;
170 import java.util.ArrayList;
171 import java.util.HashSet;
172 import java.util.List;
173 import java.util.Optional;
174 import java.util.Set;
175 import java.util.function.Supplier;
176 
177 /**
178  * View model for the window decoration with a caption and shadows. Works with
179  * {@link DesktopModeWindowDecoration}.
180  */
181 
182 public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
183         FocusTransitionListener, SnapEventHandler {
184     private static final String TAG = "DesktopModeWindowDecorViewModel";
185 
186     private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory;
187     private final IWindowManager mWindowManager;
188     private final ShellExecutor mMainExecutor;
189     private final ActivityTaskManager mActivityTaskManager;
190     private final ShellCommandHandler mShellCommandHandler;
191     private final ShellTaskOrganizer mTaskOrganizer;
192     private final DesktopUserRepositories mDesktopUserRepositories;
193     private final ShellController mShellController;
194     private final Context mContext;
195     private final @ShellMainThread Handler mMainHandler;
196     private final @ShellMainThread MainCoroutineDispatcher mMainDispatcher;
197     private final @ShellBackgroundThread CoroutineScope mBgScope;
198     private final @ShellBackgroundThread ShellExecutor mBgExecutor;
199     private final Choreographer mMainChoreographer;
200     private final DisplayController mDisplayController;
201     private final SyncTransactionQueue mSyncQueue;
202     private final DesktopTasksController mDesktopTasksController;
203     private final DesktopImmersiveController mDesktopImmersiveController;
204     private final InputManager mInputManager;
205     private final InteractionJankMonitor mInteractionJankMonitor;
206     private final MultiInstanceHelper mMultiInstanceHelper;
207     private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
208     private final Optional<DesktopTasksLimiter> mDesktopTasksLimiter;
209     private final AppHandleEducationController mAppHandleEducationController;
210     private final AppToWebEducationController mAppToWebEducationController;
211     private final AppHandleAndHeaderVisibilityHelper mAppHandleAndHeaderVisibilityHelper;
212     private final AppHeaderViewHolder.Factory mAppHeaderViewHolderFactory;
213     private final AppHandleViewHolder.Factory mAppHandleViewHolderFactory;
214     private final DesksOrganizer mDesksOrganizer;
215     private boolean mTransitionDragActive;
216 
217     private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
218 
219     private final ExclusionRegionListener mExclusionRegionListener =
220             new ExclusionRegionListenerImpl();
221 
222     private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId;
223     private final DragEventListenerImpl mDragEventListener = new DragEventListenerImpl();
224     private final InputMonitorFactory mInputMonitorFactory;
225     private TaskOperations mTaskOperations;
226     private final Supplier<SurfaceControl.Transaction> mTransactionFactory;
227     private final Transitions mTransitions;
228     private final Optional<DesktopActivityOrientationChangeHandler>
229             mActivityOrientationChangeHandler;
230 
231     private SplitScreenController mSplitScreenController;
232 
233     private MoveToDesktopAnimator mMoveToDesktopAnimator;
234     private final Rect mDragToDesktopAnimationStartBounds = new Rect();
235     private final DesktopModeKeyguardChangeListener mDesktopModeKeyguardChangeListener =
236             new DesktopModeKeyguardChangeListener();
237     private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
238     private final AppToWebGenericLinksParser mGenericLinksParser;
239     private final DisplayInsetsController mDisplayInsetsController;
240     private final Region mExclusionRegion = Region.obtain();
241     private boolean mInImmersiveMode;
242     private final String mSysUIPackageName;
243     private final AssistContentRequester mAssistContentRequester;
244     private final WindowDecorViewHostSupplier<WindowDecorViewHost> mWindowDecorViewHostSupplier;
245 
246     private final DisplayChangeController.OnDisplayChangingListener mOnDisplayChangingListener;
247     private final ISystemGestureExclusionListener mGestureExclusionListener =
248             new ISystemGestureExclusionListener.Stub() {
249                 @Override
250                 public void onSystemGestureExclusionChanged(int displayId,
251                         Region systemGestureExclusion, Region systemGestureExclusionUnrestricted) {
252                     if (mContext.getDisplayId() != displayId) {
253                         return;
254                     }
255                     mMainExecutor.execute(() -> {
256                         mExclusionRegion.set(systemGestureExclusion);
257                         onExclusionRegionChanged(displayId, mExclusionRegion);
258                     });
259                 }
260             };
261     private final TaskPositionerFactory mTaskPositionerFactory;
262     private final FocusTransitionObserver mFocusTransitionObserver;
263     private final DesktopModeEventLogger mDesktopModeEventLogger;
264     private final DesktopModeUiEventLogger mDesktopModeUiEventLogger;
265     private final WindowDecorTaskResourceLoader mTaskResourceLoader;
266     private final RecentsTransitionHandler mRecentsTransitionHandler;
267     private final DesktopModeCompatPolicy mDesktopModeCompatPolicy;
268     private final DesktopTilingDecorViewModel mDesktopTilingDecorViewModel;
269     private final MultiDisplayDragMoveIndicatorController mMultiDisplayDragMoveIndicatorController;
270     private final LatencyTracker mLatencyTracker;
271     private final CompatUIHandler mCompatUI;
272 
DesktopModeWindowDecorViewModel( Context context, ShellExecutor shellExecutor, @ShellMainThread Handler mainHandler, Choreographer mainChoreographer, @ShellMainThread MainCoroutineDispatcher mainDispatcher, @ShellBackgroundThread CoroutineScope bgScope, @ShellBackgroundThread ShellExecutor bgExecutor, ShellInit shellInit, ShellCommandHandler shellCommandHandler, IWindowManager windowManager, ShellTaskOrganizer taskOrganizer, DesktopUserRepositories desktopUserRepositories, DisplayController displayController, ShellController shellController, DisplayInsetsController displayInsetsController, SyncTransactionQueue syncQueue, Transitions transitions, Optional<DesktopTasksController> desktopTasksController, DesktopImmersiveController desktopImmersiveController, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, InteractionJankMonitor interactionJankMonitor, AppToWebGenericLinksParser genericLinksParser, AssistContentRequester assistContentRequester, @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier, MultiInstanceHelper multiInstanceHelper, Optional<DesktopTasksLimiter> desktopTasksLimiter, AppHandleEducationController appHandleEducationController, AppToWebEducationController appToWebEducationController, AppHandleAndHeaderVisibilityHelper appHandleAndHeaderVisibilityHelper, WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository, Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler, FocusTransitionObserver focusTransitionObserver, DesktopModeEventLogger desktopModeEventLogger, DesktopModeUiEventLogger desktopModeUiEventLogger, WindowDecorTaskResourceLoader taskResourceLoader, RecentsTransitionHandler recentsTransitionHandler, DesktopModeCompatPolicy desktopModeCompatPolicy, DesktopTilingDecorViewModel desktopTilingDecorViewModel, MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController, CompatUIHandler compatUI, DesksOrganizer desksOrganizer)273     public DesktopModeWindowDecorViewModel(
274             Context context,
275             ShellExecutor shellExecutor,
276             @ShellMainThread Handler mainHandler,
277             Choreographer mainChoreographer,
278             @ShellMainThread MainCoroutineDispatcher mainDispatcher,
279             @ShellBackgroundThread CoroutineScope bgScope,
280             @ShellBackgroundThread ShellExecutor bgExecutor,
281             ShellInit shellInit,
282             ShellCommandHandler shellCommandHandler,
283             IWindowManager windowManager,
284             ShellTaskOrganizer taskOrganizer,
285             DesktopUserRepositories desktopUserRepositories,
286             DisplayController displayController,
287             ShellController shellController,
288             DisplayInsetsController displayInsetsController,
289             SyncTransactionQueue syncQueue,
290             Transitions transitions,
291             Optional<DesktopTasksController> desktopTasksController,
292             DesktopImmersiveController desktopImmersiveController,
293             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
294             InteractionJankMonitor interactionJankMonitor,
295             AppToWebGenericLinksParser genericLinksParser,
296             AssistContentRequester assistContentRequester,
297             @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
298             MultiInstanceHelper multiInstanceHelper,
299             Optional<DesktopTasksLimiter> desktopTasksLimiter,
300             AppHandleEducationController appHandleEducationController,
301             AppToWebEducationController appToWebEducationController,
302             AppHandleAndHeaderVisibilityHelper appHandleAndHeaderVisibilityHelper,
303             WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
304             Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
305             FocusTransitionObserver focusTransitionObserver,
306             DesktopModeEventLogger desktopModeEventLogger,
307             DesktopModeUiEventLogger desktopModeUiEventLogger,
308             WindowDecorTaskResourceLoader taskResourceLoader,
309             RecentsTransitionHandler recentsTransitionHandler,
310             DesktopModeCompatPolicy desktopModeCompatPolicy,
311             DesktopTilingDecorViewModel desktopTilingDecorViewModel,
312             MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController,
313             CompatUIHandler compatUI,
314             DesksOrganizer desksOrganizer) {
315         this(
316                 context,
317                 shellExecutor,
318                 mainHandler,
319                 mainChoreographer,
320                 mainDispatcher,
321                 bgScope,
322                 bgExecutor,
323                 shellInit,
324                 shellCommandHandler,
325                 windowManager,
326                 taskOrganizer,
327                 desktopUserRepositories,
328                 displayController,
329                 shellController,
330                 displayInsetsController,
331                 syncQueue,
332                 transitions,
333                 desktopTasksController,
334                 desktopImmersiveController,
335                 genericLinksParser,
336                 assistContentRequester,
337                 windowDecorViewHostSupplier,
338                 multiInstanceHelper,
339                 new DesktopModeWindowDecoration.Factory(),
340                 new InputMonitorFactory(),
341                 SurfaceControl.Transaction::new,
342                 new AppHeaderViewHolder.Factory(),
343                 new AppHandleViewHolder.Factory(),
344                 rootTaskDisplayAreaOrganizer,
345                 new SparseArray<>(),
346                 interactionJankMonitor,
347                 desktopTasksLimiter,
348                 appHandleEducationController,
349                 appToWebEducationController,
350                 appHandleAndHeaderVisibilityHelper,
351                 windowDecorCaptionHandleRepository,
352                 activityOrientationChangeHandler,
353                 new TaskPositionerFactory(),
354                 focusTransitionObserver,
355                 desktopModeEventLogger,
356                 desktopModeUiEventLogger,
357                 taskResourceLoader,
358                 recentsTransitionHandler,
359                 desktopModeCompatPolicy,
360                 desktopTilingDecorViewModel,
361                 multiDisplayDragMoveIndicatorController,
362                 compatUI,
363                 desksOrganizer);
364     }
365 
366     @VisibleForTesting
DesktopModeWindowDecorViewModel( Context context, ShellExecutor shellExecutor, @ShellMainThread Handler mainHandler, Choreographer mainChoreographer, @ShellMainThread MainCoroutineDispatcher mainDispatcher, @ShellBackgroundThread CoroutineScope bgScope, @ShellBackgroundThread ShellExecutor bgExecutor, ShellInit shellInit, ShellCommandHandler shellCommandHandler, IWindowManager windowManager, ShellTaskOrganizer taskOrganizer, DesktopUserRepositories desktopUserRepositories, DisplayController displayController, ShellController shellController, DisplayInsetsController displayInsetsController, SyncTransactionQueue syncQueue, Transitions transitions, Optional<DesktopTasksController> desktopTasksController, DesktopImmersiveController desktopImmersiveController, AppToWebGenericLinksParser genericLinksParser, AssistContentRequester assistContentRequester, @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier, MultiInstanceHelper multiInstanceHelper, DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory, InputMonitorFactory inputMonitorFactory, Supplier<SurfaceControl.Transaction> transactionFactory, AppHeaderViewHolder.Factory appHeaderViewHolderFactory, AppHandleViewHolder.Factory appHandleViewHolderFactory, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId, InteractionJankMonitor interactionJankMonitor, Optional<DesktopTasksLimiter> desktopTasksLimiter, AppHandleEducationController appHandleEducationController, AppToWebEducationController appToWebEducationController, AppHandleAndHeaderVisibilityHelper appHandleAndHeaderVisibilityHelper, WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository, Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler, TaskPositionerFactory taskPositionerFactory, FocusTransitionObserver focusTransitionObserver, DesktopModeEventLogger desktopModeEventLogger, DesktopModeUiEventLogger desktopModeUiEventLogger, WindowDecorTaskResourceLoader taskResourceLoader, RecentsTransitionHandler recentsTransitionHandler, DesktopModeCompatPolicy desktopModeCompatPolicy, DesktopTilingDecorViewModel desktopTilingDecorViewModel, MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController, CompatUIHandler compatUI, DesksOrganizer desksOrganizer)367     DesktopModeWindowDecorViewModel(
368             Context context,
369             ShellExecutor shellExecutor,
370             @ShellMainThread Handler mainHandler,
371             Choreographer mainChoreographer,
372             @ShellMainThread MainCoroutineDispatcher mainDispatcher,
373             @ShellBackgroundThread CoroutineScope bgScope,
374             @ShellBackgroundThread ShellExecutor bgExecutor,
375             ShellInit shellInit,
376             ShellCommandHandler shellCommandHandler,
377             IWindowManager windowManager,
378             ShellTaskOrganizer taskOrganizer,
379             DesktopUserRepositories desktopUserRepositories,
380             DisplayController displayController,
381             ShellController shellController,
382             DisplayInsetsController displayInsetsController,
383             SyncTransactionQueue syncQueue,
384             Transitions transitions,
385             Optional<DesktopTasksController> desktopTasksController,
386             DesktopImmersiveController desktopImmersiveController,
387             AppToWebGenericLinksParser genericLinksParser,
388             AssistContentRequester assistContentRequester,
389             @NonNull WindowDecorViewHostSupplier<WindowDecorViewHost> windowDecorViewHostSupplier,
390             MultiInstanceHelper multiInstanceHelper,
391             DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory,
392             InputMonitorFactory inputMonitorFactory,
393             Supplier<SurfaceControl.Transaction> transactionFactory,
394             AppHeaderViewHolder.Factory appHeaderViewHolderFactory,
395             AppHandleViewHolder.Factory appHandleViewHolderFactory,
396             RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
397             SparseArray<DesktopModeWindowDecoration> windowDecorByTaskId,
398             InteractionJankMonitor interactionJankMonitor,
399             Optional<DesktopTasksLimiter> desktopTasksLimiter,
400             AppHandleEducationController appHandleEducationController,
401             AppToWebEducationController appToWebEducationController,
402             AppHandleAndHeaderVisibilityHelper appHandleAndHeaderVisibilityHelper,
403             WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
404             Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler,
405             TaskPositionerFactory taskPositionerFactory,
406             FocusTransitionObserver focusTransitionObserver,
407             DesktopModeEventLogger desktopModeEventLogger,
408             DesktopModeUiEventLogger desktopModeUiEventLogger,
409             WindowDecorTaskResourceLoader taskResourceLoader,
410             RecentsTransitionHandler recentsTransitionHandler,
411             DesktopModeCompatPolicy desktopModeCompatPolicy,
412             DesktopTilingDecorViewModel desktopTilingDecorViewModel,
413             MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController,
414             CompatUIHandler compatUI,
415             DesksOrganizer desksOrganizer) {
416         mContext = context;
417         mMainExecutor = shellExecutor;
418         mMainHandler = mainHandler;
419         mMainChoreographer = mainChoreographer;
420         mMainDispatcher = mainDispatcher;
421         mBgScope = bgScope;
422         mBgExecutor = bgExecutor;
423         mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
424         mTaskOrganizer = taskOrganizer;
425         mDesktopUserRepositories = desktopUserRepositories;
426         mShellController = shellController;
427         mDisplayController = displayController;
428         mDisplayInsetsController = displayInsetsController;
429         mSyncQueue = syncQueue;
430         mTransitions = transitions;
431         mDesktopTasksController = desktopTasksController.get();
432         mDesktopImmersiveController = desktopImmersiveController;
433         mMultiInstanceHelper = multiInstanceHelper;
434         mShellCommandHandler = shellCommandHandler;
435         mWindowManager = windowManager;
436         mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory;
437         mInputMonitorFactory = inputMonitorFactory;
438         mTransactionFactory = transactionFactory;
439         mAppHeaderViewHolderFactory = appHeaderViewHolderFactory;
440         mAppHandleViewHolderFactory = appHandleViewHolderFactory;
441         mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
442         mGenericLinksParser = genericLinksParser;
443         mInputManager = mContext.getSystemService(InputManager.class);
444         mWindowDecorByTaskId = windowDecorByTaskId;
445         mSysUIPackageName = mContext.getResources().getString(
446                 com.android.internal.R.string.config_systemUi);
447         mInteractionJankMonitor = interactionJankMonitor;
448         mDesktopTasksLimiter = desktopTasksLimiter;
449         mAppHandleEducationController = appHandleEducationController;
450         mAppToWebEducationController = appToWebEducationController;
451         mAppHandleAndHeaderVisibilityHelper = appHandleAndHeaderVisibilityHelper;
452         mWindowDecorCaptionHandleRepository = windowDecorCaptionHandleRepository;
453         mActivityOrientationChangeHandler = activityOrientationChangeHandler;
454         mAssistContentRequester = assistContentRequester;
455         mWindowDecorViewHostSupplier = windowDecorViewHostSupplier;
456         mCompatUI = compatUI;
457         mOnDisplayChangingListener = (displayId, fromRotation, toRotation, displayAreaInfo, t) -> {
458             DesktopModeWindowDecoration decoration;
459             RunningTaskInfo taskInfo;
460             for (int i = 0; i < mWindowDecorByTaskId.size(); i++) {
461                 decoration = mWindowDecorByTaskId.valueAt(i);
462                 if (decoration == null) {
463                     continue;
464                 } else {
465                     taskInfo = decoration.mTaskInfo;
466                 }
467 
468                 // Check if display has been rotated between portrait & landscape
469                 if (displayId == taskInfo.displayId && taskInfo.isFreeform()
470                         && (fromRotation % 2 != toRotation % 2)) {
471                     // Check if the task bounds on the rotated display will be out of bounds.
472                     // If so, then update task bounds to be within reachable area.
473                     final Rect taskBounds = new Rect(
474                             taskInfo.configuration.windowConfiguration.getBounds());
475                     if (DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(
476                             taskBounds, decoration.calculateValidDragArea())) {
477                         t.setBounds(taskInfo.token, taskBounds);
478                     }
479                 }
480             }
481         };
482         mTaskPositionerFactory = taskPositionerFactory;
483         mFocusTransitionObserver = focusTransitionObserver;
484         mDesktopModeEventLogger = desktopModeEventLogger;
485         mDesktopModeUiEventLogger = desktopModeUiEventLogger;
486         mTaskResourceLoader = taskResourceLoader;
487         mRecentsTransitionHandler = recentsTransitionHandler;
488         mDesktopModeCompatPolicy = desktopModeCompatPolicy;
489         mDesktopTilingDecorViewModel = desktopTilingDecorViewModel;
490         mDesktopTasksController.setSnapEventHandler(this);
491         mMultiDisplayDragMoveIndicatorController = multiDisplayDragMoveIndicatorController;
492         mLatencyTracker = LatencyTracker.getInstance(mContext);
493         mDesksOrganizer = desksOrganizer;
494         shellInit.addInitCallback(this::onInit, this);
495     }
496 
497     @OptIn(markerClass = ExperimentalCoroutinesApi.class)
onInit()498     private void onInit() {
499         mShellController.addKeyguardChangeListener(mDesktopModeKeyguardChangeListener);
500         mShellCommandHandler.addDumpCallback(this::dump, this);
501         mDisplayInsetsController.addGlobalInsetsChangedListener(
502                 new DesktopModeOnInsetsChangedListener());
503         mDesktopTasksController.setOnTaskResizeAnimationListener(
504                 new DesktopModeOnTaskResizeAnimationListener());
505         mDesktopTasksController.setOnTaskRepositionAnimationListener(
506                 new DesktopModeOnTaskRepositionAnimationListener());
507         if (DesktopModeFlags.ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX.isTrue()
508                 || DesktopModeFlags.ENABLE_INPUT_LAYER_TRANSITION_FIX.isTrue()) {
509             mRecentsTransitionHandler.addTransitionStateListener(
510                     new DesktopModeRecentsTransitionStateListener());
511         }
512         mDisplayController.addDisplayChangingController(mOnDisplayChangingListener);
513         try {
514             mWindowManager.registerSystemGestureExclusionListener(mGestureExclusionListener,
515                     mContext.getDisplayId());
516         } catch (RemoteException e) {
517             Log.e(TAG, "Failed to register window manager callbacks", e);
518         }
519         if (DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(mContext)
520                 && Flags.enableDesktopWindowingAppHandleEducation()) {
521             mAppHandleEducationController.setAppHandleEducationTooltipCallbacks(
522                     /* appHandleTooltipClickCallback= */(taskId) -> {
523                         openHandleMenu(taskId);
524                         return Unit.INSTANCE;
525                     },
526                     /* onToDesktopClickCallback= */(taskId, desktopModeTransitionSource) -> {
527                         onToDesktop(taskId, desktopModeTransitionSource);
528                         return Unit.INSTANCE;
529                     });
530         }
531         mFocusTransitionObserver.setLocalFocusTransitionListener(this, mMainExecutor);
532         mDesksOrganizer.setOnDesktopTaskInfoChangedListener((taskInfo) -> {
533             onTaskInfoChanged(taskInfo);
534             return Unit.INSTANCE;
535         });
536     }
537 
538     @Override
onFocusedTaskChanged(int taskId, boolean isFocusedOnDisplay, boolean isFocusedGlobally)539     public void onFocusedTaskChanged(int taskId, boolean isFocusedOnDisplay,
540             boolean isFocusedGlobally) {
541         final WindowDecoration decor = mWindowDecorByTaskId.get(taskId);
542         if (decor != null) {
543             decor.relayout(decor.mTaskInfo, isFocusedGlobally, decor.mExclusionRegion);
544         }
545     }
546 
547     @Override
setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter)548     public void setFreeformTaskTransitionStarter(FreeformTaskTransitionStarter transitionStarter) {
549         mTaskOperations = new TaskOperations(transitionStarter, mContext, mSyncQueue);
550         mDesktopTasksController.setFreeformTaskTransitionStarter(transitionStarter);
551     }
552 
553     @Override
setSplitScreenController(SplitScreenController splitScreenController)554     public void setSplitScreenController(SplitScreenController splitScreenController) {
555         mSplitScreenController = splitScreenController;
556         mAppHandleAndHeaderVisibilityHelper.setSplitScreenController(splitScreenController);
557     }
558 
559     @Override
onTaskOpening( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT)560     public boolean onTaskOpening(
561             ActivityManager.RunningTaskInfo taskInfo,
562             SurfaceControl taskSurface,
563             SurfaceControl.Transaction startT,
564             SurfaceControl.Transaction finishT) {
565         if (!shouldShowWindowDecor(taskInfo)) return false;
566         createWindowDecoration(taskInfo, taskSurface, startT, finishT);
567         return true;
568     }
569 
570     @Override
onTaskInfoChanged(RunningTaskInfo taskInfo)571     public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
572         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
573         if (decoration == null) return;
574         final RunningTaskInfo oldTaskInfo = decoration.mTaskInfo;
575 
576         if (taskInfo.displayId != oldTaskInfo.displayId
577                 && !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
578             removeTaskFromEventReceiver(oldTaskInfo.displayId);
579             incrementEventReceiverTasks(taskInfo.displayId);
580         }
581         if (enableDisplayFocusInShellTransitions()) {
582             // Pass the current global focus status to avoid updates outside of a ShellTransition.
583             decoration.relayout(taskInfo, decoration.mHasGlobalFocus, decoration.mExclusionRegion);
584         } else {
585             decoration.relayout(taskInfo, taskInfo.isFocused, decoration.mExclusionRegion);
586         }
587         mDesktopTilingDecorViewModel.onTaskInfoChange(taskInfo);
588         mActivityOrientationChangeHandler.ifPresent(handler ->
589                 handler.handleActivityOrientationChange(oldTaskInfo, taskInfo));
590     }
591 
592     @Override
onTaskVanished(RunningTaskInfo taskInfo)593     public void onTaskVanished(RunningTaskInfo taskInfo) {
594         // A task vanishing doesn't necessarily mean the task was closed, it could also mean its
595         // windowing mode changed. We're only interested in closing tasks so checking whether
596         // its info still exists in the task organizer is one way to disambiguate.
597         final boolean closed = mTaskOrganizer.getRunningTaskInfo(taskInfo.taskId) == null;
598         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "Task Vanished: #%d closed=%b", taskInfo.taskId, closed);
599         if (closed) {
600             // Destroying the window decoration is usually handled when a TRANSIT_CLOSE transition
601             // changes happen, but there are certain cases in which closing tasks aren't included
602             // in transitions, such as when a non-visible task is closed. See b/296921167.
603             // Destroy the decoration here in case the lack of transition missed it.
604             destroyWindowDecoration(taskInfo);
605         }
606     }
607 
608     @Override
onTaskChanging( RunningTaskInfo taskInfo, SurfaceControl taskSurface, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT)609     public void onTaskChanging(
610             RunningTaskInfo taskInfo,
611             SurfaceControl taskSurface,
612             SurfaceControl.Transaction startT,
613             SurfaceControl.Transaction finishT) {
614         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
615         if (!shouldShowWindowDecor(taskInfo)) {
616             if (decoration != null) {
617                 destroyWindowDecoration(taskInfo);
618             }
619             return;
620         }
621 
622         if (decoration == null) {
623             createWindowDecoration(taskInfo, taskSurface, startT, finishT);
624         } else {
625             decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
626                     false /* shouldSetTaskPositionAndCrop */,
627                     mFocusTransitionObserver.hasGlobalFocus(taskInfo), mExclusionRegion);
628         }
629     }
630 
631     @Override
onTaskClosing( RunningTaskInfo taskInfo, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT)632     public void onTaskClosing(
633             RunningTaskInfo taskInfo,
634             SurfaceControl.Transaction startT,
635             SurfaceControl.Transaction finishT) {
636         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
637         if (decoration == null) return;
638 
639         decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
640                 false /* shouldSetTaskPositionAndCrop */,
641                 mFocusTransitionObserver.hasGlobalFocus(taskInfo),
642                 mExclusionRegion);
643     }
644 
645     @Override
destroyWindowDecoration(RunningTaskInfo taskInfo)646     public void destroyWindowDecoration(RunningTaskInfo taskInfo) {
647         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
648         if (decoration == null) return;
649 
650         decoration.close();
651         final int displayId = taskInfo.displayId;
652         if (mEventReceiversByDisplay.contains(displayId)
653                 && !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
654             removeTaskFromEventReceiver(displayId);
655         }
656         // Remove the decoration from the cache last because WindowDecoration#close could still
657         // issue CANCEL MotionEvents to touch listeners before its view host is released.
658         // See b/327664694.
659         mWindowDecorByTaskId.remove(taskInfo.taskId);
660     }
661 
onExclusionRegionChanged(int displayId, @NonNull Region exclusionRegion)662     private void onExclusionRegionChanged(int displayId, @NonNull Region exclusionRegion) {
663         final int decorCount = mWindowDecorByTaskId.size();
664         for (int i = 0; i < decorCount; i++) {
665             final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.valueAt(i);
666             if (decoration.mTaskInfo.displayId != displayId) continue;
667             decoration.onExclusionRegionChanged(exclusionRegion);
668         }
669     }
670 
openHandleMenu(int taskId)671     private void openHandleMenu(int taskId) {
672         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
673         // TODO(b/379873022): Run the instance check and the AssistContent request in
674         //  createHandleMenu on the same bg thread dispatch.
675         mBgExecutor.execute(() -> {
676             final int numOfInstances = checkNumberOfOtherInstances(decoration.mTaskInfo);
677             mMainExecutor.execute(() -> {
678                 decoration.createHandleMenu(
679                         numOfInstances >= MANAGE_WINDOWS_MINIMUM_INSTANCES
680                 );
681             });
682         });
683     }
684 
onToggleSizeInteraction( int taskId, @NonNull ToggleTaskSizeInteraction.AmbiguousSource source, @Nullable MotionEvent motionEvent)685     private void onToggleSizeInteraction(
686             int taskId, @NonNull ToggleTaskSizeInteraction.AmbiguousSource source,
687             @Nullable MotionEvent motionEvent) {
688         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
689         if (decoration == null) {
690             return;
691         }
692         final ToggleTaskSizeInteraction interaction =
693                 createToggleSizeInteraction(decoration, source, motionEvent);
694         if (interaction == null) {
695             return;
696         }
697         if (interaction.getCujTracing() != null) {
698             mInteractionJankMonitor.begin(
699                     decoration.mTaskSurface, mContext, mMainHandler,
700                     interaction.getCujTracing(), interaction.getJankTag());
701         }
702         mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo, interaction);
703         decoration.closeHandleMenu();
704         decoration.closeMaximizeMenu();
705     }
706 
createToggleSizeInteraction( @onNull DesktopModeWindowDecoration decoration, @NonNull ToggleTaskSizeInteraction.AmbiguousSource source, @Nullable MotionEvent motionEvent)707     private ToggleTaskSizeInteraction createToggleSizeInteraction(
708             @NonNull DesktopModeWindowDecoration decoration,
709             @NonNull ToggleTaskSizeInteraction.AmbiguousSource source,
710             @Nullable MotionEvent motionEvent) {
711         final RunningTaskInfo taskInfo = decoration.mTaskInfo;
712 
713         final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(taskInfo.displayId);
714         if (displayLayout == null) {
715             return null;
716         }
717         final Rect stableBounds = new Rect();
718         displayLayout.getStableBounds(stableBounds);
719         boolean isMaximized = DesktopModeUtils.isTaskMaximized(taskInfo, stableBounds);
720 
721         return new ToggleTaskSizeInteraction(
722                 isMaximized
723                         ? ToggleTaskSizeInteraction.Direction.RESTORE
724                         : ToggleTaskSizeInteraction.Direction.MAXIMIZE,
725                 ToggleTaskSizeUtilsKt.toSource(source, isMaximized),
726                 DesktopModeEventLogger.getInputMethodFromMotionEvent(motionEvent)
727         );
728     }
729 
onEnterOrExitImmersive(RunningTaskInfo taskInfo)730     private void onEnterOrExitImmersive(RunningTaskInfo taskInfo) {
731         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
732         if (decoration == null) {
733             return;
734         }
735         final DesktopRepository desktopRepository = mDesktopUserRepositories.getProfile(
736                 taskInfo.userId);
737         if (desktopRepository.isTaskInFullImmersiveState(taskInfo.taskId)) {
738             mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
739                     DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_RESTORE);
740             mDesktopImmersiveController.moveTaskToNonImmersive(
741                     decoration.mTaskInfo, DesktopImmersiveController.ExitReason.USER_INTERACTION);
742         } else {
743             mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
744                     DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_IMMERSIVE);
745             mDesktopImmersiveController.moveTaskToImmersive(decoration.mTaskInfo);
746         }
747         decoration.closeMaximizeMenu();
748     }
749 
750     /** Snap-resize a task to the left or right side of the desktop. */
onSnapResize(int taskId, boolean left, InputMethod inputMethod, boolean fromMenu)751     public void onSnapResize(int taskId, boolean left, InputMethod inputMethod, boolean fromMenu) {
752         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
753         if (decoration == null) {
754             return;
755         }
756 
757         if (fromMenu) {
758             final DesktopModeUiEventLogger.DesktopUiEventEnum event = left
759                     ? DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_TILE_TO_LEFT
760                     : DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_MENU_TAP_TO_TILE_TO_RIGHT;
761             mDesktopModeUiEventLogger.log(decoration.mTaskInfo, event);
762         }
763 
764         mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext, mMainHandler,
765                 Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE, "maximize_menu_resizable");
766         mDesktopTasksController.handleInstantSnapResizingTask(
767                 decoration.mTaskInfo,
768                 left ? SnapPosition.LEFT : SnapPosition.RIGHT,
769                 left ? ResizeTrigger.SNAP_LEFT_MENU : ResizeTrigger.SNAP_RIGHT_MENU,
770                 inputMethod);
771 
772         decoration.closeHandleMenu();
773         decoration.closeMaximizeMenu();
774     }
775 
onOpenInBrowser(int taskId, @NonNull Intent intent)776     private void onOpenInBrowser(int taskId, @NonNull Intent intent) {
777         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
778         if (decoration == null) {
779             return;
780         }
781         openInBrowser(intent, decoration.getUser());
782         decoration.closeHandleMenu();
783         decoration.closeMaximizeMenu();
784     }
785 
openInBrowser(@onNull Intent intent, @NonNull UserHandle userHandle)786     private void openInBrowser(@NonNull Intent intent, @NonNull UserHandle userHandle) {
787         mContext.startActivityAsUser(intent, userHandle);
788     }
789 
onToDesktop(int taskId, DesktopModeTransitionSource source)790     private void onToDesktop(int taskId, DesktopModeTransitionSource source) {
791         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
792         if (decoration == null) {
793             return;
794         }
795         final WindowContainerTransaction wct = new WindowContainerTransaction();
796         mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext, mMainHandler,
797                 CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU);
798         mLatencyTracker.onActionStart(LatencyTracker.ACTION_DESKTOP_MODE_ENTER_APP_HANDLE_MENU);
799         // App sometimes draws before the insets from WindowDecoration#relayout have
800         // been added, so they must be added here
801         decoration.addCaptionInset(wct);
802         if (!mDesktopTasksController.moveTaskToDefaultDeskAndActivate(
803                 taskId,
804                 wct,
805                 source,
806                 /* remoteTransition= */ null,
807                 /* moveToDesktopCallback= */ null)) {
808             mLatencyTracker.onActionCancel(
809                     LatencyTracker.ACTION_DESKTOP_MODE_ENTER_APP_HANDLE_MENU);
810         }
811         decoration.closeHandleMenu();
812 
813         if (source == DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON) {
814             mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
815                     DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_MENU_TAP_TO_DESKTOP_MODE);
816         }
817     }
818 
onToFullscreen(int taskId)819     private void onToFullscreen(int taskId) {
820         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
821         if (decoration == null) {
822             return;
823         }
824         decoration.closeHandleMenu();
825         if (isTaskInSplitScreen(taskId)) {
826             mSplitScreenController.moveTaskToFullscreen(taskId,
827                     SplitScreenController.EXIT_REASON_DESKTOP_MODE);
828         } else {
829             mDesktopTasksController.moveToFullscreen(taskId,
830                     DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON);
831         }
832         mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
833                 DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_MENU_TAP_TO_FULL_SCREEN);
834     }
835 
onToSplitScreen(int taskId)836     private void onToSplitScreen(int taskId) {
837         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
838         if (decoration == null) {
839             return;
840         }
841         decoration.closeHandleMenu();
842         mDesktopTasksController.requestSplit(decoration.mTaskInfo, false /* leftOrTop */);
843         mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
844                 DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_MENU_TAP_TO_SPLIT_SCREEN);
845     }
846 
onToFloat(int taskId)847     private void onToFloat(int taskId) {
848         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
849         if (decoration == null) {
850             return;
851         }
852         decoration.closeHandleMenu();
853         // When the app enters float, the handle will no longer be visible, meaning
854         // we shouldn't receive input for it any longer.
855         decoration.disposeStatusBarInputLayer();
856         mDesktopTasksController.requestFloat(decoration.mTaskInfo);
857     }
858 
onNewWindow(int taskId)859     private void onNewWindow(int taskId) {
860         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
861         if (decoration == null) {
862             return;
863         }
864         decoration.closeHandleMenu();
865         mDesktopTasksController.openNewWindow(decoration.mTaskInfo);
866         mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
867                 DesktopUiEventEnum.DESKTOP_WINDOW_MULTI_INSTANCE_NEW_WINDOW_CLICK);
868     }
869 
onManageWindows(DesktopModeWindowDecoration decoration)870     private void onManageWindows(DesktopModeWindowDecoration decoration) {
871         if (decoration == null) {
872             return;
873         }
874         decoration.closeHandleMenu();
875         mBgExecutor.execute(() -> {
876             final ArrayList<Pair<Integer, TaskSnapshot>> snapshotList =
877                     getTaskSnapshots(decoration.mTaskInfo);
878             mMainExecutor.execute(() -> decoration.createManageWindowsMenu(
879                     snapshotList,
880                     /* onIconClickListener= */ (Integer requestedTaskId) -> {
881                         decoration.closeManageWindowsMenu();
882                         mDesktopTasksController.openInstance(decoration.mTaskInfo,
883                                 requestedTaskId);
884                         mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
885                                 DesktopUiEventEnum
886                                         .DESKTOP_WINDOW_MULTI_INSTANCE_MANAGE_WINDOWS_ICON_CLICK);
887                         return Unit.INSTANCE;
888                     }
889                 )
890             );
891         });
892     }
893 
getTaskSnapshots( @onNull RunningTaskInfo callerTaskInfo )894     private ArrayList<Pair<Integer, TaskSnapshot>> getTaskSnapshots(
895             @NonNull RunningTaskInfo callerTaskInfo
896     ) {
897         final ArrayList<Pair<Integer, TaskSnapshot>> snapshotList = new ArrayList<>();
898         final IActivityManager activityManager = ActivityManager.getService();
899         final IActivityTaskManager activityTaskManagerService = ActivityTaskManager.getService();
900         final List<ActivityManager.RecentTaskInfo> recentTasks;
901         try {
902             recentTasks = mActivityTaskManager.getRecentTasks(
903                     Integer.MAX_VALUE,
904                     ActivityManager.RECENT_WITH_EXCLUDED,
905                     activityManager.getCurrentUser().id);
906         } catch (RemoteException e) {
907             throw new RuntimeException(e);
908         }
909         final String callerPackageName = callerTaskInfo.baseActivity.getPackageName();
910         for (ActivityManager.RecentTaskInfo info : recentTasks) {
911             if (info.taskId == callerTaskInfo.taskId || info.baseActivity == null) continue;
912             final String infoPackageName = info.baseActivity.getPackageName();
913             if (!infoPackageName.equals(callerPackageName)) {
914                 continue;
915             }
916             if (info.baseActivity != null) {
917                 if (callerPackageName.equals(infoPackageName)) {
918                     // TODO(b/337903443): Fix this returning null for freeform tasks.
919                     try {
920                         TaskSnapshot screenshot = activityTaskManagerService
921                                 .getTaskSnapshot(info.taskId, false);
922                         if (screenshot == null) {
923                             screenshot = activityTaskManagerService
924                                     .takeTaskSnapshot(info.taskId, false);
925                         }
926                         snapshotList.add(new Pair(info.taskId, screenshot));
927                     } catch (RemoteException e) {
928                         throw new RuntimeException(e);
929                     }
930                 }
931             }
932         }
933         return snapshotList;
934     }
935 
936     @Override
snapToHalfScreen(@onNull RunningTaskInfo taskInfo, @NonNull Rect currentDragBounds, @NonNull SnapPosition position)937     public boolean snapToHalfScreen(@NonNull RunningTaskInfo taskInfo,
938             @NonNull Rect currentDragBounds, @NonNull SnapPosition position) {
939         return mDesktopTilingDecorViewModel.snapToHalfScreen(taskInfo,
940                 mWindowDecorByTaskId.get(taskInfo.taskId), position, currentDragBounds);
941     }
942 
943     @Override
removeTaskIfTiled(int displayId, int taskId)944     public void removeTaskIfTiled(int displayId, int taskId) {
945         mDesktopTilingDecorViewModel.removeTaskIfTiled(displayId, taskId);
946     }
947 
948     @Override
onUserChange()949     public void onUserChange() {
950         mDesktopTilingDecorViewModel.onUserChange();
951     }
952 
953     @Override
onOverviewAnimationStateChange(boolean running)954     public void onOverviewAnimationStateChange(boolean running) {
955         mDesktopTilingDecorViewModel.onOverviewAnimationStateChange(running);
956     }
957 
958     @Override
moveTaskToFrontIfTiled(@onNull RunningTaskInfo taskInfo)959     public boolean moveTaskToFrontIfTiled(@NonNull RunningTaskInfo taskInfo) {
960         return mDesktopTilingDecorViewModel.moveTaskToFrontIfTiled(taskInfo);
961     }
962 
963     @Override
964     @NotNull
getLeftSnapBoundsIfTiled(int displayId)965     public Rect getLeftSnapBoundsIfTiled(int displayId) {
966         return mDesktopTilingDecorViewModel.getLeftSnapBoundsIfTiled(displayId);
967     }
968 
969     @Override
970     @NotNull
getRightSnapBoundsIfTiled(int displayId)971     public Rect getRightSnapBoundsIfTiled(int displayId) {
972         return mDesktopTilingDecorViewModel.getRightSnapBoundsIfTiled(displayId);
973     }
974 
975     private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
976             implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
977             View.OnGenericMotionListener, DragDetector.MotionEventHandler {
978         private static final long APP_HANDLE_HOLD_TO_DRAG_DURATION_MS = 100;
979         private static final long APP_HEADER_HOLD_TO_DRAG_DURATION_MS = 0;
980 
981         private final int mTaskId;
982         private final WindowContainerToken mTaskToken;
983         private final DragPositioningCallback mDragPositioningCallback;
984         private final DragDetector mHandleDragDetector;
985         private final DragDetector mHeaderDragDetector;
986         private final GestureDetector mGestureDetector;
987         private final int mDisplayId;
988         private final Rect mOnDragStartInitialBounds = new Rect();
989 
990         /**
991          * Whether to pilfer the next motion event to send cancellations to the windows below.
992          * Useful when the caption window is spy and the gesture should be handled by the system
993          * instead of by the app for their custom header content.
994          * Should not have any effect when
995          * {@link DesktopModeFlags#ENABLE_ACCESSIBLE_CUSTOM_HEADERS}, because a spy window is not
996          * used then.
997          */
998         private boolean mIsCustomHeaderGesture;
999         private boolean mIsResizeGesture;
1000         private boolean mIsDragging;
1001         private boolean mDragInterrupted;
1002         private boolean mLongClickDisabled;
1003         private int mDragPointerId = -1;
1004         private MotionEvent mMotionEvent;
1005 
DesktopModeTouchEventListener( RunningTaskInfo taskInfo, DragPositioningCallback dragPositioningCallback)1006         private DesktopModeTouchEventListener(
1007                 RunningTaskInfo taskInfo,
1008                 DragPositioningCallback dragPositioningCallback) {
1009             mTaskId = taskInfo.taskId;
1010             mTaskToken = taskInfo.token;
1011             mDragPositioningCallback = dragPositioningCallback;
1012             final int touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
1013             final long appHandleHoldToDragDuration =
1014                     DesktopModeFlags.ENABLE_HOLD_TO_DRAG_APP_HANDLE.isTrue()
1015                             ? APP_HANDLE_HOLD_TO_DRAG_DURATION_MS : 0;
1016             mHandleDragDetector = new DragDetector(this, appHandleHoldToDragDuration,
1017                     touchSlop);
1018             mHeaderDragDetector = new DragDetector(this, APP_HEADER_HOLD_TO_DRAG_DURATION_MS,
1019                     touchSlop);
1020             mGestureDetector = new GestureDetector(mContext, this);
1021             mDisplayId = taskInfo.displayId;
1022         }
1023 
1024         @Override
onClick(View v)1025         public void onClick(View v) {
1026             if (mIsDragging) {
1027                 mIsDragging = false;
1028                 return;
1029             }
1030             final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
1031             final int id = v.getId();
1032             if (id == R.id.close_window) {
1033                 if (isTaskInSplitScreen(mTaskId)) {
1034                     mSplitScreenController.moveTaskToFullscreen(getOtherSplitTask(mTaskId).taskId,
1035                             SplitScreenController.EXIT_REASON_DESKTOP_MODE);
1036                 } else {
1037                     WindowContainerTransaction wct = new WindowContainerTransaction();
1038                     final Function1<IBinder, Unit> runOnTransitionStart =
1039                             mDesktopTasksController.onDesktopWindowClose(
1040                                     wct, mDisplayId, decoration.mTaskInfo);
1041                     final IBinder transition = mTaskOperations.closeTask(mTaskToken, wct);
1042                     if (transition != null) {
1043                         runOnTransitionStart.invoke(transition);
1044                     }
1045                 }
1046             } else if (id == R.id.back_button) {
1047                 mTaskOperations.injectBackKey(mDisplayId);
1048             } else if (id == R.id.caption_handle || id == R.id.open_menu_button) {
1049                 if (id == R.id.caption_handle && !decoration.mTaskInfo.isFreeform()) {
1050                     // Clicking the App Handle.
1051                     mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
1052                             DesktopUiEventEnum.DESKTOP_WINDOW_APP_HANDLE_TAP);
1053                 }
1054                 if (!decoration.isHandleMenuActive()) {
1055                     moveTaskToFront(decoration.mTaskInfo);
1056                     openHandleMenu(mTaskId);
1057                 }
1058             } else if (id == R.id.maximize_window) {
1059                 // TODO(b/346441962): move click detection logic into the decor's
1060                 //  {@link AppHeaderViewHolder}. Let it encapsulate the that and have it report
1061                 //  back to the decoration using
1062                 //  {@link DesktopModeWindowDecoration#setOnMaximizeOrRestoreClickListener}, which
1063                 //  should shared with the maximize menu's maximize/restore actions.
1064                 final DesktopRepository desktopRepository = mDesktopUserRepositories.getProfile(
1065                         decoration.mTaskInfo.userId);
1066                 if (DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()
1067                         && desktopRepository.isTaskInFullImmersiveState(
1068                         decoration.mTaskInfo.taskId)) {
1069                     // Task is in immersive and should exit.
1070                     onEnterOrExitImmersive(decoration.mTaskInfo);
1071                 } else {
1072                     // Full immersive is disabled or task doesn't request/support it, so just
1073                     // toggle between maximize/restore states.
1074                     onToggleSizeInteraction(decoration.mTaskInfo.taskId,
1075                             ToggleTaskSizeInteraction.AmbiguousSource.HEADER_BUTTON, mMotionEvent);
1076                 }
1077             } else if (id == R.id.minimize_window) {
1078                 mDesktopTasksController.minimizeTask(
1079                         decoration.mTaskInfo, MinimizeReason.MINIMIZE_BUTTON);
1080             }
1081         }
1082 
1083         @Override
onTouch(View v, MotionEvent e)1084         public boolean onTouch(View v, MotionEvent e) {
1085             mMotionEvent = e;
1086             final int id = v.getId();
1087             final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
1088             final boolean touchscreenSource =
1089                     (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
1090             // Disable long click during events from a non-touchscreen source
1091             mLongClickDisabled = !touchscreenSource && e.getActionMasked() != ACTION_UP
1092                     && e.getActionMasked() != ACTION_CANCEL;
1093 
1094             if (id != R.id.caption_handle && id != R.id.desktop_mode_caption
1095                     && id != R.id.open_menu_button && id != R.id.close_window
1096                     && id != R.id.maximize_window && id != R.id.minimize_window) {
1097                 return false;
1098             }
1099             final boolean isAppHandle = !getTaskInfo().isFreeform();
1100             final int actionMasked = e.getActionMasked();
1101             final boolean isDown = actionMasked == MotionEvent.ACTION_DOWN;
1102             final boolean isUpOrCancel = actionMasked == MotionEvent.ACTION_CANCEL
1103                     || actionMasked == MotionEvent.ACTION_UP;
1104             if (isDown) {
1105                 // Only move to front on down to prevent 2+ tasks from fighting
1106                 // (and thus flickering) for front status when drag-moving them simultaneously with
1107                 // two pointers.
1108                 // TODO(b/356962065): during a drag-move, this shouldn't be a WCT - just move the
1109                 //  task surface to the top of other tasks and reorder once the user releases the
1110                 //  gesture together with the bounds' WCT. This is probably still valid for other
1111                 //  gestures like simple clicks.
1112                 moveTaskToFront(decoration.mTaskInfo);
1113 
1114                 final boolean downInCustomizableCaptionRegion =
1115                         decoration.checkTouchEventInCustomizableRegion(e);
1116                 final boolean downInExclusionRegion = mExclusionRegion.contains(
1117                         (int) e.getRawX(), (int) e.getRawY());
1118                 final boolean isTransparentCaption =
1119                         TaskInfoKt.isTransparentCaptionBarAppearance(decoration.mTaskInfo);
1120                 // MotionEvent's coordinates are relative to view, we want location in window
1121                 // to offset position relative to caption as a whole.
1122                 int[] viewLocation = new int[2];
1123                 v.getLocationInWindow(viewLocation);
1124                 mIsResizeGesture = decoration.shouldResizeListenerHandleEvent(e,
1125                         new Point(viewLocation[0], viewLocation[1]));
1126                 // The caption window may be a spy window when the caption background is
1127                 // transparent, which means events will fall through to the app window. Make
1128                 // sure to cancel these events if they do not happen in the intersection of the
1129                 // customizable region and what the app reported as exclusion areas, because
1130                 // the drag-move or other caption gestures should take priority outside those
1131                 // regions.
1132                 mIsCustomHeaderGesture = downInCustomizableCaptionRegion
1133                         && downInExclusionRegion && isTransparentCaption;
1134             }
1135             if (mIsCustomHeaderGesture || mIsResizeGesture) {
1136                 // The event will be handled by the custom window below or pilfered by resize
1137                 // handler.
1138                 return false;
1139             }
1140             if (mInputManager != null
1141                     && !DesktopModeFlags.ENABLE_ACCESSIBLE_CUSTOM_HEADERS.isTrue()) {
1142                 ViewRootImpl viewRootImpl = v.getViewRootImpl();
1143                 if (viewRootImpl != null) {
1144                     // Pilfer so that windows below receive cancellations for this gesture.
1145                     mInputManager.pilferPointers(viewRootImpl.getInputToken());
1146                 }
1147             }
1148             if (isUpOrCancel) {
1149                 // Gesture is finished, reset state.
1150                 mIsCustomHeaderGesture = false;
1151                 mIsResizeGesture = false;
1152             }
1153             if (isAppHandle) {
1154                 return mHandleDragDetector.onMotionEvent(v, e);
1155             } else {
1156                 return mHeaderDragDetector.onMotionEvent(v, e);
1157             }
1158         }
1159 
1160         @Override
onLongClick(View v)1161         public boolean onLongClick(View v) {
1162             final int id = v.getId();
1163             if (id == R.id.maximize_window && !mLongClickDisabled) {
1164                 final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
1165                 moveTaskToFront(decoration.mTaskInfo);
1166                 if (decoration.isMaximizeMenuActive()) {
1167                     decoration.closeMaximizeMenu();
1168                 } else {
1169                     mDesktopModeUiEventLogger.log(decoration.mTaskInfo,
1170                             DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_REVEAL_MENU);
1171                     decoration.createMaximizeMenu();
1172                 }
1173                 return true;
1174             }
1175             return false;
1176         }
1177 
1178         /**
1179          * TODO(b/346441962): move this hover detection logic into the decor's
1180          * {@link AppHeaderViewHolder}.
1181          */
1182         @Override
onGenericMotion(View v, MotionEvent ev)1183         public boolean onGenericMotion(View v, MotionEvent ev) {
1184             mMotionEvent = ev;
1185             final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
1186             final int id = v.getId();
1187             if (ev.getAction() == ACTION_HOVER_ENTER && id == R.id.maximize_window) {
1188                 decoration.setAppHeaderMaximizeButtonHovered(true);
1189                 if (!decoration.isMaximizeMenuActive()) {
1190                     decoration.onMaximizeButtonHoverEnter();
1191                 }
1192                 return true;
1193             }
1194             if (ev.getAction() == ACTION_HOVER_EXIT && id == R.id.maximize_window) {
1195                 decoration.setAppHeaderMaximizeButtonHovered(false);
1196                 decoration.onMaximizeHoverStateChanged();
1197                 if (!decoration.isMaximizeMenuActive()) {
1198                     decoration.onMaximizeButtonHoverExit();
1199                 }
1200                 return true;
1201             }
1202             return false;
1203         }
1204 
moveTaskToFront(RunningTaskInfo taskInfo)1205         private void moveTaskToFront(RunningTaskInfo taskInfo) {
1206             if (!mFocusTransitionObserver.hasGlobalFocus(taskInfo)) {
1207                 mDesktopModeUiEventLogger.log(taskInfo,
1208                         DesktopUiEventEnum.DESKTOP_WINDOW_HEADER_TAP_TO_REFOCUS);
1209                 mDesktopTasksController.moveTaskToFront(taskInfo);
1210             }
1211         }
1212 
1213         /**
1214          * @param e {@link MotionEvent} to process
1215          * @return {@code true} if the motion event is handled.
1216          */
1217         @Override
handleMotionEvent(@ullable View v, MotionEvent e)1218         public boolean handleMotionEvent(@Nullable View v, MotionEvent e) {
1219             final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
1220             final RunningTaskInfo taskInfo = decoration.mTaskInfo;
1221             if (DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(mContext)
1222                     && !taskInfo.isFreeform()) {
1223                 return handleNonFreeformMotionEvent(decoration, v, e);
1224             } else {
1225                 return handleFreeformMotionEvent(decoration, taskInfo, v, e);
1226             }
1227         }
1228 
1229         @NonNull
getTaskInfo()1230         private RunningTaskInfo getTaskInfo() {
1231             final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
1232             return decoration.mTaskInfo;
1233         }
1234 
handleNonFreeformMotionEvent(DesktopModeWindowDecoration decoration, View v, MotionEvent e)1235         private boolean handleNonFreeformMotionEvent(DesktopModeWindowDecoration decoration,
1236                 View v, MotionEvent e) {
1237             final int id = v.getId();
1238             if (id == R.id.caption_handle) {
1239                 handleCaptionThroughStatusBar(e, decoration,
1240                         /* interruptDragCallback= */
1241                         () -> {
1242                             mDragInterrupted = true;
1243                             setIsDragging(decoration, /* isDragging= */ false);
1244                         });
1245                 final boolean wasDragging = mIsDragging;
1246                 updateDragStatus(decoration, e);
1247                 final boolean upOrCancel = e.getActionMasked() == ACTION_UP
1248                         || e.getActionMasked() == ACTION_CANCEL;
1249                 if (wasDragging && upOrCancel) {
1250                     // When finishing a drag the event will be consumed, which means the pressed
1251                     // state of the App Handle must be manually reset to scale its drawable back to
1252                     // its original shape. This is necessary for drag gestures of the Handle that
1253                     // result in a cancellation (dragging back to the top).
1254                     v.setPressed(false);
1255                 }
1256                 // Only prevent onClick from receiving this event if it's a drag.
1257                 return wasDragging;
1258             }
1259             return false;
1260         }
1261 
setIsDragging( @ullable DesktopModeWindowDecoration decor, boolean isDragging)1262         private void setIsDragging(
1263                 @Nullable DesktopModeWindowDecoration decor, boolean isDragging) {
1264             mIsDragging = isDragging;
1265             if (decor == null) return;
1266             decor.setIsDragging(isDragging);
1267         }
1268 
handleFreeformMotionEvent(DesktopModeWindowDecoration decoration, RunningTaskInfo taskInfo, View v, MotionEvent e)1269         private boolean handleFreeformMotionEvent(DesktopModeWindowDecoration decoration,
1270                 RunningTaskInfo taskInfo, View v, MotionEvent e) {
1271             final int id = v.getId();
1272             if (mGestureDetector.onTouchEvent(e)) {
1273                 return true;
1274             }
1275             final boolean touchingButton = (id == R.id.close_window || id == R.id.maximize_window
1276                     || id == R.id.open_menu_button || id == R.id.minimize_window);
1277             final DesktopRepository desktopRepository = mDesktopUserRepositories.getProfile(
1278                     taskInfo.userId);
1279             final boolean dragAllowed =
1280                     !desktopRepository.isTaskInFullImmersiveState(taskInfo.taskId);
1281             switch (e.getActionMasked()) {
1282                 case MotionEvent.ACTION_DOWN: {
1283                     if (dragAllowed) {
1284                         mDragPointerId = e.getPointerId(0);
1285                         final Rect initialBounds = mDragPositioningCallback.onDragPositioningStart(
1286                                 0 /* ctrlType */, e.getDisplayId(), e.getRawX(0),
1287                                 e.getRawY(0));
1288                         updateDragStatus(decoration, e);
1289                         mOnDragStartInitialBounds.set(initialBounds);
1290                     }
1291                     // Do not consume input event if a button is touched, otherwise it would
1292                     // prevent the button's ripple effect from showing.
1293                     return !touchingButton;
1294                 }
1295                 case ACTION_MOVE: {
1296                     // If a decor's resize drag zone is active, don't also try to reposition it.
1297                     if (decoration.isHandlingDragResize()) break;
1298                     // Dragging the header isn't allowed, so skip the positioning work.
1299                     if (!dragAllowed) break;
1300 
1301                     decoration.closeMaximizeMenu();
1302                     if (e.findPointerIndex(mDragPointerId) == -1) {
1303                         mDragPointerId = e.getPointerId(0);
1304                     }
1305                     final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
1306                     final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningMove(
1307                             e.getDisplayId(),
1308                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
1309                     mDesktopTasksController.onDragPositioningMove(taskInfo,
1310                             decoration.mTaskSurface,
1311                             e.getRawX(dragPointerIdx),
1312                             newTaskBounds);
1313                     //  Flip mIsDragging only if the bounds actually changed.
1314                     if (mIsDragging || !newTaskBounds.equals(mOnDragStartInitialBounds)) {
1315                         updateDragStatus(decoration, e);
1316                     }
1317                     return true;
1318                 }
1319                 case MotionEvent.ACTION_UP:
1320                 case MotionEvent.ACTION_CANCEL: {
1321                     final boolean wasDragging = mIsDragging;
1322                     if (!wasDragging) {
1323                         return false;
1324                     }
1325                     mDesktopModeUiEventLogger.log(taskInfo,
1326                             DesktopUiEventEnum.DESKTOP_WINDOW_MOVE_BY_HEADER_DRAG);
1327                     if (e.findPointerIndex(mDragPointerId) == -1) {
1328                         mDragPointerId = e.getPointerId(0);
1329                     }
1330                     final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
1331                     final Rect newTaskBounds = mDragPositioningCallback.onDragPositioningEnd(
1332                             e.getDisplayId(),
1333                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
1334                     // Tasks bounds haven't actually been updated (only its leash), so pass to
1335                     // DesktopTasksController to allow secondary transformations (i.e. snap resizing
1336                     // or transforming to fullscreen) before setting new task bounds.
1337                     mDesktopTasksController.onDragPositioningEnd(
1338                             taskInfo, decoration.mTaskSurface,
1339                             new PointF(e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)),
1340                             newTaskBounds, decoration.calculateValidDragArea(),
1341                             new Rect(mOnDragStartInitialBounds), e);
1342                     if (touchingButton) {
1343                         // We need the input event to not be consumed here to end the ripple
1344                         // effect on the touched button. We will reset drag state in the ensuing
1345                         // onClick call that results.
1346                         return false;
1347                     } else {
1348                         updateDragStatus(decoration, e);
1349                         return true;
1350                     }
1351                 }
1352             }
1353             return true;
1354         }
1355 
updateDragStatus(DesktopModeWindowDecoration decor, MotionEvent e)1356         private void updateDragStatus(DesktopModeWindowDecoration decor, MotionEvent e) {
1357             switch (e.getActionMasked()) {
1358                 case MotionEvent.ACTION_DOWN:
1359                 case MotionEvent.ACTION_UP:
1360                 case MotionEvent.ACTION_CANCEL: {
1361                     mDragInterrupted = false;
1362                     setIsDragging(decor, false /* isDragging */);
1363                     break;
1364                 }
1365                 case MotionEvent.ACTION_MOVE: {
1366                     if (!mDragInterrupted) {
1367                         setIsDragging(decor, true /* isDragging */);
1368                     }
1369                     break;
1370                 }
1371             }
1372         }
1373 
1374         /**
1375          * Perform a task size toggle on release of the double-tap, assuming no drag event
1376          * was handled during the double-tap.
1377          *
1378          * @param e The motion event that occurred during the double-tap gesture.
1379          * @return true if the event should be consumed, false if not
1380          */
1381         @Override
onDoubleTapEvent(@onNull MotionEvent e)1382         public boolean onDoubleTapEvent(@NonNull MotionEvent e) {
1383             final int action = e.getActionMasked();
1384             if (mIsDragging || (action != MotionEvent.ACTION_UP
1385                     && action != MotionEvent.ACTION_CANCEL)) {
1386                 return false;
1387             }
1388             final DesktopRepository desktopRepository = mDesktopUserRepositories.getCurrent();
1389             if (desktopRepository.isTaskInFullImmersiveState(mTaskId)) {
1390                 // Disallow double-tap to resize when in full immersive.
1391                 return false;
1392             }
1393             onToggleSizeInteraction(mTaskId,
1394                     ToggleTaskSizeInteraction.AmbiguousSource.DOUBLE_TAP, e);
1395             return true;
1396         }
1397     }
1398 
1399     // InputEventReceiver to listen for touch input outside of caption bounds
1400     class EventReceiver extends InputEventReceiver {
1401         private InputMonitor mInputMonitor;
1402         private int mTasksOnDisplay;
1403 
EventReceiver(InputMonitor inputMonitor, InputChannel channel, Looper looper)1404         EventReceiver(InputMonitor inputMonitor, InputChannel channel, Looper looper) {
1405             super(channel, looper);
1406             mInputMonitor = inputMonitor;
1407             mTasksOnDisplay = 1;
1408         }
1409 
1410         @Override
onInputEvent(InputEvent event)1411         public void onInputEvent(InputEvent event) {
1412             boolean handled = false;
1413             if (event instanceof MotionEvent) {
1414                 handled = true;
1415                 DesktopModeWindowDecorViewModel.this
1416                         .handleReceivedMotionEvent((MotionEvent) event, mInputMonitor);
1417             }
1418             finishInputEvent(event, handled);
1419         }
1420 
1421         @Override
dispose()1422         public void dispose() {
1423             if (mInputMonitor != null) {
1424                 mInputMonitor.dispose();
1425                 mInputMonitor = null;
1426             }
1427             super.dispose();
1428         }
1429 
1430         @Override
toString()1431         public String toString() {
1432             return "EventReceiver"
1433                     + "{"
1434                     + "tasksOnDisplay="
1435                     + mTasksOnDisplay
1436                     + "}";
1437         }
1438 
incrementTaskNumber()1439         private void incrementTaskNumber() {
1440             mTasksOnDisplay++;
1441         }
1442 
decrementTaskNumber()1443         private void decrementTaskNumber() {
1444             mTasksOnDisplay--;
1445         }
1446 
getTasksOnDisplay()1447         private int getTasksOnDisplay() {
1448             return mTasksOnDisplay;
1449         }
1450     }
1451 
1452     /**
1453      * Check if an EventReceiver exists on a particular display.
1454      * If it does, increment its task count. Otherwise, create one for that display.
1455      *
1456      * @param displayId the display to check against
1457      */
incrementEventReceiverTasks(int displayId)1458     private void incrementEventReceiverTasks(int displayId) {
1459         if (mEventReceiversByDisplay.contains(displayId)) {
1460             final EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
1461             eventReceiver.incrementTaskNumber();
1462         } else {
1463             createInputChannel(displayId);
1464         }
1465     }
1466 
1467     // If all tasks on this display are gone, we don't need to monitor its input.
removeTaskFromEventReceiver(int displayId)1468     private void removeTaskFromEventReceiver(int displayId) {
1469         if (!mEventReceiversByDisplay.contains(displayId)) return;
1470         final EventReceiver eventReceiver = mEventReceiversByDisplay.get(displayId);
1471         if (eventReceiver == null) return;
1472         eventReceiver.decrementTaskNumber();
1473         if (eventReceiver.getTasksOnDisplay() == 0) {
1474             disposeInputChannel(displayId);
1475         }
1476     }
1477 
1478     /**
1479      * Handle MotionEvents relevant to focused task's caption that don't directly touch it
1480      *
1481      * @param ev the {@link MotionEvent} received by {@link EventReceiver}
1482      */
handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor)1483     private void handleReceivedMotionEvent(MotionEvent ev, InputMonitor inputMonitor) {
1484         final DesktopModeWindowDecoration relevantDecor = getRelevantWindowDecor(ev);
1485         if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
1486             if (!mInImmersiveMode && (relevantDecor == null
1487                     || relevantDecor.mTaskInfo.getWindowingMode() != WINDOWING_MODE_FREEFORM
1488                     || mTransitionDragActive)) {
1489                 handleCaptionThroughStatusBar(ev, relevantDecor,
1490                         /* interruptDragCallback= */ () -> {});
1491             }
1492         }
1493         handleEventOutsideCaption(ev, relevantDecor);
1494         // Prevent status bar from reacting to a caption drag.
1495         if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
1496             if (mTransitionDragActive) {
1497                 inputMonitor.pilferPointers();
1498             }
1499         }
1500     }
1501 
1502     /**
1503      * If an UP/CANCEL action is received outside of the caption bounds, close the handle and
1504      * maximize the menu.
1505      *
1506      * @param relevantDecor the window decoration of the focused task's caption. This method only
1507      *                      handles motion events outside this caption's bounds.
1508      */
handleEventOutsideCaption(MotionEvent ev, DesktopModeWindowDecoration relevantDecor)1509     private void handleEventOutsideCaption(MotionEvent ev,
1510             DesktopModeWindowDecoration relevantDecor) {
1511         // Returns if event occurs within caption
1512         if (relevantDecor == null || relevantDecor.checkTouchEventInCaption(ev)) {
1513             return;
1514         }
1515         relevantDecor.updateHoverAndPressStatus(ev);
1516         final int action = ev.getActionMasked();
1517         if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
1518             if (!mTransitionDragActive && !DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
1519                 relevantDecor.closeHandleMenuIfNeeded(ev);
1520             }
1521         }
1522     }
1523 
1524 
1525     /**
1526      * Perform caption actions if not able to through normal means.
1527      * Turn on desktop mode if handle is dragged below status bar.
1528      */
handleCaptionThroughStatusBar(MotionEvent ev, DesktopModeWindowDecoration relevantDecor, Runnable interruptDragCallback)1529     private void handleCaptionThroughStatusBar(MotionEvent ev,
1530             DesktopModeWindowDecoration relevantDecor, Runnable interruptDragCallback) {
1531         if (relevantDecor == null) {
1532             if (ev.getActionMasked() == ACTION_UP) {
1533                 mMoveToDesktopAnimator = null;
1534                 mTransitionDragActive = false;
1535             }
1536             return;
1537         }
1538         switch (ev.getActionMasked()) {
1539             case MotionEvent.ACTION_HOVER_EXIT:
1540             case MotionEvent.ACTION_HOVER_MOVE:
1541             case MotionEvent.ACTION_HOVER_ENTER: {
1542                 relevantDecor.updateHoverAndPressStatus(ev);
1543                 break;
1544             }
1545             case MotionEvent.ACTION_DOWN: {
1546                 // Begin drag through status bar if applicable.
1547                 relevantDecor.checkTouchEvent(ev);
1548                 relevantDecor.updateHoverAndPressStatus(ev);
1549                 mDragToDesktopAnimationStartBounds.set(
1550                         relevantDecor.mTaskInfo.configuration.windowConfiguration.getBounds());
1551                 boolean dragFromStatusBarAllowed = false;
1552                 final int windowingMode = relevantDecor.mTaskInfo.getWindowingMode();
1553                 if (DesktopModeStatus.canEnterDesktopMode(mContext)
1554                         || BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
1555                     // In proto2 any full screen or multi-window task can be dragged to
1556                     // freeform.
1557                     dragFromStatusBarAllowed = windowingMode == WINDOWING_MODE_FULLSCREEN
1558                             || windowingMode == WINDOWING_MODE_MULTI_WINDOW;
1559                 }
1560                 final boolean shouldStartTransitionDrag =
1561                         relevantDecor.checkTouchEventInFocusedCaptionHandle(ev)
1562                                 || DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue();
1563                 if (dragFromStatusBarAllowed && shouldStartTransitionDrag) {
1564                     mTransitionDragActive = true;
1565                 }
1566                 break;
1567             }
1568             case MotionEvent.ACTION_CANCEL:
1569             case MotionEvent.ACTION_UP: {
1570                 if (mTransitionDragActive) {
1571                     final DesktopModeVisualIndicator.DragStartState dragStartState =
1572                             DesktopModeVisualIndicator.DragStartState
1573                                     .getDragStartState(relevantDecor.mTaskInfo);
1574                     if (dragStartState == null) return;
1575                     mDesktopTasksController.updateVisualIndicator(relevantDecor.mTaskInfo,
1576                             relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY(),
1577                             dragStartState);
1578                     mTransitionDragActive = false;
1579                     if (mMoveToDesktopAnimator != null) {
1580                         // Though this isn't a hover event, we need to update handle's hover state
1581                         // as it likely will change.
1582                         relevantDecor.updateHoverAndPressStatus(ev);
1583                         if (ev.getActionMasked() == ACTION_CANCEL) {
1584                             mDesktopTasksController.onDragPositioningCancelThroughStatusBar(
1585                                     relevantDecor.mTaskInfo);
1586                         } else {
1587                             endDragToDesktop(ev, relevantDecor);
1588                         }
1589                         mMoveToDesktopAnimator = null;
1590                         return;
1591                     } else {
1592                         // In cases where we create an indicator but do not start the
1593                         // move-to-desktop animation, we need to dismiss it.
1594                         mDesktopTasksController.releaseVisualIndicator();
1595                     }
1596                 }
1597                 relevantDecor.checkTouchEvent(ev);
1598                 break;
1599             }
1600             case ACTION_MOVE: {
1601                 if (relevantDecor == null) {
1602                     return;
1603                 }
1604                 if (mTransitionDragActive) {
1605                     // Do not create an indicator at all if we're not past transition height.
1606                     DisplayLayout layout = mDisplayController
1607                             .getDisplayLayout(relevantDecor.mTaskInfo.displayId);
1608                     // It's possible task is not at the top of the screen (e.g. bottom of vertical
1609                     // Splitscreen)
1610                     final int taskTop = relevantDecor.mTaskInfo.configuration.windowConfiguration
1611                             .getBounds().top;
1612                     if (ev.getRawY() < 2 * layout.stableInsets().top + taskTop
1613                             && mMoveToDesktopAnimator == null) {
1614                         return;
1615                     }
1616                     final DesktopModeVisualIndicator.DragStartState dragStartState =
1617                             DesktopModeVisualIndicator.DragStartState
1618                                     .getDragStartState(relevantDecor.mTaskInfo);
1619                     if (dragStartState == null) return;
1620                     final DesktopModeVisualIndicator.IndicatorType indicatorType =
1621                             mDesktopTasksController.updateVisualIndicator(
1622                                     relevantDecor.mTaskInfo,
1623                                     relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY(),
1624                                     dragStartState);
1625                     if (indicatorType != TO_FULLSCREEN_INDICATOR
1626                             || BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
1627                         if (mMoveToDesktopAnimator == null) {
1628                             mMoveToDesktopAnimator = new MoveToDesktopAnimator(
1629                                     mContext, mDragToDesktopAnimationStartBounds,
1630                                     relevantDecor.mTaskInfo, relevantDecor.mTaskSurface);
1631                             mDesktopTasksController.startDragToDesktop(relevantDecor.mTaskInfo,
1632                                     mMoveToDesktopAnimator, relevantDecor.mTaskSurface,
1633                                     /* dragInterruptedCallback= */ () -> {
1634                                         // Don't call into DesktopTasksController to cancel the
1635                                         // transition here - the transition handler already handles
1636                                         // that (including removing the visual indicator).
1637                                         mTransitionDragActive = false;
1638                                         mMoveToDesktopAnimator = null;
1639                                         relevantDecor.handleDragInterrupted();
1640                                         interruptDragCallback.run();
1641                                     });
1642                         }
1643                     }
1644                     if (mMoveToDesktopAnimator != null) {
1645                         mMoveToDesktopAnimator.updatePosition(ev);
1646                     }
1647                 }
1648                 break;
1649             }
1650         }
1651     }
1652 
endDragToDesktop(MotionEvent ev, DesktopModeWindowDecoration relevantDecor)1653     private void endDragToDesktop(MotionEvent ev, DesktopModeWindowDecoration relevantDecor) {
1654         DesktopModeVisualIndicator.IndicatorType resultType =
1655                 mDesktopTasksController.onDragPositioningEndThroughStatusBar(
1656                         new PointF(ev.getRawX(), ev.getRawY()),
1657                         relevantDecor.mTaskInfo,
1658                         relevantDecor.mTaskSurface);
1659         // If we are entering split select, handle will no longer be visible and
1660         // should not be receiving any input.
1661         if (resultType == TO_SPLIT_LEFT_INDICATOR
1662                 || resultType == TO_SPLIT_RIGHT_INDICATOR) {
1663             relevantDecor.disposeStatusBarInputLayer();
1664             // We should also dispose the other split task's input layer if
1665             // applicable.
1666             final int splitPosition = mSplitScreenController
1667                     .getSplitPosition(relevantDecor.mTaskInfo.taskId);
1668             if (splitPosition != SPLIT_POSITION_UNDEFINED) {
1669                 final int oppositePosition =
1670                         splitPosition == SPLIT_POSITION_TOP_OR_LEFT
1671                                 ? SPLIT_POSITION_BOTTOM_OR_RIGHT
1672                                 : SPLIT_POSITION_TOP_OR_LEFT;
1673                 final RunningTaskInfo oppositeTaskInfo =
1674                         mSplitScreenController.getTaskInfo(oppositePosition);
1675                 if (oppositeTaskInfo != null) {
1676                     mWindowDecorByTaskId.get(oppositeTaskInfo.taskId)
1677                             .disposeStatusBarInputLayer();
1678                 }
1679             }
1680         }
1681     }
1682 
1683     @Nullable
getRelevantWindowDecor(MotionEvent ev)1684     private DesktopModeWindowDecoration getRelevantWindowDecor(MotionEvent ev) {
1685         // If we are mid-transition, dragged task's decor is always relevant.
1686         final int draggedTaskId = mDesktopTasksController.getDraggingTaskId();
1687         if (draggedTaskId != INVALID_TASK_ID) {
1688             return mWindowDecorByTaskId.get(draggedTaskId);
1689         }
1690         final DesktopModeWindowDecoration focusedDecor = getFocusedDecor();
1691         if (focusedDecor == null) {
1692             return null;
1693         }
1694         final boolean splitScreenVisible = mSplitScreenController != null
1695                 && mSplitScreenController.isSplitScreenVisible();
1696         // It's possible that split tasks are visible but neither is focused, such as when there's
1697         // a fullscreen translucent window on top of them. In that case, the relevant decor should
1698         // just be that translucent focused window.
1699         final boolean focusedTaskInSplit = mSplitScreenController != null
1700                 && mSplitScreenController.isTaskInSplitScreen(focusedDecor.mTaskInfo.taskId);
1701         if (splitScreenVisible && focusedTaskInSplit) {
1702             // We can't look at focused task here as only one task will have focus.
1703             DesktopModeWindowDecoration splitTaskDecor = getSplitScreenDecor(ev);
1704             return splitTaskDecor == null ? getFocusedDecor() : splitTaskDecor;
1705         } else {
1706             return getFocusedDecor();
1707         }
1708     }
1709 
1710     @Nullable
getSplitScreenDecor(MotionEvent ev)1711     private DesktopModeWindowDecoration getSplitScreenDecor(MotionEvent ev) {
1712         ActivityManager.RunningTaskInfo topOrLeftTask =
1713                 mSplitScreenController.getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
1714         ActivityManager.RunningTaskInfo bottomOrRightTask =
1715                 mSplitScreenController.getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
1716         if (topOrLeftTask != null && topOrLeftTask.getConfiguration()
1717                 .windowConfiguration.getBounds().contains((int) ev.getX(), (int) ev.getY())) {
1718             return mWindowDecorByTaskId.get(topOrLeftTask.taskId);
1719         } else if (bottomOrRightTask != null && bottomOrRightTask.getConfiguration()
1720                 .windowConfiguration.getBounds().contains((int) ev.getX(), (int) ev.getY())) {
1721             Rect bottomOrRightBounds = bottomOrRightTask.getConfiguration().windowConfiguration
1722                     .getBounds();
1723             ev.offsetLocation(-bottomOrRightBounds.left, -bottomOrRightBounds.top);
1724             return mWindowDecorByTaskId.get(bottomOrRightTask.taskId);
1725         } else {
1726             return null;
1727         }
1728 
1729     }
1730 
1731     @Nullable
getFocusedDecor()1732     private DesktopModeWindowDecoration getFocusedDecor() {
1733         final int size = mWindowDecorByTaskId.size();
1734         DesktopModeWindowDecoration focusedDecor = null;
1735         // TODO(b/323251951): We need to iterate this in reverse to avoid potentially getting
1736         //  a decor for a closed task. This is a short term fix while the core issue is addressed,
1737         //  which involves refactoring the window decor lifecycle to be visibility based.
1738         for (int i = size - 1; i >= 0; i--) {
1739             final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
1740             if (decor != null && decor.isFocused()) {
1741                 focusedDecor = decor;
1742                 break;
1743             }
1744         }
1745         return focusedDecor;
1746     }
1747 
getStatusBarHeight(int displayId)1748     private int getStatusBarHeight(int displayId) {
1749         return mDisplayController.getDisplayLayout(displayId).stableInsets().top;
1750     }
1751 
createInputChannel(int displayId)1752     private void createInputChannel(int displayId) {
1753         final InputManager inputManager = mContext.getSystemService(InputManager.class);
1754         final InputMonitor inputMonitor =
1755                 mInputMonitorFactory.create(inputManager, displayId);
1756         final EventReceiver eventReceiver = new EventReceiver(inputMonitor,
1757                 inputMonitor.getInputChannel(), Looper.myLooper());
1758         mEventReceiversByDisplay.put(displayId, eventReceiver);
1759     }
1760 
disposeInputChannel(int displayId)1761     private void disposeInputChannel(int displayId) {
1762         final EventReceiver eventReceiver = mEventReceiversByDisplay.removeReturnOld(displayId);
1763         if (eventReceiver != null) {
1764             eventReceiver.dispose();
1765         }
1766     }
1767 
shouldShowWindowDecor(RunningTaskInfo taskInfo)1768     private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
1769         return mAppHandleAndHeaderVisibilityHelper.shouldShowAppHandleOrHeader(taskInfo);
1770     }
1771 
createWindowDecoration( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT)1772     private void createWindowDecoration(
1773             ActivityManager.RunningTaskInfo taskInfo,
1774             SurfaceControl taskSurface,
1775             SurfaceControl.Transaction startT,
1776             SurfaceControl.Transaction finishT) {
1777         final DesktopModeWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId);
1778         if (oldDecoration != null) {
1779             // close the old decoration if it exists to avoid two window decorations being added
1780             oldDecoration.close();
1781         }
1782         final DesktopModeWindowDecoration windowDecoration =
1783                 mDesktopModeWindowDecorFactory.create(
1784                         Flags.enableBugFixesForSecondaryDisplay()
1785                                 ? mDisplayController.getDisplayContext(taskInfo.displayId)
1786                                 : mContext,
1787                         mContext.createContextAsUser(UserHandle.of(taskInfo.userId), 0 /* flags */),
1788                         mDisplayController,
1789                         mTaskResourceLoader,
1790                         mSplitScreenController,
1791                         mDesktopUserRepositories,
1792                         mTaskOrganizer,
1793                         taskInfo,
1794                         taskSurface,
1795                         mMainHandler,
1796                         mMainExecutor,
1797                         mMainDispatcher,
1798                         mBgScope,
1799                         mBgExecutor,
1800                         mMainChoreographer,
1801                         mSyncQueue,
1802                         mAppHeaderViewHolderFactory,
1803                         mAppHandleViewHolderFactory,
1804                         mRootTaskDisplayAreaOrganizer,
1805                         mGenericLinksParser,
1806                         mAssistContentRequester,
1807                         mWindowDecorViewHostSupplier,
1808                         mMultiInstanceHelper,
1809                         mWindowDecorCaptionHandleRepository,
1810                         mDesktopModeEventLogger,
1811                         mDesktopModeUiEventLogger,
1812                         mDesktopModeCompatPolicy);
1813         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
1814 
1815         final TaskPositioner taskPositioner = mTaskPositionerFactory.create(
1816                 mTaskOrganizer,
1817                 windowDecoration,
1818                 mDisplayController,
1819                 mDragEventListener,
1820                 mTransitions,
1821                 mInteractionJankMonitor,
1822                 mTransactionFactory,
1823                 mMainHandler,
1824                 mMultiDisplayDragMoveIndicatorController);
1825         windowDecoration.setTaskDragResizer(taskPositioner);
1826 
1827         final DesktopModeTouchEventListener touchEventListener =
1828                 new DesktopModeTouchEventListener(taskInfo, taskPositioner);
1829         windowDecoration.setOnMaximizeOrRestoreClickListener(() -> {
1830             onToggleSizeInteraction(taskInfo.taskId,
1831                     ToggleTaskSizeInteraction.AmbiguousSource.MAXIMIZE_MENU,
1832                     touchEventListener.mMotionEvent);
1833             return Unit.INSTANCE;
1834         });
1835         windowDecoration.setOnImmersiveOrRestoreClickListener(() -> {
1836             onEnterOrExitImmersive(taskInfo);
1837             return Unit.INSTANCE;
1838         });
1839         windowDecoration.setOnLeftSnapClickListener(() -> {
1840             onSnapResize(taskInfo.taskId, /* isLeft= */ true,
1841                     DesktopModeEventLogger.getInputMethodFromMotionEvent(
1842                             touchEventListener.mMotionEvent), /* fromMenu= */ true);
1843             return Unit.INSTANCE;
1844         });
1845         windowDecoration.setOnRightSnapClickListener(() -> {
1846             onSnapResize(taskInfo.taskId, /* isLeft= */ false,
1847                     DesktopModeEventLogger.getInputMethodFromMotionEvent(
1848                             touchEventListener.mMotionEvent), /* fromMenu= */ true);
1849             return Unit.INSTANCE;
1850         });
1851         windowDecoration.setOnToDesktopClickListener(desktopModeTransitionSource -> {
1852             onToDesktop(taskInfo.taskId, desktopModeTransitionSource);
1853         });
1854         windowDecoration.setOnToFullscreenClickListener(() -> {
1855             onToFullscreen(taskInfo.taskId);
1856             return Unit.INSTANCE;
1857         });
1858         windowDecoration.setOnToSplitScreenClickListener(() -> {
1859             onToSplitScreen(taskInfo.taskId);
1860             return Unit.INSTANCE;
1861         });
1862         windowDecoration.setOnToFloatClickListener(() -> {
1863             onToFloat(taskInfo.taskId);
1864             return Unit.INSTANCE;
1865         });
1866         windowDecoration.setOpenInBrowserClickListener((intent) -> {
1867             onOpenInBrowser(taskInfo.taskId, intent);
1868         });
1869         windowDecoration.setOnNewWindowClickListener(() -> {
1870             onNewWindow(taskInfo.taskId);
1871             return Unit.INSTANCE;
1872         });
1873         windowDecoration.setManageWindowsClickListener(() -> {
1874             onManageWindows(windowDecoration);
1875             return Unit.INSTANCE;
1876         });
1877         windowDecoration.setOnChangeAspectRatioClickListener(() -> {
1878             CompatUIController.launchUserAspectRatioSettings(mContext, taskInfo);
1879             return Unit.INSTANCE;
1880         });
1881         windowDecoration.setOnRestartClickListener(() -> {
1882             mCompatUI.sendCompatUIRequest(new CompatUIRequests.DisplayCompatShowRestartDialog(
1883                     taskInfo.taskId));
1884             return Unit.INSTANCE;
1885         });
1886         windowDecoration.setOnMaximizeHoverListener(() -> {
1887             if (!windowDecoration.isMaximizeMenuActive()) {
1888                 mDesktopModeUiEventLogger.log(taskInfo,
1889                         DesktopUiEventEnum.DESKTOP_WINDOW_MAXIMIZE_BUTTON_REVEAL_MENU);
1890                 windowDecoration.createMaximizeMenu();
1891             }
1892             return Unit.INSTANCE;
1893         });
1894         windowDecoration.setCaptionListeners(
1895                 touchEventListener, touchEventListener, touchEventListener, touchEventListener);
1896         windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
1897         windowDecoration.setDragPositioningCallback(taskPositioner);
1898         windowDecoration.relayout(taskInfo, startT, finishT,
1899                 false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */,
1900                 mFocusTransitionObserver.hasGlobalFocus(taskInfo),
1901                 mExclusionRegion);
1902         if (!DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
1903             incrementEventReceiverTasks(taskInfo.displayId);
1904         }
1905     }
1906 
1907     @Nullable
getOtherSplitTask(int taskId)1908     private RunningTaskInfo getOtherSplitTask(int taskId) {
1909         @SplitPosition int remainingTaskPosition = mSplitScreenController
1910                 .getSplitPosition(taskId) == SPLIT_POSITION_BOTTOM_OR_RIGHT
1911                 ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT;
1912         return mSplitScreenController.getTaskInfo(remainingTaskPosition);
1913     }
1914 
isTaskInSplitScreen(int taskId)1915     private boolean isTaskInSplitScreen(int taskId) {
1916         return mSplitScreenController != null
1917                 && mSplitScreenController.isTaskInSplitScreen(taskId);
1918     }
1919 
dump(PrintWriter pw, String prefix)1920     private void dump(PrintWriter pw, String prefix) {
1921         final String innerPrefix = prefix + "  ";
1922         pw.println(prefix + "DesktopModeWindowDecorViewModel");
1923         pw.println(innerPrefix + "DesktopModeStatus="
1924                 + DesktopModeStatus.canEnterDesktopMode(mContext));
1925         pw.println(innerPrefix + "mTransitionDragActive=" + mTransitionDragActive);
1926         pw.println(innerPrefix + "mEventReceiversByDisplay=" + mEventReceiversByDisplay);
1927         pw.println(innerPrefix + "mWindowDecorByTaskId=" + mWindowDecorByTaskId);
1928         pw.println(innerPrefix + "mExclusionRegion=" + mExclusionRegion);
1929     }
1930 
1931     private class DesktopModeOnTaskRepositionAnimationListener
1932             implements OnTaskRepositionAnimationListener {
1933         @Override
onAnimationStart(int taskId)1934         public void onAnimationStart(int taskId) {
1935             final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
1936             if (decoration != null) {
1937                 decoration.setAnimatingTaskResizeOrReposition(true);
1938             }
1939         }
1940 
1941         @Override
onAnimationEnd(int taskId)1942         public void onAnimationEnd(int taskId) {
1943             final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
1944             if (decoration != null) {
1945                 decoration.setAnimatingTaskResizeOrReposition(false);
1946             }
1947         }
1948     }
1949 
1950     private class DesktopModeOnTaskResizeAnimationListener
1951             implements OnTaskResizeAnimationListener {
1952         @Override
onAnimationStart(int taskId, Transaction t, Rect bounds)1953         public void onAnimationStart(int taskId, Transaction t, Rect bounds) {
1954             final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
1955             if (decoration == null) {
1956                 t.apply();
1957                 return;
1958             }
1959             decoration.showResizeVeil(t, bounds);
1960             decoration.setAnimatingTaskResizeOrReposition(true);
1961         }
1962 
1963         @Override
onBoundsChange(int taskId, Transaction t, Rect bounds)1964         public void onBoundsChange(int taskId, Transaction t, Rect bounds) {
1965             final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
1966             if (decoration == null) return;
1967             decoration.updateResizeVeil(t, bounds);
1968         }
1969 
1970         @Override
onAnimationEnd(int taskId)1971         public void onAnimationEnd(int taskId) {
1972             final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
1973             if (decoration == null) return;
1974             decoration.hideResizeVeil();
1975             decoration.setAnimatingTaskResizeOrReposition(false);
1976         }
1977     }
1978 
1979     private class DesktopModeRecentsTransitionStateListener
1980             implements RecentsTransitionStateListener {
1981         final Set<Integer> mAnimatingTaskIds = new HashSet<>();
1982 
1983         @Override
onTransitionStateChanged(int state)1984         public void onTransitionStateChanged(int state) {
1985             switch (state) {
1986                 case RecentsTransitionStateListener.TRANSITION_STATE_REQUESTED:
1987                     for (int n = 0; n < mWindowDecorByTaskId.size(); n++) {
1988                         int taskId = mWindowDecorByTaskId.keyAt(n);
1989                         mAnimatingTaskIds.add(taskId);
1990                         setIsRecentsTransitionRunningForTask(taskId, true);
1991                     }
1992                     return;
1993                 case RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING:
1994                     // No Recents transition running - clean up window decorations
1995                     for (int taskId : mAnimatingTaskIds) {
1996                         setIsRecentsTransitionRunningForTask(taskId, false);
1997                     }
1998                     mAnimatingTaskIds.clear();
1999                     return;
2000                 default:
2001             }
2002         }
2003 
setIsRecentsTransitionRunningForTask(int taskId, boolean isRecentsRunning)2004         private void setIsRecentsTransitionRunningForTask(int taskId, boolean isRecentsRunning) {
2005             final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
2006             if (decoration == null) return;
2007             decoration.setIsRecentsTransitionRunning(isRecentsRunning);
2008         }
2009     }
2010 
2011     private class DragEventListenerImpl
2012             implements DragPositioningCallbackUtility.DragEventListener {
2013         @Override
onDragStart(int taskId)2014         public void onDragStart(int taskId) {
2015             final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
2016             decoration.closeHandleMenu();
2017         }
2018 
2019         @Override
onDragMove(int taskId)2020         public void onDragMove(int taskId) {
2021 
2022         }
2023     }
2024 
2025     /**
2026      * Gets the number of instances of a task running, not including the specified task itself.
2027      */
checkNumberOfOtherInstances(@onNull RunningTaskInfo info)2028     private int checkNumberOfOtherInstances(@NonNull RunningTaskInfo info) {
2029         // TODO(b/336289597): Rather than returning number of instances, return a list of valid
2030         //  instances, then refer to the list's size and reuse the list for Manage Windows menu.
2031         final IActivityTaskManager activityTaskManager = ActivityTaskManager.getService();
2032         try {
2033             // TODO(b/389184897): Move the following into a helper method of
2034             //  RecentsTasksController, similar to #findTaskInBackground.
2035             final String packageName = ComponentUtils.getPackageName(info);
2036             return activityTaskManager.getRecentTasks(Integer.MAX_VALUE,
2037                     ActivityManager.RECENT_WITH_EXCLUDED,
2038                     info.userId).getList().stream().filter(
2039                     recentTaskInfo -> {
2040                         if (recentTaskInfo.taskId == info.taskId) {
2041                             return false;
2042                         }
2043                         final String recentTaskPackageName =
2044                                 ComponentUtils.getPackageName(recentTaskInfo);
2045                         return packageName != null
2046                                 && packageName.equals(recentTaskPackageName);
2047                     }
2048             ).toList().size();
2049         } catch (RemoteException e) {
2050             throw new RuntimeException(e);
2051         }
2052     }
2053 
2054     static class InputMonitorFactory {
2055         InputMonitor create(InputManager inputManager, int displayId) {
2056             return inputManager.monitorGestureInput("caption-touch", displayId);
2057         }
2058     }
2059 
2060     private class ExclusionRegionListenerImpl
2061             implements ExclusionRegionListener {
2062 
2063         @Override
2064         public void onExclusionRegionChanged(int taskId, Region region) {
2065             mDesktopTasksController.onExclusionRegionChanged(taskId, region);
2066         }
2067 
2068         @Override
2069         public void onExclusionRegionDismissed(int taskId) {
2070             mDesktopTasksController.removeExclusionRegionForTask(taskId);
2071         }
2072     }
2073 
2074     class DesktopModeKeyguardChangeListener implements KeyguardChangeListener {
2075         @Override
2076         public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
2077                 boolean animatingDismiss) {
2078             final int size = mWindowDecorByTaskId.size();
2079             for (int i = size - 1; i >= 0; i--) {
2080                 final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
2081                 if (decor != null) {
2082                     decor.onKeyguardStateChanged(visible, occluded);
2083                 }
2084             }
2085         }
2086     }
2087 
2088     @VisibleForTesting
2089     class DesktopModeOnInsetsChangedListener implements
2090             DisplayInsetsController.OnInsetsChangedListener {
2091         @Override
2092         public void insetsChanged(int displayId, @NonNull InsetsState insetsState) {
2093             final int size = mWindowDecorByTaskId.size();
2094             for (int i = size - 1; i >= 0; i--) {
2095                 final DesktopModeWindowDecoration decor = mWindowDecorByTaskId.valueAt(i);
2096                 if (decor == null) {
2097                     continue;
2098                 }
2099                 if (decor.mTaskInfo.displayId == displayId
2100                         && DesktopModeFlags
2101                         .ENABLE_DESKTOP_WINDOWING_IMMERSIVE_HANDLE_HIDING.isTrue()) {
2102                     decor.onInsetsStateChanged(insetsState);
2103                 }
2104                 if (!DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue()) {
2105                     // If status bar inset is visible, top task is not in immersive mode.
2106                     // This value is only needed when the App Handle input is being handled
2107                     // through the global input monitor (hence the flag check) to ignore gestures
2108                     // when the app is in immersive mode. When disabled, the view itself handles
2109                     // input, and since it's removed when in immersive there's no need to track
2110                     // this here.
2111                     mInImmersiveMode = !InsetsStateKt.isVisible(insetsState, statusBars());
2112                 }
2113             }
2114         }
2115     }
2116 
2117     @VisibleForTesting
2118     static class TaskPositionerFactory {
2119         TaskPositioner create(
2120                 ShellTaskOrganizer taskOrganizer,
2121                 DesktopModeWindowDecoration windowDecoration,
2122                 DisplayController displayController,
2123                 DragPositioningCallbackUtility.DragEventListener dragEventListener,
2124                 Transitions transitions,
2125                 InteractionJankMonitor interactionJankMonitor,
2126                 Supplier<SurfaceControl.Transaction> transactionFactory,
2127                 Handler handler,
2128                 MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController) {
2129             final TaskPositioner taskPositioner = DesktopModeStatus.isVeiledResizeEnabled()
2130                     // TODO(b/383632995): Update when the flag is launched.
2131                     ? (Flags.enableConnectedDisplaysWindowDrag()
2132                         ? new MultiDisplayVeiledResizeTaskPositioner(
2133                             taskOrganizer,
2134                             windowDecoration,
2135                             displayController,
2136                             dragEventListener,
2137                             transitions,
2138                             interactionJankMonitor,
2139                             handler,
2140                             multiDisplayDragMoveIndicatorController)
2141                         : new VeiledResizeTaskPositioner(
2142                             taskOrganizer,
2143                             windowDecoration,
2144                             displayController,
2145                             dragEventListener,
2146                             transitions,
2147                             interactionJankMonitor,
2148                             handler))
2149                     : new FluidResizeTaskPositioner(
2150                             taskOrganizer,
2151                             transitions,
2152                             windowDecoration,
2153                             displayController,
2154                             dragEventListener,
2155                             transactionFactory);
2156 
2157             if (DesktopModeFlags.ENABLE_WINDOWING_SCALED_RESIZING.isTrue()) {
2158                 return new FixedAspectRatioTaskPositionerDecorator(windowDecoration,
2159                         taskPositioner);
2160             }
2161             return taskPositioner;
2162         }
2163     }
2164 }
2165