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