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