1 /* 2 * Copyright (C) 2017 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.quickstep.views; 18 19 import static android.app.ActivityTaskManager.INVALID_TASK_ID; 20 import static android.os.Trace.traceBegin; 21 import static android.os.Trace.traceEnd; 22 import static android.view.Surface.ROTATION_0; 23 import static android.view.View.MeasureSpec.EXACTLY; 24 import static android.view.View.MeasureSpec.makeMeasureSpec; 25 26 import static com.android.app.animation.Interpolators.ACCELERATE; 27 import static com.android.app.animation.Interpolators.ACCELERATE_0_75; 28 import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE; 29 import static com.android.app.animation.Interpolators.DECELERATE_2; 30 import static com.android.app.animation.Interpolators.EMPHASIZED; 31 import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE; 32 import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN; 33 import static com.android.app.animation.Interpolators.FINAL_FRAME; 34 import static com.android.app.animation.Interpolators.LINEAR; 35 import static com.android.app.animation.Interpolators.clampToProgress; 36 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE; 37 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS; 38 import static com.android.launcher3.Flags.enableAdditionalHomeAnimations; 39 import static com.android.launcher3.Flags.enableDesktopExplodedView; 40 import static com.android.launcher3.Flags.enableDesktopTaskAlphaAnimation; 41 import static com.android.launcher3.Flags.enableGridOnlyOverview; 42 import static com.android.launcher3.Flags.enableLargeDesktopWindowingTile; 43 import static com.android.launcher3.Flags.enableOverviewBackgroundWallpaperBlur; 44 import static com.android.launcher3.Flags.enableRefactorTaskThumbnail; 45 import static com.android.launcher3.Flags.enableSeparateExternalDisplayTasks; 46 import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS; 47 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA; 48 import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR; 49 import static com.android.launcher3.LauncherState.BACKGROUND_APP; 50 import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION; 51 import static com.android.launcher3.Utilities.EDGE_NAV_BAR; 52 import static com.android.launcher3.Utilities.mapToRange; 53 import static com.android.launcher3.Utilities.squaredHypot; 54 import static com.android.launcher3.Utilities.squaredTouchSlop; 55 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_ACTIONS_SPLIT; 56 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_ORIENTATION_CHANGED; 57 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL; 58 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP; 59 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN; 60 import static com.android.launcher3.statehandlers.DesktopVisibilityController.INACTIVE_DESK_ID; 61 import static com.android.launcher3.testing.shared.TestProtocol.DISMISS_ANIMATION_ENDS_MESSAGE; 62 import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLATE; 63 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 64 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; 65 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE; 66 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK; 67 import static com.android.quickstep.BaseContainerInterface.getTaskDimension; 68 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId; 69 import static com.android.quickstep.util.DesksUtils.areMultiDesksFlagsEnabled; 70 import static com.android.quickstep.util.LogUtils.splitFailureMessage; 71 import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA; 72 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_ACTIONS_IN_MENU; 73 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_DESKTOP; 74 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION; 75 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS; 76 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS; 77 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_SPLIT_SELECT_ACTIVE; 78 import static com.android.quickstep.views.RecentsViewUtils.DESK_EXPLODE_PROGRESS; 79 import static com.android.quickstep.views.TaskView.SPLIT_ALPHA; 80 81 import android.animation.Animator; 82 import android.animation.AnimatorListenerAdapter; 83 import android.animation.AnimatorSet; 84 import android.animation.LayoutTransition; 85 import android.animation.LayoutTransition.TransitionListener; 86 import android.animation.ObjectAnimator; 87 import android.animation.PropertyValuesHolder; 88 import android.animation.ValueAnimator; 89 import android.annotation.SuppressLint; 90 import android.content.Context; 91 import android.content.Intent; 92 import android.content.LocusId; 93 import android.content.res.Configuration; 94 import android.graphics.Bitmap; 95 import android.graphics.BlendMode; 96 import android.graphics.Canvas; 97 import android.graphics.Color; 98 import android.graphics.Matrix; 99 import android.graphics.Point; 100 import android.graphics.PointF; 101 import android.graphics.Rect; 102 import android.graphics.RectF; 103 import android.graphics.Typeface; 104 import android.graphics.drawable.Drawable; 105 import android.os.Bundle; 106 import android.os.SystemClock; 107 import android.os.Trace; 108 import android.os.UserHandle; 109 import android.os.VibrationEffect; 110 import android.text.Layout; 111 import android.text.StaticLayout; 112 import android.text.TextPaint; 113 import android.util.AttributeSet; 114 import android.util.FloatProperty; 115 import android.util.Log; 116 import android.util.Pair; 117 import android.util.SparseBooleanArray; 118 import android.view.HapticFeedbackConstants; 119 import android.view.KeyEvent; 120 import android.view.LayoutInflater; 121 import android.view.MotionEvent; 122 import android.view.RemoteAnimationTarget; 123 import android.view.View; 124 import android.view.ViewDebug; 125 import android.view.ViewGroup; 126 import android.view.ViewTreeObserver.OnScrollChangedListener; 127 import android.view.accessibility.AccessibilityEvent; 128 import android.view.accessibility.AccessibilityNodeInfo; 129 import android.view.animation.Interpolator; 130 import android.widget.ListView; 131 import android.widget.OverScroller; 132 import android.widget.Toast; 133 import android.window.DesktopModeFlags; 134 import android.window.PictureInPictureSurfaceTransaction; 135 import android.window.TransitionInfo; 136 137 import androidx.annotation.NonNull; 138 import androidx.annotation.Nullable; 139 import androidx.annotation.UiThread; 140 import androidx.core.graphics.ColorUtils; 141 import androidx.dynamicanimation.animation.SpringAnimation; 142 143 import com.android.internal.jank.Cuj; 144 import com.android.launcher3.AbstractFloatingView; 145 import com.android.launcher3.BaseActivity.MultiWindowModeChangedListener; 146 import com.android.launcher3.DeviceProfile; 147 import com.android.launcher3.Flags; 148 import com.android.launcher3.Insettable; 149 import com.android.launcher3.PagedView; 150 import com.android.launcher3.R; 151 import com.android.launcher3.Utilities; 152 import com.android.launcher3.anim.AnimatedFloat; 153 import com.android.launcher3.anim.AnimatorListeners; 154 import com.android.launcher3.anim.AnimatorPlaybackController; 155 import com.android.launcher3.anim.PendingAnimation; 156 import com.android.launcher3.anim.SpringProperty; 157 import com.android.launcher3.compat.AccessibilityManagerCompat; 158 import com.android.launcher3.config.FeatureFlags; 159 import com.android.launcher3.desktop.DesktopRecentsTransitionController; 160 import com.android.launcher3.logger.LauncherAtom; 161 import com.android.launcher3.logging.StatsLogManager; 162 import com.android.launcher3.model.data.ItemInfo; 163 import com.android.launcher3.statehandlers.DepthController; 164 import com.android.launcher3.statehandlers.DesktopVisibilityController; 165 import com.android.launcher3.statemanager.BaseState; 166 import com.android.launcher3.statemanager.StateManager; 167 import com.android.launcher3.statemanager.StatefulContainer; 168 import com.android.launcher3.testing.TestLogging; 169 import com.android.launcher3.testing.shared.TestProtocol; 170 import com.android.launcher3.touch.OverScroll; 171 import com.android.launcher3.util.CancellableTask; 172 import com.android.launcher3.util.DynamicResource; 173 import com.android.launcher3.util.IntArray; 174 import com.android.launcher3.util.IntSet; 175 import com.android.launcher3.util.ResourceBasedOverride.Overrides; 176 import com.android.launcher3.util.RunnableList; 177 import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds; 178 import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource; 179 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition; 180 import com.android.launcher3.util.Themes; 181 import com.android.launcher3.util.TraceHelper; 182 import com.android.launcher3.util.TranslateEdgeEffect; 183 import com.android.launcher3.util.VibratorWrapper; 184 import com.android.launcher3.util.ViewPool; 185 import com.android.launcher3.util.coroutines.DispatcherProvider; 186 import com.android.launcher3.util.window.WindowManagerProxy.DesktopVisibilityListener; 187 import com.android.quickstep.BaseContainerInterface; 188 import com.android.quickstep.GestureState; 189 import com.android.quickstep.HighResLoadingState; 190 import com.android.quickstep.OverviewCommandHelper; 191 import com.android.quickstep.RecentsAnimationController; 192 import com.android.quickstep.RecentsAnimationTargets; 193 import com.android.quickstep.RecentsFilterState; 194 import com.android.quickstep.RecentsModel; 195 import com.android.quickstep.RemoteAnimationTargets; 196 import com.android.quickstep.RemoteTargetGluer; 197 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle; 198 import com.android.quickstep.RotationTouchHelper; 199 import com.android.quickstep.SplitSelectionListener; 200 import com.android.quickstep.SystemUiProxy; 201 import com.android.quickstep.TaskOverlayFactory; 202 import com.android.quickstep.TaskViewUtils; 203 import com.android.quickstep.TopTaskTracker; 204 import com.android.quickstep.ViewUtils; 205 import com.android.quickstep.fallback.window.RecentsWindowFlags; 206 import com.android.quickstep.orientation.RecentsPagedOrientationHandler; 207 import com.android.quickstep.recents.data.RecentTasksRepository; 208 import com.android.quickstep.recents.data.RecentsDeviceProfileRepository; 209 import com.android.quickstep.recents.data.RecentsDeviceProfileRepositoryImpl; 210 import com.android.quickstep.recents.data.RecentsRotationStateRepository; 211 import com.android.quickstep.recents.data.RecentsRotationStateRepositoryImpl; 212 import com.android.quickstep.recents.di.RecentsDependencies; 213 import com.android.quickstep.recents.viewmodel.RecentsViewData; 214 import com.android.quickstep.recents.viewmodel.RecentsViewModel; 215 import com.android.quickstep.util.ActiveGestureProtoLogProxy; 216 import com.android.quickstep.util.AnimUtils; 217 import com.android.quickstep.util.DesktopTask; 218 import com.android.quickstep.util.GroupTask; 219 import com.android.quickstep.util.LayoutUtils; 220 import com.android.quickstep.util.RecentsAtomicAnimationFactory; 221 import com.android.quickstep.util.RecentsOrientedState; 222 import com.android.quickstep.util.SingleTask; 223 import com.android.quickstep.util.SplitAnimationController.Companion.SplitAnimInitProps; 224 import com.android.quickstep.util.SplitAnimationTimings; 225 import com.android.quickstep.util.SplitSelectStateController; 226 import com.android.quickstep.util.SplitTask; 227 import com.android.quickstep.util.SurfaceTransaction; 228 import com.android.quickstep.util.SurfaceTransactionApplier; 229 import com.android.quickstep.util.TaskGridNavHelper; 230 import com.android.quickstep.util.TaskViewSimulator; 231 import com.android.quickstep.util.TaskVisualsChangeListener; 232 import com.android.quickstep.util.TransformParams; 233 import com.android.quickstep.util.VibrationConstants; 234 import com.android.systemui.plugins.ResourceProvider; 235 import com.android.systemui.shared.recents.model.Task; 236 import com.android.systemui.shared.recents.model.Task.TaskKey; 237 import com.android.systemui.shared.recents.model.ThumbnailData; 238 import com.android.systemui.shared.system.ActivityManagerWrapper; 239 import com.android.systemui.shared.system.InteractionJankMonitorWrapper; 240 import com.android.systemui.shared.system.PackageManagerWrapper; 241 import com.android.systemui.shared.system.TaskStackChangeListener; 242 import com.android.systemui.shared.system.TaskStackChangeListeners; 243 import com.android.wm.shell.common.pip.IPipAnimationListener; 244 import com.android.wm.shell.shared.GroupedTaskInfo; 245 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; 246 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource; 247 248 import kotlin.Unit; 249 import kotlin.collections.CollectionsKt; 250 import kotlin.jvm.functions.Function0; 251 252 import kotlinx.coroutines.CoroutineScope; 253 254 import java.util.ArrayList; 255 import java.util.Arrays; 256 import java.util.Collections; 257 import java.util.HashMap; 258 import java.util.HashSet; 259 import java.util.List; 260 import java.util.Map; 261 import java.util.Objects; 262 import java.util.Optional; 263 import java.util.Set; 264 import java.util.function.Consumer; 265 import java.util.stream.Collectors; 266 /** 267 * A list of recent tasks. 268 * 269 * @param <CONTAINER_TYPE> : the container that should host recents view 270 * @param <STATE_TYPE> : the type of base state that will be used 271 */ 272 public abstract class RecentsView< 273 CONTAINER_TYPE extends Context & RecentsViewContainer & StatefulContainer<STATE_TYPE>, 274 STATE_TYPE extends BaseState<STATE_TYPE>> extends PagedView implements Insettable, 275 HighResLoadingState.HighResLoadingStateChangedCallback, 276 TaskVisualsChangeListener, DesktopVisibilityListener { 277 278 protected static final String TAG = "RecentsView"; 279 private static final boolean DEBUG = false; 280 281 public static final FloatProperty<RecentsView<?, ?>> CONTENT_ALPHA = 282 new FloatProperty<>("contentAlpha") { 283 @Override 284 public void setValue(RecentsView view, float v) { 285 view.setContentAlpha(v); 286 } 287 288 @Override 289 public Float get(RecentsView view) { 290 return view.getContentAlpha(); 291 } 292 }; 293 294 public static final FloatProperty<RecentsView<?, ?>> FULLSCREEN_PROGRESS = 295 new FloatProperty<>("fullscreenProgress") { 296 @Override 297 public void setValue(RecentsView recentsView, float v) { 298 recentsView.setFullscreenProgress(v); 299 } 300 301 @Override 302 public Float get(RecentsView recentsView) { 303 return recentsView.mFullscreenProgress; 304 } 305 }; 306 307 public static final FloatProperty<RecentsView<?, ?>> TASK_MODALNESS = 308 new FloatProperty<>("taskModalness") { 309 @Override 310 public void setValue(RecentsView recentsView, float v) { 311 recentsView.setTaskModalness(v); 312 } 313 314 @Override 315 public Float get(RecentsView recentsView) { 316 return recentsView.mTaskModalness; 317 } 318 }; 319 320 public static final FloatProperty<RecentsView<?, ?>> ADJACENT_PAGE_HORIZONTAL_OFFSET = 321 new FloatProperty<>("adjacentPageHorizontalOffset") { 322 @Override 323 public void setValue(RecentsView recentsView, float v) { 324 if (recentsView.mAdjacentPageHorizontalOffset != v) { 325 recentsView.mAdjacentPageHorizontalOffset = v; 326 recentsView.updatePageOffsets(); 327 } 328 } 329 330 @Override 331 public Float get(RecentsView recentsView) { 332 return recentsView.mAdjacentPageHorizontalOffset; 333 } 334 }; 335 336 public static final FloatProperty<RecentsView<?, ?>> RUNNING_TASK_ATTACH_ALPHA = 337 new FloatProperty<>("runningTaskAttachAlpha") { 338 @Override 339 public void setValue(RecentsView recentsView, float v) { 340 recentsView.mRunningTaskAttachAlpha = v; 341 recentsView.applyAttachAlpha(); 342 } 343 344 @Override 345 public Float get(RecentsView recentsView) { 346 return recentsView.mRunningTaskAttachAlpha; 347 } 348 }; 349 350 public static final int SCROLL_VIBRATION_PRIMITIVE = 351 VibrationEffect.Composition.PRIMITIVE_LOW_TICK; 352 public static final float SCROLL_VIBRATION_PRIMITIVE_SCALE = 0.6f; 353 public static final VibrationEffect SCROLL_VIBRATION_FALLBACK = 354 VibrationConstants.EFFECT_TEXTURE_TICK; 355 public static final int UNBOUND_TASK_VIEW_ID = -1; 356 357 /** 358 * Can be used to tint the color of the RecentsView to simulate a scrim that can views 359 * excluded from. Really should be a proper scrim. 360 */ 361 private static final FloatProperty<RecentsView<?, ?>> COLOR_TINT = 362 new FloatProperty<>("colorTint") { 363 @Override 364 public void setValue(RecentsView recentsView, float v) { 365 recentsView.setColorTint(v); 366 } 367 368 @Override 369 public Float get(RecentsView recentsView) { 370 return recentsView.getColorTint(); 371 } 372 }; 373 374 /** 375 * Even though {@link TaskView} has distinct offsetTranslationX/Y and resistance property, they 376 * are currently both used to apply secondary translation. Should their use cases change to be 377 * more specific, we'd want to create a similar FloatProperty just for a TaskView's 378 * offsetX/Y property 379 */ 380 public static final FloatProperty<RecentsView<?, ?>> TASK_SECONDARY_TRANSLATION = 381 new FloatProperty<>("taskSecondaryTranslation") { 382 @Override 383 public void setValue(RecentsView recentsView, float v) { 384 recentsView.setTaskViewsResistanceTranslation(v); 385 } 386 387 @Override 388 public Float get(RecentsView recentsView) { 389 return recentsView.mTaskViewsSecondaryTranslation; 390 } 391 }; 392 393 /** 394 * Even though {@link TaskView} has distinct offsetTranslationX/Y and resistance property, they 395 * are currently both used to apply secondary translation. Should their use cases change to be 396 * more specific, we'd want to create a similar FloatProperty just for a TaskView's 397 * offsetX/Y property 398 */ 399 public static final FloatProperty<RecentsView<?, ?>> TASK_PRIMARY_SPLIT_TRANSLATION = 400 new FloatProperty<>("taskPrimarySplitTranslation") { 401 @Override 402 public void setValue(RecentsView recentsView, float v) { 403 recentsView.setTaskViewsPrimarySplitTranslation(v); 404 } 405 406 @Override 407 public Float get(RecentsView recentsView) { 408 return recentsView.mTaskViewsPrimarySplitTranslation; 409 } 410 }; 411 412 public static final FloatProperty<RecentsView<?, ?>> TASK_SECONDARY_SPLIT_TRANSLATION = 413 new FloatProperty<>("taskSecondarySplitTranslation") { 414 @Override 415 public void setValue(RecentsView recentsView, float v) { 416 recentsView.setTaskViewsSecondarySplitTranslation(v); 417 } 418 419 @Override 420 public Float get(RecentsView recentsView) { 421 return recentsView.mTaskViewsSecondarySplitTranslation; 422 } 423 }; 424 425 /** Same as normal SCALE_PROPERTY, but also updates page offsets that depend on this scale. */ 426 public static final FloatProperty<RecentsView<?, ?>> RECENTS_SCALE_PROPERTY = 427 new FloatProperty<>("recentsScale") { 428 @Override 429 public void setValue(RecentsView view, float scale) { 430 view.setScaleX(scale); 431 view.setScaleY(scale); 432 view.mLastComputedTaskStartPushOutDistance = null; 433 view.mLastComputedTaskEndPushOutDistance = null; 434 view.runActionOnRemoteHandles(new Consumer<RemoteTargetHandle>() { 435 @Override 436 public void accept(RemoteTargetHandle remoteTargetHandle) { 437 remoteTargetHandle.getTaskViewSimulator().recentsViewScale.value = 438 scale; 439 } 440 }); 441 view.setTaskViewsResistanceTranslation(view.mTaskViewsSecondaryTranslation); 442 view.updateTaskViewsSnapshotRadius(); 443 view.updatePageOffsets(); 444 } 445 446 @Override 447 public Float get(RecentsView view) { 448 return view.getScaleX(); 449 } 450 }; 451 452 /** 453 * Progress of Recents view from carousel layout to grid layout. If Recents is not shown as a 454 * grid, then the value remains 0. 455 */ 456 public static final FloatProperty<RecentsView<?, ?>> RECENTS_GRID_PROGRESS = 457 new FloatProperty<>("recentsGrid") { 458 @Override 459 public void setValue(RecentsView view, float gridProgress) { 460 view.setGridProgress(gridProgress); 461 } 462 463 @Override 464 public Float get(RecentsView view) { 465 return view.mGridProgress; 466 } 467 }; 468 469 public static final FloatProperty<RecentsView<?, ?>> DESKTOP_CAROUSEL_DETACH_PROGRESS = 470 new FloatProperty<>("desktopCarouselDetachProgress") { 471 @Override 472 public void setValue(RecentsView view, float offset) { 473 view.mDesktopCarouselDetachProgress = offset; 474 view.applyAttachAlpha(); 475 view.updatePageOffsets(); 476 } 477 478 @Override 479 public Float get(RecentsView view) { 480 return view.mDesktopCarouselDetachProgress; 481 } 482 }; 483 484 /** 485 * Alpha of the task thumbnail splash, where being in BackgroundAppState has a value of 1, and 486 * being in any other state has a value of 0. 487 */ 488 public static final FloatProperty<RecentsView<?, ?>> TASK_THUMBNAIL_SPLASH_ALPHA = 489 new FloatProperty<>("taskThumbnailSplashAlpha") { 490 @Override 491 public void setValue(RecentsView view, float taskThumbnailSplashAlpha) { 492 view.setTaskThumbnailSplashAlpha(taskThumbnailSplashAlpha); 493 } 494 495 @Override 496 public Float get(RecentsView view) { 497 return view.mTaskThumbnailSplashAlpha; 498 } 499 }; 500 501 // OverScroll constants 502 private static final int OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION = 270; 503 504 private static final int DEFAULT_ACTIONS_VIEW_ALPHA_ANIMATION_DURATION = 300; 505 506 private static final int DISMISS_TASK_DURATION = 300; 507 private static final int ADDITION_TASK_DURATION = 200; 508 private static final float INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.55f; 509 private static final float ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.05f; 510 private static final float ANIMATION_DISMISS_PROGRESS_MIDPOINT = 0.5f; 511 private static final float END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.75f; 512 513 private static final float SIGNIFICANT_MOVE_SCREEN_WIDTH_PERCENTAGE = 0.15f; 514 515 private static final float FOREGROUND_SCRIM_TINT = 0.32f; 516 517 protected final RecentsOrientedState mOrientationState; 518 protected final BaseContainerInterface<STATE_TYPE, ?> mSizeStrategy; 519 @Nullable 520 protected RecentsAnimationController mRecentsAnimationController; 521 @Nullable 522 protected SurfaceTransactionApplier mSyncTransactionApplier; 523 protected int mTaskWidth; 524 protected int mTaskHeight; 525 // Used to position the top of a task in the top row of the grid 526 private float mTaskGridVerticalDiff; 527 // The vertical space one grid task takes + space between top and bottom row. 528 private float mTopBottomRowHeightDiff; 529 // mTaskGridVerticalDiff and mTopBottomRowHeightDiff summed together provides the top 530 // position for bottom row of grid tasks. 531 532 @Nullable 533 protected RemoteTargetHandle[] mRemoteTargetHandles; 534 protected final Rect mLastComputedTaskSize = new Rect(); 535 protected final Rect mLastComputedGridSize = new Rect(); 536 protected final Rect mLastComputedGridTaskSize = new Rect(); 537 // How much a task that is directly offscreen will be pushed out due to RecentsView scale/pivot. 538 @Nullable 539 protected Float mLastComputedTaskStartPushOutDistance = null; 540 @Nullable 541 protected Float mLastComputedTaskEndPushOutDistance = null; 542 protected boolean mEnableDrawingLiveTile = false; 543 protected final Rect mTempRect = new Rect(); 544 protected final RectF mTempRectF = new RectF(); 545 private final PointF mTempPointF = new PointF(); 546 private final Matrix mTempMatrix = new Matrix(); 547 private final float[] mTempFloat = new float[1]; 548 private final List<OnScrollChangedListener> mScrollListeners = new ArrayList<>(); 549 550 // The threshold at which we update the SystemUI flags when animating from the task into the app 551 public static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.85f; 552 553 protected final CONTAINER_TYPE mContainer; 554 private final float mFastFlingVelocity; 555 private final int mScrollHapticMinGapMillis; 556 private final RecentsModel mModel; 557 private final int mSplitPlaceholderSize; 558 private final int mSplitPlaceholderInset; 559 private final ClearAllButton mClearAllButton; 560 @Nullable 561 private AddDesktopButton mAddDesktopButton = null; 562 private final Rect mClearAllButtonDeadZoneRect = new Rect(); 563 private final Rect mTaskViewDeadZoneRect = new Rect(); 564 private final Rect mTopRowDeadZoneRect = new Rect(); 565 private final Rect mBottomRowDeadZoneRect = new Rect(); 566 567 @Nullable 568 private DesktopVisibilityController mDesktopVisibilityController = null; 569 570 /** 571 * Reflects if Recents is currently in the middle of a gesture, and if so, which related 572 * [GroupedTaskInfo] is running. If a gesture is not in progress, this will be null. 573 */ 574 private @Nullable GroupedTaskInfo mActiveGestureGroupedTaskInfo; 575 576 // Keeps track of the previously known visible tasks for purposes of loading/unloading task data 577 private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray(); 578 579 /** 580 * Getting views should be done via {@link #getTaskViewFromPool(int)} 581 */ 582 private final ViewPool<TaskView> mTaskViewPool; 583 private final ViewPool<GroupedTaskView> mGroupedTaskViewPool; 584 private final ViewPool<DesktopTaskView> mDesktopTaskViewPool; 585 586 protected final TaskOverlayFactory mTaskOverlayFactory; 587 588 protected boolean mDisallowScrollToClearAll; 589 // True if it is not allowed to scroll to [AddDesktopButton]. 590 protected boolean mDisallowScrollToAddDesk; 591 private boolean mOverlayEnabled; 592 protected boolean mFreezeViewVisibility; 593 private boolean mOverviewGridEnabled; 594 private boolean mOverviewFullscreenEnabled; 595 private boolean mOverviewSelectEnabled; 596 597 private boolean mShouldClampScrollOffset; 598 private int mClampedScrollOffsetBound; 599 600 private float mAdjacentPageHorizontalOffset = 0; 601 private float mDesktopCarouselDetachProgress = 0; 602 protected float mTaskViewsSecondaryTranslation = 0; 603 protected float mTaskViewsPrimarySplitTranslation = 0; 604 protected float mTaskViewsSecondarySplitTranslation = 0; 605 // Progress from 0 to 1 where 0 is a carousel and 1 is a 2 row grid. 606 private float mGridProgress = 0; 607 private float mTaskThumbnailSplashAlpha = 0; 608 private boolean mBorderEnabled = false; 609 private boolean mShowAsGridLastOnLayout = false; 610 protected final IntSet mTopRowIdSet = new IntSet(); 611 private int mClearAllShortTotalWidthTranslation = 0; 612 613 // The GestureEndTarget that is still in progress. 614 @Nullable 615 protected GestureState.GestureEndTarget mCurrentGestureEndTarget; 616 617 private float mColorTint; 618 private final int mTintingColor; 619 @Nullable 620 private ObjectAnimator mTintingAnimator; 621 622 private int mOverScrollShift = 0; 623 private long mScrollLastHapticTimestamp; 624 625 private int mKeyboardTaskFocusSnapAnimationDuration; 626 private int mKeyboardTaskFocusIndex = INVALID_PAGE; 627 628 private Map<TaskView, Integer> mTaskViewsDismissPrimaryTranslations = 629 new HashMap<TaskView, Integer>(); 630 631 /** 632 * TODO: Call reloadIdNeeded in onTaskStackChanged. 633 */ 634 private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() { 635 @Override 636 public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { 637 if (!mHandleTaskStackChanges) { 638 return; 639 } 640 // Check this is for the right user 641 if (!checkCurrentOrManagedUserId(userId, getContext())) { 642 return; 643 } 644 645 // Remove the task immediately from the task list 646 TaskView taskView = getTaskViewByTaskId(taskId); 647 if (taskView != null) { 648 removeView(taskView); 649 } 650 } 651 652 @Override 653 public void onActivityUnpinned() { 654 if (!mHandleTaskStackChanges) { 655 return; 656 } 657 658 reloadIfNeeded(); 659 enableLayoutTransitions(); 660 } 661 662 @Override 663 public void onTaskRemoved(int taskId) { 664 if (!mHandleTaskStackChanges) { 665 Log.d(TAG, "onTaskRemoved: " + taskId + ", not handling task stack changes"); 666 return; 667 } 668 669 TaskContainer taskContainer = mUtils.getTaskContainerById(taskId); 670 if (taskContainer == null) { 671 Log.d(TAG, "onTaskRemoved: " + taskId + ", no associated Task"); 672 return; 673 } 674 Log.d(TAG, "onTaskRemoved: " + taskId); 675 TaskKey taskKey = taskContainer.getTask().key; 676 UI_HELPER_EXECUTOR.execute(new CancellableTask<>( 677 () -> PackageManagerWrapper.getInstance() 678 .getActivityInfo(taskKey.getComponent(), taskKey.userId) == null, 679 MAIN_EXECUTOR, 680 apkRemoved -> { 681 if (apkRemoved) { 682 dismissTask(taskId, /*animate=*/true, /*removeTask=*/false); 683 } else { 684 mModel.isTaskRemoved(taskKey.id, taskRemoved -> { 685 if (taskRemoved) { 686 dismissTask(taskId, /*animate=*/true, /*removeTask=*/false); 687 } 688 }, RecentsFilterState.getFilter(mFilterState.getPackageNameToFilter())); 689 } 690 })); 691 } 692 }; 693 694 private final PinnedStackAnimationListener mIPipAnimationListener = 695 new PinnedStackAnimationListener(); 696 private int mPipCornerRadius; 697 private int mPipShadowRadius; 698 699 // Used to keep track of the last requested task list id, so that we do not request to load the 700 // tasks again if we have already requested it and the task list has not changed 701 private int mTaskListChangeId = -1; 702 703 // Only valid until the launcher state changes to NORMAL 704 /** 705 * ID for the current running TaskView view, unique amongst TaskView instances. ID's are set 706 * through {@link #getTaskViewFromPool(boolean)} and incremented by {@link #mTaskViewIdCount} 707 */ 708 protected int mRunningTaskViewId = -1; 709 private int mTaskViewIdCount; 710 protected boolean mRunningTaskTileHidden; 711 protected int mFocusedTaskViewId = INVALID_TASK_ID; 712 713 private boolean mTaskIconVisible = true; 714 private boolean mRunningTaskShowScreenshot = false; 715 private float mRunningTaskAttachAlpha; 716 717 private boolean mOverviewStateEnabled; 718 private boolean mHandleTaskStackChanges; 719 private boolean mSwipeDownShouldLaunchApp; 720 private boolean mTouchDownToStartHome; 721 private final float mSquaredTouchSlop; 722 private int mDownX; 723 private int mDownY; 724 725 @Nullable 726 private PendingAnimation mPendingAnimation; 727 @Nullable 728 private LayoutTransition mLayoutTransition; 729 730 @ViewDebug.ExportedProperty(category = "launcher") 731 protected float mContentAlpha = 1; 732 @ViewDebug.ExportedProperty(category = "launcher") 733 protected float mFullscreenProgress = 0; 734 /** 735 * How modal is the current task to be displayed, 1 means the task is fully modal and no other 736 * tasks are show. 0 means the task is displays in context in the list with other tasks. 737 */ 738 @ViewDebug.ExportedProperty(category = "launcher") 739 protected float mTaskModalness = 0; 740 741 // Keeps track of task id whose visual state should not be reset 742 private int mIgnoreResetTaskId = -1; 743 protected boolean mLoadPlanEverApplied; 744 745 // Variables for empty state 746 private final Drawable mEmptyIcon; 747 private final CharSequence mEmptyMessage; 748 private final TextPaint mEmptyMessagePaint; 749 private final Point mLastMeasureSize = new Point(); 750 private final int mEmptyMessagePadding; 751 private boolean mShowEmptyMessage; 752 @Nullable 753 private OnEmptyMessageUpdatedListener mOnEmptyMessageUpdatedListener; 754 @Nullable 755 private Layout mEmptyTextLayout; 756 757 /** 758 * Placeholder view indicating where the first split screen selected app will be placed 759 */ 760 protected SplitSelectStateController mSplitSelectStateController; 761 762 /** 763 * The first task that split screen selection was initiated with. When split select state is 764 * initialized, we create a 765 * {@link #createTaskDismissAnimation(TaskView, boolean, boolean, long, boolean)} for this 766 * TaskView but don't actually remove the task since the user might back out. As such, we also 767 * ensure this View doesn't go back into the {@link #mTaskViewPool}, 768 * see {@link #onViewRemoved(View)} 769 */ 770 @Nullable 771 private TaskView mSplitHiddenTaskView; 772 @Nullable 773 private TaskView mSecondSplitHiddenView; 774 @Nullable 775 private SplitBounds mSplitBoundsConfig; 776 private final Toast mSplitUnsupportedToast = Toast.makeText(getContext(), 777 R.string.toast_split_app_unsupported, Toast.LENGTH_SHORT); 778 779 @Nullable 780 private SplitSelectSource mSplitSelectSource; 781 782 private final SplitSelectionListener mSplitSelectionListener = new SplitSelectionListener() { 783 @Override 784 public void onSplitSelectionConfirmed() { 785 } 786 787 @Override 788 public void onSplitSelectionActive() { 789 } 790 791 @Override 792 public void onSplitSelectionExit(boolean launchedSplit) { 793 resetFromSplitSelectionState(); 794 } 795 }; 796 797 /** 798 * Keeps track of the index of the TaskView that split screen was initialized with so we know 799 * where to insert it back into list of taskViews in case user backs out of entering split 800 * screen. 801 * NOTE: This index is the index while {@link #mSplitHiddenTaskView} was a child of recentsView, 802 * this doesn't get adjusted to reflect the new child count after the taskView is dismissed/ 803 * removed from recentsView 804 */ 805 private int mSplitHiddenTaskViewIndex = -1; 806 @Nullable 807 private FloatingTaskView mSecondFloatingTaskView; 808 /** 809 * A fullscreen scrim that goes behind the splitscreen animation to hide color conflicts and 810 * possible flickers. Removed after tasks + divider finish animating in. 811 */ 812 private View mSplitScrim; 813 814 /** 815 * The task to be removed and immediately re-added. Should not be added to task pool. 816 */ 817 @Nullable 818 private TaskView mMovingTaskView; 819 820 private OverviewActionsView mActionsView; 821 private ObjectAnimator mActionsViewAlphaAnimator; 822 private float mActionsViewAlphaAnimatorFinalValue; 823 824 @Nullable 825 private DesktopRecentsTransitionController mDesktopRecentsTransitionController; 826 827 private MultiWindowModeChangedListener mMultiWindowModeChangedListener = 828 new MultiWindowModeChangedListener() { 829 @Override 830 public void onMultiWindowModeChanged(boolean inMultiWindowMode) { 831 mOrientationState.setMultiWindowMode(inMultiWindowMode); 832 setLayoutRotation(mOrientationState.getTouchRotation(), 833 mOrientationState.getDisplayRotation()); 834 mUtils.updateChildTaskOrientations(); 835 if (!inMultiWindowMode && mOverviewStateEnabled) { 836 // TODO: Re-enable layout transitions for addition of the unpinned task 837 reloadIfNeeded(); 838 } 839 } 840 }; 841 842 @Nullable 843 private RunnableList mSideTaskLaunchCallback; 844 @Nullable 845 private TaskLaunchListener mTaskLaunchListener; 846 @Nullable 847 private Runnable mOnTaskLaunchCancelledRunnable; 848 849 850 // keeps track of the state of the filter for tasks in recents view 851 private final RecentsFilterState mFilterState = new RecentsFilterState(); 852 853 private int mOffsetMidpointIndexOverride = INVALID_PAGE; 854 855 /** 856 * Whether or not any task has been dismissed i.e. swiped away by the user, in the lifetime of 857 * RecentsView being open and displayed to the user. It is reset in the {@link #reset()} method 858 * i.e. when RecentsView closes. 859 */ 860 private boolean mAnyTaskHasBeenDismissed; 861 862 protected final RecentsViewModel mRecentsViewModel; 863 private final RecentsViewModelHelper mHelper; 864 protected final RecentsViewUtils mUtils = new RecentsViewUtils(this); 865 protected final RecentsDismissUtils mDismissUtils = new RecentsDismissUtils(this); 866 867 private final Matrix mTmpMatrix = new Matrix(); 868 869 private int mTaskViewCount = 0; 870 871 protected final BlurUtils mBlurUtils = new BlurUtils(this); 872 873 @Nullable getFirstTaskView()874 public TaskView getFirstTaskView() { 875 return mUtils.getFirstTaskView(); 876 } 877 RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr)878 public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 879 super(context, attrs, defStyleAttr); 880 setEnableFreeScroll(true); 881 882 mContainer = RecentsViewContainer.containerFromContext(context); 883 mSizeStrategy = getContainerInterface(mContainer.getDisplayId()); 884 885 mOrientationState = new RecentsOrientedState( 886 context, mSizeStrategy, this::animateRecentsRotationInPlace); 887 final int rotation = mContainer.getDisplay().getRotation(); 888 mOrientationState.setRecentsRotation(rotation); 889 890 // Start Recents Dependency graph 891 if (enableRefactorTaskThumbnail()) { 892 RecentsDependencies recentsDependencies = RecentsDependencies.Companion.maybeInitialize( 893 context); 894 String scopeId = recentsDependencies.createRecentsViewScope(context); 895 mRecentsViewModel = new RecentsViewModel( 896 recentsDependencies.inject(RecentTasksRepository.class, scopeId), 897 recentsDependencies.inject(RecentsViewData.class, scopeId) 898 ); 899 mHelper = new RecentsViewModelHelper( 900 mRecentsViewModel, 901 recentsDependencies.inject(CoroutineScope.class, scopeId), 902 recentsDependencies.inject(DispatcherProvider.class, scopeId) 903 ); 904 905 recentsDependencies.provide(RecentsRotationStateRepository.class, scopeId, 906 () -> new RecentsRotationStateRepositoryImpl(mOrientationState)); 907 908 recentsDependencies.provide(RecentsDeviceProfileRepository.class, scopeId, 909 () -> new RecentsDeviceProfileRepositoryImpl(mContainer)); 910 } else { 911 mRecentsViewModel = null; 912 mHelper = null; 913 } 914 915 mScrollHapticMinGapMillis = getResources() 916 .getInteger(R.integer.recentsScrollHapticMinGapMillis); 917 mFastFlingVelocity = getResources() 918 .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity); 919 mModel = RecentsModel.INSTANCE.get(context); 920 921 mClearAllButton = (ClearAllButton) LayoutInflater.from(context) 922 .inflate(R.layout.overview_clear_all_button, this, false); 923 mClearAllButton.setOnClickListener(this::dismissAllTasks); 924 925 if (DesktopModeStatus.enableMultipleDesktops(mContext)) { 926 mAddDesktopButton = (AddDesktopButton) LayoutInflater.from(context).inflate( 927 R.layout.overview_add_desktop_button, this, false); 928 mAddDesktopButton.setOnClickListener(this::createDesk); 929 930 mDesktopVisibilityController = DesktopVisibilityController.INSTANCE.get(mContext); 931 } 932 933 mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */, 934 10 /* initial size */); 935 int groupedViewPoolInitialSize = enableRefactorTaskThumbnail() ? 2 : 10; 936 mGroupedTaskViewPool = new ViewPool<>(context, this, 937 R.layout.task_grouped, 20 /* max size */, groupedViewPoolInitialSize); 938 int desktopViewPoolInitialSize = DesktopModeStatus.canEnterDesktopMode(mContext) ? 1 : 0; 939 mDesktopTaskViewPool = new ViewPool<>(context, this, R.layout.task_desktop, 940 5 /* max size */, desktopViewPoolInitialSize); 941 942 setOrientationHandler(mOrientationState.getOrientationHandler()); 943 mIsRtl = getPagedOrientationHandler().getRecentsRtlSetting(getResources()); 944 setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR); 945 mSplitPlaceholderSize = getResources().getDimensionPixelSize( 946 R.dimen.split_placeholder_size); 947 mSplitPlaceholderInset = getResources().getDimensionPixelSize( 948 R.dimen.split_placeholder_inset); 949 mSquaredTouchSlop = squaredTouchSlop(context); 950 mClampedScrollOffsetBound = getResources().getDimensionPixelSize( 951 R.dimen.transient_taskbar_clamped_offset_bound); 952 953 mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents); 954 mEmptyIcon.setCallback(this); 955 mEmptyMessage = context.getText(R.string.recents_empty_message); 956 mEmptyMessagePaint = new TextPaint(); 957 mEmptyMessagePaint.setColor(Themes.getAttrColor(context, android.R.attr.textColorPrimary)); 958 mEmptyMessagePaint.setTextSize(getResources() 959 .getDimension(R.dimen.recents_empty_message_text_size)); 960 Typeface typeface = Typeface.create( 961 Typeface.create(Themes.getDefaultBodyFont(context), Typeface.NORMAL), 962 getFontWeight(), 963 false); 964 mEmptyMessagePaint.setTypeface(typeface); 965 mEmptyMessagePaint.setAntiAlias(true); 966 mEmptyMessagePadding = getResources() 967 .getDimensionPixelSize(R.dimen.recents_empty_message_text_padding); 968 setWillNotDraw(false); 969 updateEmptyMessage(); 970 971 mTaskOverlayFactory = Overrides.getObject( 972 TaskOverlayFactory.class, 973 context.getApplicationContext(), 974 R.string.task_overlay_factory_class); 975 976 // Initialize quickstep specific cache params here, as this is constructed only once 977 mContainer.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5); 978 979 mTintingColor = getForegroundScrimDimColor(context); 980 981 // if multi-instance feature is enabled 982 if (FeatureFlags.ENABLE_MULTI_INSTANCE.get()) { 983 // invalidate the current list of tasks if filter changes with a fading in/out animation 984 mFilterState.setOnFilterUpdatedListener(() -> { 985 Animator animatorFade = getStateManager().createStateElementAnimation( 986 RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM, 1f, 0f); 987 Animator animatorAppear = getStateManager().createStateElementAnimation( 988 RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM, 0f, 1f); 989 animatorFade.addListener(new AnimatorListenerAdapter() { 990 @Override 991 public void onAnimationEnd(@NonNull Animator animation) { 992 RecentsView.this.invalidateTaskList(); 993 updateClearAllFunction(); 994 reloadIfNeeded(); 995 if (mPendingAnimation != null) { 996 mPendingAnimation.addEndListener(success -> { 997 animatorAppear.start(); 998 }); 999 } else { 1000 animatorAppear.start(); 1001 } 1002 } 1003 }); 1004 animatorFade.start(); 1005 }); 1006 } 1007 // make sure filter is turned off by default 1008 mFilterState.setFilterBy(null); 1009 } 1010 1011 /** Get the state of the filter */ getFilterState()1012 public RecentsFilterState getFilterState() { 1013 return mFilterState; 1014 } 1015 1016 /** 1017 * Toggles the filter and reloads the recents view if needed. 1018 * 1019 * @param packageName package name to filter by if the filter is being turned on; 1020 * should be null if filter is being turned off 1021 */ setAndApplyFilter(@ullable String packageName)1022 public void setAndApplyFilter(@Nullable String packageName) { 1023 mFilterState.setFilterBy(packageName); 1024 } 1025 1026 /** 1027 * Updates the "Clear All" button and its function depending on the recents view state. 1028 * 1029 * TODO: add a different button for going back to overview. Present solution is for demo only. 1030 */ updateClearAllFunction()1031 public void updateClearAllFunction() { 1032 if (mFilterState.isFiltered()) { 1033 mClearAllButton.setText(R.string.recents_back); 1034 mClearAllButton.setOnClickListener((view) -> { 1035 this.setAndApplyFilter(null); 1036 }); 1037 } else { 1038 mClearAllButton.setText(R.string.recents_clear_all); 1039 mClearAllButton.setOnClickListener(this::dismissAllTasks); 1040 } 1041 } 1042 1043 /** 1044 * Invalidates the list of tasks so that an update occurs to the list of tasks if requested. 1045 */ invalidateTaskList()1046 private void invalidateTaskList() { 1047 mTaskListChangeId = -1; 1048 } 1049 getScroller()1050 public OverScroller getScroller() { 1051 return mScroller; 1052 } 1053 isRtl()1054 public boolean isRtl() { 1055 return mIsRtl; 1056 } 1057 1058 @Override initEdgeEffect()1059 protected void initEdgeEffect() { 1060 mEdgeGlowLeft = new TranslateEdgeEffect(getContext()); 1061 mEdgeGlowRight = new TranslateEdgeEffect(getContext()); 1062 } 1063 1064 @Override drawEdgeEffect(Canvas canvas)1065 protected void drawEdgeEffect(Canvas canvas) { 1066 // Do not draw edge effect 1067 } 1068 1069 @Override dispatchDraw(Canvas canvas)1070 protected void dispatchDraw(Canvas canvas) { 1071 // Draw overscroll 1072 if (mAllowOverScroll && (!mEdgeGlowRight.isFinished() || !mEdgeGlowLeft.isFinished())) { 1073 final int restoreCount = canvas.save(); 1074 1075 int primarySize = getPagedOrientationHandler().getPrimaryValue(getWidth(), getHeight()); 1076 int scroll = OverScroll.dampedScroll(getUndampedOverScrollShift(), primarySize); 1077 getPagedOrientationHandler().setPrimary(canvas, CANVAS_TRANSLATE, scroll); 1078 1079 if (mOverScrollShift != scroll) { 1080 mOverScrollShift = scroll; 1081 dispatchScrollChanged(); 1082 } 1083 1084 super.dispatchDraw(canvas); 1085 canvas.restoreToCount(restoreCount); 1086 } else { 1087 if (mOverScrollShift != 0) { 1088 mOverScrollShift = 0; 1089 dispatchScrollChanged(); 1090 } 1091 super.dispatchDraw(canvas); 1092 } 1093 if (mEnableDrawingLiveTile && mRemoteTargetHandles != null) { 1094 redrawLiveTile(); 1095 } 1096 } 1097 getUndampedOverScrollShift()1098 private float getUndampedOverScrollShift() { 1099 final int width = getWidth(); 1100 final int height = getHeight(); 1101 int primarySize = getPagedOrientationHandler().getPrimaryValue(width, height); 1102 int secondarySize = getPagedOrientationHandler().getSecondaryValue(width, height); 1103 1104 float effectiveShift = 0; 1105 if (!mEdgeGlowLeft.isFinished()) { 1106 mEdgeGlowLeft.setSize(secondarySize, primarySize); 1107 if (((TranslateEdgeEffect) mEdgeGlowLeft).getTranslationShift(mTempFloat)) { 1108 effectiveShift = mTempFloat[0]; 1109 postInvalidateOnAnimation(); 1110 } 1111 } 1112 if (!mEdgeGlowRight.isFinished()) { 1113 mEdgeGlowRight.setSize(secondarySize, primarySize); 1114 if (((TranslateEdgeEffect) mEdgeGlowRight).getTranslationShift(mTempFloat)) { 1115 effectiveShift -= mTempFloat[0]; 1116 postInvalidateOnAnimation(); 1117 } 1118 } 1119 1120 return effectiveShift * primarySize; 1121 } 1122 1123 /** 1124 * Returns the view shift due to overscroll 1125 */ getOverScrollShift()1126 public int getOverScrollShift() { 1127 return mOverScrollShift; 1128 } 1129 1130 @Override 1131 @Nullable onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData)1132 public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) { 1133 if (enableRefactorTaskThumbnail()) { 1134 return null; 1135 } 1136 if (mHandleTaskStackChanges) { 1137 if (!enableRefactorTaskThumbnail()) { 1138 TaskView taskView = getTaskViewByTaskId(taskId); 1139 if (taskView != null) { 1140 for (TaskContainer container : taskView.getTaskContainers()) { 1141 if (taskId != container.getTask().key.id) { 1142 continue; 1143 } 1144 container.getThumbnailViewDeprecated().setThumbnail(container.getTask(), 1145 thumbnailData); 1146 } 1147 } 1148 } 1149 } 1150 return null; 1151 } 1152 1153 @Override onTaskIconChanged(@onNull String pkg, @NonNull UserHandle user)1154 public void onTaskIconChanged(@NonNull String pkg, @NonNull UserHandle user) { 1155 for (TaskView taskView : getTaskViews()) { 1156 Task firstTask = taskView.getFirstTask(); 1157 if (firstTask != null && pkg.equals(firstTask.key.getPackageName()) 1158 && firstTask.key.userId == user.getIdentifier()) { 1159 firstTask.icon = null; 1160 if (taskView.getTaskContainers().stream().anyMatch( 1161 container -> container.getIconView().getDrawable() != null)) { 1162 taskView.onTaskListVisibilityChanged(true /* visible */); 1163 } 1164 } 1165 } 1166 } 1167 1168 @Override onTaskIconChanged(int taskId)1169 public void onTaskIconChanged(int taskId) { 1170 if (enableRefactorTaskThumbnail()) { 1171 return; 1172 } 1173 TaskView taskView = getTaskViewByTaskId(taskId); 1174 if (taskView != null) { 1175 taskView.refreshTaskThumbnailSplash(); 1176 } 1177 } 1178 1179 /** Updates the thumbnail(s) of the relevant TaskView. */ updateThumbnail(Map<Integer, ThumbnailData> thumbnailData)1180 public void updateThumbnail(Map<Integer, ThumbnailData> thumbnailData) { 1181 if (!enableRefactorTaskThumbnail()) { 1182 for (Map.Entry<Integer, ThumbnailData> entry : thumbnailData.entrySet()) { 1183 Integer id = entry.getKey(); 1184 ThumbnailData thumbnail = entry.getValue(); 1185 TaskView taskView = getTaskViewByTaskId(id); 1186 if (taskView == null) { 1187 continue; 1188 } 1189 // taskView could be a GroupedTaskView, so select the relevant task by ID 1190 TaskContainer taskContainer = taskView.getTaskContainerById(id); 1191 if (taskContainer == null) { 1192 continue; 1193 } 1194 Task task = taskContainer.getTask(); 1195 TaskThumbnailViewDeprecated taskThumbnailViewDeprecated = 1196 taskContainer.getThumbnailViewDeprecated(); 1197 taskThumbnailViewDeprecated.setThumbnail(task, thumbnail, /*refreshNow=*/false); 1198 } 1199 } 1200 } 1201 1202 @Override onWindowVisibilityChanged(int visibility)1203 protected void onWindowVisibilityChanged(int visibility) { 1204 super.onWindowVisibilityChanged(visibility); 1205 updateTaskStackListenerState(); 1206 } 1207 init(OverviewActionsView actionsView, SplitSelectStateController splitController, @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController)1208 public void init(OverviewActionsView actionsView, SplitSelectStateController splitController, 1209 @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController) { 1210 mActionsView = actionsView; 1211 mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, !hasTaskViews()); 1212 // Update flags for 1p/3p launchers 1213 mActionsView.updateFor3pLauncher(!supportsAppPairs()); 1214 mSplitSelectStateController = splitController; 1215 mDesktopRecentsTransitionController = desktopRecentsTransitionController; 1216 } 1217 getSplitSelectController()1218 public SplitSelectStateController getSplitSelectController() { 1219 return mSplitSelectStateController; 1220 } 1221 isSplitSelectionActive()1222 public boolean isSplitSelectionActive() { 1223 return mSplitSelectStateController.isSplitSelectActive(); 1224 } 1225 1226 /** 1227 * See overridden implementations 1228 * 1229 * @return {@code true} if child TaskViews can be launched when user taps on them 1230 */ canLaunchFullscreenTask()1231 public boolean canLaunchFullscreenTask() { 1232 return true; 1233 } 1234 1235 @Override onAttachedToWindow()1236 protected void onAttachedToWindow() { 1237 super.onAttachedToWindow(); 1238 updateTaskStackListenerState(); 1239 mModel.getThumbnailCache().getHighResLoadingState().addCallback(this); 1240 mContainer.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener); 1241 TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener); 1242 mSyncTransactionApplier = new SurfaceTransactionApplier(this); 1243 runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams() 1244 .setSyncTransactionApplier(mSyncTransactionApplier)); 1245 RecentsModel.INSTANCE.get(mContext).addThumbnailChangeListener(this); 1246 mIPipAnimationListener.setActivityAndRecentsView(mContainer, this); 1247 SystemUiProxy.INSTANCE.get(mContext).setPipAnimationListener( 1248 mIPipAnimationListener); 1249 mOrientationState.initListeners(); 1250 mTaskOverlayFactory.initListeners(); 1251 mSplitSelectStateController.registerSplitListener(mSplitSelectionListener); 1252 if (mDesktopVisibilityController != null) { 1253 mDesktopVisibilityController.registerDesktopVisibilityListener(this); 1254 } 1255 } 1256 1257 @Override onDetachedFromWindow()1258 protected void onDetachedFromWindow() { 1259 super.onDetachedFromWindow(); 1260 1261 updateTaskStackListenerState(); 1262 mModel.getThumbnailCache().getHighResLoadingState().removeCallback(this); 1263 mContainer.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener); 1264 TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener); 1265 mSyncTransactionApplier = null; 1266 runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams() 1267 .setSyncTransactionApplier(null)); 1268 executeSideTaskLaunchCallback(); 1269 RecentsModel.INSTANCE.get(mContext).removeThumbnailChangeListener(this); 1270 SystemUiProxy.INSTANCE.get(mContext).setPipAnimationListener(null); 1271 mIPipAnimationListener.setActivityAndRecentsView(null, null); 1272 mOrientationState.destroyListeners(); 1273 mTaskOverlayFactory.removeListeners(); 1274 mSplitSelectStateController.unregisterSplitListener(mSplitSelectionListener); 1275 if (mDesktopVisibilityController != null) { 1276 mDesktopVisibilityController.unregisterDesktopVisibilityListener(this); 1277 } 1278 reset(); 1279 } 1280 1281 /** 1282 * Execute clean-up logic needed when the view is destroyed. 1283 */ destroy()1284 public void destroy() { 1285 Log.d(TAG, "destroy"); 1286 if (enableRefactorTaskThumbnail()) { 1287 try { 1288 mTaskViewPool.killOngoingInitializations(); 1289 mGroupedTaskViewPool.killOngoingInitializations(); 1290 mDesktopTaskViewPool.killOngoingInitializations(); 1291 } catch (InterruptedException e) { 1292 Log.e(TAG, "Ongoing initializations could not be killed", e); 1293 } 1294 mHelper.onDestroy(); 1295 RecentsDependencies.destroy(getContext()); 1296 } 1297 } 1298 1299 @Override onViewRemoved(View child)1300 public void onViewRemoved(View child) { 1301 traceBegin(Trace.TRACE_TAG_APP, "RecentsView.onViewRemoved"); 1302 super.onViewRemoved(child); 1303 // Clear the task data for the removed child if it was visible unless: 1304 // - It's the initial taskview for entering split screen, we only pretend to dismiss the 1305 // task 1306 // - It's the focused task to be moved to the front, we immediately re-add the task 1307 if (child instanceof TaskView) { 1308 mTaskViewCount = Math.max(0, --mTaskViewCount); 1309 if (child != mSplitHiddenTaskView && child != mMovingTaskView) { 1310 clearAndRecycleTaskView((TaskView) child); 1311 } 1312 } 1313 traceEnd(Trace.TRACE_TAG_APP); 1314 } 1315 clearAndRecycleTaskView(TaskView taskView)1316 private void clearAndRecycleTaskView(TaskView taskView) { 1317 for (int i : taskView.getTaskIds()) { 1318 mHasVisibleTaskData.delete(i); 1319 } 1320 if (taskView instanceof GroupedTaskView) { 1321 mGroupedTaskViewPool.recycle((GroupedTaskView) taskView); 1322 } else if (taskView instanceof DesktopTaskView) { 1323 mDesktopTaskViewPool.recycle((DesktopTaskView) taskView); 1324 } else { 1325 mTaskViewPool.recycle(taskView); 1326 } 1327 mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, !hasTaskViews()); 1328 } 1329 1330 @Override onViewAdded(View child)1331 public void onViewAdded(View child) { 1332 traceBegin(Trace.TRACE_TAG_APP, "RecentsView.onViewAdded"); 1333 super.onViewAdded(child); 1334 if (child instanceof TaskView) { 1335 mTaskViewCount++; 1336 } 1337 child.setAlpha(mContentAlpha); 1338 // RecentsView is set to RTL in the constructor when system is using LTR. Here we set the 1339 // child direction back to match system settings. 1340 child.setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_LTR : View.LAYOUT_DIRECTION_RTL); 1341 mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, false); 1342 updateEmptyMessage(); 1343 traceEnd(Trace.TRACE_TAG_APP); 1344 } 1345 1346 @Override draw(Canvas canvas)1347 public void draw(Canvas canvas) { 1348 maybeDrawEmptyMessage(canvas); 1349 super.draw(canvas); 1350 } 1351 1352 @Override requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate)1353 public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) { 1354 if (isModal()) { 1355 // Do not scroll when clicking on a modal grid task, as it will already be centered 1356 // on screen. 1357 return false; 1358 } 1359 return super.requestChildRectangleOnScreen(child, rectangle, immediate); 1360 } 1361 addSideTaskLaunchCallback(RunnableList callback)1362 public void addSideTaskLaunchCallback(RunnableList callback) { 1363 if (mSideTaskLaunchCallback == null) { 1364 mSideTaskLaunchCallback = new RunnableList(); 1365 } 1366 mSideTaskLaunchCallback.add(callback::executeAllAndDestroy); 1367 } 1368 1369 /** 1370 * This is a one-time callback when touching in live tile mode. It's reset to null right 1371 * after it's called. 1372 */ setTaskLaunchListener(TaskLaunchListener taskLaunchListener)1373 public void setTaskLaunchListener(TaskLaunchListener taskLaunchListener) { 1374 mTaskLaunchListener = taskLaunchListener; 1375 } 1376 onTaskLaunchedInLiveTileMode()1377 public void onTaskLaunchedInLiveTileMode() { 1378 if (mTaskLaunchListener != null) { 1379 mTaskLaunchListener.onTaskLaunched(); 1380 mTaskLaunchListener = null; 1381 } 1382 } 1383 1384 /** 1385 * This is a one-time callback when touching in live tile mode. It's reset to null right 1386 * after it's called. 1387 */ setTaskLaunchCancelledRunnable(Runnable onTaskLaunchCancelledRunnable)1388 public void setTaskLaunchCancelledRunnable(Runnable onTaskLaunchCancelledRunnable) { 1389 mOnTaskLaunchCancelledRunnable = onTaskLaunchCancelledRunnable; 1390 } 1391 onTaskLaunchedInLiveTileModeCancelled()1392 public void onTaskLaunchedInLiveTileModeCancelled() { 1393 if (mOnTaskLaunchCancelledRunnable != null) { 1394 mOnTaskLaunchCancelledRunnable.run(); 1395 mOnTaskLaunchCancelledRunnable = null; 1396 } 1397 } 1398 executeSideTaskLaunchCallback()1399 private void executeSideTaskLaunchCallback() { 1400 if (mSideTaskLaunchCallback != null) { 1401 mSideTaskLaunchCallback.executeAllAndDestroy(); 1402 mSideTaskLaunchCallback = null; 1403 } 1404 } 1405 1406 /** 1407 * TODO(b/195675206) Check both taskIDs from runningTaskViewId 1408 * and launch if either of them is {@param taskId} 1409 */ launchSideTaskInLiveTileModeForRestartedApp(int taskId)1410 public void launchSideTaskInLiveTileModeForRestartedApp(int taskId) { 1411 int runningTaskViewId = getTaskViewIdFromTaskId(taskId); 1412 if (mRunningTaskViewId == -1 || 1413 mRunningTaskViewId != runningTaskViewId || 1414 mRemoteTargetHandles == null) { 1415 return; 1416 } 1417 1418 TransformParams params = mRemoteTargetHandles[0].getTransformParams(); 1419 RemoteAnimationTargets targets = params.getTargetSet(); 1420 if (targets != null && targets.findTask(taskId) != null) { 1421 launchSideTaskInLiveTileMode(taskId, targets.apps, targets.wallpapers, 1422 targets.nonApps, /* transitionInfo= */ null); 1423 } 1424 } 1425 launchSideTaskInLiveTileMode(int taskId, RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpaper, RemoteAnimationTarget[] nonApps, @Nullable TransitionInfo transitionInfo)1426 public void launchSideTaskInLiveTileMode(int taskId, RemoteAnimationTarget[] apps, 1427 RemoteAnimationTarget[] wallpaper, RemoteAnimationTarget[] nonApps, 1428 @Nullable TransitionInfo transitionInfo) { 1429 AnimatorSet anim = new AnimatorSet(); 1430 TaskView taskView = getTaskViewByTaskId(taskId); 1431 if (taskView == null || !isTaskViewVisible(taskView)) { 1432 // TODO: Refine this animation. 1433 SurfaceTransactionApplier surfaceApplier = 1434 new SurfaceTransactionApplier(mContainer.getDragLayer()); 1435 ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1); 1436 appAnimator.setDuration(RECENTS_LAUNCH_DURATION); 1437 appAnimator.setInterpolator(ACCELERATE_DECELERATE); 1438 final Matrix matrix = new Matrix(); 1439 appAnimator.addUpdateListener(valueAnimator -> { 1440 float percent = valueAnimator.getAnimatedFraction(); 1441 SurfaceTransaction transaction = new SurfaceTransaction(); 1442 for (int i = apps.length - 1; i >= 0; --i) { 1443 RemoteAnimationTarget app = apps[i]; 1444 1445 float dx = mContainer.getDeviceProfile().widthPx * (1 - percent) / 2 1446 + app.screenSpaceBounds.left * percent; 1447 float dy = mContainer.getDeviceProfile().heightPx * (1 - percent) / 2 1448 + app.screenSpaceBounds.top * percent; 1449 matrix.setScale(percent, percent); 1450 matrix.postTranslate(dx, dy); 1451 transaction.forSurface(app.leash) 1452 .setAlpha(percent) 1453 .setMatrix(matrix); 1454 } 1455 surfaceApplier.scheduleApply(transaction); 1456 }); 1457 appAnimator.addListener(new AnimatorListenerAdapter() { 1458 @Override 1459 public void onAnimationStart(Animator animation) { 1460 super.onAnimationStart(animation); 1461 final SurfaceTransaction showTransaction = new SurfaceTransaction(); 1462 for (int i = apps.length - 1; i >= 0; --i) { 1463 showTransaction.getTransaction().show(apps[i].leash); 1464 showTransaction.forSurface(apps[i].leash).setLayer( 1465 Integer.MAX_VALUE - 1000 + apps[i].prefixOrderIndex); 1466 } 1467 surfaceApplier.scheduleApply(showTransaction); 1468 } 1469 }); 1470 anim.play(appAnimator); 1471 anim.addListener(new AnimatorListenerAdapter() { 1472 @Override 1473 public void onAnimationEnd(Animator animation) { 1474 finishRecentsAnimation(false /* toRecents */, true /*shouldPip*/, 1475 allAppsAreTranslucent(apps), null); 1476 } 1477 }); 1478 } else { 1479 TaskViewUtils.composeRecentsLaunchAnimator(anim, taskView, apps, wallpaper, nonApps, 1480 true /* launcherClosing */, getStateManager(), this, 1481 getDepthController(), transitionInfo); 1482 } 1483 anim.start(); 1484 } 1485 allAppsAreTranslucent(RemoteAnimationTarget[] apps)1486 private boolean allAppsAreTranslucent(RemoteAnimationTarget[] apps) { 1487 if (apps == null) { 1488 return false; 1489 } 1490 for (int i = apps.length - 1; i >= 0; --i) { 1491 if (!apps[i].isTranslucent) { 1492 return false; 1493 } 1494 } 1495 return true; 1496 } 1497 isTaskViewVisible(TaskView tv)1498 public boolean isTaskViewVisible(TaskView tv) { 1499 if (showAsGrid()) { 1500 int screenStart = getPagedOrientationHandler().getPrimaryScroll(this); 1501 int screenEnd = screenStart + getPagedOrientationHandler().getMeasuredSize(this); 1502 return isTaskViewWithinBounds(tv, screenStart, screenEnd, /*taskViewTranslation=*/ 0); 1503 } else { 1504 // For now, just check if it's the active task or an adjacent task 1505 return Math.abs(indexOfChild(tv) - getNextPage()) <= 1; 1506 } 1507 } 1508 isTaskViewFullyVisible(TaskView tv)1509 public boolean isTaskViewFullyVisible(TaskView tv) { 1510 if (showAsGrid()) { 1511 int screenStart = getPagedOrientationHandler().getPrimaryScroll(this); 1512 int screenEnd = screenStart + getPagedOrientationHandler().getMeasuredSize(this); 1513 return isTaskViewFullyWithinBounds(tv, screenStart, screenEnd); 1514 } else { 1515 // For now, just check if it's the active task 1516 return indexOfChild(tv) == getNextPage(); 1517 } 1518 } 1519 1520 @Nullable getLastGridTaskView()1521 private TaskView getLastGridTaskView() { 1522 return getLastGridTaskView(mUtils.getTopRowIdArray(), mUtils.getBottomRowIdArray()); 1523 } 1524 1525 @Nullable getLastGridTaskView(IntArray topRowIdArray, IntArray bottomRowIdArray)1526 private TaskView getLastGridTaskView(IntArray topRowIdArray, IntArray bottomRowIdArray) { 1527 if (topRowIdArray.isEmpty() && bottomRowIdArray.isEmpty()) { 1528 return null; 1529 } 1530 int lastTaskViewId = topRowIdArray.size() >= bottomRowIdArray.size() ? topRowIdArray.get( 1531 topRowIdArray.size() - 1) : bottomRowIdArray.get(bottomRowIdArray.size() - 1); 1532 return getTaskViewFromTaskViewId(lastTaskViewId); 1533 } 1534 getSnapToLastTaskScrollDiff()1535 private int getSnapToLastTaskScrollDiff() { 1536 // Snap to a position where ClearAll is just invisible. 1537 int screenStart = getPagedOrientationHandler().getPrimaryScroll(this); 1538 int clearAllScroll = getScrollForPage(indexOfChild(mClearAllButton)); 1539 int clearAllWidth = getPagedOrientationHandler().getPrimarySize(mClearAllButton); 1540 int lastTaskScroll = getLastTaskScroll(clearAllScroll, clearAllWidth); 1541 return screenStart - lastTaskScroll; 1542 } 1543 getLastTaskScroll(int clearAllScroll, int clearAllWidth)1544 private int getLastTaskScroll(int clearAllScroll, int clearAllWidth) { 1545 int distance = clearAllWidth + getClearAllExtraPageSpacing(); 1546 return clearAllScroll + (mIsRtl ? distance : -distance); 1547 } 1548 1549 /** 1550 * Launch running task view if it is instance of DesktopTaskView. 1551 * @return provides runnable list to attach runnable at end of Desktop Mode launch 1552 */ 1553 @Nullable launchRunningDesktopTaskView()1554 public RunnableList launchRunningDesktopTaskView() { 1555 TaskView taskView = getRunningTaskView(); 1556 if (taskView instanceof DesktopTaskView) { 1557 return taskView.launchWithAnimation(); 1558 } 1559 return null; 1560 } 1561 1562 /* 1563 * Returns if TaskView is within screen bounds defined in [screenStart, screenEnd]. 1564 * 1565 * @param taskViewTranslation taskView is considered within bounds if either translated or 1566 * original position of taskView is within screen bounds. 1567 */ isTaskViewWithinBounds(TaskView taskView, int screenStart, int screenEnd, int taskViewTranslation)1568 protected boolean isTaskViewWithinBounds(TaskView taskView, int screenStart, int screenEnd, 1569 int taskViewTranslation) { 1570 int taskStart = getPagedOrientationHandler().getChildStart(taskView) 1571 + (int) taskView.getOffsetAdjustment(showAsGrid()); 1572 int taskSize = (int) (getPagedOrientationHandler().getMeasuredSize(taskView) 1573 * taskView.getSizeAdjustment(showAsFullscreen())); 1574 int taskEnd = taskStart + taskSize; 1575 1576 int translatedTaskStart = taskStart + taskViewTranslation; 1577 int translatedTaskEnd = taskEnd + taskViewTranslation; 1578 1579 taskStart = Math.min(taskStart, translatedTaskStart); 1580 taskEnd = Math.max(taskEnd, translatedTaskEnd); 1581 1582 return (taskStart >= screenStart && taskStart <= screenEnd) || (taskEnd >= screenStart 1583 && taskEnd <= screenEnd); 1584 } 1585 isTaskViewFullyWithinBounds(TaskView tv, int start, int end)1586 private boolean isTaskViewFullyWithinBounds(TaskView tv, int start, int end) { 1587 int taskStart = getPagedOrientationHandler().getChildStart(tv) 1588 + (int) tv.getOffsetAdjustment(showAsGrid()); 1589 int taskSize = (int) (getPagedOrientationHandler().getMeasuredSize(tv) 1590 * tv.getSizeAdjustment(showAsFullscreen())); 1591 int taskEnd = taskStart + taskSize; 1592 return taskStart >= start && taskEnd <= end; 1593 } 1594 1595 /** 1596 * Returns true if the given TaskView is in expected scroll position. 1597 */ isTaskInExpectedScrollPosition(@onNull TaskView taskView)1598 public boolean isTaskInExpectedScrollPosition(@NonNull TaskView taskView) { 1599 return getScrollForPage(indexOfChild(taskView)) 1600 == getPagedOrientationHandler().getPrimaryScroll(this); 1601 } 1602 1603 /** 1604 * Returns true if the focused TaskView is in expected scroll position. 1605 */ isFocusedTaskInExpectedScrollPosition()1606 public boolean isFocusedTaskInExpectedScrollPosition() { 1607 TaskView focusedTask = getFocusedTaskView(); 1608 return focusedTask != null && isTaskInExpectedScrollPosition(focusedTask); 1609 } 1610 1611 /** 1612 * Returns a {@link TaskView} that has taskId matching {@code taskId} or null if no match. 1613 */ 1614 @Nullable getTaskViewByTaskId(int taskId)1615 public TaskView getTaskViewByTaskId(int taskId) { 1616 if (taskId == INVALID_TASK_ID) { 1617 return null; 1618 } 1619 1620 for (TaskView taskView : getTaskViews()) { 1621 if (taskView.containsTaskId(taskId)) { 1622 return taskView; 1623 } 1624 } 1625 return null; 1626 } 1627 1628 /** 1629 * Returns a {@link TaskView} that has taskIds matching {@code taskIds} or null if no match. 1630 */ 1631 @Nullable getTaskViewByTaskIds(int[] taskIds)1632 public TaskView getTaskViewByTaskIds(int[] taskIds) { 1633 if (!hasAllValidTaskIds(taskIds)) { 1634 return null; 1635 } 1636 1637 // We're looking for a taskView that matches these ids, regardless of order 1638 int[] taskIdsCopy = Arrays.copyOf(taskIds, taskIds.length); 1639 Arrays.sort(taskIdsCopy); 1640 1641 for (TaskView taskView : getTaskViews()) { 1642 int[] taskViewIdsCopy = taskView.getTaskIds(); 1643 Arrays.sort(taskViewIdsCopy); 1644 if (Arrays.equals(taskIdsCopy, taskViewIdsCopy)) { 1645 return taskView; 1646 } 1647 } 1648 return null; 1649 } 1650 1651 /** Returns false if {@code taskIds} is null or contains any invalid values, true otherwise */ hasAllValidTaskIds(int[] taskIds)1652 private boolean hasAllValidTaskIds(int[] taskIds) { 1653 return taskIds != null 1654 && taskIds.length > 0 1655 && Arrays.stream(taskIds).noneMatch(taskId -> taskId == INVALID_TASK_ID); 1656 } 1657 setOverviewStateEnabled(boolean enabled)1658 public void setOverviewStateEnabled(boolean enabled) { 1659 mOverviewStateEnabled = enabled; 1660 updateTaskStackListenerState(); 1661 mOrientationState.setRotationWatcherEnabled(enabled); 1662 if (!enabled) { 1663 mSplitBoundsConfig = null; 1664 mTaskOverlayFactory.clearAllActiveState(); 1665 } 1666 updateLocusId(); 1667 } 1668 1669 /** 1670 * Enable or disable showing border on hover and focus change on task views 1671 */ setTaskBorderEnabled(boolean enabled)1672 public void setTaskBorderEnabled(boolean enabled) { 1673 mBorderEnabled = enabled; 1674 for (TaskView taskView : getTaskViews()) { 1675 taskView.setBorderEnabled(enabled); 1676 } 1677 mClearAllButton.setBorderEnabled(enabled); 1678 if (mAddDesktopButton != null) { 1679 mAddDesktopButton.setBorderEnabled(enabled); 1680 } 1681 } 1682 1683 /** 1684 * Whether the Clear All button is hidden or fully visible. Used to determine if center 1685 * displayed page is a task or the Clear All button. 1686 * 1687 * @return True = Clear All button not fully visible, center page is a task. False = Clear All 1688 * button fully visible, center page is Clear All button. 1689 */ isClearAllHidden()1690 public boolean isClearAllHidden() { 1691 return mClearAllButton.getAlpha() != 1f; 1692 } 1693 1694 @Override onPageBeginTransition()1695 protected void onPageBeginTransition() { 1696 super.onPageBeginTransition(); 1697 if (!mContainer.getDeviceProfile().isTablet) { 1698 mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, true); 1699 } 1700 if (mOverviewStateEnabled) { // only when in overview 1701 InteractionJankMonitorWrapper.begin(/* view= */ this, Cuj.CUJ_RECENTS_SCROLLING); 1702 } 1703 } 1704 1705 @Override onPageEndTransition()1706 protected void onPageEndTransition() { 1707 super.onPageEndTransition(); 1708 ActiveGestureProtoLogProxy.logOnPageEndTransition(getNextPage()); 1709 if (isClearAllHidden() && !mContainer.getDeviceProfile().isTablet) { 1710 mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false); 1711 } 1712 if (getNextPage() > 0) { 1713 setSwipeDownShouldLaunchApp(true); 1714 } 1715 InteractionJankMonitorWrapper.end(Cuj.CUJ_RECENTS_SCROLLING); 1716 } 1717 1718 @Override isSignificantMove(float absoluteDelta, int pageOrientedSize)1719 protected boolean isSignificantMove(float absoluteDelta, int pageOrientedSize) { 1720 DeviceProfile deviceProfile = mContainer.getDeviceProfile(); 1721 if (!deviceProfile.isTablet) { 1722 return super.isSignificantMove(absoluteDelta, pageOrientedSize); 1723 } 1724 1725 return absoluteDelta 1726 > deviceProfile.availableWidthPx * SIGNIFICANT_MOVE_SCREEN_WIDTH_PERCENTAGE; 1727 } 1728 1729 @Override onInterceptTouchEvent(MotionEvent ev)1730 public boolean onInterceptTouchEvent(MotionEvent ev) { 1731 boolean intercept = super.onInterceptTouchEvent(ev); 1732 if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) { 1733 Log.d("b/318590728", "onInterceptTouchEvent: " + ev); 1734 } 1735 return intercept; 1736 } 1737 1738 @Override onTouchEvent(MotionEvent ev)1739 public boolean onTouchEvent(MotionEvent ev) { 1740 super.onTouchEvent(ev); 1741 1742 if (showAsGrid()) { 1743 for (TaskView taskView : getTaskViews()) { 1744 if (isTaskViewVisible(taskView) && taskView.offerTouchToChildren(ev)) { 1745 // Keep consuming events to pass to delegate 1746 return true; 1747 } 1748 } 1749 } else { 1750 TaskView taskView = getCurrentPageTaskView(); 1751 if (taskView != null && taskView.offerTouchToChildren(ev)) { 1752 // Keep consuming events to pass to delegate 1753 return true; 1754 } 1755 } 1756 1757 final int x = (int) ev.getX(); 1758 final int y = (int) ev.getY(); 1759 switch (ev.getAction()) { 1760 case MotionEvent.ACTION_UP: 1761 if (mTouchDownToStartHome) { 1762 startHome(); 1763 } 1764 mTouchDownToStartHome = false; 1765 break; 1766 case MotionEvent.ACTION_CANCEL: 1767 mTouchDownToStartHome = false; 1768 break; 1769 case MotionEvent.ACTION_MOVE: 1770 // Passing the touch slop will not allow dismiss to home 1771 if (mTouchDownToStartHome && 1772 (isHandlingTouch() || 1773 squaredHypot(mDownX - x, mDownY - y) > mSquaredTouchSlop)) { 1774 mTouchDownToStartHome = false; 1775 } 1776 break; 1777 case MotionEvent.ACTION_DOWN: 1778 // Touch down anywhere but the deadzone around the visible clear all button and 1779 // between the task views will start home on touch up 1780 if (!isHandlingTouch() && !isModal()) { 1781 if (mShowEmptyMessage) { 1782 mTouchDownToStartHome = true; 1783 } else { 1784 updateDeadZoneRects(); 1785 final boolean clearAllButtonDeadZoneConsumed = 1786 mClearAllButton.getAlpha() == 1 1787 && mClearAllButtonDeadZoneRect.contains(x, y); 1788 final boolean cameFromNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0; 1789 int adjustedX = x + getScrollX(); 1790 if (!clearAllButtonDeadZoneConsumed && !cameFromNavBar 1791 && !mTaskViewDeadZoneRect.contains(adjustedX, y) 1792 && !mTopRowDeadZoneRect.contains(adjustedX, y) 1793 && !mBottomRowDeadZoneRect.contains(adjustedX, y)) { 1794 mTouchDownToStartHome = true; 1795 } 1796 } 1797 } 1798 mDownX = x; 1799 mDownY = y; 1800 break; 1801 } 1802 1803 return isHandlingTouch(); 1804 } 1805 1806 @Override onNotSnappingToPageInFreeScroll()1807 protected void onNotSnappingToPageInFreeScroll() { 1808 int finalPos = mScroller.getFinalX(); 1809 if (finalPos > mMinScroll && finalPos < mMaxScroll) { 1810 int firstPageScroll = getScrollForPage(!mIsRtl ? 0 : getPageCount() - 1); 1811 int lastPageScroll = getScrollForPage(!mIsRtl ? getPageCount() - 1 : 0); 1812 1813 // If scrolling ends in the half of the added space that is closer to 1814 // the end, settle to the end. Otherwise snap to the nearest page. 1815 // If flinging past one of the ends, don't change the velocity as it 1816 // will get stopped at the end anyway. 1817 int pageSnapped = finalPos < (firstPageScroll + mMinScroll) / 2 1818 ? mMinScroll 1819 : finalPos > (lastPageScroll + mMaxScroll) / 2 1820 ? mMaxScroll 1821 : getScrollForPage(mNextPage); 1822 1823 if (showAsGrid()) { 1824 if (isSplitSelectionActive()) { 1825 return; 1826 } 1827 TaskView taskView = getTaskViewAt(mNextPage); 1828 boolean shouldSnapToLargeTask = taskView != null && taskView.isLargeTile() 1829 && !mUtils.isAnySmallTaskFullyVisible(); 1830 boolean shouldSnapToClearAll = mNextPage == indexOfChild(mClearAllButton); 1831 // Snap to large tile when grid tasks aren't fully visible or the clear all button. 1832 if (!shouldSnapToLargeTask && !shouldSnapToClearAll) { 1833 return; 1834 } 1835 } 1836 1837 mScroller.setFinalX(pageSnapped); 1838 // Ensure the scroll/snap doesn't happen too fast; 1839 int extraScrollDuration = OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION 1840 - mScroller.getDuration(); 1841 if (extraScrollDuration > 0) { 1842 mScroller.extendDuration(extraScrollDuration); 1843 } 1844 } 1845 } 1846 1847 @Override onEdgeAbsorbingScroll()1848 protected void onEdgeAbsorbingScroll() { 1849 vibrateForScroll(); 1850 } 1851 1852 @Override onScrollOverPageChanged()1853 protected void onScrollOverPageChanged() { 1854 vibrateForScroll(); 1855 } 1856 vibrateForScroll()1857 private void vibrateForScroll() { 1858 long now = SystemClock.uptimeMillis(); 1859 if (now - mScrollLastHapticTimestamp > mScrollHapticMinGapMillis) { 1860 mScrollLastHapticTimestamp = now; 1861 VibratorWrapper.INSTANCE.get(mContext).vibrate(SCROLL_VIBRATION_PRIMITIVE, 1862 SCROLL_VIBRATION_PRIMITIVE_SCALE, SCROLL_VIBRATION_FALLBACK); 1863 } 1864 } 1865 1866 @Override determineScrollingStart(MotionEvent ev, float touchSlopScale)1867 protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) { 1868 // Enables swiping to the left or right only if the task overlay is not modal. 1869 if (!isModal()) { 1870 super.determineScrollingStart(ev, touchSlopScale); 1871 } 1872 } 1873 1874 /** 1875 * Moves the running task to the expected position in the carousel. In tablets, this minimize 1876 * animation required to move the running task into focused task position. 1877 */ moveRunningTaskToExpectedPosition()1878 public void moveRunningTaskToExpectedPosition() { 1879 TaskView runningTaskView = getRunningTaskView(); 1880 if (runningTaskView == null || mCurrentPage != indexOfChild(runningTaskView)) { 1881 return; 1882 } 1883 1884 int runningTaskExpectedIndex = mUtils.getRunningTaskExpectedIndex(runningTaskView); 1885 if (mCurrentPage == runningTaskExpectedIndex) { 1886 return; 1887 } 1888 1889 int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this); 1890 int currentPageScroll = getScrollForPage(mCurrentPage); 1891 mCurrentPageScrollDiff = primaryScroll - currentPageScroll; 1892 1893 mMovingTaskView = runningTaskView; 1894 removeView(runningTaskView); 1895 mMovingTaskView = null; 1896 runningTaskView.resetPersistentViewTransforms(); 1897 1898 addView(runningTaskView, runningTaskExpectedIndex); 1899 setCurrentPage(runningTaskExpectedIndex); 1900 1901 updateTaskSize(); 1902 } 1903 1904 @Override onScrollerAnimationAborted()1905 protected void onScrollerAnimationAborted() { 1906 ActiveGestureProtoLogProxy.logOnScrollerAnimationAborted(); 1907 } 1908 1909 @Override isPageScrollsInitialized()1910 protected boolean isPageScrollsInitialized() { 1911 return super.isPageScrollsInitialized() && mLoadPlanEverApplied; 1912 } 1913 applyLoadPlan(List<GroupTask> taskGroups)1914 protected void applyLoadPlan(List<GroupTask> taskGroups) { 1915 if (mPendingAnimation != null) { 1916 final List<GroupTask> finalTaskGroups = taskGroups; 1917 mPendingAnimation.addEndListener(success -> applyLoadPlan(finalTaskGroups)); 1918 return; 1919 } 1920 1921 if (taskGroups == null) { 1922 Log.d(TAG, "applyLoadPlan - taskGroups is null"); 1923 } else { 1924 Log.d(TAG, "applyLoadPlan - taskGroups: " + taskGroups.stream().map( 1925 GroupTask::toString).toList()); 1926 } 1927 mLoadPlanEverApplied = true; 1928 if (taskGroups == null || taskGroups.isEmpty()) { 1929 removeAllTaskViews(); 1930 onTaskStackUpdated(); 1931 // With all tasks removed, touch handling in PagedView is disabled and we need to reset 1932 // touch state or otherwise values will be obsolete. 1933 resetTouchState(); 1934 if (isPageScrollsInitialized()) { 1935 onPageScrollsInitialized(); 1936 } 1937 return; 1938 } 1939 1940 // Start here to avoid early returns and empty cases which have special logic 1941 traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan"); 1942 1943 TaskView currentTaskView = getTaskViewAt(mCurrentPage); 1944 int[] currentTaskIds = null; 1945 // Track the current DesktopTaskView through [deskId] as a desk can be empty without any 1946 // tasks. 1947 int currentTaskViewDeskId = INACTIVE_DESK_ID; 1948 if (areMultiDesksFlagsEnabled() 1949 && currentTaskView instanceof DesktopTaskView desktopTaskView) { 1950 currentTaskViewDeskId = desktopTaskView.getDeskId(); 1951 } else if (currentTaskView != null) { 1952 currentTaskIds = currentTaskView.getTaskIds(); 1953 } 1954 1955 // Unload existing visible task data 1956 unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); 1957 1958 TaskView ignoreResetTaskView = 1959 mIgnoreResetTaskId == INVALID_TASK_ID 1960 ? null : getTaskViewByTaskId(mIgnoreResetTaskId); 1961 1962 // Save running task ID if it exists before rebinding all taskViews, otherwise the task from 1963 // the runningTaskView currently bound could get assigned to another TaskView 1964 TaskView runningTaskView = getRunningTaskView(); 1965 int[] runningTaskIds = null; 1966 1967 // Track the running TaskView through [deskId] as a desk can be empty without any tasks. 1968 int runningTaskViewDeskId = INACTIVE_DESK_ID; 1969 if (areMultiDesksFlagsEnabled() 1970 && runningTaskView instanceof DesktopTaskView desktopTaskView) { 1971 runningTaskViewDeskId = desktopTaskView.getDeskId(); 1972 } else if (runningTaskView != null) { 1973 runningTaskIds = runningTaskView.getTaskIds(); 1974 } 1975 1976 int[] focusedTaskIds = getTaskIdsForTaskViewId(mFocusedTaskViewId); 1977 // Reset the focused task to avoiding initializing TaskViews layout as focused task during 1978 // binding. The focused task view will be updated after all the TaskViews are bound. 1979 setFocusedTaskViewId(INVALID_TASK_ID); 1980 1981 // Removing views sets the currentPage to 0, so we save this and restore it after 1982 // the new set of views are added 1983 int previousCurrentPage = mCurrentPage; 1984 int previousFocusedPage = indexOfChild(getFocusedChild()); 1985 // TaskIds will no longer be valid after remove and re-add, clearing mTopRowIdSet. 1986 mAnyTaskHasBeenDismissed = false; 1987 mTopRowIdSet.clear(); 1988 traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.removeAllViews"); 1989 removeAllViews(); 1990 traceEnd(Trace.TRACE_TAG_APP); 1991 // If we are entering Overview as a result of initiating a split from somewhere else 1992 // (e.g. split from Home), we need to make sure the staged app is not drawn as a thumbnail. 1993 int stagedTaskIdToBeRemoved; 1994 if (isSplitSelectionActive()) { 1995 stagedTaskIdToBeRemoved = mSplitSelectStateController.getInitialTaskId(); 1996 updateCurrentTaskActionsVisibility(); 1997 } else { 1998 stagedTaskIdToBeRemoved = INVALID_TASK_ID; 1999 } 2000 // update the map of instance counts 2001 mFilterState.updateInstanceCountMap(taskGroups); 2002 2003 // Clear out desktop view if it is set 2004 2005 // Move Desktop Tasks to the end of the list 2006 if (enableLargeDesktopWindowingTile()) { 2007 taskGroups = mUtils.sortDesktopTasksToFront(taskGroups); 2008 } 2009 if (enableSeparateExternalDisplayTasks()) { 2010 taskGroups = mUtils.sortExternalDisplayTasksToFront(taskGroups); 2011 } 2012 2013 if (mAddDesktopButton != null) { 2014 // Add `mAddDesktopButton` as the first child. 2015 addView(mAddDesktopButton); 2016 } 2017 traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.forLoop"); 2018 2019 // Add views as children based on whether it's grouped or single task. Looping through 2020 // taskGroups backwards populates the thumbnail grid from least recent to most recent. 2021 for (int i = taskGroups.size() - 1; i >= 0; i--) { 2022 GroupTask groupTask = taskGroups.get(i); 2023 boolean containsStagedTask = stagedTaskIdToBeRemoved != INVALID_TASK_ID 2024 && groupTask.containsTask(stagedTaskIdToBeRemoved); 2025 boolean shouldSkipGroupTask = containsStagedTask && groupTask instanceof SingleTask; 2026 2027 if ((isSplitSelectionActive() && groupTask.taskViewType == TaskViewType.DESKTOP) 2028 || shouldSkipGroupTask) { 2029 // To avoid these tasks from being chosen as the app pair, the creation of a 2030 // TaskView is bypassed. The staged task is already selected for the app pair, 2031 // and the Desktop task should be hidden when selecting a pair. 2032 continue; 2033 } 2034 2035 // If we need to remove half of a pair of tasks, force a TaskView with Type.SINGLE 2036 // to be a temporary container for the remaining task. 2037 traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.forLoop.createTaskView"); 2038 TaskView taskView = getTaskViewFromPool( 2039 containsStagedTask ? TaskViewType.SINGLE : groupTask.taskViewType); 2040 traceEnd(Trace.TRACE_TAG_APP); 2041 traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.forLoop.bind"); 2042 if (taskView instanceof GroupedTaskView groupedTaskView) { 2043 var splitTask = (SplitTask) groupTask; 2044 groupedTaskView.bind(splitTask.getTopLeftTask(), 2045 splitTask.getBottomRightTask(), mOrientationState, 2046 mTaskOverlayFactory, splitTask.getSplitBounds()); 2047 } else if (taskView instanceof DesktopTaskView desktopTaskView) { 2048 desktopTaskView.bind((DesktopTask) groupTask, mOrientationState, 2049 mTaskOverlayFactory); 2050 } else if (groupTask instanceof SplitTask splitTask) { 2051 Task task = splitTask.getTopLeftTask().key.id == stagedTaskIdToBeRemoved 2052 ? splitTask.getBottomRightTask() 2053 : splitTask.getTopLeftTask(); 2054 taskView.bind(task, mOrientationState, mTaskOverlayFactory); 2055 } else { 2056 taskView.bind(((SingleTask) groupTask).getTask(), mOrientationState, 2057 mTaskOverlayFactory); 2058 } 2059 traceEnd(Trace.TRACE_TAG_APP); 2060 traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.forLoop.addTaskView"); 2061 addView(taskView); 2062 traceEnd(Trace.TRACE_TAG_APP); 2063 2064 // enables instance filtering if the feature flag for it is on 2065 if (FeatureFlags.ENABLE_MULTI_INSTANCE.get()) { 2066 taskView.setUpShowAllInstancesListener(); 2067 } 2068 } 2069 // For loop end trace 2070 traceEnd(Trace.TRACE_TAG_APP); 2071 2072 addView(mClearAllButton); 2073 2074 // Keep same previous focused task 2075 TaskView newFocusedTaskView = null; 2076 if (!enableGridOnlyOverview()) { 2077 newFocusedTaskView = getTaskViewByTaskIds(focusedTaskIds); 2078 if (enableLargeDesktopWindowingTile() 2079 && newFocusedTaskView instanceof DesktopTaskView) { 2080 newFocusedTaskView = null; 2081 } 2082 // If the list changed, maybe the focused task doesn't exist anymore. 2083 if (newFocusedTaskView == null) { 2084 newFocusedTaskView = mUtils.getFirstNonDesktopTaskView(); 2085 } 2086 } 2087 setFocusedTaskViewId( 2088 newFocusedTaskView != null ? newFocusedTaskView.getTaskViewId() : INVALID_TASK_ID); 2089 2090 traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.layouts"); 2091 updateTaskSize(); 2092 mUtils.updateChildTaskOrientations(); 2093 traceEnd(Trace.TRACE_TAG_APP); 2094 2095 TaskView newRunningTaskView = mUtils.getDesktopTaskViewForDeskId(runningTaskViewDeskId); 2096 if (newRunningTaskView == null) { 2097 // Update mRunningTaskViewId to be the new TaskView that was assigned by binding 2098 // the full list of tasks to taskViews 2099 newRunningTaskView = getTaskViewByTaskIds(runningTaskIds); 2100 } 2101 if (newRunningTaskView != null) { 2102 setRunningTaskViewId(newRunningTaskView.getTaskViewId()); 2103 } else { 2104 if (mActiveGestureGroupedTaskInfo != null) { 2105 // This will update mRunningTaskViewId and create a stub view if necessary. 2106 // We try to avoid this because it can cause a scroll jump, but it is needed 2107 // for cases where the running task isn't included in this load plan (e.g. if 2108 // the current running task is excludedFromRecents.) 2109 showCurrentTask(mActiveGestureGroupedTaskInfo, "applyLoadPlan"); 2110 newRunningTaskView = getRunningTaskView(); 2111 } else { 2112 setRunningTaskViewId(INVALID_TASK_ID); 2113 } 2114 } 2115 2116 int targetPage = -1; 2117 if (mNextPage != INVALID_PAGE) { 2118 // Restore mCurrentPage but don't call setCurrentPage() as that clobbers the scroll. 2119 mCurrentPage = previousCurrentPage; 2120 currentTaskView = mUtils.getDesktopTaskViewForDeskId(currentTaskViewDeskId); 2121 if (currentTaskView == null) { 2122 currentTaskView = getTaskViewByTaskIds(currentTaskIds); 2123 } 2124 if (currentTaskView != null) { 2125 targetPage = indexOfChild(currentTaskView); 2126 } 2127 } else if (previousFocusedPage != INVALID_PAGE) { 2128 targetPage = previousFocusedPage; 2129 } else { 2130 targetPage = indexOfChild( 2131 mUtils.getExpectedCurrentTask(newRunningTaskView, newFocusedTaskView)); 2132 } 2133 if (targetPage != -1 && mCurrentPage != targetPage) { 2134 int finalTargetPage = targetPage; 2135 runOnPageScrollsInitialized(() -> { 2136 // TODO(b/246283207): Remove logging once root cause of flake detected. 2137 if (Utilities.isRunningInTestHarness()) { 2138 Log.d("b/246283207", "RecentsView#applyLoadPlan() -> " 2139 + "previousCurrentPage: " + previousCurrentPage 2140 + ", targetPage: " + finalTargetPage 2141 + ", getScrollForPage(targetPage): " 2142 + getScrollForPage(finalTargetPage)); 2143 } 2144 setCurrentPage(finalTargetPage); 2145 }); 2146 } 2147 2148 traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.cleanupStates"); 2149 if (mIgnoreResetTaskId != INVALID_TASK_ID && 2150 getTaskViewByTaskId(mIgnoreResetTaskId) != ignoreResetTaskView) { 2151 // If the taskView mapping is changing, do not preserve the visuals. Since we are 2152 // mostly preserving the first task, and new taskViews are added to the end, it should 2153 // generally map to the same task. 2154 mIgnoreResetTaskId = INVALID_TASK_ID; 2155 } 2156 2157 resetTaskVisuals(); 2158 onTaskStackUpdated(); 2159 updateEnabledOverlays(); 2160 if (isPageScrollsInitialized()) { 2161 onPageScrollsInitialized(); 2162 } 2163 traceEnd(Trace.TRACE_TAG_APP); 2164 2165 // applyLoadPlan end trace 2166 traceEnd(Trace.TRACE_TAG_APP); 2167 } 2168 isModal()2169 private boolean isModal() { 2170 return mTaskModalness > 0; 2171 } 2172 isLoadingTasks()2173 public boolean isLoadingTasks() { 2174 return mModel.isLoadingTasksInBackground(); 2175 } 2176 removeAllTaskViews()2177 private void removeAllTaskViews() { 2178 // This handles an edge case where applyLoadPlan happens during a gesture when the only 2179 // Task is one with excludeFromRecents, in which case we should not remove it. 2180 CollectionsKt 2181 .filter(getTaskViews(), taskView -> !isGestureActive() || !taskView.isRunningTask()) 2182 .forEach(this::removeView); 2183 if (!hasTaskViews()) { 2184 removeView(mAddDesktopButton); 2185 removeView(mClearAllButton); 2186 } 2187 } 2188 2189 /** Returns true if there are at least one TaskView has been added to the RecentsView. */ hasTaskViews()2190 public boolean hasTaskViews() { 2191 return mUtils.hasTaskViews(); 2192 } 2193 getTaskViewCount()2194 public int getTaskViewCount() { 2195 return mTaskViewCount; 2196 } 2197 2198 /** Counts {@link TaskView}s that are not {@link DesktopTaskView} instances. */ getNonDesktopTaskViewCount()2199 public int getNonDesktopTaskViewCount() { 2200 return mUtils.getNonDesktopTaskViewCount(); 2201 } 2202 2203 /** 2204 * Returns the number of tasks in the top row of the overview grid. 2205 */ getTopRowTaskCountForTablet()2206 public int getTopRowTaskCountForTablet() { 2207 return mTopRowIdSet.size(); 2208 } 2209 2210 /** 2211 * Returns the number of tasks in the bottom row of the overview grid. 2212 */ getBottomRowTaskCountForTablet()2213 public int getBottomRowTaskCountForTablet() { 2214 return getTaskViewCount() - mTopRowIdSet.size() - (enableGridOnlyOverview() ? 0 : 1); 2215 } 2216 onTaskStackUpdated()2217 protected void onTaskStackUpdated() { 2218 // Lazily update the empty message only when the task stack is reapplied 2219 updateEmptyMessage(); 2220 } 2221 resetTaskVisuals()2222 public void resetTaskVisuals() { 2223 for (TaskView taskView : getTaskViews()) { 2224 if (Arrays.stream(taskView.getTaskIds()).noneMatch( 2225 taskId -> taskId == mIgnoreResetTaskId)) { 2226 taskView.resetViewTransforms(); 2227 taskView.setIconVisibleForGesture(mTaskIconVisible); 2228 taskView.setStableAlpha(mContentAlpha); 2229 taskView.setFullscreenProgress(mFullscreenProgress); 2230 taskView.setModalness(mTaskModalness); 2231 taskView.setTaskThumbnailSplashAlpha(mTaskThumbnailSplashAlpha); 2232 taskView.setBorderEnabled(mBorderEnabled); 2233 } 2234 } 2235 // resetTaskVisuals is called at the end of dismiss animation which could update 2236 // primary and secondary translation of the live tile cut out. We will need to do so 2237 // here accordingly. 2238 runActionOnRemoteHandles(remoteTargetHandle -> { 2239 TaskViewSimulator simulator = remoteTargetHandle.getTaskViewSimulator(); 2240 simulator.taskPrimaryTranslation.value = 0; 2241 simulator.taskSecondaryTranslation.value = 0; 2242 simulator.fullScreenProgress.value = 0; 2243 simulator.recentsViewScale.value = 1; 2244 }); 2245 // Reapply runningTask related attributes as they might have been reset by 2246 // resetViewTransforms(). 2247 setRunningTaskViewShowScreenshot(mRunningTaskShowScreenshot); 2248 applyAttachAlpha(); 2249 2250 updateCurveProperties(); 2251 // Update the set of visible task's data 2252 loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); 2253 setTaskModalness(0); 2254 setColorTint(0); 2255 } 2256 setFullscreenProgress(float fullscreenProgress)2257 public void setFullscreenProgress(float fullscreenProgress) { 2258 mFullscreenProgress = fullscreenProgress; 2259 for (TaskView taskView : getTaskViews()) { 2260 taskView.setFullscreenProgress(mFullscreenProgress); 2261 } 2262 mClearAllButton.setFullscreenProgress(fullscreenProgress); 2263 2264 // Fade out the actions view quickly (0.1 range) 2265 mActionsView.getFullscreenAlpha().updateValue( 2266 mapToRange(fullscreenProgress, 0, 0.1f, 1f, 0f, LINEAR)); 2267 } 2268 updateTaskStackListenerState()2269 private void updateTaskStackListenerState() { 2270 boolean handleTaskStackChanges = mOverviewStateEnabled && isAttachedToWindow() 2271 && getWindowVisibility() == VISIBLE; 2272 if (handleTaskStackChanges != mHandleTaskStackChanges) { 2273 Log.d(TAG, "updateTaskStackListenerState: " + handleTaskStackChanges); 2274 mHandleTaskStackChanges = handleTaskStackChanges; 2275 if (handleTaskStackChanges) { 2276 reloadIfNeeded(); 2277 } 2278 } 2279 } 2280 2281 @Override setInsets(Rect insets)2282 public void setInsets(Rect insets) { 2283 mInsets.set(insets); 2284 2285 // Update DeviceProfile dependant state. 2286 DeviceProfile dp = mContainer.getDeviceProfile(); 2287 setOverviewGridEnabled( 2288 getStateManager().getState().displayOverviewTasksAsGrid(dp)); 2289 if (enableGridOnlyOverview()) { 2290 mActionsView.updateHiddenFlags(HIDDEN_ACTIONS_IN_MENU, dp.isTablet); 2291 } 2292 setPageSpacing(dp.overviewPageSpacing); 2293 2294 // Propagate DeviceProfile change event. 2295 runActionOnRemoteHandles( 2296 remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator().setDp(dp)); 2297 mOrientationState.setDeviceProfile(dp); 2298 2299 // Update RecentsView and TaskView's DeviceProfile dependent layout. 2300 updateOrientationHandler(); 2301 mActionsView.updateDimension(dp, mLastComputedTaskSize); 2302 } 2303 updateOrientationHandler()2304 private void updateOrientationHandler() { 2305 updateOrientationHandler(true); 2306 } 2307 updateOrientationHandler(boolean forceRecreateDragLayerControllers)2308 private void updateOrientationHandler(boolean forceRecreateDragLayerControllers) { 2309 // Handle orientation changes. 2310 RecentsPagedOrientationHandler oldOrientationHandler = getPagedOrientationHandler(); 2311 setOrientationHandler(mOrientationState.getOrientationHandler()); 2312 2313 mIsRtl = getPagedOrientationHandler().getRecentsRtlSetting(getResources()); 2314 setLayoutDirection(mIsRtl 2315 ? View.LAYOUT_DIRECTION_RTL 2316 : View.LAYOUT_DIRECTION_LTR); 2317 mClearAllButton.setLayoutDirection(mIsRtl 2318 ? View.LAYOUT_DIRECTION_LTR 2319 : View.LAYOUT_DIRECTION_RTL); 2320 mClearAllButton.setRotation(getPagedOrientationHandler().getDegreesRotated()); 2321 2322 boolean isOrientationHandlerChanged = 2323 !getPagedOrientationHandler().equals(oldOrientationHandler); 2324 if (forceRecreateDragLayerControllers || isOrientationHandlerChanged) { 2325 // Changed orientations, update controllers so they intercept accordingly. 2326 mContainer.getDragLayer().recreateControllers(); 2327 onOrientationChanged(); 2328 resetTaskVisuals(); 2329 // Log fake orientation changed. 2330 if (isOrientationHandlerChanged) { 2331 logOrientationChanged(); 2332 } 2333 } 2334 2335 boolean isInLandscape = mOrientationState.getTouchRotation() != ROTATION_0 2336 || mOrientationState.getRecentsActivityRotation() != ROTATION_0; 2337 mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION, 2338 !mOrientationState.isRecentsActivityRotationAllowed() && isInLandscape); 2339 2340 // Recalculate DeviceProfile dependent layout. 2341 updateSizeAndPadding(); 2342 2343 // Update TaskView's DeviceProfile dependent layout. 2344 mUtils.updateChildTaskOrientations(); 2345 2346 requestLayout(); 2347 // Reapply the current page to update page scrolls. 2348 setCurrentPage(mCurrentPage); 2349 } 2350 onOrientationChanged()2351 private void onOrientationChanged() { 2352 // If overview is in modal state when rotate, reset it to overview state without running 2353 // animation. 2354 setModalStateEnabled(/* taskId= */ INVALID_TASK_ID, /* animate= */ false); 2355 if (isSplitSelectionActive()) { 2356 onRotateInSplitSelectionState(); 2357 } 2358 } 2359 2360 // Update task size and padding that are dependent on DeviceProfile and insets. updateSizeAndPadding()2361 private void updateSizeAndPadding() { 2362 DeviceProfile dp = mContainer.getDeviceProfile(); 2363 getTaskSize(mLastComputedTaskSize); 2364 mTaskWidth = mLastComputedTaskSize.width(); 2365 mTaskHeight = mLastComputedTaskSize.height(); 2366 setPadding(mLastComputedTaskSize.left - mInsets.left, 2367 mLastComputedTaskSize.top - dp.overviewTaskThumbnailTopMarginPx - mInsets.top, 2368 dp.widthPx - mInsets.right - mLastComputedTaskSize.right, 2369 dp.heightPx - mInsets.bottom - mLastComputedTaskSize.bottom); 2370 2371 mSizeStrategy.calculateGridSize(dp, mContainer, mLastComputedGridSize); 2372 mSizeStrategy.calculateGridTaskSize(mContainer, dp, mLastComputedGridTaskSize, 2373 getPagedOrientationHandler()); 2374 2375 mTaskGridVerticalDiff = mLastComputedGridTaskSize.top - mLastComputedTaskSize.top; 2376 mTopBottomRowHeightDiff = 2377 mLastComputedGridTaskSize.height() + dp.overviewTaskThumbnailTopMarginPx 2378 + dp.overviewRowSpacing; 2379 2380 // Force TaskView to update size from thumbnail 2381 updateTaskSize(); 2382 updatePivots(); 2383 } 2384 2385 /** 2386 * Updates TaskView scaling and translation required to support variable width. 2387 */ updateTaskSize()2388 private void updateTaskSize() { 2389 if (!hasTaskViews()) { 2390 return; 2391 } 2392 2393 float accumulatedTranslationX = 0; 2394 for (TaskView taskView : getTaskViews()) { 2395 taskView.updateTaskSize(mLastComputedTaskSize, mLastComputedGridTaskSize); 2396 taskView.setNonGridTranslationX(accumulatedTranslationX); 2397 // Compensate space caused by TaskView scaling. 2398 float widthDiff = 2399 taskView.getLayoutParams().width * (1 - taskView.getNonGridScale()); 2400 accumulatedTranslationX += mIsRtl ? widthDiff : -widthDiff; 2401 } 2402 2403 mClearAllButton.setFullscreenTranslationPrimary(accumulatedTranslationX); 2404 2405 float taskAlignmentTranslationY = getTaskAlignmentTranslationY(); 2406 mClearAllButton.setTaskAlignmentTranslationY(taskAlignmentTranslationY); 2407 if (mAddDesktopButton != null) { 2408 mAddDesktopButton.setTranslationY(taskAlignmentTranslationY); 2409 } 2410 2411 updateGridProperties(); 2412 } 2413 getTaskSize(Rect outRect)2414 public void getTaskSize(Rect outRect) { 2415 mSizeStrategy.calculateTaskSize(mContainer, mContainer.getDeviceProfile(), outRect, 2416 getPagedOrientationHandler()); 2417 } 2418 2419 /** 2420 * Returns the currently selected TaskView in Select mode. 2421 */ 2422 @Nullable getSelectedTaskView()2423 public TaskView getSelectedTaskView() { 2424 return mUtils.getSelectedTaskView(); 2425 } 2426 2427 /** 2428 * Sets the selected TaskView in Select mode. 2429 */ setSelectedTask(int lastSelectedTaskId)2430 public void setSelectedTask(int lastSelectedTaskId) { 2431 mUtils.setSelectedTaskView(getTaskViewByTaskId(lastSelectedTaskId)); 2432 } 2433 2434 /** 2435 * Returns the bounds of the task selected to enter modal state. 2436 */ getSelectedTaskBounds()2437 public Rect getSelectedTaskBounds() { 2438 if (getSelectedTaskView() == null) { 2439 return mLastComputedTaskSize; 2440 } 2441 return getTaskBounds(getSelectedTaskView()); 2442 } 2443 2444 /** 2445 * Get the Y translation that should be applied to the non-TaskView item inside the RecentsView 2446 * (ClearAllButton and AddDesktopButton) in the original layout position, before scrolling. This 2447 * is done to make sure the button is aligned to the middle of Task thumbnail in y coordinate. 2448 */ getTaskAlignmentTranslationY()2449 private float getTaskAlignmentTranslationY() { 2450 DeviceProfile deviceProfile = mContainer.getDeviceProfile(); 2451 if (deviceProfile.isTablet) { 2452 return deviceProfile.overviewRowSpacing; 2453 } 2454 return deviceProfile.overviewTaskThumbnailTopMarginPx / 2.0f; 2455 } 2456 getTaskBounds(TaskView taskView)2457 protected Rect getTaskBounds(TaskView taskView) { 2458 int selectedPage = indexOfChild(taskView); 2459 int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this); 2460 int selectedPageScroll = getScrollForPage(selectedPage); 2461 boolean isTopRow = mTopRowIdSet.contains(taskView.getTaskViewId()); 2462 Rect outRect = new Rect( 2463 taskView.isGridTask() ? mLastComputedGridTaskSize : mLastComputedTaskSize); 2464 outRect.offset( 2465 -(primaryScroll - (selectedPageScroll + getOffsetFromScrollPosition(selectedPage))), 2466 (int) (showAsGrid() && enableGridOnlyOverview() && !isTopRow 2467 ? mTopBottomRowHeightDiff : 0)); 2468 return outRect; 2469 } 2470 2471 /** Gets the last computed task size */ getLastComputedTaskSize()2472 public Rect getLastComputedTaskSize() { 2473 return mLastComputedTaskSize; 2474 } 2475 getLastComputedGridTaskSize()2476 public Rect getLastComputedGridTaskSize() { 2477 return mLastComputedGridTaskSize; 2478 } 2479 2480 /** Gets the task size for modal state. */ getModalTaskSize(Rect outRect)2481 public void getModalTaskSize(Rect outRect) { 2482 mSizeStrategy.calculateModalTaskSize(mContainer, mContainer.getDeviceProfile(), outRect, 2483 getPagedOrientationHandler()); 2484 } 2485 2486 @Override computeScrollHelper()2487 protected boolean computeScrollHelper() { 2488 boolean scrolling = super.computeScrollHelper(); 2489 boolean isFlingingFast = false; 2490 updateCurveProperties(); 2491 if (scrolling || isHandlingTouch()) { 2492 if (scrolling) { 2493 // Check if we are flinging quickly to disable high res thumbnail loading 2494 isFlingingFast = mScroller.getCurrVelocity() > mFastFlingVelocity; 2495 } 2496 2497 // After scrolling, update the visible task's data 2498 loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); 2499 } 2500 2501 // Update ActionsView's visibility when scroll changes. 2502 updateActionsViewFocusedScroll(); 2503 2504 // Update the high res thumbnail loader state 2505 mModel.getThumbnailCache().getHighResLoadingState().setFlingingFast(isFlingingFast); 2506 return scrolling; 2507 } 2508 updateActionsViewFocusedScroll()2509 private void updateActionsViewFocusedScroll() { 2510 if (showAsGrid()) { 2511 float actionsViewAlphaValue = isFocusedTaskInExpectedScrollPosition() ? 1 : 0; 2512 // If animation is already in progress towards the same end value, do not restart. 2513 if (mActionsViewAlphaAnimator == null || !mActionsViewAlphaAnimator.isStarted() 2514 || (mActionsViewAlphaAnimator.isStarted() 2515 && mActionsViewAlphaAnimatorFinalValue != actionsViewAlphaValue)) { 2516 animateActionsViewAlpha(actionsViewAlphaValue, 2517 DEFAULT_ACTIONS_VIEW_ALPHA_ANIMATION_DURATION); 2518 } 2519 } 2520 } 2521 animateActionsViewAlpha(float alphaValue, long duration)2522 private void animateActionsViewAlpha(float alphaValue, long duration) { 2523 mActionsViewAlphaAnimator = ObjectAnimator.ofFloat(mActionsView.getVisibilityAlpha(), 2524 AnimatedFloat.VALUE, alphaValue); 2525 mActionsViewAlphaAnimatorFinalValue = alphaValue; 2526 mActionsViewAlphaAnimator.setDuration(duration); 2527 // Set autocancel to prevent race-conditiony setting of alpha from other animations 2528 mActionsViewAlphaAnimator.setAutoCancel(true); 2529 mActionsViewAlphaAnimator.start(); 2530 } 2531 2532 /** 2533 * Scales and adjusts translation of adjacent pages as if on a curved carousel. 2534 */ updateCurveProperties()2535 public void updateCurveProperties() { 2536 if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) { 2537 return; 2538 } 2539 int scroll = getPagedOrientationHandler().getPrimaryScroll(this); 2540 mClearAllButton.onRecentsViewScroll(scroll, mOverviewGridEnabled); 2541 2542 // Clear all button alpha was set by the previous line. 2543 mActionsView.getIndexScrollAlpha().updateValue(1 - mClearAllButton.getScrollAlpha()); 2544 } 2545 2546 @Override getDestinationPage(int scaledScroll)2547 protected int getDestinationPage(int scaledScroll) { 2548 if (!mContainer.getDeviceProfile().isTablet) { 2549 return super.getDestinationPage(scaledScroll); 2550 } 2551 if (!isPageScrollsInitialized()) { 2552 Log.e(TAG, 2553 "Cannot get destination page: RecentsView not properly initialized", 2554 new IllegalStateException()); 2555 return INVALID_PAGE; 2556 } 2557 2558 // When in tablet with variable task width, return the page which scroll is closest to 2559 // screenStart instead of page nearest to center of screen. 2560 int minDistanceFromScreenStart = Integer.MAX_VALUE; 2561 int minDistanceFromScreenStartIndex = INVALID_PAGE; 2562 for (int i = 0; i < getChildCount(); ++i) { 2563 // Do not set the destination page to the AddDesktopButton, which has the same page 2564 // scrolls as the first [TaskView] and shouldn't be scrolled to. 2565 if (getChildAt(i) instanceof AddDesktopButton) { 2566 continue; 2567 } 2568 int distanceFromScreenStart = Math.abs(mPageScrolls[i] - scaledScroll); 2569 if (distanceFromScreenStart < minDistanceFromScreenStart) { 2570 minDistanceFromScreenStart = distanceFromScreenStart; 2571 minDistanceFromScreenStartIndex = i; 2572 } 2573 } 2574 return minDistanceFromScreenStartIndex; 2575 } 2576 2577 /** 2578 * Iterates through all the tasks, and loads the associated task data for newly visible tasks, 2579 * and unloads the associated task data for tasks that are no longer visible. 2580 */ loadVisibleTaskData(@askView.TaskDataChanges int dataChanges)2581 public void loadVisibleTaskData(@TaskView.TaskDataChanges int dataChanges) { 2582 boolean hasLeftOverview = !mOverviewStateEnabled && mScroller.isFinished(); 2583 if (hasLeftOverview || mTaskListChangeId == -1) { 2584 // Skip loading visible task data if we've already left the overview state, or if the 2585 // task list hasn't been loaded yet (the task views will not reflect the task list) 2586 return; 2587 } 2588 2589 int lowerIndex, upperIndex, visibleStart, visibleEnd; 2590 if (showAsGrid()) { 2591 int screenStart = getPagedOrientationHandler().getPrimaryScroll(this); 2592 int pageOrientedSize = getPagedOrientationHandler().getMeasuredSize(this); 2593 // For GRID_ONLY_OVERVIEW, use +/- 1 task column as visible area for preloading 2594 // adjacent thumbnails, otherwise use +/-50% screen width 2595 int extraWidth = 2596 enableGridOnlyOverview() ? getLastComputedTaskSize().width() + getPageSpacing() 2597 : pageOrientedSize / 2; 2598 lowerIndex = upperIndex = 0; 2599 visibleStart = screenStart - extraWidth; 2600 visibleEnd = screenStart + pageOrientedSize + extraWidth; 2601 } else { 2602 int centerPageIndex = getPageNearestToCenterOfScreen(); 2603 int numChildren = getChildCount(); 2604 lowerIndex = Math.max(0, centerPageIndex - 2); 2605 upperIndex = Math.min(centerPageIndex + 2, numChildren - 1); 2606 visibleStart = visibleEnd = 0; 2607 } 2608 2609 List<Integer> visibleTaskIds = new ArrayList<>(); 2610 // Update the task data for the in/visible children 2611 getTaskViews().forEachWithIndexInParent((index, taskView) -> { 2612 List<TaskContainer> containers = taskView.getTaskContainers(); 2613 if (containers.isEmpty()) { 2614 return; 2615 } 2616 boolean visible; 2617 if (showAsGrid()) { 2618 visible = isTaskViewWithinBounds(taskView, visibleStart, visibleEnd, 2619 mTaskViewsDismissPrimaryTranslations.getOrDefault(taskView, 0)); 2620 } else { 2621 visible = index >= lowerIndex && index <= upperIndex; 2622 } 2623 if (visible) { 2624 // Default update all non-null tasks, then remove running ones 2625 List<Task> tasksToUpdate = containers.stream() 2626 .map(TaskContainer::getTask) 2627 .collect(Collectors.toCollection(ArrayList::new)); 2628 if (enableRefactorTaskThumbnail()) { 2629 visibleTaskIds.addAll( 2630 tasksToUpdate.stream().map((task) -> task.key.id).toList()); 2631 } 2632 if (tasksToUpdate.isEmpty()) { 2633 return; 2634 } 2635 int visibilityChanges = 0; 2636 for (Task task : tasksToUpdate) { 2637 if (!mHasVisibleTaskData.get(task.key.id)) { 2638 // Ignore thumbnail update if it's current running task during the gesture 2639 // We snapshot at end of gesture, it will update then 2640 int changes = dataChanges; 2641 if (taskView == getRunningTaskView() && isGestureActive()) { 2642 changes &= ~TaskView.FLAG_UPDATE_THUMBNAIL; 2643 } 2644 visibilityChanges |= changes; 2645 } 2646 mHasVisibleTaskData.put(task.key.id, true); 2647 } 2648 if (visibilityChanges != 0) { 2649 taskView.onTaskListVisibilityChanged(true /* visible */, visibilityChanges); 2650 } 2651 } else { 2652 int visibilityChanges = 0; 2653 for (TaskContainer container : containers) { 2654 if (container == null) { 2655 continue; 2656 } 2657 2658 if (mHasVisibleTaskData.get(container.getTask().key.id)) { 2659 visibilityChanges = dataChanges; 2660 } 2661 mHasVisibleTaskData.delete(container.getTask().key.id); 2662 } 2663 if (visibilityChanges != 0) { 2664 taskView.onTaskListVisibilityChanged(false /* visible */, visibilityChanges); 2665 } 2666 } 2667 }); 2668 if (enableRefactorTaskThumbnail()) { 2669 mRecentsViewModel.updateVisibleTasks(visibleTaskIds); 2670 } 2671 } 2672 2673 /** 2674 * Unloads any associated data from the currently visible tasks 2675 */ unloadVisibleTaskData(@askView.TaskDataChanges int dataChanges)2676 private void unloadVisibleTaskData(@TaskView.TaskDataChanges int dataChanges) { 2677 for (int i = 0; i < mHasVisibleTaskData.size(); i++) { 2678 if (mHasVisibleTaskData.valueAt(i)) { 2679 TaskView taskView = getTaskViewByTaskId(mHasVisibleTaskData.keyAt(i)); 2680 if (taskView != null) { 2681 taskView.onTaskListVisibilityChanged(false /* visible */, dataChanges); 2682 } 2683 } 2684 } 2685 mHasVisibleTaskData.clear(); 2686 } 2687 2688 @Override onHighResLoadingStateChanged(boolean enabled)2689 public void onHighResLoadingStateChanged(boolean enabled) { 2690 // Preload cache when no overview task is visible (e.g. not in overview page), so when 2691 // user goes to overview next time, the task thumbnails would show up without delay 2692 if (mHasVisibleTaskData.size() == 0) { 2693 mModel.preloadCacheIfNeeded(); 2694 } 2695 2696 if (enableRefactorTaskThumbnail()) { 2697 return; 2698 } 2699 2700 // Whenever the high res loading state changes, poke each of the visible tasks to see if 2701 // they want to updated their thumbnail state 2702 for (int i = 0; i < mHasVisibleTaskData.size(); i++) { 2703 if (mHasVisibleTaskData.valueAt(i)) { 2704 TaskView taskView = getTaskViewByTaskId(mHasVisibleTaskData.keyAt(i)); 2705 if (taskView != null) { 2706 // Poke the view again, which will trigger it to load high res if the state 2707 // is enabled 2708 taskView.onTaskListVisibilityChanged(true /* visible */); 2709 } 2710 } 2711 } 2712 } 2713 startHome()2714 public void startHome() { 2715 startHome(mContainer.isStarted()); 2716 } 2717 startHome(boolean animated)2718 public void startHome(boolean animated) { 2719 if (!canStartHomeSafely()) return; 2720 handleStartHome(animated); 2721 } 2722 handleStartHome(boolean animated)2723 protected abstract void handleStartHome(boolean animated); 2724 2725 /** Returns whether user can start home based on state in {@link OverviewCommandHelper}. */ canStartHomeSafely()2726 protected abstract boolean canStartHomeSafely(); 2727 2728 /** Returns the state manager used in RecentsView **/ 2729 public abstract StateManager<STATE_TYPE, getStateManager()2730 ? extends StatefulContainer<STATE_TYPE>> getStateManager(); 2731 reset()2732 public void reset() { 2733 setCurrentTask(-1); 2734 mCurrentPageScrollDiff = 0; 2735 mIgnoreResetTaskId = -1; 2736 mTaskListChangeId = -1; 2737 setFocusedTaskViewId(INVALID_TASK_ID); 2738 mAnyTaskHasBeenDismissed = false; 2739 2740 if (enableRefactorTaskThumbnail()) { 2741 // TODO(b/353917593): RecentsView is never destroyed, so its dependencies need to 2742 // be cleaned up during the reset, but re-created when RecentsView is "resumed". 2743 // RecentsDependencies.Companion.destroy(); 2744 } 2745 2746 Log.d(TAG, "reset - mEnableDrawingLiveTile: " + mEnableDrawingLiveTile 2747 + ", mRecentsAnimationController: " + mRecentsAnimationController); 2748 if (mEnableDrawingLiveTile) { 2749 if (mRecentsAnimationController != null) { 2750 // We owns mRecentsAnimationController, finish it now to clean up. 2751 finishRecentsAnimation(true /* toRecents */, null); 2752 } else { 2753 // Only clean up target set if we no longer owns mRecentsAnimationController. 2754 runActionOnRemoteHandles(remoteTargetHandle -> 2755 remoteTargetHandle.getTransformParams().setTargetSet(null)); 2756 } 2757 setEnableDrawingLiveTile(false); 2758 } 2759 mBlurUtils.setDrawLiveTileBelowRecents(false); 2760 // These are relatively expensive and don't need to be done this frame (RecentsView isn't 2761 // visible anyway), so defer by a frame to get off the critical path, e.g. app to home. 2762 post(this::onReset); 2763 } 2764 onReset()2765 private void onReset() { 2766 unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); 2767 setCurrentPage(0); 2768 LayoutUtils.setViewEnabled(mActionsView, true); 2769 if (mOrientationState.setGestureActive(false)) { 2770 updateOrientationHandler(/* forceRecreateDragLayerControllers = */ false); 2771 } 2772 if (enableRefactorTaskThumbnail()) { 2773 mRecentsViewModel.onReset(); 2774 } 2775 } 2776 getRunningTaskViewId()2777 public int getRunningTaskViewId() { 2778 return mRunningTaskViewId; 2779 } 2780 getTaskIdsForRunningTaskView()2781 protected int[] getTaskIdsForRunningTaskView() { 2782 return getTaskIdsForTaskViewId(mRunningTaskViewId); 2783 } 2784 getTaskIdsForTaskViewId(int taskViewId)2785 private int[] getTaskIdsForTaskViewId(int taskViewId) { 2786 // For now 2 distinct task IDs is max for split screen 2787 TaskView runningTaskView = getTaskViewFromTaskViewId(taskViewId); 2788 if (runningTaskView == null) { 2789 return new int[0]; 2790 } 2791 2792 return runningTaskView.getTaskIds(); 2793 } 2794 getRunningTaskView()2795 public @Nullable TaskView getRunningTaskView() { 2796 return getTaskViewFromTaskViewId(mRunningTaskViewId); 2797 } 2798 getFocusedTaskView()2799 public @Nullable TaskView getFocusedTaskView() { 2800 return getTaskViewFromTaskViewId(mFocusedTaskViewId); 2801 } 2802 2803 @Nullable getTaskViewFromTaskViewId(int taskViewId)2804 TaskView getTaskViewFromTaskViewId(int taskViewId) { 2805 if (taskViewId == -1) { 2806 return null; 2807 } 2808 2809 for (TaskView taskView : getTaskViews()) { 2810 if (taskView.getTaskViewId() == taskViewId) { 2811 return taskView; 2812 } 2813 } 2814 return null; 2815 } 2816 getRunningTaskIndex()2817 public int getRunningTaskIndex() { 2818 TaskView taskView = getRunningTaskView(); 2819 return taskView == null ? -1 : indexOfChild(taskView); 2820 } 2821 getHomeTaskView()2822 protected @Nullable TaskView getHomeTaskView() { 2823 return null; 2824 } 2825 2826 /** 2827 * Handle the edge case where Recents could increment task count very high over long 2828 * period of device usage. Probably will never happen, but meh. 2829 */ getTaskViewFromPool(TaskViewType type)2830 protected TaskView getTaskViewFromPool(TaskViewType type) { 2831 TaskView taskView; 2832 switch (type) { 2833 case GROUPED: 2834 taskView = mGroupedTaskViewPool.getView(); 2835 break; 2836 case DESKTOP: 2837 taskView = mDesktopTaskViewPool.getView(); 2838 break; 2839 case SINGLE: 2840 default: 2841 taskView = mTaskViewPool.getView(); 2842 } 2843 taskView.setTaskViewId(mTaskViewIdCount); 2844 if (mTaskViewIdCount == Integer.MAX_VALUE) { 2845 mTaskViewIdCount = 0; 2846 } else { 2847 mTaskViewIdCount++; 2848 } 2849 2850 return taskView; 2851 } 2852 2853 /** 2854 * Get the index of the task view whose id matches {@param taskId}. 2855 * 2856 * @return -1 if there is no task view for the task id, else the index of the task view. 2857 */ getTaskIndexForId(int taskId)2858 public int getTaskIndexForId(int taskId) { 2859 TaskView tv = getTaskViewByTaskId(taskId); 2860 return tv == null ? -1 : indexOfChild(tv); 2861 } 2862 2863 /** 2864 * Reloads the view if anything in recents changed. 2865 */ reloadIfNeeded()2866 public void reloadIfNeeded() { 2867 if (!mModel.isTaskListValid(mTaskListChangeId)) { 2868 mTaskListChangeId = mModel.getTasks(this::applyLoadPlan, RecentsFilterState 2869 .getFilter(mFilterState.getPackageNameToFilter())); 2870 Log.d(TAG, "reloadIfNeeded - getTasks: " + mTaskListChangeId); 2871 if (enableRefactorTaskThumbnail()) { 2872 mRecentsViewModel.refreshAllTaskData(); 2873 } 2874 } else { 2875 Log.d(TAG, "reloadIfNeeded - task list still valid: " + mTaskListChangeId); 2876 } 2877 } 2878 2879 /** 2880 * Called when a gesture from an app is starting. 2881 */ 2882 // TODO: b/401582344 - Implement a way to exclude the `DesktopWallpaperActivity` from being 2883 // considered in Overview. onGestureAnimationStart(GroupedTaskInfo groupedTaskInfo)2884 public void onGestureAnimationStart(GroupedTaskInfo groupedTaskInfo) { 2885 Log.d(TAG, "onGestureAnimationStart - groupedTaskInfo: " + groupedTaskInfo); 2886 mActiveGestureGroupedTaskInfo = groupedTaskInfo; 2887 2888 // This needs to be called before the other states are set since it can create the task view 2889 if (mOrientationState.setGestureActive(true)) { 2890 reapplyActiveRotation(); 2891 // Force update to ensure the initial task size is computed even if the orientation has 2892 // not changed. 2893 updateSizeAndPadding(); 2894 } 2895 2896 showCurrentTask(groupedTaskInfo, "onGestureAnimationStart"); 2897 setEnableFreeScroll(false); 2898 setEnableDrawingLiveTile(false); 2899 setRunningTaskHidden(true); 2900 setTaskIconVisible(false); 2901 } 2902 2903 /** 2904 * Returns whether the running task's attach alpha should be updated during the attach animation 2905 */ shouldUpdateRunningTaskAlpha()2906 public boolean shouldUpdateRunningTaskAlpha() { 2907 return enableDesktopTaskAlphaAnimation() && getRunningTaskView() instanceof DesktopTaskView; 2908 } 2909 isGestureActive()2910 private boolean isGestureActive() { 2911 return mActiveGestureGroupedTaskInfo != null; 2912 } 2913 2914 /** 2915 * Called only when a swipe-up gesture from an app has completed. Only called after 2916 * {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}. 2917 */ onSwipeUpAnimationSuccess()2918 public void onSwipeUpAnimationSuccess() { 2919 startIconFadeInOnGestureComplete(); 2920 setSwipeDownShouldLaunchApp(true); 2921 } 2922 animateRecentsRotationInPlace(int newRotation)2923 private void animateRecentsRotationInPlace(int newRotation) { 2924 if (mOrientationState.isRecentsActivityRotationAllowed()) { 2925 // Let system take care of the rotation 2926 return; 2927 } 2928 2929 if (mRunningTaskShowScreenshot) { 2930 animateRotation(newRotation); 2931 } else { 2932 // Animate the rotation and stops running task 2933 switchToScreenshot(() -> { 2934 animateRotation(newRotation); 2935 finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, 2936 null /* onFinishComplete */); 2937 }); 2938 } 2939 } 2940 animateRotation(int newRotation)2941 private void animateRotation(int newRotation) { 2942 AbstractFloatingView.closeAllOpenViewsExcept(mContainer, false, TYPE_REBIND_SAFE); 2943 AnimatorSet pa = setRecentsChangedOrientation(true); 2944 pa.addListener(AnimatorListeners.forSuccessCallback(() -> { 2945 setLayoutRotation(newRotation, mOrientationState.getDisplayRotation()); 2946 mContainer.getDragLayer().recreateControllers(); 2947 setRecentsChangedOrientation(false).start(); 2948 })); 2949 pa.start(); 2950 } 2951 setRecentsChangedOrientation(boolean fadeOut)2952 public AnimatorSet setRecentsChangedOrientation(boolean fadeOut) { 2953 AnimatorSet as = new AnimatorSet(); 2954 as.play(ObjectAnimator.ofFloat(this, View.ALPHA, fadeOut ? 0 : 1)); 2955 return as; 2956 } 2957 2958 /** 2959 * Called when a gesture from an app has finished, and an end target has been determined. 2960 */ onPrepareGestureEndAnimation( @ullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget, RemoteTargetHandle[] remoteTargetHandles)2961 public void onPrepareGestureEndAnimation( 2962 @Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget, 2963 RemoteTargetHandle[] remoteTargetHandles) { 2964 Log.d(TAG, "onPrepareGestureEndAnimation - endTarget: " + endTarget); 2965 mCurrentGestureEndTarget = endTarget; 2966 boolean isOverviewEndTarget = endTarget == GestureState.GestureEndTarget.RECENTS; 2967 if (isOverviewEndTarget) { 2968 updateGridProperties(); 2969 } 2970 2971 BaseState<?> endState = mSizeStrategy.stateFromGestureEndTarget(endTarget); 2972 // Starting the desk exploded animation when the gesture from an app is released. 2973 if (enableDesktopExplodedView()) { 2974 if (animatorSet == null) { 2975 mUtils.setDeskExplodeProgress(endState.showExplodedDesktopView() ? 1f : 0f); 2976 } else { 2977 animatorSet.play( 2978 ObjectAnimator.ofFloat(this, DESK_EXPLODE_PROGRESS, 2979 endState.showExplodedDesktopView() ? 1f : 0f)); 2980 } 2981 2982 for (TaskView taskView : getTaskViews()) { 2983 if (taskView instanceof DesktopTaskView desktopTaskView) { 2984 desktopTaskView.setRemoteTargetHandles(remoteTargetHandles); 2985 } 2986 } 2987 } 2988 2989 if (endState.displayOverviewTasksAsGrid(mContainer.getDeviceProfile())) { 2990 TaskView runningTaskView = getRunningTaskView(); 2991 float runningTaskGridTranslationX = 0; 2992 float runningTaskGridTranslationY = 0; 2993 if (runningTaskView != null) { 2994 // Apply the grid translation to running task unless it's being snapped to 2995 // and removes the current translation applied to the running task. 2996 runningTaskGridTranslationX = runningTaskView.getGridTranslationX() 2997 - runningTaskView.getNonGridTranslationX(); 2998 runningTaskGridTranslationY = runningTaskView.getGridTranslationY(); 2999 } 3000 for (RemoteTargetHandle remoteTargetHandle : remoteTargetHandles) { 3001 TaskViewSimulator tvs = remoteTargetHandle.getTaskViewSimulator(); 3002 if (animatorSet == null) { 3003 setGridProgress(1); 3004 if (enableGridOnlyOverview()) { 3005 tvs.taskGridTranslationX.value = runningTaskGridTranslationX; 3006 tvs.taskGridTranslationY.value = runningTaskGridTranslationY; 3007 } else { 3008 tvs.taskPrimaryTranslation.value = runningTaskGridTranslationX; 3009 tvs.taskSecondaryTranslation.value = runningTaskGridTranslationY; 3010 } 3011 } else { 3012 animatorSet.play(ObjectAnimator.ofFloat(this, RECENTS_GRID_PROGRESS, 1)); 3013 if (enableGridOnlyOverview()) { 3014 animatorSet.play(tvs.carouselScale.animateToValue(1)); 3015 animatorSet.play(tvs.taskGridTranslationX.animateToValue( 3016 runningTaskGridTranslationX)); 3017 animatorSet.play(tvs.taskGridTranslationY.animateToValue( 3018 runningTaskGridTranslationY)); 3019 } else { 3020 animatorSet.play(tvs.taskPrimaryTranslation.animateToValue( 3021 runningTaskGridTranslationX)); 3022 animatorSet.play(tvs.taskSecondaryTranslation.animateToValue( 3023 runningTaskGridTranslationY)); 3024 } 3025 } 3026 } 3027 } 3028 int splashAlpha = endState.showTaskThumbnailSplash() ? 1 : 0; 3029 if (animatorSet == null) { 3030 setTaskThumbnailSplashAlpha(splashAlpha); 3031 } else { 3032 animatorSet.play( 3033 ObjectAnimator.ofFloat(this, TASK_THUMBNAIL_SPLASH_ALPHA, splashAlpha)); 3034 } 3035 if (enableLargeDesktopWindowingTile()) { 3036 if (animatorSet != null) { 3037 animatorSet.play( 3038 ObjectAnimator.ofFloat(this, DESKTOP_CAROUSEL_DETACH_PROGRESS, 0f)); 3039 } else { 3040 DESKTOP_CAROUSEL_DETACH_PROGRESS.set(this, 0f); 3041 } 3042 } 3043 } 3044 3045 /** 3046 * Called when a gesture from an app has finished, and the animation to the target has ended. 3047 */ onGestureAnimationEnd()3048 public void onGestureAnimationEnd() { 3049 mActiveGestureGroupedTaskInfo = null; 3050 if (mOrientationState.setGestureActive(false)) { 3051 updateOrientationHandler(/* forceRecreateDragLayerControllers = */ false); 3052 } 3053 3054 setEnableFreeScroll(true); 3055 setEnableDrawingLiveTile(mCurrentGestureEndTarget == GestureState.GestureEndTarget.RECENTS); 3056 Log.d(TAG, "onGestureAnimationEnd - mEnableDrawingLiveTile: " + mEnableDrawingLiveTile); 3057 setRunningTaskHidden(false); 3058 startIconFadeInOnGestureComplete(); 3059 animateActionsViewIn(); 3060 3061 if (mEnableDrawingLiveTile) { 3062 if (enableDesktopExplodedView()) { 3063 for (TaskView taskView : getTaskViews()) { 3064 if (taskView instanceof DesktopTaskView desktopTaskView) { 3065 desktopTaskView.setRemoteTargetHandles(mRemoteTargetHandles); 3066 } 3067 } 3068 } 3069 TaskView runningTaskView = getRunningTaskView(); 3070 if (showAsGrid() && enableGridOnlyOverview() && runningTaskView != null) { 3071 runActionOnRemoteHandles(remoteTargetHandle -> { 3072 TaskViewSimulator taskViewSimulator = remoteTargetHandle.getTaskViewSimulator(); 3073 // After settling in Overview, recentsScroll will be used to adjust horizontally 3074 // location and taskGridTranslationX doesn't needs to be applied. 3075 taskViewSimulator.taskGridTranslationX.value = 0; 3076 taskViewSimulator.taskGridTranslationY.value = 3077 runningTaskView.getGridTranslationY(); 3078 }); 3079 } 3080 } 3081 3082 mCurrentGestureEndTarget = null; 3083 } 3084 3085 /** 3086 * Returns true if we should add a stub taskView for the running task id 3087 */ shouldAddStubTaskView(GroupedTaskInfo groupedTaskInfo)3088 protected boolean shouldAddStubTaskView(GroupedTaskInfo groupedTaskInfo) { 3089 int[] runningTaskIds; 3090 if (groupedTaskInfo != null) { 3091 runningTaskIds = groupedTaskInfo.getTaskInfoList().stream().mapToInt( 3092 taskInfo -> taskInfo.taskId).toArray(); 3093 } else { 3094 runningTaskIds = new int[0]; 3095 } 3096 TaskView matchingTaskView = null; 3097 if (groupedTaskInfo != null && groupedTaskInfo.isBaseType(GroupedTaskInfo.TYPE_DESK) 3098 && runningTaskIds.length == 1) { 3099 // TODO(b/342635213): Unsure if it's expected, desktop runningTasks only have a single 3100 // taskId, therefore we match any DesktopTaskView that contains the runningTaskId. 3101 TaskView taskview = getTaskViewByTaskId(runningTaskIds[0]); 3102 if (taskview instanceof DesktopTaskView) { 3103 matchingTaskView = taskview; 3104 } 3105 } else { 3106 matchingTaskView = getTaskViewByTaskIds(runningTaskIds); 3107 } 3108 return matchingTaskView == null; 3109 } 3110 3111 /** 3112 * Creates a task view (if necessary) to represent the tasks with the {@param groupedTaskInfo}. 3113 * 3114 * All subsequent calls to reload will keep the task as the first item until {@link #reset()} 3115 * is called. Also scrolls the view to this task. 3116 */ showCurrentTask(GroupedTaskInfo groupedTaskInfo, String caller)3117 private void showCurrentTask(GroupedTaskInfo groupedTaskInfo, String caller) { 3118 Log.d(TAG, "showCurrentTask(" + caller + ") - groupedTaskInfo: " + groupedTaskInfo); 3119 if (groupedTaskInfo == null) { 3120 return; 3121 } 3122 3123 int runningTaskViewId = -1; 3124 if (shouldAddStubTaskView(groupedTaskInfo)) { 3125 boolean wasEmpty = getChildCount() == 0; 3126 // Add an empty view for now until the task plan is loaded and applied 3127 final TaskView taskView; 3128 if (groupedTaskInfo.isBaseType(GroupedTaskInfo.TYPE_DESK)) { 3129 taskView = mUtils.createDesktopTaskViewForActiveDesk(groupedTaskInfo); 3130 } else if (groupedTaskInfo.isBaseType(GroupedTaskInfo.TYPE_SPLIT)) { 3131 taskView = getTaskViewFromPool(TaskViewType.GROUPED); 3132 // When we create a placeholder task view mSplitBoundsConfig will be null, but with 3133 // the actual app running we won't need to show the thumbnail until all the tasks 3134 // load later anyways 3135 ((GroupedTaskView) taskView).bind(Task.from(groupedTaskInfo.getTaskInfo1()), 3136 Task.from(groupedTaskInfo.getTaskInfo2()), mOrientationState, 3137 mTaskOverlayFactory, mSplitBoundsConfig); 3138 } else { 3139 taskView = getTaskViewFromPool(TaskViewType.SINGLE); 3140 taskView.bind(Task.from(groupedTaskInfo.getTaskInfo1()), mOrientationState, 3141 mTaskOverlayFactory); 3142 } 3143 if (mAddDesktopButton != null && wasEmpty) { 3144 addView(mAddDesktopButton); 3145 } 3146 addView(taskView, mUtils.getRunningTaskExpectedIndex(taskView)); 3147 runningTaskViewId = taskView.getTaskViewId(); 3148 if (wasEmpty) { 3149 addView(mClearAllButton); 3150 } 3151 3152 // Measure and layout immediately so that the scroll values is updated instantly 3153 // as the user might be quick-switching 3154 measure(makeMeasureSpec(getMeasuredWidth(), EXACTLY), 3155 makeMeasureSpec(getMeasuredHeight(), EXACTLY)); 3156 layout(getLeft(), getTop(), getRight(), getBottom()); 3157 } else { 3158 var runningTaskView = getTaskViewByTaskId(groupedTaskInfo.getTaskInfo1().taskId); 3159 if (runningTaskView != null) { 3160 runningTaskViewId = runningTaskView.getTaskViewId(); 3161 } 3162 } 3163 3164 boolean runningTaskTileHidden = mRunningTaskTileHidden; 3165 setCurrentTask(runningTaskViewId); 3166 3167 int focusedTaskViewId; 3168 if (enableGridOnlyOverview()) { 3169 focusedTaskViewId = INVALID_TASK_ID; 3170 } else if (enableLargeDesktopWindowingTile() 3171 && getRunningTaskView() instanceof DesktopTaskView) { 3172 TaskView focusedTaskView = mUtils.getFirstNonDesktopTaskView(); 3173 focusedTaskViewId = 3174 focusedTaskView != null ? focusedTaskView.getTaskViewId() : INVALID_TASK_ID; 3175 } else { 3176 focusedTaskViewId = runningTaskViewId; 3177 } 3178 setFocusedTaskViewId(focusedTaskViewId); 3179 3180 runOnPageScrollsInitialized(() -> setCurrentPage(getRunningTaskIndex())); 3181 setRunningTaskViewShowScreenshot(false); 3182 setRunningTaskHidden(runningTaskTileHidden); 3183 // Update task size after setting current task. 3184 updateTaskSize(); 3185 mUtils.updateChildTaskOrientations(); 3186 3187 // Reload the task list 3188 reloadIfNeeded(); 3189 } 3190 3191 /** 3192 * Sets the running task id, cleaning up the old running task if necessary. 3193 */ setCurrentTask(int runningTaskViewId)3194 public void setCurrentTask(int runningTaskViewId) { 3195 if (mRunningTaskViewId == runningTaskViewId) { 3196 return; 3197 } 3198 3199 if (mRunningTaskViewId != -1) { 3200 // Reset the state on the old running task view 3201 setTaskIconVisible(true); 3202 setRunningTaskViewShowScreenshot(true); 3203 setRunningTaskHidden(false); 3204 } 3205 setRunningTaskViewId(runningTaskViewId); 3206 } 3207 setRunningTaskViewId(int runningTaskViewId)3208 private void setRunningTaskViewId(int runningTaskViewId) { 3209 mRunningTaskViewId = runningTaskViewId; 3210 3211 if (enableRefactorTaskThumbnail()) { 3212 TaskView runningTaskView = getTaskViewFromTaskViewId(runningTaskViewId); 3213 mRecentsViewModel.updateRunningTask( 3214 runningTaskView != null ? runningTaskView.getTaskIdSet() 3215 : Collections.emptySet()); 3216 } 3217 } 3218 setFocusedTaskViewId(int viewId)3219 private void setFocusedTaskViewId(int viewId) { 3220 mFocusedTaskViewId = viewId; 3221 } 3222 getTaskViewIdFromTaskId(int taskId)3223 private int getTaskViewIdFromTaskId(int taskId) { 3224 TaskView taskView = getTaskViewByTaskId(taskId); 3225 return taskView != null ? taskView.getTaskViewId() : -1; 3226 } 3227 3228 /** 3229 * Hides the tile associated with {@link #mRunningTaskViewId} 3230 */ setRunningTaskHidden(boolean isHidden)3231 public void setRunningTaskHidden(boolean isHidden) { 3232 mRunningTaskTileHidden = isHidden; 3233 // mRunningTaskAttachAlpha can be changed by RUNNING_TASK_ATTACH_ALPHA animation without 3234 // changing mRunningTaskTileHidden. 3235 mRunningTaskAttachAlpha = isHidden ? 0f : 1f; 3236 TaskView runningTask = getRunningTaskView(); 3237 if (runningTask == null) { 3238 return; 3239 } 3240 applyAttachAlpha(); 3241 if (!isHidden) { 3242 AccessibilityManagerCompat.sendCustomAccessibilityEvent( 3243 runningTask, AccessibilityEvent.TYPE_VIEW_FOCUSED, null); 3244 } 3245 } 3246 applyAttachAlpha()3247 private void applyAttachAlpha() { 3248 // Only hide non running task carousel when it's fully off screen, otherwise it needs to 3249 // be visible to move to on screen. 3250 mUtils.applyAttachAlpha( 3251 /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress == 1f); 3252 } 3253 setRunningTaskViewShowScreenshot(boolean showScreenshot)3254 private void setRunningTaskViewShowScreenshot(boolean showScreenshot) { 3255 setRunningTaskViewShowScreenshot(showScreenshot, /*updatedThumbnails=*/null); 3256 } 3257 setRunningTaskViewShowScreenshot(boolean showScreenshot, @Nullable Map<Integer, ThumbnailData> updatedThumbnails)3258 private void setRunningTaskViewShowScreenshot(boolean showScreenshot, 3259 @Nullable Map<Integer, ThumbnailData> updatedThumbnails) { 3260 mRunningTaskShowScreenshot = showScreenshot; 3261 TaskView runningTaskView = getRunningTaskView(); 3262 if (runningTaskView != null) { 3263 runningTaskView.setShouldShowScreenshot(mRunningTaskShowScreenshot, updatedThumbnails); 3264 } 3265 if (enableRefactorTaskThumbnail()) { 3266 mRecentsViewModel.setRunningTaskShowScreenshot(showScreenshot); 3267 } 3268 } 3269 3270 /** 3271 * Updates icon visibility when going in or out of overview. 3272 */ setTaskIconVisible(boolean isVisible)3273 public void setTaskIconVisible(boolean isVisible) { 3274 if (mTaskIconVisible != isVisible) { 3275 mTaskIconVisible = isVisible; 3276 for (TaskView taskView : getTaskViews()) { 3277 taskView.setIconVisibleForGesture(mTaskIconVisible); 3278 } 3279 } 3280 } 3281 animateActionsViewIn()3282 private void animateActionsViewIn() { 3283 if (!showAsGrid() || isFocusedTaskInExpectedScrollPosition()) { 3284 animateActionsViewAlpha(1, TaskView.FADE_IN_ICON_DURATION); 3285 } 3286 } 3287 3288 /** 3289 * Updates icon visibility when gesture is settled. 3290 */ startIconFadeInOnGestureComplete()3291 public void startIconFadeInOnGestureComplete() { 3292 mTaskIconVisible = true; 3293 for (TaskView taskView : getTaskViews()) { 3294 taskView.startIconFadeInOnGestureComplete(); 3295 } 3296 } 3297 3298 /** 3299 * Updates TaskView and ClearAllButtion scaling and translation required to turn into grid 3300 * layout. 3301 * 3302 * Skips rebalance. 3303 */ updateGridProperties()3304 private void updateGridProperties() { 3305 updateGridProperties(null); 3306 } 3307 3308 /** 3309 * Updates TaskView and ClearAllButton scaling and translation required to turn into grid 3310 * layout. 3311 * 3312 * This method only calculates the potential position and depends on {@link #setGridProgress} to 3313 * apply the actual scaling and translation. 3314 * 3315 * @param lastVisibleTaskViewDuringDismiss which TaskView to start rebalancing from. Use 3316 * `null` to skip rebalance. 3317 */ updateGridProperties(TaskView lastVisibleTaskViewDuringDismiss)3318 private void updateGridProperties(TaskView lastVisibleTaskViewDuringDismiss) { 3319 if (!hasTaskViews()) { 3320 return; 3321 } 3322 3323 DeviceProfile deviceProfile = mContainer.getDeviceProfile(); 3324 int taskTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx; 3325 3326 int topRowWidth = 0; 3327 int bottomRowWidth = 0; 3328 int largeTileRowWidth = 0; 3329 float topAccumulatedTranslationX = 0; 3330 float bottomAccumulatedTranslationX = 0; 3331 3332 // Horizontal grid translation for each task. 3333 Map<TaskView, Float> gridTranslations = new HashMap<>(); 3334 3335 TaskView lastLargeTaskView = mUtils.getLastLargeTaskView(); 3336 int focusedTaskViewShift = 0; 3337 int largeTaskWidthAndSpacing = 0; 3338 int snappedTaskRowWidth = 0; 3339 int expectedCurrentTaskRowWidth = 0; 3340 int snappedPage = isKeyboardTaskFocusPending() ? mKeyboardTaskFocusIndex : getNextPage(); 3341 TaskView snappedTaskView = getTaskViewAt(snappedPage); 3342 TaskView homeTaskView = getHomeTaskView(); 3343 TaskView expectedCurrentTaskView = mUtils.getExpectedCurrentTask(getRunningTaskView(), 3344 getFocusedTaskView()); 3345 TaskView nextFocusedTaskView = null; 3346 3347 // Don't clear the top row, if the user has dismissed a task, to maintain the task order. 3348 if (!mAnyTaskHasBeenDismissed) { 3349 mTopRowIdSet.clear(); 3350 } 3351 3352 // Consecutive task views in the top row or bottom row, which means another one set will 3353 // be cleared up while starting to add TaskViews to one of them. Also means only one of 3354 // them can be non-empty at most. 3355 Set<TaskView> lastTopTaskViews = new HashSet<>(); 3356 Set<TaskView> lastBottomTaskViews = new HashSet<>(); 3357 3358 int largeTasksCount = 0; 3359 // True if the last large TaskView has been visited during the TaskViews iteration. 3360 boolean encounteredLastLargeTaskView = false; 3361 // True if the highest index visible TaskView has been visited during the TaskViews 3362 // iteration. 3363 boolean encounteredLastVisibleTaskView = false; 3364 for (TaskView taskView : getTaskViews()) { 3365 if (taskView == lastLargeTaskView) { 3366 encounteredLastLargeTaskView = true; 3367 } 3368 if (taskView == lastVisibleTaskViewDuringDismiss) { 3369 encounteredLastVisibleTaskView = true; 3370 } 3371 float gridTranslation = 0f; 3372 int taskWidthAndSpacing = taskView.getLayoutParams().width + mPageSpacing; 3373 // Evenly distribute tasks between rows unless rearranging due to task dismissal, in 3374 // which case keep tasks in their respective rows. For the running task, don't join 3375 // the grid. 3376 if (taskView.isLargeTile()) { 3377 largeTasksCount++; 3378 // DesktopTaskView`s are hidden during split select state, so we shouldn't count 3379 // them when calculating row width. 3380 if (!(taskView instanceof DesktopTaskView && isSplitSelectionActive())) { 3381 topRowWidth += taskWidthAndSpacing; 3382 bottomRowWidth += taskWidthAndSpacing; 3383 largeTileRowWidth += taskWidthAndSpacing; 3384 } 3385 gridTranslation += focusedTaskViewShift; 3386 gridTranslation += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing; 3387 3388 // Center view vertically in case it's from different orientation. 3389 taskView.setGridTranslationY((mLastComputedTaskSize.height() + taskTopMargin 3390 - taskView.getLayoutParams().height) / 2f); 3391 3392 largeTaskWidthAndSpacing = taskWidthAndSpacing; 3393 3394 if (taskView == snappedTaskView) { 3395 snappedTaskRowWidth = largeTileRowWidth; 3396 } 3397 if (taskView == expectedCurrentTaskView) { 3398 expectedCurrentTaskRowWidth = largeTileRowWidth; 3399 } 3400 } else { 3401 if (encounteredLastLargeTaskView) { 3402 // For tasks after the last large task, shift by large task's width and spacing. 3403 gridTranslation += 3404 mIsRtl ? largeTaskWidthAndSpacing : -largeTaskWidthAndSpacing; 3405 } else { 3406 // For TaskViews before the new focused TaskView, accumulate the width and 3407 // spacing to calculate the distance the new focused TaskView needs to shift. 3408 // This could happen for example after multiple times of dismissing the 3409 // focused TaskView, the triggered rebalance might set a non-first TaskView 3410 // inside `mChildren` as the new focused TaskView. 3411 focusedTaskViewShift += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing; 3412 } 3413 int taskViewId = taskView.getTaskViewId(); 3414 3415 boolean isTopRow; 3416 if (mAnyTaskHasBeenDismissed) { 3417 // Rebalance the grid starting after a certain index. 3418 if (encounteredLastVisibleTaskView) { 3419 mTopRowIdSet.remove(taskViewId); 3420 isTopRow = topRowWidth <= bottomRowWidth; 3421 } else { 3422 isTopRow = mTopRowIdSet.contains(taskViewId); 3423 } 3424 } else { 3425 isTopRow = topRowWidth <= bottomRowWidth; 3426 } 3427 3428 if (isTopRow) { 3429 if (homeTaskView != null && nextFocusedTaskView == null) { 3430 // TaskView will be focused when swipe up, don't count towards row width. 3431 nextFocusedTaskView = taskView; 3432 } else { 3433 topRowWidth += taskWidthAndSpacing; 3434 } 3435 mTopRowIdSet.add(taskViewId); 3436 taskView.setGridTranslationY(mTaskGridVerticalDiff); 3437 3438 // Move horizontally into empty space. 3439 float widthOffset = 0; 3440 for (TaskView bottomTaskView : lastBottomTaskViews) { 3441 widthOffset += bottomTaskView.getLayoutParams().width + mPageSpacing; 3442 } 3443 3444 float currentTaskTranslationX = mIsRtl ? widthOffset : -widthOffset; 3445 gridTranslation += topAccumulatedTranslationX + currentTaskTranslationX; 3446 topAccumulatedTranslationX += currentTaskTranslationX; 3447 lastTopTaskViews.add(taskView); 3448 lastBottomTaskViews.clear(); 3449 } else { 3450 bottomRowWidth += taskWidthAndSpacing; 3451 3452 // Move into bottom row. 3453 taskView.setGridTranslationY(mTopBottomRowHeightDiff + mTaskGridVerticalDiff); 3454 3455 // Move horizontally into empty space. 3456 float widthOffset = 0; 3457 for (TaskView topTaskView : lastTopTaskViews) { 3458 widthOffset += topTaskView.getLayoutParams().width + mPageSpacing; 3459 } 3460 3461 float currentTaskTranslationX = mIsRtl ? widthOffset : -widthOffset; 3462 gridTranslation += bottomAccumulatedTranslationX + currentTaskTranslationX; 3463 bottomAccumulatedTranslationX += currentTaskTranslationX; 3464 lastBottomTaskViews.add(taskView); 3465 lastTopTaskViews.clear(); 3466 } 3467 int taskViewRowWidth = isTopRow ? topRowWidth : bottomRowWidth; 3468 if (taskView == snappedTaskView) { 3469 snappedTaskRowWidth = taskViewRowWidth; 3470 } 3471 if (taskView == expectedCurrentTaskView) { 3472 expectedCurrentTaskRowWidth = taskViewRowWidth; 3473 } 3474 } 3475 gridTranslations.put(taskView, gridTranslation); 3476 } 3477 3478 // We need to maintain snapped task's page scroll invariant between quick switch and 3479 // overview, so we sure snapped task's grid translation is 0, and add a non-fullscreen 3480 // translationX that is the same as snapped task's full scroll adjustment. 3481 float snappedTaskNonGridScrollAdjustment = 0; 3482 float snappedTaskGridTranslationX = 0; 3483 if (snappedTaskView != null) { 3484 snappedTaskNonGridScrollAdjustment = snappedTaskView.getScrollAdjustment( 3485 /*gridEnabled=*/false); 3486 snappedTaskGridTranslationX = gridTranslations.getOrDefault(snappedTaskView, 0f); 3487 } 3488 3489 // Use the accumulated translation of the row containing the last task. 3490 float clearAllAccumulatedTranslation = !lastTopTaskViews.isEmpty() 3491 ? topAccumulatedTranslationX : bottomAccumulatedTranslationX; 3492 3493 // If the last task is on the shorter row, ClearAllButton will embed into the shorter row 3494 // which is not what we want. Compensate the width difference of the 2 rows in that case. 3495 float shorterRowCompensation = 0; 3496 if (topRowWidth <= bottomRowWidth) { 3497 if (!lastTopTaskViews.isEmpty()) { 3498 shorterRowCompensation = bottomRowWidth - topRowWidth; 3499 } 3500 } else { 3501 if (!lastBottomTaskViews.isEmpty()) { 3502 shorterRowCompensation = topRowWidth - bottomRowWidth; 3503 } 3504 } 3505 float clearAllShorterRowCompensation = 3506 mIsRtl ? -shorterRowCompensation : shorterRowCompensation; 3507 3508 // If the total width is shorter than one grid's width, move ClearAllButton further away 3509 // accordingly. Update longRowWidth if ClearAllButton has been moved. 3510 float clearAllShortTotalWidthTranslation = 0; 3511 int longRowWidth = Math.max(topRowWidth, bottomRowWidth); 3512 3513 // If first task is not in the expected position (mLastComputedTaskSize) and being too close 3514 // to ClearAllButton, then apply extra translation to ClearAllButton. 3515 int rowWidthAfterExpectedCurrentTask = longRowWidth - expectedCurrentTaskRowWidth; 3516 int expectedCurrentTaskWidthAndSpacing = 3517 (expectedCurrentTaskView != null 3518 ? expectedCurrentTaskView.getLayoutParams().width 3519 : 0 3520 ) + mPageSpacing; 3521 int firstTaskStart = mLastComputedGridSize.left + rowWidthAfterExpectedCurrentTask 3522 + expectedCurrentTaskWidthAndSpacing; 3523 int expectedFirstTaskStart = mLastComputedTaskSize.right; 3524 if (firstTaskStart < expectedFirstTaskStart) { 3525 mClearAllShortTotalWidthTranslation = expectedFirstTaskStart - firstTaskStart; 3526 clearAllShortTotalWidthTranslation = mIsRtl 3527 ? -mClearAllShortTotalWidthTranslation : mClearAllShortTotalWidthTranslation; 3528 if (snappedTaskRowWidth == longRowWidth) { 3529 // Updated snappedTaskRowWidth as well if it's same as longRowWidth. 3530 snappedTaskRowWidth += mClearAllShortTotalWidthTranslation; 3531 } 3532 longRowWidth += mClearAllShortTotalWidthTranslation; 3533 } else { 3534 mClearAllShortTotalWidthTranslation = 0; 3535 } 3536 3537 float clearAllTotalTranslationX = 3538 clearAllAccumulatedTranslation + clearAllShorterRowCompensation 3539 + clearAllShortTotalWidthTranslation + snappedTaskNonGridScrollAdjustment; 3540 if (largeTasksCount > 0) { 3541 // Shift by focused task's width and spacing if a task is focused. 3542 clearAllTotalTranslationX += 3543 mIsRtl ? largeTaskWidthAndSpacing : -largeTaskWidthAndSpacing; 3544 } 3545 3546 // Make sure there are enough space between snapped page and ClearAllButton, for the case 3547 // of swiping up after quick switch. 3548 if (snappedTaskView != null) { 3549 int distanceFromClearAll = longRowWidth - snappedTaskRowWidth; 3550 // ClearAllButton should be off screen when snapped task is in its snapped position. 3551 int minimumDistance = 3552 (mIsRtl 3553 ? mLastComputedTaskSize.left 3554 : deviceProfile.widthPx - mLastComputedTaskSize.right) 3555 - deviceProfile.overviewGridSideMargin - mPageSpacing 3556 + (mTaskWidth - snappedTaskView.getLayoutParams().width) 3557 - mClearAllShortTotalWidthTranslation; 3558 if (distanceFromClearAll < minimumDistance) { 3559 int distanceDifference = minimumDistance - distanceFromClearAll; 3560 snappedTaskGridTranslationX += mIsRtl ? distanceDifference : -distanceDifference; 3561 } 3562 } 3563 3564 for (TaskView taskView : getTaskViews()) { 3565 taskView.setGridTranslationX( 3566 gridTranslations.getOrDefault(taskView, 0f) - snappedTaskGridTranslationX 3567 + snappedTaskNonGridScrollAdjustment); 3568 } 3569 3570 if (mAddDesktopButton != null) { 3571 TaskView firstTaskView = getFirstTaskView(); 3572 float translationX = 0f; 3573 if (firstTaskView != null) { 3574 translationX += firstTaskView.getGridTranslationX(); 3575 } 3576 if (focusedTaskViewShift != 0) { 3577 // If the focused task is inserted between `firstTaskView` and 3578 // `mAddDesktopButton`, shift `mAddDesktopButton` to accommodate. 3579 translationX += largeTaskWidthAndSpacing; 3580 } 3581 mAddDesktopButton.setGridTranslationX(translationX); 3582 } 3583 3584 mClearAllButton.setGridTranslationPrimary( 3585 clearAllTotalTranslationX - snappedTaskGridTranslationX); 3586 mClearAllButton.setGridScrollOffset( 3587 mIsRtl ? mLastComputedTaskSize.left - mLastComputedGridSize.left 3588 : mLastComputedTaskSize.right - mLastComputedGridSize.right); 3589 setGridProgress(mGridProgress); 3590 } 3591 isSameGridRow(TaskView taskView1, TaskView taskView2)3592 protected boolean isSameGridRow(TaskView taskView1, TaskView taskView2) { 3593 if (taskView1 == null || taskView2 == null) { 3594 return false; 3595 } 3596 if (taskView1.isLargeTile() || taskView2.isLargeTile()) { 3597 return false; 3598 } 3599 int taskViewId1 = taskView1.getTaskViewId(); 3600 int taskViewId2 = taskView2.getTaskViewId(); 3601 return (mTopRowIdSet.contains(taskViewId1) && mTopRowIdSet.contains(taskViewId2)) || ( 3602 !mTopRowIdSet.contains(taskViewId1) && !mTopRowIdSet.contains(taskViewId2)); 3603 } 3604 3605 /** 3606 * Moves TaskView and ClearAllButton between carousel and 2 row grid. 3607 * 3608 * @param gridProgress 0 = carousel; 1 = 2 row grid. 3609 */ setGridProgress(float gridProgress)3610 private void setGridProgress(float gridProgress) { 3611 mGridProgress = gridProgress; 3612 3613 for (TaskView taskView : getTaskViews()) { 3614 taskView.setGridProgress(gridProgress); 3615 } 3616 mClearAllButton.setGridProgress(gridProgress); 3617 } 3618 setTaskThumbnailSplashAlpha(float taskThumbnailSplashAlpha)3619 private void setTaskThumbnailSplashAlpha(float taskThumbnailSplashAlpha) { 3620 mTaskThumbnailSplashAlpha = taskThumbnailSplashAlpha; 3621 for (TaskView taskView : getTaskViews()) { 3622 taskView.setTaskThumbnailSplashAlpha(taskThumbnailSplashAlpha); 3623 } 3624 } 3625 enableLayoutTransitions()3626 private void enableLayoutTransitions() { 3627 if (mLayoutTransition == null) { 3628 mLayoutTransition = new LayoutTransition(); 3629 mLayoutTransition.enableTransitionType(LayoutTransition.APPEARING); 3630 mLayoutTransition.setDuration(ADDITION_TASK_DURATION); 3631 mLayoutTransition.setStartDelay(LayoutTransition.APPEARING, 0); 3632 3633 mLayoutTransition.addTransitionListener(new TransitionListener() { 3634 @Override 3635 public void startTransition(LayoutTransition transition, ViewGroup viewGroup, 3636 View view, int i) { 3637 } 3638 3639 @Override 3640 public void endTransition(LayoutTransition transition, ViewGroup viewGroup, 3641 View view, int i) { 3642 // When the unpinned task is added, snap to first page and disable transitions 3643 if (view instanceof TaskView) { 3644 snapToPage(0); 3645 setLayoutTransition(null); 3646 } 3647 3648 } 3649 }); 3650 } 3651 setLayoutTransition(mLayoutTransition); 3652 } 3653 setSwipeDownShouldLaunchApp(boolean swipeDownShouldLaunchApp)3654 public void setSwipeDownShouldLaunchApp(boolean swipeDownShouldLaunchApp) { 3655 mSwipeDownShouldLaunchApp = swipeDownShouldLaunchApp; 3656 } 3657 shouldSwipeDownLaunchApp()3658 public boolean shouldSwipeDownLaunchApp() { 3659 return mSwipeDownShouldLaunchApp; 3660 } 3661 setIgnoreResetTask(int taskId)3662 public void setIgnoreResetTask(int taskId) { 3663 mIgnoreResetTaskId = taskId; 3664 } 3665 clearIgnoreResetTask(int taskId)3666 public void clearIgnoreResetTask(int taskId) { 3667 if (mIgnoreResetTaskId == taskId) { 3668 mIgnoreResetTaskId = -1; 3669 } 3670 } 3671 addDismissedTaskAnimations(TaskView taskView, long duration, PendingAnimation anim)3672 private void addDismissedTaskAnimations(TaskView taskView, long duration, 3673 PendingAnimation anim) { 3674 // Use setFloat instead of setViewAlpha as we want to keep the view visible even when it's 3675 // alpha is set to 0 so that it can be recycled in the view pool properly 3676 anim.setFloat(taskView, VIEW_ALPHA, 0, 3677 clampToProgress(isOnGridBottomRow(taskView) ? ACCELERATE : FINAL_FRAME, 0, 0.5f)); 3678 FloatProperty<TaskView> secondaryViewTranslate = 3679 taskView.getSecondaryDismissTranslationProperty(); 3680 int secondaryTaskDimension = getPagedOrientationHandler().getSecondaryDimension(taskView); 3681 int verticalFactor = getPagedOrientationHandler().getSecondaryTranslationDirectionFactor(); 3682 3683 ResourceProvider rp = DynamicResource.provider(mContainer); 3684 SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_START) 3685 .setDampingRatio(rp.getFloat(R.dimen.dismiss_task_trans_y_damping_ratio)) 3686 .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_y_stiffness)); 3687 3688 anim.add(ObjectAnimator.ofFloat(taskView, secondaryViewTranslate, 3689 verticalFactor * secondaryTaskDimension * 2).setDuration(duration), LINEAR, sp); 3690 3691 if (taskView.isRunningTask()) { 3692 anim.addOnFrameCallback(() -> { 3693 if (!mEnableDrawingLiveTile) return; 3694 runActionOnRemoteHandles(remoteTargetHandle -> 3695 remoteTargetHandle.getTaskViewSimulator().taskSecondaryTranslation.value = 3696 taskView.getSecondaryDismissTranslationProperty().get(taskView)); 3697 redrawLiveTile(); 3698 }); 3699 } 3700 } 3701 3702 /** 3703 * Places an {@link FloatingTaskView} on top of the thumbnail for {@link #mSplitHiddenTaskView} 3704 * and then animates it into the split position that was desired 3705 */ createInitialSplitSelectAnimation(PendingAnimation anim)3706 private void createInitialSplitSelectAnimation(PendingAnimation anim) { 3707 getPagedOrientationHandler().getInitialSplitPlaceholderBounds(mSplitPlaceholderSize, 3708 mSplitPlaceholderInset, mContainer.getDeviceProfile(), 3709 mSplitSelectStateController.getActiveSplitStagePosition(), mTempRect); 3710 SplitAnimationTimings timings = 3711 AnimUtils.getDeviceOverviewToSplitTimings(mContainer.getDeviceProfile().isTablet); 3712 3713 RectF startingTaskRect = new RectF(); 3714 safeRemoveDragLayerView(mSplitSelectStateController.getFirstFloatingTaskView()); 3715 SplitAnimInitProps splitAnimInitProps = 3716 mSplitSelectStateController.getSplitAnimationController().getFirstAnimInitViews( 3717 () -> mSplitHiddenTaskView, () -> mSplitSelectSource); 3718 if (mSplitSelectStateController.isAnimateCurrentTaskDismissal()) { 3719 // Create the split select animation from Overview 3720 mSplitHiddenTaskView.setThumbnailVisibility(INVISIBLE, 3721 mSplitSelectStateController.getInitialTaskId()); 3722 anim.setViewAlpha(splitAnimInitProps.getIconView(), 0, clampToProgress(LINEAR, 3723 timings.getIconFadeStartOffset(), 3724 timings.getIconFadeEndOffset())); 3725 } 3726 3727 FloatingTaskView firstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mContainer, 3728 splitAnimInitProps.getOriginalView(), 3729 splitAnimInitProps.getOriginalBitmap(), 3730 splitAnimInitProps.getIconDrawable(), startingTaskRect); 3731 firstFloatingTaskView.setAlpha(1); 3732 firstFloatingTaskView.addStagingAnimation(anim, startingTaskRect, mTempRect, 3733 splitAnimInitProps.getFadeWithThumbnail(), splitAnimInitProps.isStagedTask()); 3734 mSplitSelectStateController.setFirstFloatingTaskView(firstFloatingTaskView); 3735 3736 // Allow user to click staged app to launch into fullscreen 3737 firstFloatingTaskView.setOnClickListener(view -> 3738 mSplitSelectStateController.getSplitAnimationController(). 3739 playAnimPlaceholderToFullscreen(mContainer, view, 3740 Optional.of(() -> resetFromSplitSelectionState()))); 3741 firstFloatingTaskView.setContentDescription(splitAnimInitProps.getContentDescription()); 3742 3743 // SplitInstructionsView: animate in 3744 safeRemoveDragLayerView(mSplitSelectStateController.getSplitInstructionsView()); 3745 SplitInstructionsView splitInstructionsView = 3746 SplitInstructionsView.getSplitInstructionsView(mContainer); 3747 splitInstructionsView.setAlpha(0); 3748 anim.setViewAlpha(splitInstructionsView, 1, clampToProgress(LINEAR, 3749 timings.getInstructionsContainerFadeInStartOffset(), 3750 timings.getInstructionsContainerFadeInEndOffset())); 3751 anim.addFloat(splitInstructionsView, splitInstructionsView.UNFOLD, 0.1f, 1, 3752 clampToProgress(EMPHASIZED_DECELERATE, 3753 timings.getInstructionsUnfoldStartOffset(), 3754 timings.getInstructionsUnfoldEndOffset())); 3755 mSplitSelectStateController.setSplitInstructionsView(splitInstructionsView); 3756 3757 InteractionJankMonitorWrapper.begin(this, Cuj.CUJ_SPLIT_SCREEN_ENTER, 3758 "First tile selected"); 3759 anim.addListener(new AnimatorListenerAdapter() { 3760 @Override 3761 public void onAnimationStart(Animator animation) { 3762 if (mSplitHiddenTaskView == getRunningTaskView()) { 3763 finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, 3764 null /* onFinishComplete */); 3765 } else { 3766 switchToScreenshot( 3767 () -> finishRecentsAnimation(true /* toRecents */, 3768 false /* shouldPip */, null /* onFinishComplete */)); 3769 } 3770 } 3771 }); 3772 anim.addEndListener(success -> { 3773 if (success) { 3774 InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER); 3775 } else { 3776 // If transition to split select was interrupted, clean up to prevent glitches 3777 mSplitSelectStateController.resetState(); 3778 InteractionJankMonitorWrapper.cancel(Cuj.CUJ_SPLIT_SCREEN_ENTER); 3779 } 3780 3781 updateCurrentTaskActionsVisibility(); 3782 }); 3783 } 3784 3785 /** 3786 * Creates a {@link PendingAnimation} for dismissing the specified {@link TaskView}. 3787 * 3788 * @param dismissedTaskView the {@link TaskView} to be dismissed 3789 * @param animateTaskView whether the {@link TaskView} to be dismissed should be 3790 * animated 3791 * @param shouldRemoveTask whether the associated {@link Task} should be removed from 3792 * ActivityManager after dismissal 3793 * @param duration duration of the animation 3794 * @param dismissingForSplitSelection task dismiss animation is used for entering split 3795 * selection state from app icon 3796 * @param isExpressiveDismiss runs expressive animations controlled via 3797 * {@link RecentsDismissUtils} 3798 */ createTaskDismissAnimation(PendingAnimation anim, @Nullable TaskView dismissedTaskView, boolean animateTaskView, boolean shouldRemoveTask, long duration, boolean dismissingForSplitSelection, boolean isExpressiveDismiss)3799 public void createTaskDismissAnimation(PendingAnimation anim, 3800 @Nullable TaskView dismissedTaskView, 3801 boolean animateTaskView, boolean shouldRemoveTask, long duration, 3802 boolean dismissingForSplitSelection, boolean isExpressiveDismiss) { 3803 if (mPendingAnimation != null) { 3804 mPendingAnimation.createPlaybackController().dispatchOnCancel().dispatchOnEnd(); 3805 } 3806 3807 int count = getPageCount(); 3808 if (count == 0) { 3809 return; 3810 } 3811 3812 boolean showAsGrid = showAsGrid(); 3813 int taskCount = getTaskViewCount(); 3814 int dismissedIndex = indexOfChild(dismissedTaskView); 3815 int dismissedTaskViewId = 3816 dismissedTaskView != null ? dismissedTaskView.getTaskViewId() : INVALID_TASK_ID; 3817 3818 // Grid specific properties. 3819 boolean isFocusedTaskDismissed = false; 3820 boolean isStagingFocusedTask = false; 3821 boolean isSlidingTasks = false; 3822 TaskView nextFocusedTaskView = null; 3823 boolean nextFocusedTaskFromTop = false; 3824 float dismissedTaskWidth = 0; 3825 float nextFocusedTaskWidth = 0; 3826 3827 int[] oldScroll = new int[count]; 3828 int[] newScroll = new int[count]; 3829 int scrollDiffPerPage = 0; 3830 // Non-grid specific properties. 3831 boolean needsCurveUpdates = false; 3832 boolean areAllDesktopTasksDismissed = false; 3833 3834 if (showAsGrid) { 3835 if (dismissedTaskView != null) { 3836 dismissedTaskWidth = dismissedTaskView.getLayoutParams().width + mPageSpacing; 3837 } 3838 isFocusedTaskDismissed = dismissedTaskViewId != INVALID_TASK_ID 3839 && dismissedTaskViewId == mFocusedTaskViewId; 3840 if (dismissingForSplitSelection && getTaskViewAt( 3841 mCurrentPage) instanceof DesktopTaskView) { 3842 areAllDesktopTasksDismissed = true; 3843 } 3844 if (isFocusedTaskDismissed) { 3845 if (isSplitSelectionActive()) { 3846 isStagingFocusedTask = true; 3847 } else { 3848 nextFocusedTaskFromTop = 3849 !mTopRowIdSet.isEmpty() && mTopRowIdSet.size() >= (taskCount - 1) / 2f; 3850 // Pick the next focused task from the preferred row. 3851 for (TaskView taskView : getTaskViews()) { 3852 if (taskView == dismissedTaskView || taskView.isLargeTile()) { 3853 continue; 3854 } 3855 boolean isTopRow = mTopRowIdSet.contains(taskView.getTaskViewId()); 3856 if ((nextFocusedTaskFromTop && isTopRow 3857 || (!nextFocusedTaskFromTop && !isTopRow))) { 3858 nextFocusedTaskView = taskView; 3859 break; 3860 } 3861 } 3862 if (nextFocusedTaskView != null) { 3863 nextFocusedTaskWidth = 3864 nextFocusedTaskView.getLayoutParams().width + mPageSpacing; 3865 } 3866 } 3867 } 3868 } 3869 3870 getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC); 3871 getPageScrolls(newScroll, false, 3872 v -> v.getVisibility() != GONE && v != dismissedTaskView); 3873 if (count > 1) { 3874 scrollDiffPerPage = Math.abs(oldScroll[1] - oldScroll[0]); 3875 } 3876 3877 isSlidingTasks = isStagingFocusedTask || areAllDesktopTasksDismissed; 3878 float dismissTranslationInterpolationEnd = 1; 3879 boolean closeGapBetweenClearAll = false; 3880 boolean isClearAllHidden = isClearAllHidden(); 3881 boolean snapToLastTask = false; 3882 boolean isLeftRightSplit = 3883 mContainer.getDeviceProfile().isLeftRightSplit && isSplitSelectionActive(); 3884 TaskView lastGridTaskView = showAsGrid ? getLastGridTaskView() : null; 3885 int currentPageScroll = getScrollForPage(mCurrentPage); 3886 int lastGridTaskScroll = getScrollForPage(indexOfChild(lastGridTaskView)); 3887 boolean currentPageSnapsToEndOfGrid = currentPageScroll == lastGridTaskScroll; 3888 3889 int topGridRowSize = mTopRowIdSet.size(); 3890 int numLargeTiles = mUtils.getLargeTileCount(); 3891 int bottomGridRowSize = taskCount - mTopRowIdSet.size() - numLargeTiles; 3892 boolean topRowLonger = topGridRowSize > bottomGridRowSize; 3893 boolean bottomRowLonger = bottomGridRowSize > topGridRowSize; 3894 boolean dismissedTaskFromTop = mTopRowIdSet.contains(dismissedTaskViewId); 3895 boolean dismissedTaskFromBottom = !dismissedTaskFromTop && !isFocusedTaskDismissed; 3896 if (dismissedTaskFromTop || (isFocusedTaskDismissed && nextFocusedTaskFromTop)) { 3897 topGridRowSize--; 3898 } 3899 if (dismissedTaskFromBottom || (isFocusedTaskDismissed && !nextFocusedTaskFromTop)) { 3900 bottomGridRowSize--; 3901 } 3902 int longRowWidth = Math.max(topGridRowSize, bottomGridRowSize) 3903 * (mLastComputedGridTaskSize.width() + mPageSpacing); 3904 if (!enableGridOnlyOverview() && !isStagingFocusedTask) { 3905 longRowWidth += mLastComputedTaskSize.width() + mPageSpacing; 3906 } 3907 // Compensate the removed gap if we don't already have shortTotalCompensation, 3908 // and adjust accordingly to the new shortTotalCompensation after dismiss. 3909 int newClearAllShortTotalWidthTranslation = 0; 3910 if (mClearAllShortTotalWidthTranslation == 0) { 3911 // If first task is not in the expected position (mLastComputedTaskSize) and being too 3912 // close to ClearAllButton, then apply extra translation to ClearAllButton. 3913 int firstTaskStart = mLastComputedGridSize.left + longRowWidth; 3914 int expectedFirstTaskStart = mLastComputedTaskSize.right; 3915 if (firstTaskStart < expectedFirstTaskStart) { 3916 newClearAllShortTotalWidthTranslation = expectedFirstTaskStart - firstTaskStart; 3917 } 3918 } 3919 if (lastGridTaskView != null && ( 3920 (!isExpressiveDismiss && lastGridTaskView.isVisibleToUser()) || (isExpressiveDismiss 3921 && (isTaskViewVisible(lastGridTaskView) 3922 || lastGridTaskView == dismissedTaskView)))) { 3923 // After dismissal, animate translation of the remaining tasks to fill any gap left 3924 // between the end of the grid and the clear all button. Only animate if the clear 3925 // all button is visible or would become visible after dismissal. 3926 float longGridRowWidthDiff = 0; 3927 3928 float gapWidth = 0; 3929 if ((topRowLonger && dismissedTaskFromTop) 3930 || (bottomRowLonger && dismissedTaskFromBottom)) { 3931 gapWidth = dismissedTaskWidth; 3932 } else if (nextFocusedTaskView != null 3933 && ((topRowLonger && nextFocusedTaskFromTop) 3934 || (bottomRowLonger && !nextFocusedTaskFromTop))) { 3935 gapWidth = nextFocusedTaskWidth; 3936 } 3937 if (gapWidth > 0) { 3938 if (mClearAllShortTotalWidthTranslation == 0) { 3939 float gapCompensation = gapWidth - newClearAllShortTotalWidthTranslation; 3940 longGridRowWidthDiff += mIsRtl ? -gapCompensation : gapCompensation; 3941 } 3942 if (isClearAllHidden) { 3943 // If ClearAllButton isn't fully shown, snap to the last task. 3944 snapToLastTask = true; 3945 } 3946 } 3947 if (isLeftRightSplit && !isStagingFocusedTask) { 3948 // LastTask's scroll is the minimum scroll in split select, if current scroll is 3949 // beyond that, we'll need to snap to last task instead. 3950 TaskView lastTask = getLastGridTaskView(); 3951 if (lastTask != null) { 3952 int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this); 3953 int lastTaskScroll = getScrollForPage(indexOfChild(lastTask)); 3954 if ((mIsRtl && primaryScroll < lastTaskScroll) 3955 || (!mIsRtl && primaryScroll > lastTaskScroll)) { 3956 snapToLastTask = true; 3957 } 3958 } 3959 } 3960 if (snapToLastTask) { 3961 longGridRowWidthDiff += getSnapToLastTaskScrollDiff(); 3962 } else if (isLeftRightSplit && currentPageSnapsToEndOfGrid) { 3963 // Use last task as reference point for scroll diff and snapping calculation as it's 3964 // the only invariant point in landscape split screen. 3965 snapToLastTask = true; 3966 } 3967 if (mUtils.getGridTaskCount() == 1 && dismissedTaskView.isGridTask()) { 3968 TaskView lastLargeTile = mUtils.getLastLargeTaskView(); 3969 if (lastLargeTile != null) { 3970 // Calculate the distance to put last large tile back to middle of the screen. 3971 int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this); 3972 int lastLargeTileScroll = getScrollForPage(indexOfChild(lastLargeTile)); 3973 longGridRowWidthDiff = primaryScroll - lastLargeTileScroll; 3974 3975 if (!isClearAllHidden) { 3976 // If ClearAllButton is visible, reduce the distance by scroll difference 3977 // between ClearAllButton and the last task. 3978 longGridRowWidthDiff += getLastTaskScroll(/*clearAllScroll=*/0, 3979 getPagedOrientationHandler().getPrimarySize(mClearAllButton)); 3980 } 3981 } 3982 } 3983 3984 // If we need to animate the grid to compensate the clear all gap, we split the second 3985 // half of the dismiss pending animation (in which the non-dismissed tasks slide into 3986 // place) in half again, making the first quarter the existing non-dismissal sliding 3987 // and the second quarter this new animation of gap filling. This is due to the fact 3988 // that PendingAnimation is a single animation, not a sequence of animations, so we 3989 // fake it using interpolation. 3990 if (longGridRowWidthDiff != 0) { 3991 closeGapBetweenClearAll = true; 3992 // Stagger the offsets of each additional task for a delayed animation. We use 3993 // half here as this animation is half of half of an animation (1/4th). 3994 float halfAdditionalDismissTranslationOffset = 3995 (0.5f * ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET); 3996 dismissTranslationInterpolationEnd = Utilities.boundToRange( 3997 END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET 3998 + (taskCount - 1) * halfAdditionalDismissTranslationOffset, 3999 END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET, 1); 4000 for (TaskView taskView : getTaskViews()) { 4001 anim.setFloat(taskView, TaskView.GRID_END_TRANSLATION_X, longGridRowWidthDiff, 4002 clampToProgress(LINEAR, dismissTranslationInterpolationEnd, 1)); 4003 dismissTranslationInterpolationEnd = Utilities.boundToRange( 4004 dismissTranslationInterpolationEnd 4005 - halfAdditionalDismissTranslationOffset, 4006 END_DISMISS_TRANSLATION_INTERPOLATION_OFFSET, 1); 4007 if (mEnableDrawingLiveTile && taskView.isRunningTask()) { 4008 anim.addOnFrameCallback(() -> { 4009 runActionOnRemoteHandles( 4010 remoteTargetHandle -> 4011 remoteTargetHandle.getTaskViewSimulator() 4012 .taskPrimaryTranslation.value = 4013 TaskView.GRID_END_TRANSLATION_X.get(taskView)); 4014 redrawLiveTile(); 4015 }); 4016 } 4017 } 4018 4019 // Change alpha of clear all if translating grid to hide it 4020 if (isClearAllHidden) { 4021 anim.setFloat(mClearAllButton, DISMISS_ALPHA, 0, LINEAR); 4022 anim.addListener(new AnimatorListenerAdapter() { 4023 @Override 4024 public void onAnimationEnd(Animator animation) { 4025 super.onAnimationEnd(animation); 4026 mClearAllButton.setDismissAlpha(1); 4027 } 4028 }); 4029 } 4030 } 4031 } 4032 4033 SplitAnimationTimings splitTimings = 4034 AnimUtils.getDeviceOverviewToSplitTimings(mContainer.getDeviceProfile().isTablet); 4035 4036 int distanceFromDismissedTask = 1; 4037 int slidingTranslation = 0; 4038 if (isSlidingTasks) { 4039 int nextSnappedPage = indexOfChild(isStagingFocusedTask 4040 ? mUtils.getFirstSmallTaskView() 4041 : mUtils.getFirstNonDesktopTaskView()); 4042 slidingTranslation = getPagedOrientationHandler().getPrimaryScroll(this) 4043 - getScrollForPage(nextSnappedPage); 4044 slidingTranslation += mIsRtl ? newClearAllShortTotalWidthTranslation 4045 : -newClearAllShortTotalWidthTranslation; 4046 } 4047 mTaskViewsDismissPrimaryTranslations.clear(); 4048 int lastTaskViewIndex = indexOfChild(mUtils.getLastTaskView()); 4049 for (int i = 0; i < count; i++) { 4050 View child = getChildAt(i); 4051 if (child == dismissedTaskView) { 4052 if (animateTaskView && !dismissingForSplitSelection) { 4053 addDismissedTaskAnimations(dismissedTaskView, duration, anim); 4054 } 4055 } else if (!showAsGrid || (enableLargeDesktopWindowingTile() 4056 && dismissedTaskView != null && dismissedTaskView.isLargeTile() 4057 && nextFocusedTaskView == null && !dismissingForSplitSelection)) { 4058 int offset = getOffsetToDismissedTask(scrollDiffPerPage, dismissedIndex, 4059 lastTaskViewIndex); 4060 int scrollDiff = newScroll[i] - oldScroll[i] + offset; 4061 if (scrollDiff != 0) { 4062 if (!isExpressiveDismiss) { 4063 translateTaskWhenDismissed( 4064 child, 4065 Math.abs(i - dismissedIndex), 4066 scrollDiff, 4067 anim, 4068 splitTimings); 4069 } 4070 if (child instanceof TaskView taskView) { 4071 mTaskViewsDismissPrimaryTranslations.put(taskView, scrollDiffPerPage); 4072 } 4073 needsCurveUpdates = true; 4074 } 4075 } else if (child instanceof TaskView taskView) { 4076 // Animate task with index >= dismissed index and in the same row as the 4077 // dismissed index or next focused index. Offset successive task dismissal 4078 // durations for a staggered effect. 4079 int staggerColumn = isSlidingTasks 4080 ? (int) Math.ceil(distanceFromDismissedTask / 2f) 4081 : distanceFromDismissedTask; 4082 // Set timings based on if user is initiating splitscreen on the focused task, 4083 // or splitting/dismissing some other task. 4084 final float animationStartProgress; 4085 if (isSlidingTasks) { 4086 float slidingStartOffset = splitTimings.getGridSlideStartOffset() 4087 + (splitTimings.getGridSlideStaggerOffset() * staggerColumn); 4088 if (areAllDesktopTasksDismissed) { 4089 animationStartProgress = Utilities.boundToRange( 4090 slidingStartOffset 4091 + splitTimings.getDesktopFadeSplitAnimationEndOffset(), 4092 0f, 4093 dismissTranslationInterpolationEnd); 4094 } else { 4095 animationStartProgress = Utilities.boundToRange( 4096 slidingStartOffset, 4097 0f, 4098 dismissTranslationInterpolationEnd); 4099 } 4100 } else { 4101 animationStartProgress = Utilities.boundToRange( 4102 INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET 4103 + ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET 4104 * staggerColumn, 0f, dismissTranslationInterpolationEnd); 4105 } 4106 4107 final float animationEndProgress; 4108 if (isSlidingTasks && taskView != nextFocusedTaskView) { 4109 animationEndProgress = Utilities.boundToRange( 4110 splitTimings.getGridSlideStartOffset() 4111 + (splitTimings.getGridSlideStaggerOffset() * staggerColumn) 4112 + splitTimings.getGridSlideDurationOffset(), 4113 0f, 4114 dismissTranslationInterpolationEnd); 4115 } else { 4116 animationEndProgress = dismissTranslationInterpolationEnd; 4117 } 4118 4119 Interpolator dismissInterpolator = isSlidingTasks ? EMPHASIZED : LINEAR; 4120 4121 float primaryTranslation = 0; 4122 if (taskView == nextFocusedTaskView) { 4123 // Enlarge the task to be focused next, and translate into focus position. 4124 float scale = mTaskWidth / (float) mLastComputedGridTaskSize.width(); 4125 anim.setFloat(taskView, TaskView.DISMISS_SCALE, scale, 4126 clampToProgress(LINEAR, animationStartProgress, 4127 dismissTranslationInterpolationEnd)); 4128 primaryTranslation += dismissedTaskWidth; 4129 float secondaryTranslation = -mTaskGridVerticalDiff; 4130 if (!nextFocusedTaskFromTop) { 4131 secondaryTranslation -= mTopBottomRowHeightDiff; 4132 } 4133 anim.setFloat(taskView, taskView.getSecondaryDismissTranslationProperty(), 4134 secondaryTranslation, clampToProgress(LINEAR, animationStartProgress, 4135 dismissTranslationInterpolationEnd)); 4136 anim.add(taskView.getDismissIconFadeOutAnimator(), 4137 clampToProgress(LINEAR, 0f, ANIMATION_DISMISS_PROGRESS_MIDPOINT)); 4138 } else if ((isFocusedTaskDismissed && nextFocusedTaskView != null && isSameGridRow( 4139 taskView, nextFocusedTaskView)) 4140 || (!isFocusedTaskDismissed && i >= dismissedIndex && isSameGridRow( 4141 taskView, dismissedTaskView))) { 4142 primaryTranslation += 4143 nextFocusedTaskView != null ? nextFocusedTaskWidth : dismissedTaskWidth; 4144 } 4145 if (!(taskView instanceof DesktopTaskView)) { 4146 primaryTranslation += mIsRtl ? slidingTranslation : -slidingTranslation; 4147 } 4148 4149 if (primaryTranslation != 0) { 4150 float finalTranslation = mIsRtl ? primaryTranslation : -primaryTranslation; 4151 float startTranslation = 0; 4152 if (!(taskView instanceof DesktopTaskView) && slidingTranslation != 0) { 4153 startTranslation = isTaskViewVisible(taskView) ? 0 4154 : finalTranslation + (mIsRtl ? -mLastComputedTaskSize.right 4155 : mLastComputedTaskSize.right); 4156 } 4157 // Expressive dismiss will animate the translations of taskViews itself. 4158 if (!isExpressiveDismiss) { 4159 Animator dismissAnimator = ObjectAnimator.ofFloat(taskView, 4160 taskView.getPrimaryDismissTranslationProperty(), 4161 startTranslation, finalTranslation); 4162 dismissAnimator.setInterpolator( 4163 clampToProgress(dismissInterpolator, animationStartProgress, 4164 animationEndProgress)); 4165 anim.add(dismissAnimator); 4166 } 4167 mTaskViewsDismissPrimaryTranslations.put(taskView, (int) finalTranslation); 4168 distanceFromDismissedTask++; 4169 } 4170 } 4171 } 4172 if (dismissingForSplitSelection) { 4173 createInitialSplitSelectAnimation(anim); 4174 } 4175 4176 if (needsCurveUpdates) { 4177 anim.addOnFrameCallback(this::updateCurveProperties); 4178 } 4179 4180 // Add a tiny bit of translation Z, so that it draws on top of other views. This is relevant 4181 // (e.g.) when we dismiss a task by sliding it upward: if there is a row of icons above, we 4182 // want the dragged task to stay above all other views. 4183 if (animateTaskView && dismissedTaskView != null) { 4184 dismissedTaskView.setTranslationZ(0.1f); 4185 } 4186 loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); 4187 if (!dismissingForSplitSelection) { 4188 anim.addStartListener(() -> InteractionJankMonitorWrapper.begin(this, 4189 Cuj.CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS)); 4190 } 4191 mPendingAnimation = anim; 4192 final TaskView finalNextFocusedTaskView = nextFocusedTaskView; 4193 final boolean finalCloseGapBetweenClearAll = closeGapBetweenClearAll; 4194 final boolean finalSnapToLastTask = snapToLastTask; 4195 final boolean finalIsFocusedTaskDismissed = isFocusedTaskDismissed; 4196 mPendingAnimation.addEndListener(new Consumer<>() { 4197 @Override 4198 public void accept(Boolean success) { 4199 if (mEnableDrawingLiveTile && dismissedTaskView != null 4200 && dismissedTaskView.isRunningTask() && success) { 4201 finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, 4202 () -> onEnd(true)); 4203 } else { 4204 onEnd(success); 4205 } 4206 } 4207 4208 @SuppressWarnings("WrongCall") 4209 private void onEnd(boolean success) { 4210 // Reset task translations as they may have updated via animations in 4211 // createTaskDismissAnimation 4212 resetTaskVisuals(); 4213 4214 if (success) { 4215 mAnyTaskHasBeenDismissed = true; 4216 if (shouldRemoveTask && dismissedTaskView != null) { 4217 if (dismissedTaskView.isRunningTask()) { 4218 finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, 4219 () -> removeTaskInternal(dismissedTaskView)); 4220 } else { 4221 removeTaskInternal(dismissedTaskView); 4222 } 4223 mContainer.getStatsLogManager().logger() 4224 .withItemInfo(dismissedTaskView.getItemInfo()) 4225 .log(LAUNCHER_TASK_DISMISS_SWIPE_UP); 4226 } 4227 4228 int pageToSnapTo = mCurrentPage; 4229 mCurrentPageScrollDiff = 0; 4230 int taskViewIdToSnapTo = -1; 4231 if (showAsGrid) { 4232 if (finalCloseGapBetweenClearAll) { 4233 if (finalSnapToLastTask) { 4234 // Last task will be determined after removing dismissed task. 4235 pageToSnapTo = -1; 4236 } else if (taskCount > 2) { 4237 pageToSnapTo = indexOfChild(mClearAllButton); 4238 } else if (isClearAllHidden) { 4239 // Snap to focused task if clear all is hidden. 4240 pageToSnapTo = indexOfChild(getFirstTaskView()); 4241 } 4242 } else { 4243 // Get the id of the task view we will snap to based on the current 4244 // page's relative position as the order of indices change over time due 4245 // to dismissals. 4246 TaskView snappedTaskView = getTaskViewAt(mCurrentPage); 4247 boolean calculateScrollDiff = true; 4248 if (snappedTaskView != null && !finalSnapToLastTask) { 4249 if (snappedTaskView.getTaskViewId() == mFocusedTaskViewId) { 4250 if (finalNextFocusedTaskView != null) { 4251 taskViewIdToSnapTo = 4252 finalNextFocusedTaskView.getTaskViewId(); 4253 } else if (dismissedTaskViewId != mFocusedTaskViewId) { 4254 taskViewIdToSnapTo = mFocusedTaskViewId; 4255 } else { 4256 // Won't focus next task in split select, so snap to the 4257 // first task. 4258 pageToSnapTo = indexOfChild(getFirstTaskView()); 4259 calculateScrollDiff = false; 4260 } 4261 } else { 4262 int snappedTaskViewId = snappedTaskView.getTaskViewId(); 4263 boolean isSnappedTaskInTopRow = mTopRowIdSet.contains( 4264 snappedTaskViewId); 4265 IntArray taskViewIdArray = 4266 isSnappedTaskInTopRow ? mUtils.getTopRowIdArray() 4267 : mUtils.getBottomRowIdArray(); 4268 int snappedIndex = taskViewIdArray.indexOf(snappedTaskViewId); 4269 taskViewIdArray.removeValue(dismissedTaskViewId); 4270 if (finalNextFocusedTaskView != null) { 4271 taskViewIdArray.removeValue( 4272 finalNextFocusedTaskView.getTaskViewId()); 4273 } 4274 if (snappedIndex >= 0 4275 && snappedIndex < taskViewIdArray.size()) { 4276 taskViewIdToSnapTo = taskViewIdArray.get(snappedIndex); 4277 } else if (snappedIndex == taskViewIdArray.size()) { 4278 // If the snapped task is the last item from the 4279 // dismissed row, 4280 // snap to the same column in the other grid row 4281 IntArray inverseRowTaskViewIdArray = 4282 isSnappedTaskInTopRow ? mUtils.getBottomRowIdArray() 4283 : mUtils.getTopRowIdArray(); 4284 if (snappedIndex < inverseRowTaskViewIdArray.size()) { 4285 taskViewIdToSnapTo = inverseRowTaskViewIdArray.get( 4286 snappedIndex); 4287 } 4288 } 4289 } 4290 } 4291 4292 if (calculateScrollDiff) { 4293 int primaryScroll = getPagedOrientationHandler().getPrimaryScroll( 4294 RecentsView.this); 4295 int currentPageScroll = getScrollForPage(mCurrentPage); 4296 mCurrentPageScrollDiff = primaryScroll - currentPageScroll; 4297 } 4298 } 4299 } else if (dismissedIndex < pageToSnapTo || pageToSnapTo == lastTaskViewIndex) { 4300 pageToSnapTo--; 4301 } 4302 boolean isHomeTaskDismissed = dismissedTaskView == getHomeTaskView(); 4303 removeViewInLayout(dismissedTaskView); 4304 mTopRowIdSet.remove(dismissedTaskViewId); 4305 4306 if (taskCount == 1) { 4307 removeViewInLayout(mClearAllButton); 4308 removeViewInLayout(mAddDesktopButton); 4309 if (isHomeTaskDismissed) { 4310 updateEmptyMessage(); 4311 } else if (!mSplitSelectStateController.isSplitSelectActive()) { 4312 startHome(); 4313 } 4314 } else { 4315 // Update focus task and its size. 4316 if (finalIsFocusedTaskDismissed && finalNextFocusedTaskView != null) { 4317 setFocusedTaskViewId(enableGridOnlyOverview() 4318 ? INVALID_TASK_ID 4319 : finalNextFocusedTaskView.getTaskViewId()); 4320 mTopRowIdSet.remove(mFocusedTaskViewId); 4321 finalNextFocusedTaskView.getDismissIconFadeInAnimator().start(); 4322 } 4323 updateTaskSize(); 4324 mUtils.updateChildTaskOrientations(); 4325 // Update scroll and snap to page. 4326 updateScrollSynchronously(); 4327 4328 if (showAsGrid) { 4329 // Rebalance tasks in the grid 4330 TaskView highestVisibleTaskView = getHighestVisibleTaskView(); 4331 if (highestVisibleTaskView != null) { 4332 boolean shouldRebalance; 4333 int screenStart = getPagedOrientationHandler().getPrimaryScroll( 4334 RecentsView.this); 4335 int taskStart = getPagedOrientationHandler().getChildStart( 4336 highestVisibleTaskView) 4337 + (int) highestVisibleTaskView.getOffsetAdjustment( 4338 /*gridEnabled=*/true); 4339 4340 // Rebalance only if there is a maximum gap between the task and the 4341 // screen's edge; this ensures that rebalanced tasks are outside the 4342 // visible screen. 4343 if (mIsRtl) { 4344 shouldRebalance = taskStart <= screenStart + mPageSpacing; 4345 } else { 4346 int screenEnd = screenStart 4347 + getPagedOrientationHandler().getMeasuredSize( 4348 RecentsView.this); 4349 int taskSize = (int) ( 4350 getPagedOrientationHandler().getMeasuredSize( 4351 highestVisibleTaskView) * highestVisibleTaskView 4352 .getSizeAdjustment(/*fullscreenEnabled=*/ 4353 false)); 4354 int taskEnd = taskStart + taskSize; 4355 4356 shouldRebalance = taskEnd >= screenEnd - mPageSpacing; 4357 } 4358 4359 if (shouldRebalance) { 4360 updateGridProperties(highestVisibleTaskView); 4361 updateScrollSynchronously(); 4362 } 4363 } 4364 4365 IntArray topRowIdArray = mUtils.getTopRowIdArray(); 4366 IntArray bottomRowIdArray = mUtils.getBottomRowIdArray(); 4367 if (finalSnapToLastTask) { 4368 // If snapping to last task, find the last task after dismissal. 4369 pageToSnapTo = indexOfChild( 4370 getLastGridTaskView(topRowIdArray, bottomRowIdArray)); 4371 4372 if (pageToSnapTo == INVALID_PAGE) { 4373 // Snap to latest large tile page after dismissing the 4374 // last grid task. This will prevent snapping to page 0 when 4375 // desktop task is visible as large tile. 4376 pageToSnapTo = indexOfChild(mUtils.getLastLargeTaskView()); 4377 } 4378 } else if (taskViewIdToSnapTo != -1) { 4379 // If snapping to another page due to indices rearranging, find 4380 // the new index after dismissal & rearrange using the task view id. 4381 pageToSnapTo = indexOfChild( 4382 getTaskViewFromTaskViewId(taskViewIdToSnapTo)); 4383 if (!currentPageSnapsToEndOfGrid) { 4384 // If it wasn't snapped to one of the last pages, but is now 4385 // snapped to last pages, we'll need to compensate for the 4386 // offset from the page's scroll to its visual position. 4387 mCurrentPageScrollDiff += getOffsetFromScrollPosition( 4388 pageToSnapTo, topRowIdArray, bottomRowIdArray); 4389 } 4390 } 4391 } 4392 pageBeginTransition(); 4393 setCurrentPage(pageToSnapTo); 4394 // Update various scroll-dependent UI. 4395 dispatchScrollChanged(); 4396 updateActionsViewFocusedScroll(); 4397 if (isClearAllHidden() && !mContainer.getDeviceProfile().isTablet) { 4398 mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, 4399 false); 4400 } 4401 } 4402 } 4403 updateCurrentTaskActionsVisibility(); 4404 onDismissAnimationEnds(); 4405 mPendingAnimation = null; 4406 mTaskViewsDismissPrimaryTranslations.clear(); 4407 4408 if (!dismissingForSplitSelection && success) { 4409 InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS); 4410 } else if (!dismissingForSplitSelection) { 4411 InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS); 4412 } 4413 } 4414 }); 4415 } 4416 4417 /** 4418 * Compute scroll offsets from task dismissal for animation. 4419 * If we just take newScroll - oldScroll, everything to the right of dragged task 4420 * translates to the left. We need to offset this in some cases: 4421 * - In RTL, add page offset to all pages, since we want pages to move to the right 4422 * Additionally, add a page offset if: 4423 * - Current page is rightmost page (leftmost for RTL) 4424 * - Dragging an adjacent page on the left side (right side for RTL) 4425 */ getOffsetToDismissedTask(int scrollDiffPerPage, int dismissedIndex, int lastTaskViewIndex)4426 private int getOffsetToDismissedTask(int scrollDiffPerPage, int dismissedIndex, 4427 int lastTaskViewIndex) { 4428 // If `mCurrentPage` is beyond `lastTaskViewIndex`, use the last TaskView instead to 4429 // calculate offset. 4430 int currentPage = Math.min(mCurrentPage, lastTaskViewIndex); 4431 int offset = mIsRtl ? scrollDiffPerPage : 0; 4432 if (currentPage == dismissedIndex) { 4433 if (currentPage == lastTaskViewIndex) { 4434 offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage; 4435 } 4436 } else { 4437 // Dismissing an adjacent page. 4438 int negativeAdjacent = currentPage - 1; // (Right in RTL, left in LTR) 4439 if (dismissedIndex == negativeAdjacent) { 4440 offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage; 4441 } 4442 } 4443 return offset; 4444 } 4445 translateTaskWhenDismissed( View view, int indexDiff, int scrollDiffPerPage, PendingAnimation pendingAnimation, SplitAnimationTimings splitTimings)4446 private void translateTaskWhenDismissed( 4447 View view, 4448 int indexDiff, 4449 int scrollDiffPerPage, 4450 PendingAnimation pendingAnimation, 4451 SplitAnimationTimings splitTimings) { 4452 // No need to translate the AddDesktopButton on dismissing a TaskView, which should be 4453 // always at the right most position, even when dismissing the last TaskView. 4454 if (view instanceof AddDesktopButton) { 4455 return; 4456 } 4457 FloatProperty translationProperty = view instanceof TaskView 4458 ? ((TaskView) view).getPrimaryDismissTranslationProperty() 4459 : getPagedOrientationHandler().getPrimaryViewTranslate(); 4460 4461 float additionalDismissDuration = 4462 ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET * indexDiff; 4463 4464 // We are in non-grid layout. 4465 // If dismissing for split select, use split timings. 4466 // If not, use dismiss timings. 4467 float animationStartProgress = isSplitSelectionActive() 4468 ? Utilities.boundToRange(splitTimings.getGridSlideStartOffset(), 0f, 1f) 4469 : Utilities.boundToRange( 4470 INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET 4471 + additionalDismissDuration, 0f, 1f); 4472 4473 float animationEndProgress = isSplitSelectionActive() 4474 ? Utilities.boundToRange(splitTimings.getGridSlideStartOffset() 4475 + splitTimings.getGridSlideDurationOffset(), 0f, 1f) 4476 : 1f; 4477 4478 // Slide tiles in horizontally to fill dismissed area 4479 pendingAnimation.setFloat( 4480 view, 4481 translationProperty, 4482 scrollDiffPerPage, 4483 clampToProgress( 4484 splitTimings.getGridSlidePrimaryInterpolator(), 4485 animationStartProgress, 4486 animationEndProgress 4487 ) 4488 ); 4489 if (mEnableDrawingLiveTile && view instanceof TaskView 4490 && ((TaskView) view).isRunningTask()) { 4491 pendingAnimation.addOnFrameCallback(() -> { 4492 runActionOnRemoteHandles( 4493 remoteTargetHandle -> 4494 remoteTargetHandle.getTaskViewSimulator() 4495 .taskPrimaryTranslation.value = 4496 getPagedOrientationHandler().getPrimaryValue( 4497 view.getTranslationX(), 4498 view.getTranslationY() 4499 )); 4500 redrawLiveTile(); 4501 }); 4502 } 4503 } 4504 4505 /** 4506 * Hides all overview actions if user is halfway through split selection, shows otherwise. 4507 * We only show split option if: 4508 * * Focused view is a single app 4509 * * Device is large screen 4510 */ updateCurrentTaskActionsVisibility()4511 private void updateCurrentTaskActionsVisibility() { 4512 TaskView taskView = getCurrentPageTaskView(); 4513 boolean isCurrentSplit = taskView instanceof GroupedTaskView; 4514 GroupedTaskView groupedTaskView = isCurrentSplit ? (GroupedTaskView) taskView : null; 4515 // Update flags to see if entire actions bar should be hidden. 4516 mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SELECT_ACTIVE, isSplitSelectionActive()); 4517 // Update flags to see if actions bar should show buttons for a single task or a pair of 4518 // tasks. 4519 boolean canSaveAppPair = isCurrentSplit && supportsAppPairs() && 4520 getSplitSelectController().getAppPairsController().canSaveAppPair(groupedTaskView); 4521 mActionsView.updateForGroupedTask(isCurrentSplit, canSaveAppPair); 4522 4523 boolean isCurrentDesktop = taskView instanceof DesktopTaskView; 4524 mActionsView.updateHiddenFlags(HIDDEN_DESKTOP, isCurrentDesktop); 4525 } 4526 4527 /** Returns if app pairs are supported in this launcher. Overridden in subclasses. */ supportsAppPairs()4528 public boolean supportsAppPairs() { 4529 return true; 4530 } 4531 4532 /** 4533 * Iterate the grid by columns instead of by TaskView index, starting after the focused task and 4534 * up to the last balanced column. 4535 * 4536 * @return the highest visible TaskView between both rows 4537 */ getHighestVisibleTaskView()4538 private TaskView getHighestVisibleTaskView() { 4539 if (mTopRowIdSet.isEmpty()) return null; // return earlier 4540 4541 TaskView lastVisibleTaskView = null; 4542 IntArray topRowIdArray = mUtils.getTopRowIdArray(); 4543 IntArray bottomRowIdArray = mUtils.getBottomRowIdArray(); 4544 int balancedColumns = Math.min(bottomRowIdArray.size(), topRowIdArray.size()); 4545 4546 for (int i = 0; i < balancedColumns; i++) { 4547 TaskView topTask = getTaskViewFromTaskViewId(topRowIdArray.get(i)); 4548 4549 if (isTaskViewVisible(topTask)) { 4550 TaskView bottomTask = getTaskViewFromTaskViewId(bottomRowIdArray.get(i)); 4551 lastVisibleTaskView = 4552 indexOfChild(topTask) > indexOfChild(bottomTask) ? topTask : bottomTask; 4553 } else if (lastVisibleTaskView != null) { 4554 break; 4555 } 4556 } 4557 4558 return lastVisibleTaskView; 4559 } 4560 removeTaskInternal(@onNull TaskView dismissedTaskView)4561 private void removeTaskInternal(@NonNull TaskView dismissedTaskView) { 4562 UI_HELPER_EXECUTOR 4563 .getHandler() 4564 .post( 4565 () -> { 4566 if (dismissedTaskView instanceof DesktopTaskView desktopTaskView) { 4567 removeDesktopTaskView(desktopTaskView); 4568 } else { 4569 for (int taskId : dismissedTaskView.getTaskIds()) { 4570 ActivityManagerWrapper.getInstance().removeTask(taskId); 4571 } 4572 } 4573 }); 4574 } 4575 removeDesktopTaskView(DesktopTaskView desktopTaskView)4576 private void removeDesktopTaskView(DesktopTaskView desktopTaskView) { 4577 if (areMultiDesksFlagsEnabled()) { 4578 SystemUiProxy.INSTANCE 4579 .get(getContext()) 4580 .removeDesk(desktopTaskView.getDeskId()); 4581 } else if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) { 4582 SystemUiProxy.INSTANCE 4583 .get(getContext()) 4584 .removeDefaultDeskInDisplay( 4585 mContainer.getDisplay().getDisplayId()); 4586 } 4587 } 4588 onDismissAnimationEnds()4589 protected void onDismissAnimationEnds() { 4590 AccessibilityManagerCompat.sendTestProtocolEventToTest(getContext(), 4591 DISMISS_ANIMATION_ENDS_MESSAGE); 4592 } 4593 createAllTasksDismissAnimation(long duration)4594 public PendingAnimation createAllTasksDismissAnimation(long duration) { 4595 if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) { 4596 throw new IllegalStateException("Another pending animation is still running"); 4597 } 4598 PendingAnimation anim = new PendingAnimation(duration); 4599 4600 for (TaskView taskView : getTaskViews()) { 4601 addDismissedTaskAnimations(taskView, duration, anim); 4602 } 4603 4604 mPendingAnimation = anim; 4605 mPendingAnimation.addEndListener(isSuccess -> { 4606 if (isSuccess) { 4607 // Remove desktops first, since desks can be empty (so they have no recent tasks), 4608 // and closing all tasks on a desk doesn't always necessarily mean that the desk 4609 // will be removed. So, there are no guarantees that the below call to 4610 // `ActivityManagerWrapper::removeAllRecentTasks()` will be enough. 4611 SystemUiProxy.INSTANCE.get(getContext()).removeAllDesks(); 4612 4613 // Remove all the task views now 4614 finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, () -> { 4615 UI_HELPER_EXECUTOR.getHandler().post( 4616 ActivityManagerWrapper.getInstance()::removeAllRecentTasks); 4617 removeAllTaskViews(); 4618 startHome(); 4619 }); 4620 } 4621 mPendingAnimation = null; 4622 }); 4623 return anim; 4624 } 4625 snapToPageRelative(int delta, boolean cycle, TaskGridNavHelper.TaskNavDirection direction)4626 private boolean snapToPageRelative(int delta, boolean cycle, 4627 TaskGridNavHelper.TaskNavDirection direction) { 4628 // Set next page if scroll animation is still running, otherwise cannot snap to the 4629 // next page on successive key presses. Setting the current page aborts the scroll. 4630 if (!mScroller.isFinished()) { 4631 setCurrentPage(getNextPage()); 4632 } 4633 int pageCount = getPageCount(); 4634 if (pageCount == 0) { 4635 return false; 4636 } 4637 final int newPageUnbound = getNextPageInternal(delta, direction, cycle); 4638 if (!cycle && (newPageUnbound < 0 || newPageUnbound > pageCount)) { 4639 return false; 4640 } 4641 snapToPage((newPageUnbound + pageCount) % pageCount); 4642 getChildAt(getNextPage()).requestFocus(); 4643 return true; 4644 } 4645 getNextPageInternal(int delta, TaskGridNavHelper.TaskNavDirection direction, boolean cycle)4646 private int getNextPageInternal(int delta, TaskGridNavHelper.TaskNavDirection direction, 4647 boolean cycle) { 4648 if (!showAsGrid()) { 4649 return getNextPage() + delta; 4650 } 4651 4652 // Init task grid nav helper with top/bottom id arrays. 4653 TaskGridNavHelper taskGridNavHelper = new TaskGridNavHelper(mUtils.getTopRowIdArray(), 4654 mUtils.getBottomRowIdArray(), mUtils.getLargeTaskViewIds(), 4655 mAddDesktopButton != null); 4656 4657 // Get current page's task view ID. 4658 TaskView currentPageTaskView = getCurrentPageTaskView(); 4659 int currentPageTaskViewId; 4660 final int clearAllButtonIndex = indexOfChild(mClearAllButton); 4661 final int addDesktopButtonIndex = indexOfChild(mAddDesktopButton); 4662 if (currentPageTaskView != null) { 4663 currentPageTaskViewId = currentPageTaskView.getTaskViewId(); 4664 } else if (mCurrentPage == clearAllButtonIndex) { 4665 currentPageTaskViewId = TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID; 4666 } else if (mCurrentPage == addDesktopButtonIndex) { 4667 currentPageTaskViewId = TaskGridNavHelper.ADD_DESK_PLACEHOLDER_ID; 4668 } else { 4669 return INVALID_PAGE; 4670 } 4671 4672 final int nextGridPage = 4673 taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle); 4674 if (nextGridPage == TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID) { 4675 return clearAllButtonIndex; 4676 } 4677 if (nextGridPage == TaskGridNavHelper.ADD_DESK_PLACEHOLDER_ID) { 4678 return addDesktopButtonIndex; 4679 } 4680 return indexOfChild(getTaskViewFromTaskViewId(nextGridPage)); 4681 } 4682 runDismissAnimation(PendingAnimation pendingAnim)4683 private void runDismissAnimation(PendingAnimation pendingAnim) { 4684 AnimatorPlaybackController controller = pendingAnim.createPlaybackController(); 4685 controller.dispatchOnStart(); 4686 controller.getAnimationPlayer().setInterpolator(FAST_OUT_SLOW_IN); 4687 controller.start(); 4688 } 4689 4690 @UiThread dismissTask(int taskId, boolean animate, boolean removeTask)4691 public void dismissTask(int taskId, boolean animate, boolean removeTask) { 4692 TaskView taskView = getTaskViewByTaskId(taskId); 4693 if (taskView == null) { 4694 Log.d(TAG, "dismissTask: " + taskId + ", no associated TaskView"); 4695 return; 4696 } 4697 Log.d(TAG, "dismissTask: " + taskId); 4698 4699 if (enableDesktopExplodedView() && taskView instanceof DesktopTaskView desktopTaskView) { 4700 desktopTaskView.removeTaskFromExplodedView(taskId, animate); 4701 4702 if (removeTask) { 4703 ActivityManagerWrapper.getInstance().removeTask(taskId); 4704 } 4705 } else { 4706 dismissTaskView(taskView, animate, removeTask); 4707 } 4708 } 4709 4710 /** Dismisses the entire [taskView]. */ dismissTaskView(TaskView taskView, boolean animateTaskView, boolean removeTask)4711 public void dismissTaskView(TaskView taskView, boolean animateTaskView, boolean removeTask) { 4712 PendingAnimation pa = new PendingAnimation(DISMISS_TASK_DURATION); 4713 createTaskDismissAnimation(pa, taskView, animateTaskView, removeTask, DISMISS_TASK_DURATION, 4714 false /* dismissingForSplitSelection*/, false /* isExpressiveDismiss */); 4715 runDismissAnimation(pa); 4716 } 4717 expressiveDismissTaskView(TaskView taskView, Function0<Unit> onEndRunnable)4718 protected void expressiveDismissTaskView(TaskView taskView, Function0<Unit> onEndRunnable) { 4719 PendingAnimation pa = new PendingAnimation(DISMISS_TASK_DURATION); 4720 createTaskDismissAnimation(pa, taskView, false /* animateTaskView */, true /* removeTask */, 4721 DISMISS_TASK_DURATION, false /* dismissingForSplitSelection*/, 4722 true /* isExpressiveDismiss */); 4723 pa.addEndListener((success) -> onEndRunnable.invoke()); 4724 runDismissAnimation(pa); 4725 } 4726 4727 @SuppressWarnings("unused") dismissAllTasks(View view)4728 private void dismissAllTasks(View view) { 4729 runDismissAnimation(createAllTasksDismissAnimation(DISMISS_TASK_DURATION)); 4730 mContainer.getStatsLogManager().logger().log(LAUNCHER_TASK_CLEAR_ALL); 4731 } 4732 dismissCurrentTask()4733 private void dismissCurrentTask() { 4734 TaskView taskView = getNextPageTaskView(); 4735 if (taskView != null) { 4736 dismissTaskView(taskView, true /*animateTaskView*/, true /*removeTask*/); 4737 } 4738 } 4739 createDesk(View view)4740 private void createDesk(View view) { 4741 SystemUiProxy.INSTANCE 4742 .get(getContext()) 4743 .createDesk(mContainer.getDisplay().getDisplayId()); 4744 } 4745 4746 @Override dispatchKeyEvent(KeyEvent event)4747 public boolean dispatchKeyEvent(KeyEvent event) { 4748 if (isHandlingTouch() || event.getAction() != KeyEvent.ACTION_DOWN) { 4749 return super.dispatchKeyEvent(event); 4750 } 4751 4752 if (mUtils.shouldInterceptKeyEvent(event)) { 4753 return super.dispatchKeyEvent(event); 4754 } 4755 4756 switch (event.getKeyCode()) { 4757 case KeyEvent.KEYCODE_TAB: 4758 return snapToPageRelative(event.isShiftPressed() ? -1 : 1, true /* cycle */, 4759 TaskGridNavHelper.TaskNavDirection.TAB); 4760 case KeyEvent.KEYCODE_DPAD_RIGHT: 4761 return snapToPageRelative(mIsRtl ? -1 : 1, true /* cycle */, 4762 TaskGridNavHelper.TaskNavDirection.RIGHT); 4763 case KeyEvent.KEYCODE_DPAD_LEFT: 4764 return snapToPageRelative(mIsRtl ? 1 : -1, true /* cycle */, 4765 TaskGridNavHelper.TaskNavDirection.LEFT); 4766 case KeyEvent.KEYCODE_DPAD_UP: 4767 return snapToPageRelative(1, false /* cycle */, 4768 TaskGridNavHelper.TaskNavDirection.UP); 4769 case KeyEvent.KEYCODE_DPAD_DOWN: 4770 return snapToPageRelative(1, false /* cycle */, 4771 TaskGridNavHelper.TaskNavDirection.DOWN); 4772 case KeyEvent.KEYCODE_DEL: 4773 case KeyEvent.KEYCODE_FORWARD_DEL: 4774 dismissCurrentTask(); 4775 return true; 4776 case KeyEvent.KEYCODE_NUMPAD_DOT: 4777 if (event.isAltPressed()) { 4778 // Numpad DEL pressed while holding Alt. 4779 dismissCurrentTask(); 4780 return true; 4781 } 4782 } 4783 return super.dispatchKeyEvent(event); 4784 } 4785 4786 @Override onFocusChanged(boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect)4787 protected void onFocusChanged(boolean gainFocus, int direction, 4788 @Nullable Rect previouslyFocusedRect) { 4789 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 4790 if (gainFocus && getChildCount() > 0) { 4791 switch (direction) { 4792 case FOCUS_FORWARD: 4793 setCurrentPage(0); 4794 break; 4795 case FOCUS_BACKWARD: 4796 case FOCUS_RIGHT: 4797 case FOCUS_LEFT: 4798 setCurrentPage(getChildCount() - 1); 4799 break; 4800 } 4801 } 4802 } 4803 getContentAlpha()4804 public float getContentAlpha() { 4805 return mContentAlpha; 4806 } 4807 setContentAlpha(float alpha)4808 public void setContentAlpha(float alpha) { 4809 if (alpha == mContentAlpha) { 4810 return; 4811 } 4812 alpha = Utilities.boundToRange(alpha, 0, 1); 4813 mContentAlpha = alpha; 4814 4815 for (TaskView taskView : getTaskViews()) { 4816 taskView.setStableAlpha(alpha); 4817 } 4818 mClearAllButton.setContentAlpha(mContentAlpha); 4819 4820 if (mAddDesktopButton != null) { 4821 mAddDesktopButton.setContentAlpha(mContentAlpha); 4822 } 4823 int alphaInt = Math.round(alpha * 255); 4824 mEmptyMessagePaint.setAlpha(alphaInt); 4825 mEmptyIcon.setAlpha(alphaInt); 4826 mActionsView.getContentAlpha().updateValue(mContentAlpha); 4827 4828 if (alpha > 0) { 4829 setVisibility(VISIBLE); 4830 } else if (!mFreezeViewVisibility) { 4831 setVisibility(INVISIBLE); 4832 } 4833 } 4834 4835 /** 4836 * Freezes the view visibility change. When frozen, the view will not change its visibility 4837 * to gone due to alpha changes. 4838 */ setFreezeViewVisibility(boolean freezeViewVisibility)4839 public void setFreezeViewVisibility(boolean freezeViewVisibility) { 4840 if (mFreezeViewVisibility != freezeViewVisibility) { 4841 mFreezeViewVisibility = freezeViewVisibility; 4842 if (!mFreezeViewVisibility) { 4843 setVisibility(mContentAlpha > 0 ? VISIBLE : INVISIBLE); 4844 } 4845 } 4846 } 4847 4848 @Override setVisibility(int visibility)4849 public void setVisibility(int visibility) { 4850 super.setVisibility(visibility); 4851 if (mActionsView != null) { 4852 mActionsView.updateHiddenFlags(HIDDEN_NO_RECENTS, visibility != VISIBLE); 4853 if (visibility != VISIBLE) { 4854 mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false); 4855 } 4856 } 4857 } 4858 4859 @Override onConfigurationChanged(Configuration newConfig)4860 protected void onConfigurationChanged(Configuration newConfig) { 4861 super.onConfigurationChanged(newConfig); 4862 updateRecentsRotation(); 4863 onOrientationChanged(); 4864 } 4865 4866 /** 4867 * Updates {@link RecentsOrientedState}'s cached RecentsView rotation. 4868 */ updateRecentsRotation()4869 public void updateRecentsRotation() { 4870 final int rotation = TraceHelper.allowIpcs( 4871 "RecentsView.updateRecentsRotation", () -> mContainer.getDisplay().getRotation()); 4872 // Log real orientation change. 4873 if (mOrientationState.setRecentsRotation(rotation)) { 4874 logOrientationChanged(); 4875 } 4876 } 4877 reapplyActiveRotation()4878 public void reapplyActiveRotation() { 4879 RotationTouchHelper rotationTouchHelper = RotationTouchHelper.INSTANCE.get(getContext()); 4880 setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(), 4881 rotationTouchHelper.getDisplayRotation()); 4882 } 4883 setLayoutRotation(int touchRotation, int displayRotation)4884 public void setLayoutRotation(int touchRotation, int displayRotation) { 4885 if (mOrientationState.update(touchRotation, displayRotation)) { 4886 updateOrientationHandler(); 4887 } 4888 } 4889 getPagedViewOrientedState()4890 public RecentsOrientedState getPagedViewOrientedState() { 4891 return mOrientationState; 4892 } 4893 getPagedOrientationHandler()4894 public RecentsPagedOrientationHandler getPagedOrientationHandler() { 4895 return (RecentsPagedOrientationHandler) super.getPagedOrientationHandler(); 4896 } 4897 4898 @Nullable getNextTaskView()4899 public TaskView getNextTaskView() { 4900 return getTaskViewAt(getRunningTaskIndex() + 1); 4901 } 4902 4903 @Nullable getPreviousTaskView()4904 public TaskView getPreviousTaskView() { 4905 return getTaskViewAt(getRunningTaskIndex() - 1); 4906 } 4907 4908 @Nullable getLastLargeTaskView()4909 public TaskView getLastLargeTaskView() { 4910 return mUtils.getLastLargeTaskView(); 4911 } 4912 getLargeTilesCount()4913 public int getLargeTilesCount() { 4914 return mUtils.getLargeTileCount(); 4915 } 4916 4917 @Nullable getCurrentPageTaskView()4918 public TaskView getCurrentPageTaskView() { 4919 return getTaskViewAt(getCurrentPage()); 4920 } 4921 4922 @Nullable getNextPageTaskView()4923 public TaskView getNextPageTaskView() { 4924 return getTaskViewAt(getNextPage()); 4925 } 4926 4927 @Nullable getTaskViewNearestToCenterOfScreen()4928 public TaskView getTaskViewNearestToCenterOfScreen() { 4929 return getTaskViewAt(getPageNearestToCenterOfScreen()); 4930 } 4931 4932 /** 4933 * Returns null instead of indexOutOfBoundsError when index is not in range 4934 */ 4935 @Nullable getTaskViewAt(int index)4936 public TaskView getTaskViewAt(int index) { 4937 View child = getChildAt(index); 4938 return child instanceof TaskView ? (TaskView) child : null; 4939 } 4940 4941 /** 4942 * Returns iterable [TaskView] children. 4943 */ getTaskViews()4944 public RecentsViewUtils.TaskViewsIterable getTaskViews() { 4945 return mUtils.getTaskViews(); 4946 } 4947 setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener listener)4948 public void setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener listener) { 4949 mOnEmptyMessageUpdatedListener = listener; 4950 } 4951 updateEmptyMessage()4952 public void updateEmptyMessage() { 4953 boolean isEmpty = !hasTaskViews(); 4954 boolean hasSizeChanged = mLastMeasureSize.x != getWidth() 4955 || mLastMeasureSize.y != getHeight(); 4956 if (isEmpty == mShowEmptyMessage && !hasSizeChanged) { 4957 return; 4958 } 4959 setContentDescription(isEmpty ? mEmptyMessage : ""); 4960 setFocusable(isEmpty); 4961 mShowEmptyMessage = isEmpty; 4962 updateEmptyStateUi(hasSizeChanged); 4963 invalidate(); 4964 4965 if (mOnEmptyMessageUpdatedListener != null) { 4966 mOnEmptyMessageUpdatedListener.onEmptyMessageUpdated(mShowEmptyMessage); 4967 } 4968 } 4969 4970 @Override onLayout(boolean changed, int left, int top, int right, int bottom)4971 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 4972 // If we're going to a state without overview panel, avoid unnecessary onLayout that 4973 // cause TaskViews to re-arrange during animation to that state. 4974 if (!mOverviewStateEnabled && !mFirstLayout) { 4975 return; 4976 } 4977 4978 mShowAsGridLastOnLayout = showAsGrid(); 4979 4980 super.onLayout(changed, left, top, right, bottom); 4981 4982 updateEmptyStateUi(changed); 4983 4984 setTaskModalness(mTaskModalness); 4985 mLastComputedTaskStartPushOutDistance = null; 4986 mLastComputedTaskEndPushOutDistance = null; 4987 updatePageOffsets(); 4988 runActionOnRemoteHandles( 4989 remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator() 4990 .setScroll(getScrollOffset())); 4991 setImportantForAccessibility(isModal() ? IMPORTANT_FOR_ACCESSIBILITY_NO 4992 : IMPORTANT_FOR_ACCESSIBILITY_AUTO); 4993 } 4994 updatePivots()4995 private void updatePivots() { 4996 if (mOverviewSelectEnabled && !enableGridOnlyOverview()) { 4997 mTempPointF.set(mLastComputedTaskSize.centerX(), mLastComputedTaskSize.bottom); 4998 } else { 4999 mTempRect.set(mLastComputedTaskSize); 5000 getPagedViewOrientedState().getFullScreenScaleAndPivot(mTempRect, 5001 mContainer.getDeviceProfile(), mTempPointF); 5002 } 5003 setPivotX(mTempPointF.x); 5004 setPivotY(mTempPointF.y); 5005 if (enableGridOnlyOverview()) { 5006 runActionOnRemoteHandles(remoteTargetHandle -> 5007 remoteTargetHandle.getTaskViewSimulator().setPivotOverride(mTempPointF)); 5008 } 5009 } 5010 5011 /** 5012 * Sets whether we should force-override the page offset mid-point to the current task, rather 5013 * than the running task, when updating page offsets. 5014 */ setOffsetMidpointIndexOverride(int offsetMidpointIndexOverride)5015 public void setOffsetMidpointIndexOverride(int offsetMidpointIndexOverride) { 5016 if (!enableAdditionalHomeAnimations()) { 5017 return; 5018 } 5019 mOffsetMidpointIndexOverride = offsetMidpointIndexOverride; 5020 updatePageOffsets(); 5021 } 5022 updatePageOffsets()5023 private void updatePageOffsets() { 5024 float offset = mAdjacentPageHorizontalOffset; 5025 float modalOffset = ACCELERATE_0_75.getInterpolation(mTaskModalness); 5026 int count = getChildCount(); 5027 boolean showAsGrid = showAsGrid(); 5028 5029 TaskView runningTask = mRunningTaskViewId == INVALID_PAGE || !mRunningTaskTileHidden 5030 ? null : getRunningTaskView(); 5031 int midpoint = mOffsetMidpointIndexOverride == INVALID_PAGE 5032 ? (runningTask == null ? INVALID_PAGE : indexOfChild(runningTask)) 5033 : mOffsetMidpointIndexOverride; 5034 int modalMidpoint = getCurrentPage(); 5035 TaskView carouselHiddenMidpointTask = runningTask != null ? runningTask 5036 : mUtils.getFirstTaskViewInCarousel(/*nonRunningTaskCarouselHidden=*/true, 5037 /*runningTaskView=*/null); 5038 int carouselHiddenMidpoint = indexOfChild(carouselHiddenMidpointTask); 5039 boolean shouldCalculateOffsetForAllTasks = showAsGrid 5040 && (enableGridOnlyOverview() || enableLargeDesktopWindowingTile()) 5041 && mTaskModalness > 0; 5042 if (shouldCalculateOffsetForAllTasks) { 5043 modalMidpoint = indexOfChild(getSelectedTaskView()); 5044 } 5045 5046 float midpointOffsetSize = 0; 5047 float leftOffsetSize = midpoint - 1 >= 0 5048 ? getHorizontalOffsetSize(midpoint - 1, midpoint, offset) 5049 : 0; 5050 float rightOffsetSize = midpoint + 1 < count 5051 ? getHorizontalOffsetSize(midpoint + 1, midpoint, offset) 5052 : 0; 5053 5054 float modalMidpointOffsetSize = 0; 5055 float modalLeftOffsetSize = 0; 5056 float modalRightOffsetSize = 0; 5057 float gridOffsetSize = 0; 5058 float carouselHiddenOffsetSize = 0; 5059 5060 if (showAsGrid) { 5061 // In grid, we only focus the task on the side. The reference index used for offset 5062 // calculation is the task directly next to the focus task in the grid. 5063 int referenceIndex = modalMidpoint == 0 ? 1 : 0; 5064 gridOffsetSize = referenceIndex < count 5065 ? getHorizontalOffsetSize(referenceIndex, modalMidpoint, modalOffset) 5066 : 0; 5067 } else { 5068 modalLeftOffsetSize = modalMidpoint - 1 >= 0 5069 ? getHorizontalOffsetSize(modalMidpoint - 1, modalMidpoint, modalOffset) 5070 : 0; 5071 modalRightOffsetSize = modalMidpoint + 1 < count 5072 ? getHorizontalOffsetSize(modalMidpoint + 1, modalMidpoint, modalOffset) 5073 : 0; 5074 } 5075 5076 int primarySize = getPagedOrientationHandler().getPrimaryValue(getWidth(), getHeight()); 5077 float maxOverscroll = primarySize * OverScroll.OVERSCROLL_DAMP_FACTOR; 5078 for (int i = 0; i < count; i++) { 5079 View child = getChildAt(i); 5080 float translation = i == midpoint 5081 ? midpointOffsetSize 5082 : i < midpoint 5083 ? leftOffsetSize 5084 : rightOffsetSize; 5085 if (shouldCalculateOffsetForAllTasks) { 5086 gridOffsetSize = getHorizontalOffsetSize(i, modalMidpoint, modalOffset); 5087 gridOffsetSize = Math.abs(gridOffsetSize) * (i <= modalMidpoint ? 1 : -1); 5088 } 5089 if (enableLargeDesktopWindowingTile()) { 5090 if (child instanceof TaskView 5091 && !mUtils.isVisibleInCarousel((TaskView) child, 5092 runningTask, /*nonRunningTaskCarouselHidden=*/true)) { 5093 // Increment carouselHiddenOffsetSize by maxOverscroll so it won't be on screen 5094 // even when user overscroll. 5095 carouselHiddenOffsetSize = (Math.abs(getMaxHorizontalOffsetSize(i, 5096 carouselHiddenMidpoint)) + maxOverscroll) 5097 * mDesktopCarouselDetachProgress; 5098 carouselHiddenOffsetSize = carouselHiddenOffsetSize * ( 5099 i <= carouselHiddenMidpoint ? 1 : -1); 5100 } else { 5101 carouselHiddenOffsetSize = 0; 5102 } 5103 } 5104 float modalTranslation = i == modalMidpoint 5105 ? modalMidpointOffsetSize 5106 : showAsGrid 5107 ? gridOffsetSize 5108 : i < modalMidpoint ? modalLeftOffsetSize : modalRightOffsetSize; 5109 boolean skipTranslationOffset = enableDesktopTaskAlphaAnimation() 5110 && i == getRunningTaskIndex() 5111 && child instanceof DesktopTaskView; 5112 float totalTranslationX = (skipTranslationOffset ? 0f : translation) + modalTranslation 5113 + carouselHiddenOffsetSize; 5114 if (child instanceof TaskView taskView) { 5115 taskView.getPrimaryTaskOffsetTranslationProperty().set(taskView, totalTranslationX); 5116 } else if (child instanceof ClearAllButton) { 5117 getPagedOrientationHandler().getPrimaryViewTranslate().set(child, 5118 totalTranslationX); 5119 } else if (child instanceof AddDesktopButton addDesktopButton) { 5120 addDesktopButton.setOffsetTranslationX(totalTranslationX); 5121 } 5122 if (mEnableDrawingLiveTile && i == getRunningTaskIndex()) { 5123 runActionOnRemoteHandles( 5124 remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator() 5125 .taskPrimaryTranslation.value = totalTranslationX); 5126 redrawLiveTile(); 5127 } 5128 5129 if (showAsGrid && enableGridOnlyOverview() && child instanceof TaskView taskView) { 5130 float totalTranslationY = getVerticalOffsetSize(taskView, modalOffset); 5131 FloatProperty<TaskView> translationPropertyY = 5132 taskView.getSecondaryTaskOffsetTranslationProperty(); 5133 translationPropertyY.set(taskView, totalTranslationY); 5134 } 5135 } 5136 updateCurveProperties(); 5137 } 5138 5139 /** 5140 * Computes the child position with persistent translation considered (see 5141 * {@link TaskView#getPersistentTranslationX()}. 5142 */ 5143 private void getPersistentChildPosition(int childIndex, int midPointScroll, RectF outRect) { 5144 View child = getChildAt(childIndex); 5145 outRect.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom()); 5146 if (child instanceof TaskView) { 5147 TaskView taskView = (TaskView) child; 5148 outRect.offset(taskView.getPersistentTranslationX(), 5149 taskView.getPersistentTranslationY()); 5150 outRect.top += mContainer.getDeviceProfile().overviewTaskThumbnailTopMarginPx; 5151 5152 mTempMatrix.reset(); 5153 float persistentScale = taskView.getPersistentScale(); 5154 mTempMatrix.postScale(persistentScale, persistentScale, 5155 mIsRtl ? outRect.right : outRect.left, outRect.top); 5156 mTempMatrix.mapRect(outRect); 5157 } 5158 outRect.offset(getPagedOrientationHandler().getPrimaryValue(-midPointScroll, 0), 5159 getPagedOrientationHandler().getSecondaryValue(-midPointScroll, 0)); 5160 } 5161 5162 /** 5163 * Computes the distance to offset the given child such that it is completely offscreen when 5164 * translating away from the given midpoint. 5165 * 5166 * @param offsetProgress From 0 to 1 where 0 means no offset and 1 means offset offscreen. 5167 */ 5168 private float getHorizontalOffsetSize(int childIndex, int midpointIndex, float offsetProgress) { 5169 if (offsetProgress == 0) { 5170 // Don't bother calculating everything below if we won't offset anyway. 5171 return 0; 5172 } 5173 5174 return getMaxHorizontalOffsetSize(childIndex, midpointIndex) * offsetProgress; 5175 } 5176 5177 /** 5178 * Computes the distance to offset the given child such that it is completely offscreen when 5179 * translating away from the given midpoint. 5180 */ 5181 private float getMaxHorizontalOffsetSize(int childIndex, int midpointIndex) { 5182 // First, get the position of the task relative to the midpoint. If there is no midpoint 5183 // then we just use the normal (centered) task position. 5184 RectF taskPosition = mTempRectF; 5185 // Whether the task should be shifted to start direction (i.e. left edge for portrait, top 5186 // edge for landscape/seascape). 5187 boolean isStartShift; 5188 if (midpointIndex > -1) { 5189 // When there is a midpoint reference task, adjacent tasks have less distance to travel 5190 // to reach offscreen. Offset the task position to the task's starting point, and offset 5191 // by current page's scroll diff. 5192 int midpointScroll = getScrollForPage(midpointIndex) 5193 + getPagedOrientationHandler().getPrimaryScroll(this) 5194 - getScrollForPage(mCurrentPage); 5195 5196 getPersistentChildPosition(midpointIndex, midpointScroll, taskPosition); 5197 float midpointStart = getPagedOrientationHandler().getStart(taskPosition); 5198 5199 getPersistentChildPosition(childIndex, midpointScroll, taskPosition); 5200 // Assume child does not overlap with midPointChild. 5201 isStartShift = getPagedOrientationHandler().getStart(taskPosition) < midpointStart; 5202 } else { 5203 // Position the task at scroll position. 5204 getPersistentChildPosition(childIndex, getScrollForPage(childIndex), taskPosition); 5205 isStartShift = mIsRtl; 5206 } 5207 5208 // Next, calculate the distance to move the task off screen. We also need to account for 5209 // RecentsView scale, because it moves tasks based on its pivot. To do this, we move the 5210 // task position to where it would be offscreen at scale = 1 (computed above), then we 5211 // apply the scale via getMatrix() to determine how much that moves the task from its 5212 // desired position, and adjust the computed distance accordingly. 5213 float distanceToOffscreen; 5214 if (isStartShift) { 5215 float desiredStart = -getPagedOrientationHandler().getPrimarySize(taskPosition); 5216 distanceToOffscreen = -getPagedOrientationHandler().getEnd(taskPosition); 5217 if (mLastComputedTaskStartPushOutDistance == null) { 5218 taskPosition.offsetTo( 5219 getPagedOrientationHandler().getPrimaryValue(desiredStart, 0f), 5220 getPagedOrientationHandler().getSecondaryValue(desiredStart, 0f)); 5221 getMatrix().mapRect(taskPosition); 5222 mLastComputedTaskStartPushOutDistance = getPagedOrientationHandler().getEnd( 5223 taskPosition) / getPagedOrientationHandler().getPrimaryScale(this); 5224 } 5225 distanceToOffscreen -= mLastComputedTaskStartPushOutDistance; 5226 } else { 5227 float desiredStart = getPagedOrientationHandler().getPrimarySize(this); 5228 distanceToOffscreen = desiredStart - getPagedOrientationHandler().getStart( 5229 taskPosition); 5230 if (mLastComputedTaskEndPushOutDistance == null) { 5231 taskPosition.offsetTo( 5232 getPagedOrientationHandler().getPrimaryValue(desiredStart, 0f), 5233 getPagedOrientationHandler().getSecondaryValue(desiredStart, 0f)); 5234 getMatrix().mapRect(taskPosition); 5235 mLastComputedTaskEndPushOutDistance = (getPagedOrientationHandler().getStart( 5236 taskPosition) - desiredStart) 5237 / getPagedOrientationHandler().getPrimaryScale(this); 5238 } 5239 distanceToOffscreen -= mLastComputedTaskEndPushOutDistance; 5240 } 5241 return distanceToOffscreen; 5242 } 5243 5244 /** 5245 * Computes the vertical distance to offset a given child such that it is completely offscreen. 5246 * 5247 * @param offsetProgress From 0 to 1 where 0 means no offset and 1 means offset offscreen. 5248 */ 5249 private float getVerticalOffsetSize(TaskView taskView, float offsetProgress) { 5250 if (offsetProgress == 0 || !(showAsGrid() && enableGridOnlyOverview()) 5251 || getSelectedTaskView() == null) { 5252 // Don't bother calculating everything below if we won't offset vertically. 5253 return 0; 5254 } 5255 5256 // First, get the position of the task relative to the top row. 5257 Rect taskPosition = getTaskBounds(taskView); 5258 5259 boolean isSelectedTaskTopRow = mTopRowIdSet.contains(getSelectedTaskView().getTaskViewId()); 5260 boolean isChildTopRow = mTopRowIdSet.contains(taskView.getTaskViewId()); 5261 // Whether the task should be shifted to the top. 5262 boolean isTopShift = !isSelectedTaskTopRow && isChildTopRow; 5263 boolean isBottomShift = isSelectedTaskTopRow && !isChildTopRow; 5264 5265 // Next, calculate the distance to move the task off screen at scale = 1. 5266 float distanceToOffscreen = 0; 5267 if (isTopShift) { 5268 distanceToOffscreen = -taskPosition.bottom; 5269 } else if (isBottomShift) { 5270 distanceToOffscreen = mContainer.getDeviceProfile().heightPx - taskPosition.top; 5271 } 5272 return distanceToOffscreen * offsetProgress; 5273 } 5274 5275 protected void setTaskViewsResistanceTranslation(float translation) { 5276 mTaskViewsSecondaryTranslation = translation; 5277 for (TaskView taskView : getTaskViews()) { 5278 taskView.getTaskResistanceTranslationProperty().set(taskView, 5279 translation / getScaleY()); 5280 } 5281 runActionOnRemoteHandles( 5282 remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator() 5283 .recentsViewSecondaryTranslation.value = translation); 5284 } 5285 5286 private void updateTaskViewsSnapshotRadius() { 5287 for (TaskView taskView : getTaskViews()) { 5288 taskView.updateFullscreenParams(); 5289 } 5290 } 5291 5292 protected void setTaskViewsPrimarySplitTranslation(float translation) { 5293 mTaskViewsPrimarySplitTranslation = translation; 5294 for (TaskView taskView : getTaskViews()) { 5295 taskView.getPrimarySplitTranslationProperty().set(taskView, translation); 5296 } 5297 } 5298 5299 protected void setTaskViewsSecondarySplitTranslation(float translation) { 5300 mTaskViewsSecondarySplitTranslation = translation; 5301 for (TaskView taskView : getTaskViews()) { 5302 if (taskView == mSplitHiddenTaskView && !taskView.containsMultipleTasks()) { 5303 continue; 5304 } 5305 taskView.getSecondarySplitTranslationProperty().set(taskView, translation); 5306 } 5307 } 5308 5309 /** 5310 * Resets the visuals when exit modal state. 5311 */ 5312 public void resetModalVisuals() { 5313 if (getSelectedTaskView() != null) { 5314 getSelectedTaskView().taskContainers.forEach( 5315 taskContainer -> taskContainer.getOverlay().resetModalVisuals()); 5316 } 5317 } 5318 5319 /** 5320 * Primarily used by overview actions to initiate split from focused task, logs the source 5321 * of split invocation as such. 5322 */ 5323 public void initiateSplitSelect(TaskContainer taskContainer) { 5324 int defaultSplitPosition = getPagedOrientationHandler() 5325 .getDefaultSplitPosition(mContainer.getDeviceProfile()); 5326 initiateSplitSelect(taskContainer, defaultSplitPosition, LAUNCHER_OVERVIEW_ACTIONS_SPLIT); 5327 } 5328 5329 /** TODO(b/266477929): Consolidate this call w/ the one below */ 5330 public void initiateSplitSelect(TaskContainer taskContainer, 5331 @StagePosition int stagePosition, 5332 StatsLogManager.EventEnum splitEvent) { 5333 TaskView taskView = taskContainer.getTaskView(); 5334 mSplitHiddenTaskView = taskView; 5335 mSplitSelectStateController.setInitialTaskSelect(null /*intent*/, stagePosition, 5336 taskContainer.getItemInfo(), splitEvent, taskContainer.getTask().key.id); 5337 mSplitSelectStateController.setAnimateCurrentTaskDismissal( 5338 true /*animateCurrentTaskDismissal*/); 5339 mSplitHiddenTaskViewIndex = indexOfChild(taskView); 5340 } 5341 5342 /** 5343 * Called when staging a split from Home/AllApps/Overview (Taskbar), 5344 * using the icon long-press menu. 5345 * Attempts to initiate split with an existing taskView, if one exists 5346 */ 5347 public void initiateSplitSelect(SplitSelectSource splitSelectSource) { 5348 TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "enterSplitSelect"); 5349 mSplitSelectSource = splitSelectSource; 5350 mSplitHiddenTaskView = getTaskViewByTaskId(splitSelectSource.alreadyRunningTaskId); 5351 mSplitHiddenTaskViewIndex = indexOfChild(mSplitHiddenTaskView); 5352 mSplitSelectStateController 5353 .setAnimateCurrentTaskDismissal(splitSelectSource.animateCurrentTaskDismissal 5354 && mSplitHiddenTaskView != null 5355 && !(mSplitHiddenTaskView instanceof DesktopTaskView)); 5356 5357 // Prevent dismissing whole task if we're only initiating from one of 2 tasks in split pair 5358 mSplitSelectStateController.setDismissingFromSplitPair(mSplitHiddenTaskView != null 5359 && mSplitHiddenTaskView instanceof GroupedTaskView); 5360 mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent, 5361 splitSelectSource.position.stagePosition, splitSelectSource.getItemInfo(), 5362 splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTaskId); 5363 } 5364 5365 /** 5366 * Animate DesktopTaskView(s) to hide in split select 5367 */ 5368 public void handleDesktopTaskInSplitSelectState(PendingAnimation builder, 5369 Interpolator deskTopFadeInterPolator) { 5370 SplitAnimationTimings timings = AnimUtils.getDeviceOverviewToSplitTimings( 5371 mContainer.getDeviceProfile().isTablet); 5372 if (enableLargeDesktopWindowingTile()) { 5373 getTaskViews().forEachWithIndexInParent((index, taskView) -> { 5374 if (taskView instanceof DesktopTaskView) { 5375 // Setting pivot to scale down from screen centre. 5376 if (isTaskViewVisible(taskView)) { 5377 float pivotX = 0f; 5378 if (index < mCurrentPage) { 5379 pivotX = mIsRtl ? taskView.getWidth() / 2f - mPageSpacing 5380 - taskView.getWidth() 5381 : taskView.getWidth() / 2f + mPageSpacing + taskView.getWidth(); 5382 } else if (index == mCurrentPage) { 5383 pivotX = taskView.getWidth() / 2f; 5384 } else { 5385 pivotX = mIsRtl ? taskView.getWidth() + mPageSpacing 5386 + taskView.getWidth() 5387 : taskView.getWidth() - mPageSpacing - taskView.getWidth(); 5388 } 5389 taskView.setPivotX(pivotX); 5390 taskView.setPivotY(taskView.getHeight() / 2f); 5391 builder.add(ObjectAnimator 5392 .ofFloat(taskView, TaskView.DISMISS_SCALE, 0.95f), 5393 clampToProgress(timings.getDesktopTaskScaleInterpolator(), 0f, 5394 timings.getDesktopFadeSplitAnimationEndOffset())); 5395 } 5396 builder.addFloat(taskView, SPLIT_ALPHA, 1f, 0f, 5397 clampToProgress(deskTopFadeInterPolator, 0f, 5398 timings.getDesktopFadeSplitAnimationEndOffset())); 5399 } 5400 }); 5401 } 5402 } 5403 5404 /** 5405 * While exiting from split mode, show all existing DesktopTaskViews. 5406 */ 5407 public void resetDesktopTaskFromSplitSelectState() { 5408 if (enableLargeDesktopWindowingTile()) { 5409 for (TaskView taskView : getTaskViews()) { 5410 if (taskView instanceof DesktopTaskView) { 5411 taskView.setSplitAlpha(1f); 5412 } 5413 } 5414 } 5415 } 5416 5417 /** 5418 * Modifies a PendingAnimation with the animations for entering split staging 5419 */ 5420 public void createSplitSelectInitAnimation(PendingAnimation builder, int duration) { 5421 boolean isInitiatingSplitFromTaskView = 5422 mSplitSelectStateController.isAnimateCurrentTaskDismissal(); 5423 boolean isInitiatingTaskViewSplitPair = 5424 mSplitSelectStateController.isDismissingFromSplitPair(); 5425 if (isInitiatingSplitFromTaskView && isInitiatingTaskViewSplitPair 5426 && mSplitHiddenTaskView instanceof GroupedTaskView groupedTaskView) { 5427 // Splitting from Overview for split pair task 5428 createInitialSplitSelectAnimation(builder); 5429 5430 // Animate pair thumbnail into full thumbnail 5431 boolean primaryTaskSelected = groupedTaskView.getLeftTopTaskContainer().getTask().key.id 5432 == mSplitSelectStateController.getInitialTaskId(); 5433 TaskContainer taskContainer = 5434 primaryTaskSelected ? groupedTaskView.getRightBottomTaskContainer() 5435 : groupedTaskView.getLeftTopTaskContainer(); 5436 mSplitSelectStateController.getSplitAnimationController() 5437 .addInitialSplitFromPair(taskContainer, builder, 5438 mContainer.getDeviceProfile(), 5439 mSplitHiddenTaskView.getLayoutParams().width, 5440 mSplitHiddenTaskView.getLayoutParams().height, 5441 primaryTaskSelected); 5442 builder.addOnFrameCallback(() -> { 5443 if (!enableRefactorTaskThumbnail()) { 5444 taskContainer.getThumbnailViewDeprecated().refreshSplashView(); 5445 } 5446 mSplitHiddenTaskView.updateFullscreenParams(); 5447 }); 5448 } else if (isInitiatingSplitFromTaskView) { 5449 if (Flags.enableHoverOfChildElementsInTaskview()) { 5450 mSplitHiddenTaskView.setBorderEnabled(false); 5451 } 5452 // Splitting from Overview for fullscreen task 5453 createTaskDismissAnimation(builder, mSplitHiddenTaskView, true, false, duration, 5454 true /* dismissingForSplitSelection*/, false /* isExpressiveDismiss */); 5455 } else { 5456 // Splitting from Home 5457 TaskView currentPageTaskView = getTaskViewAt(mCurrentPage); 5458 // When current page is a Desktop task it needs special handling to 5459 // display correct animation in split mode 5460 if (currentPageTaskView instanceof DesktopTaskView) { 5461 createTaskDismissAnimation(builder, null, true, false, duration, 5462 true /* dismissingForSplitSelection*/, false /* isExpressiveDismiss */); 5463 } else { 5464 createInitialSplitSelectAnimation(builder); 5465 } 5466 } 5467 } 5468 5469 /** 5470 * Confirms the selection of the next split task. The extra data is passed through because the 5471 * user may be selecting a subtask in a group. 5472 * 5473 * @param containerTaskView If our second selected app is currently running in Recents, this is 5474 * the "container" TaskView from Recents. If we are starting a fresh 5475 * instance of the app from an Intent, this will be null. 5476 * @param task The Task corresponding to our second selected app. If we are 5477 * starting a fresh 5478 * instance of the app from an Intent, this will be null. 5479 * @param drawable The Drawable corresponding to our second selected app's icon. 5480 * @param secondView The View representing the current space on the screen where the 5481 * second app 5482 * is (either the ThumbnailView or the tapped icon). 5483 * @param intent If we are launching a fresh instance of the app, this is the Intent 5484 * for it. If 5485 * the second app is already running in Recents, this will be null. 5486 * @param user If we are launching a fresh instance of the app, this is the 5487 * UserHandle for it. 5488 * If the second app is already running in Recents, this will be null. 5489 * @return true if waiting for confirmation of second app or if split animations are running, 5490 * false otherwise 5491 */ 5492 public boolean confirmSplitSelect(TaskView containerTaskView, Task task, Drawable drawable, 5493 View secondView, @Nullable Bitmap thumbnail, Intent intent, UserHandle user, 5494 ItemInfo itemInfo) { 5495 if (canLaunchFullscreenTask()) { 5496 return false; 5497 } 5498 if (mSplitSelectStateController.isBothSplitAppsConfirmed()) { 5499 Log.w(TAG, splitFailureMessage( 5500 "confirmSplitSelect", "both apps have already been set")); 5501 return true; 5502 } 5503 // Second task is selected either as an already-running Task or an Intent 5504 if (task != null) { 5505 if (!task.isDockable) { 5506 // Task does not support split screen 5507 mSplitUnsupportedToast.show(); 5508 Log.w(TAG, splitFailureMessage("confirmSplitSelect", 5509 "selected Task (" + task.key.getPackageName() 5510 + ") is not dockable / does not support splitscreen")); 5511 return true; 5512 } 5513 mSplitSelectStateController.setSecondTask(task, itemInfo); 5514 } else { 5515 mSplitSelectStateController.setSecondTask(intent, user, itemInfo); 5516 } 5517 5518 RectF secondTaskStartingBounds = new RectF(); 5519 Rect secondTaskEndingBounds = new Rect(); 5520 // TODO(194414938) starting bounds seem slightly off, investigate 5521 Rect firstTaskStartingBounds = new Rect(); 5522 Rect firstTaskEndingBounds = mTempRect; 5523 5524 boolean isTablet = mContainer.getDeviceProfile().isTablet; 5525 SplitAnimationTimings timings = AnimUtils.getDeviceSplitToConfirmTimings(isTablet); 5526 PendingAnimation pendingAnimation = new PendingAnimation(timings.getDuration()); 5527 5528 int halfDividerSize = getResources() 5529 .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2; 5530 getPagedOrientationHandler().getFinalSplitPlaceholderBounds(halfDividerSize, 5531 mContainer.getDeviceProfile(), 5532 mSplitSelectStateController.getActiveSplitStagePosition(), firstTaskEndingBounds, 5533 secondTaskEndingBounds); 5534 5535 mSplitScrim = mSplitSelectStateController.getSplitAnimationController() 5536 .addScrimBehindAnim(pendingAnimation, mContainer, getContext()); 5537 FloatingTaskView firstFloatingTaskView = 5538 mSplitSelectStateController.getFirstFloatingTaskView(); 5539 firstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds); 5540 firstFloatingTaskView.addConfirmAnimation(pendingAnimation, 5541 new RectF(firstTaskStartingBounds), firstTaskEndingBounds, 5542 false /* fadeWithThumbnail */, true /* isStagedTask */); 5543 5544 safeRemoveDragLayerView(mSecondFloatingTaskView); 5545 5546 mSecondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mContainer, secondView, 5547 thumbnail, drawable, secondTaskStartingBounds); 5548 mSecondFloatingTaskView.setAlpha(1); 5549 mSecondFloatingTaskView.addConfirmAnimation(pendingAnimation, secondTaskStartingBounds, 5550 secondTaskEndingBounds, true /* fadeWithThumbnail */, false /* isStagedTask */); 5551 5552 pendingAnimation.setViewAlpha(mSplitSelectStateController.getSplitInstructionsView(), 0, 5553 clampToProgress(LINEAR, timings.getInstructionsFadeStartOffset(), 5554 timings.getInstructionsFadeEndOffset())); 5555 5556 pendingAnimation.addEndListener(aBoolean -> { 5557 mSplitSelectStateController.launchSplitTasks( 5558 aBoolean1 -> { 5559 InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER); 5560 mSplitSelectStateController.resetState(); 5561 }); 5562 }); 5563 5564 mSecondSplitHiddenView = containerTaskView; 5565 if (mSecondSplitHiddenView != null) { 5566 mSecondSplitHiddenView.setThumbnailVisibility(INVISIBLE, 5567 mSplitSelectStateController.getSecondTaskId()); 5568 } 5569 5570 InteractionJankMonitorWrapper.begin(this, Cuj.CUJ_SPLIT_SCREEN_ENTER, 5571 "Second tile selected"); 5572 5573 // Fade out all other views underneath placeholders 5574 ObjectAnimator tvFade = ObjectAnimator.ofFloat(this, RecentsView.CONTENT_ALPHA, 1, 0); 5575 pendingAnimation.add(tvFade, DECELERATE_2, SpringProperty.DEFAULT); 5576 pendingAnimation.buildAnim().start(); 5577 return true; 5578 } 5579 5580 @SuppressLint("WrongCall") 5581 protected void resetFromSplitSelectionState() { 5582 safeRemoveDragLayerView(mSplitSelectStateController.getFirstFloatingTaskView()); 5583 safeRemoveDragLayerView(mSecondFloatingTaskView); 5584 safeRemoveDragLayerView(mSplitSelectStateController.getSplitInstructionsView()); 5585 safeRemoveDragLayerView(mSplitScrim); 5586 mSecondFloatingTaskView = null; 5587 mSplitSelectSource = null; 5588 mSplitSelectStateController.getSplitAnimationController() 5589 .removeSplitInstructionsView(mContainer); 5590 5591 if (mSecondSplitHiddenView != null) { 5592 mSecondSplitHiddenView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID); 5593 mSecondSplitHiddenView = null; 5594 } 5595 5596 // We are leaving split selection state, so it is safe to reset thumbnail translations for 5597 // the next time split is invoked. 5598 setTaskViewsPrimarySplitTranslation(0); 5599 setTaskViewsSecondarySplitTranslation(0); 5600 5601 if (mSplitHiddenTaskViewIndex == -1) { 5602 return; 5603 } 5604 if (!mContainer.getDeviceProfile().isTablet) { 5605 int pageToSnapTo = mCurrentPage; 5606 if (mSplitHiddenTaskViewIndex <= pageToSnapTo) { 5607 pageToSnapTo += 1; 5608 } else { 5609 pageToSnapTo = mSplitHiddenTaskViewIndex; 5610 } 5611 snapToPageImmediately(pageToSnapTo); 5612 } 5613 onLayout(false /* changed */, getLeft(), getTop(), getRight(), getBottom()); 5614 5615 resetTaskVisuals(); 5616 mSplitHiddenTaskViewIndex = -1; 5617 if (mSplitHiddenTaskView != null) { 5618 mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID); 5619 // mSplitHiddenTaskView is set when split select animation starts. The TaskView is only 5620 // removed when when the animation finishes. So in the case of overview being dismissed 5621 // during the animation, we should not call clearAndRecycleTaskView() because it has 5622 // not been removed yet. 5623 if (mSplitHiddenTaskView.getParent() == null) { 5624 clearAndRecycleTaskView(mSplitHiddenTaskView); 5625 } 5626 mSplitHiddenTaskView = null; 5627 } 5628 } 5629 5630 private void safeRemoveDragLayerView(@Nullable View viewToRemove) { 5631 if (viewToRemove != null) { 5632 mContainer.getDragLayer().removeView(viewToRemove); 5633 } 5634 } 5635 5636 /** 5637 * Returns how much additional translation there should be for each of the child TaskViews. 5638 * Note that the translation can be its primary or secondary dimension. 5639 */ 5640 public float getSplitSelectTranslation() { 5641 DeviceProfile deviceProfile = mContainer.getDeviceProfile(); 5642 RecentsPagedOrientationHandler orientationHandler = getPagedOrientationHandler(); 5643 int splitPosition = getSplitSelectController().getActiveSplitStagePosition(); 5644 int splitPlaceholderSize = 5645 mContainer.getResources().getDimensionPixelSize(R.dimen.split_placeholder_size); 5646 int direction = orientationHandler.getSplitTranslationDirectionFactor( 5647 splitPosition, deviceProfile); 5648 5649 if (deviceProfile.isTablet && deviceProfile.isLeftRightSplit) { 5650 // Only shift TaskViews if there is not enough space on the side of 5651 // mLastComputedTaskSize to minimize motion. 5652 int sideSpace = mIsRtl 5653 ? deviceProfile.widthPx - mLastComputedTaskSize.right 5654 : mLastComputedTaskSize.left; 5655 int extraSpace = splitPlaceholderSize + mPageSpacing - sideSpace; 5656 if (extraSpace <= 0f) { 5657 return 0f; 5658 } 5659 5660 return extraSpace * direction; 5661 } 5662 5663 return splitPlaceholderSize * direction; 5664 } 5665 5666 protected void onRotateInSplitSelectionState() { 5667 getPagedOrientationHandler().getInitialSplitPlaceholderBounds(mSplitPlaceholderSize, 5668 mSplitPlaceholderInset, mContainer.getDeviceProfile(), 5669 mSplitSelectStateController.getActiveSplitStagePosition(), mTempRect); 5670 mTempRectF.set(mTempRect); 5671 FloatingTaskView firstFloatingTaskView = 5672 mSplitSelectStateController.getFirstFloatingTaskView(); 5673 firstFloatingTaskView.updateOrientationHandler(getPagedOrientationHandler()); 5674 firstFloatingTaskView.update(mTempRectF, /*progress=*/1f); 5675 5676 RecentsPagedOrientationHandler orientationHandler = getPagedOrientationHandler(); 5677 Pair<FloatProperty<RecentsView<?, ?>>, FloatProperty<RecentsView<?, ?>>> taskViewsFloat = 5678 orientationHandler.getSplitSelectTaskOffset( 5679 TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION, 5680 mContainer.getDeviceProfile()); 5681 taskViewsFloat.first.set(this, getSplitSelectTranslation()); 5682 taskViewsFloat.second.set(this, 0f); 5683 5684 if (mSplitSelectStateController.getSplitInstructionsView() != null) { 5685 mSplitSelectStateController.getSplitInstructionsView().ensureProperRotation(); 5686 } 5687 } 5688 5689 private void updateDeadZoneRects() { 5690 // Get the deadzone rect surrounding the clear all button to not dismiss overview to home 5691 mClearAllButtonDeadZoneRect.setEmpty(); 5692 if (mClearAllButton.getWidth() > 0) { 5693 int verticalMargin = getResources() 5694 .getDimensionPixelSize(R.dimen.recents_clear_all_deadzone_vertical_margin); 5695 mClearAllButton.getHitRect(mClearAllButtonDeadZoneRect); 5696 mClearAllButtonDeadZoneRect.inset(-getPaddingRight() / 2, -verticalMargin); 5697 } 5698 5699 mUtils.updateTaskViewDeadZoneRect(mTaskViewDeadZoneRect, mTopRowDeadZoneRect, 5700 mBottomRowDeadZoneRect); 5701 } 5702 5703 private void updateEmptyStateUi(boolean sizeChanged) { 5704 boolean hasValidSize = getWidth() > 0 && getHeight() > 0; 5705 if (sizeChanged && hasValidSize) { 5706 mEmptyTextLayout = null; 5707 mLastMeasureSize.set(getWidth(), getHeight()); 5708 } 5709 5710 if (mShowEmptyMessage && hasValidSize && mEmptyTextLayout == null) { 5711 int availableWidth = mLastMeasureSize.x - mEmptyMessagePadding - mEmptyMessagePadding; 5712 mEmptyTextLayout = StaticLayout.Builder.obtain(mEmptyMessage, 0, mEmptyMessage.length(), 5713 mEmptyMessagePaint, availableWidth) 5714 .setAlignment(Layout.Alignment.ALIGN_CENTER) 5715 .build(); 5716 int totalHeight = mEmptyTextLayout.getHeight() 5717 + mEmptyMessagePadding + mEmptyIcon.getIntrinsicHeight(); 5718 5719 int top = (mLastMeasureSize.y - totalHeight) / 2; 5720 int left = (mLastMeasureSize.x - mEmptyIcon.getIntrinsicWidth()) / 2; 5721 mEmptyIcon.setBounds(left, top, left + mEmptyIcon.getIntrinsicWidth(), 5722 top + mEmptyIcon.getIntrinsicHeight()); 5723 } 5724 } 5725 5726 @Override verifyDrawable(Drawable who)5727 protected boolean verifyDrawable(Drawable who) { 5728 return super.verifyDrawable(who) || (mShowEmptyMessage && who == mEmptyIcon); 5729 } 5730 maybeDrawEmptyMessage(Canvas canvas)5731 protected void maybeDrawEmptyMessage(Canvas canvas) { 5732 if (mShowEmptyMessage && mEmptyTextLayout != null) { 5733 // Offsets icon and text up so that the vertical center of screen (accounting for 5734 // insets) is between icon and text. 5735 int offset = (mEmptyIcon.getIntrinsicHeight() + mEmptyMessagePadding) / 2; 5736 5737 canvas.save(); 5738 canvas.translate(getScrollX() + (mInsets.left - mInsets.right) / 2f, 5739 (mInsets.top - mInsets.bottom) / 2f - offset); 5740 mEmptyIcon.draw(canvas); 5741 canvas.translate(mEmptyMessagePadding, 5742 mEmptyIcon.getBounds().bottom + mEmptyMessagePadding); 5743 mEmptyTextLayout.draw(canvas); 5744 canvas.restore(); 5745 } 5746 } 5747 5748 /** 5749 * Animate adjacent tasks off screen while scaling up. 5750 * 5751 * If launching one of the adjacent tasks, parallax the center task and other adjacent task 5752 * to the right. 5753 */ 5754 @SuppressLint("Recycle") createAdjacentPageAnimForTaskLaunch(TaskView taskView)5755 public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView taskView) { 5756 AnimatorSet anim = new AnimatorSet(); 5757 5758 int taskIndex = indexOfChild(taskView); 5759 int centerTaskIndex = getCurrentPage(); 5760 5761 float toScale = getMaxScaleForFullScreen(); 5762 boolean showAsGrid = showAsGrid(); 5763 boolean zoomInTaskView = showAsGrid ? taskView.isLargeTile() : taskIndex == centerTaskIndex; 5764 if (zoomInTaskView) { 5765 anim.play(ObjectAnimator.ofFloat(this, RECENTS_SCALE_PROPERTY, toScale)); 5766 anim.play(ObjectAnimator.ofFloat(this, FULLSCREEN_PROGRESS, 1)); 5767 anim.addListener(new AnimatorListenerAdapter() { 5768 @Override 5769 public void onAnimationStart(@NonNull Animator animation) { 5770 taskView.getThumbnailBounds(mTempRect, /*relativeToDragLayer=*/true); 5771 getTaskDimension(mContext, mContainer.getDeviceProfile(), mTempPointF); 5772 Rect fullscreenBounds = new Rect(0, 0, (int) mTempPointF.x, 5773 (int) mTempPointF.y); 5774 Utilities.getPivotsForScalingRectToRect(mTempRect, fullscreenBounds, 5775 mTempPointF); 5776 setPivotX(mTempPointF.x); 5777 setPivotY(mTempPointF.y); 5778 5779 // If live tile is not launching, apply pivot to live tile as well and bring it 5780 // above RecentsView to avoid wallpaper blur from being applied to it. 5781 if (!taskView.isRunningTask()) { 5782 runActionOnRemoteHandles( 5783 remoteTargetHandle -> 5784 remoteTargetHandle.getTaskViewSimulator() 5785 .setPivotOverride(mTempPointF)); 5786 mBlurUtils.setDrawLiveTileBelowRecents(false); 5787 } 5788 } 5789 5790 @Override 5791 public void onAnimationEnd(Animator animation) { 5792 // If live tile is not launching, reset the pivot applied above. 5793 if (!taskView.isRunningTask()) { 5794 runActionOnRemoteHandles( 5795 remoteTargetHandle -> { 5796 remoteTargetHandle.getTaskViewSimulator().setPivotOverride( 5797 null); 5798 }); 5799 } 5800 } 5801 }); 5802 } else if (!showAsGrid) { 5803 // We are launching an adjacent task, so parallax the center and other adjacent task. 5804 float displacementX = taskView.getWidth() * (toScale - 1f); 5805 float primaryTranslation = mIsRtl ? -displacementX : displacementX; 5806 anim.play(ObjectAnimator.ofFloat(getPageAt(centerTaskIndex), 5807 getPagedOrientationHandler().getPrimaryViewTranslate(), primaryTranslation)); 5808 int runningTaskIndex = getRunningTaskIndex(); 5809 if (runningTaskIndex != -1 && runningTaskIndex != taskIndex 5810 && getRemoteTargetHandles() != null) { 5811 for (RemoteTargetHandle remoteHandle : getRemoteTargetHandles()) { 5812 anim.play(ObjectAnimator.ofFloat( 5813 remoteHandle.getTaskViewSimulator().taskPrimaryTranslation, 5814 AnimatedFloat.VALUE, 5815 primaryTranslation)); 5816 } 5817 } 5818 5819 int otherAdjacentTaskIndex = centerTaskIndex + (centerTaskIndex - taskIndex); 5820 if (otherAdjacentTaskIndex >= 0 && otherAdjacentTaskIndex < getPageCount()) { 5821 PropertyValuesHolder[] properties = new PropertyValuesHolder[3]; 5822 properties[0] = PropertyValuesHolder.ofFloat( 5823 getPagedOrientationHandler().getPrimaryViewTranslate(), primaryTranslation); 5824 properties[1] = PropertyValuesHolder.ofFloat(View.SCALE_X, 1); 5825 properties[2] = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1); 5826 5827 anim.play(ObjectAnimator.ofPropertyValuesHolder(getPageAt(otherAdjacentTaskIndex), 5828 properties)); 5829 } 5830 } 5831 anim.play(ObjectAnimator.ofFloat(this, TASK_THUMBNAIL_SPLASH_ALPHA, 0, 1)); 5832 if (taskView instanceof DesktopTaskView) { 5833 anim.play(ObjectAnimator.ofArgb(mContainer.getScrimView(), VIEW_BACKGROUND_COLOR, 5834 Color.TRANSPARENT)); 5835 if (enableDesktopExplodedView()) { 5836 anim.play(ObjectAnimator.ofFloat(this, DESK_EXPLODE_PROGRESS, 1f, 0f)); 5837 } 5838 } 5839 DepthController depthController = getDepthController(); 5840 if (depthController != null) { 5841 float targetDepth = taskView instanceof DesktopTaskView ? 0 : BACKGROUND_APP.getDepth( 5842 mContainer); 5843 anim.play(ObjectAnimator.ofFloat(depthController.stateDepth, MULTI_PROPERTY_VALUE, 5844 targetDepth)); 5845 } 5846 return anim; 5847 } 5848 5849 /** 5850 * Returns the scale up required on the view, so that it coves the screen completely 5851 */ getMaxScaleForFullScreen()5852 public float getMaxScaleForFullScreen() { 5853 if (mLastComputedTaskSize.isEmpty()) { 5854 getTaskSize(mLastComputedTaskSize); 5855 } 5856 mTempRect.set(mLastComputedTaskSize); 5857 return getPagedViewOrientedState().getFullScreenScaleAndPivot( 5858 mTempRect, mContainer.getDeviceProfile(), mTempPointF); 5859 } 5860 5861 /** 5862 * Clears the existing PendingAnimation. 5863 */ clearPendingAnimation()5864 public void clearPendingAnimation() { 5865 mPendingAnimation = null; 5866 } 5867 createTaskLaunchAnimation( TaskView taskView, long duration, Interpolator interpolator)5868 public PendingAnimation createTaskLaunchAnimation( 5869 TaskView taskView, long duration, Interpolator interpolator) { 5870 if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) { 5871 throw new IllegalStateException("Another pending animation is still running"); 5872 } 5873 5874 if (!hasTaskViews()) { 5875 return new PendingAnimation(duration); 5876 } 5877 5878 // When swiping down from overview to tasks, ensures the snapped page's scroll maintain 5879 // invariant between quick switch and overview, to ensure a smooth animation transition. 5880 updateGridProperties(); 5881 updateScrollSynchronously(); 5882 5883 int targetSysUiFlags = taskView.getSysUiStatusNavFlags(); 5884 final boolean[] passedOverviewThreshold = new boolean[]{false}; 5885 AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(taskView); 5886 anim.play(new AnimatedFloat(v -> { 5887 // Once we pass a certain threshold, update the sysui flags to match the target 5888 // tasks' flags 5889 if (v > UPDATE_SYSUI_FLAGS_THRESHOLD) { 5890 mContainer.getSystemUiController().updateUiState( 5891 UI_STATE_FULLSCREEN_TASK, targetSysUiFlags); 5892 } else { 5893 mContainer.getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, 0); 5894 } 5895 5896 // Passing the threshold from taskview to fullscreen app will vibrate 5897 final boolean passed = v >= SUCCESS_TRANSITION_PROGRESS; 5898 if (passed != passedOverviewThreshold[0]) { 5899 passedOverviewThreshold[0] = passed; 5900 performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, 5901 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); 5902 // Also update recents animation controller state if it is ongoing. 5903 if (mRecentsAnimationController != null) { 5904 mRecentsAnimationController.setWillFinishToHome(!passed); 5905 } 5906 } 5907 }).animateToValue(0f, 1f)); 5908 anim.setInterpolator(interpolator); 5909 5910 mPendingAnimation = new PendingAnimation(duration); 5911 mPendingAnimation.add(anim); 5912 if (taskView.isRunningTask()) { 5913 runActionOnRemoteHandles( 5914 remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator() 5915 .addOverviewToAppAnim(mPendingAnimation, interpolator)); 5916 mPendingAnimation.addOnFrameCallback(this::redrawLiveTile); 5917 } 5918 mPendingAnimation.addListener(new AnimatorListenerAdapter() { 5919 @Override 5920 public void onAnimationStart(Animator animation) { 5921 mBlurUtils.setDrawLiveTileBelowRecents(false); 5922 } 5923 }); 5924 mPendingAnimation.addEndListener(isSuccess -> { 5925 if (isSuccess) { 5926 if (taskView instanceof GroupedTaskView && hasAllValidTaskIds(taskView.getTaskIds()) 5927 && mRemoteTargetHandles != null) { 5928 // TODO(b/194414938): make this part of the animations instead. 5929 TaskViewUtils.createSplitAuxiliarySurfacesAnimator( 5930 mRemoteTargetHandles[0].getTransformParams().getTargetSet().nonApps, 5931 true /*shown*/, (dividerAnimator) -> { 5932 dividerAnimator.start(); 5933 dividerAnimator.end(); 5934 }); 5935 } 5936 if (taskView.isRunningTask()) { 5937 finishRecentsAnimation(false /* toRecents */, null); 5938 onTaskLaunchAnimationEnd(true /* success */); 5939 } else { 5940 taskView.launchWithoutAnimation(this::onTaskLaunchAnimationEnd); 5941 } 5942 mContainer.getStatsLogManager().logger().withItemInfo(taskView.getItemInfo()) 5943 .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN); 5944 } else { 5945 onTaskLaunchAnimationEnd(false); 5946 } 5947 mPendingAnimation = null; 5948 }); 5949 return mPendingAnimation; 5950 } 5951 onTaskLaunchAnimationEnd(boolean success)5952 protected Unit onTaskLaunchAnimationEnd(boolean success) { 5953 if (success) { 5954 resetTaskVisuals(); 5955 } else { 5956 // If launch animation didn't complete i.e. user dragged live tile down and then 5957 // back up and returned to Overview, then we need to ensure we reset the 5958 // view to draw below recents so that it can't be interacted with. 5959 mBlurUtils.setDrawLiveTileBelowRecents(true); 5960 redrawLiveTile(); 5961 } 5962 return Unit.INSTANCE; 5963 } 5964 5965 @Override notifyPageSwitchListener(int prevPage)5966 protected void notifyPageSwitchListener(int prevPage) { 5967 super.notifyPageSwitchListener(prevPage); 5968 updateCurrentTaskActionsVisibility(); 5969 loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); 5970 updateEnabledOverlays(); 5971 if (enableRefactorTaskThumbnail()) { 5972 mUtils.updateCentralTask(); 5973 } 5974 } 5975 5976 @Override getCurrentPageDescription()5977 protected String getCurrentPageDescription() { 5978 return ""; 5979 } 5980 5981 @Override addChildrenForAccessibility(ArrayList<View> outChildren)5982 public void addChildrenForAccessibility(ArrayList<View> outChildren) { 5983 outChildren.addAll(getAccessibilityChildren()); 5984 } 5985 getAccessibilityChildren()5986 public List<View> getAccessibilityChildren() { 5987 return mUtils.getAccessibilityChildren(); 5988 } 5989 5990 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)5991 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 5992 super.onInitializeAccessibilityNodeInfo(info); 5993 final AccessibilityNodeInfo.CollectionInfo 5994 collectionInfo = new AccessibilityNodeInfo.CollectionInfo( 5995 1, getAccessibilityChildren().size(), false); 5996 info.setCollectionInfo(collectionInfo); 5997 } 5998 5999 @Override onInitializeAccessibilityEvent(AccessibilityEvent event)6000 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 6001 super.onInitializeAccessibilityEvent(event); 6002 event.setScrollable(hasTaskViews()); 6003 6004 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 6005 final List<View> accessibilityChildren = getAccessibilityChildren(); 6006 final int[] visibleTasks = getVisibleChildrenRange(); 6007 event.setFromIndex(accessibilityChildren.indexOf(getChildAt(visibleTasks[1]))); 6008 event.setToIndex(accessibilityChildren.indexOf(getChildAt(visibleTasks[0]))); 6009 event.setItemCount(accessibilityChildren.size()); 6010 } 6011 } 6012 6013 @Override getAccessibilityClassName()6014 public CharSequence getAccessibilityClassName() { 6015 // To hear position-in-list related feedback from Talkback. 6016 return ListView.class.getName(); 6017 } 6018 6019 @Override isPageOrderFlipped()6020 protected boolean isPageOrderFlipped() { 6021 return true; 6022 } 6023 setEnableDrawingLiveTile(boolean enableDrawingLiveTile)6024 public void setEnableDrawingLiveTile(boolean enableDrawingLiveTile) { 6025 mEnableDrawingLiveTile = enableDrawingLiveTile; 6026 } 6027 getEnableDrawingLiveTile()6028 public boolean getEnableDrawingLiveTile() { 6029 return mEnableDrawingLiveTile; 6030 } 6031 redrawLiveTile()6032 public void redrawLiveTile() { 6033 runActionOnRemoteHandles(remoteTargetHandle -> { 6034 TransformParams params = remoteTargetHandle.getTransformParams(); 6035 if (params.getTargetSet() != null) { 6036 remoteTargetHandle.getTaskViewSimulator().apply(params); 6037 } 6038 }); 6039 } 6040 6041 @Nullable getRemoteTargetHandles()6042 public RemoteTargetHandle[] getRemoteTargetHandles() { 6043 return mRemoteTargetHandles; 6044 } 6045 6046 // TODO: To be removed in a follow up CL setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController, RecentsAnimationTargets recentsAnimationTargets)6047 public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController, 6048 RecentsAnimationTargets recentsAnimationTargets) { 6049 Log.d(TAG, "setRecentsAnimationTargets " 6050 + "- recentsAnimationController: " + recentsAnimationController 6051 + ", recentsAnimationTargets: " + recentsAnimationTargets); 6052 mRecentsAnimationController = recentsAnimationController; 6053 mSplitSelectStateController.setRecentsAnimationRunning(true); 6054 if (recentsAnimationTargets == null || recentsAnimationTargets.apps.length == 0) { 6055 return; 6056 } 6057 6058 RemoteTargetGluer gluer; 6059 if (recentsAnimationTargets.hasDesktopTasks(mContext)) { 6060 gluer = new RemoteTargetGluer(getContext(), getSizeStrategy(), recentsAnimationTargets, 6061 true /* forDesktop */); 6062 mRemoteTargetHandles = gluer.assignTargetsForDesktop( 6063 recentsAnimationTargets, /* transitionInfo= */ null); 6064 } else { 6065 gluer = new RemoteTargetGluer(getContext(), getSizeStrategy(), recentsAnimationTargets, 6066 false); 6067 mRemoteTargetHandles = gluer.assignTargetsForSplitScreen(recentsAnimationTargets); 6068 } 6069 mSplitBoundsConfig = gluer.getSplitBounds(); 6070 // Add release check to the targets from the RemoteTargetGluer and not the targets 6071 // passed in because in the event we're in split screen, we use the passed in targets 6072 // to create new RemoteAnimationTargets in assignTargetsForSplitScreen(), and the 6073 // mSyncTransactionApplier doesn't get transferred over 6074 runActionOnRemoteHandles(remoteTargetHandle -> { 6075 final TransformParams params = remoteTargetHandle.getTransformParams(); 6076 if (RecentsWindowFlags.Companion.getEnableOverviewInWindow()) { 6077 params.setHomeBuilderProxy((builder, app, transformParams) -> { 6078 mTmpMatrix.setScale( 6079 1f, 1f, app.localBounds.exactCenterX(), app.localBounds.exactCenterY()); 6080 builder.setMatrix(mTmpMatrix).setAlpha(1f).setShow(); 6081 }); 6082 } 6083 6084 if (mSyncTransactionApplier != null) { 6085 params.setSyncTransactionApplier(mSyncTransactionApplier); 6086 params.getTargetSet().addReleaseCheck(mSyncTransactionApplier); 6087 } 6088 6089 TaskViewSimulator tvs = remoteTargetHandle.getTaskViewSimulator(); 6090 tvs.setOrientationState(mOrientationState); 6091 tvs.setDp(mContainer.getDeviceProfile()); 6092 tvs.recentsViewScale.value = 1; 6093 }); 6094 6095 TaskView runningTaskView = getRunningTaskView(); 6096 if (runningTaskView instanceof GroupedTaskView) { 6097 // We initially create a GroupedTaskView in showCurrentTask() before launcher even 6098 // receives the leashes for the remote apps, so the mSplitBoundsConfig that gets passed 6099 // in there is either null or outdated, so we need to update here as soon as we're 6100 // notified. 6101 ((GroupedTaskView) runningTaskView).updateSplitBoundsConfig(mSplitBoundsConfig); 6102 } 6103 } 6104 6105 /** Helper to avoid writing some for-loops to iterate over {@link #mRemoteTargetHandles} */ runActionOnRemoteHandles(Consumer<RemoteTargetHandle> consumer)6106 public void runActionOnRemoteHandles(Consumer<RemoteTargetHandle> consumer) { 6107 if (mRemoteTargetHandles == null) { 6108 return; 6109 } 6110 6111 for (RemoteTargetHandle handle : mRemoteTargetHandles) { 6112 consumer.accept(handle); 6113 } 6114 } 6115 6116 /** 6117 * Finish recents animation. 6118 */ finishRecentsAnimation(boolean toRecents, @Nullable Runnable onFinishComplete)6119 public void finishRecentsAnimation(boolean toRecents, @Nullable Runnable onFinishComplete) { 6120 finishRecentsAnimation(toRecents, true /* shouldPip */, onFinishComplete); 6121 } 6122 6123 /** 6124 * Finish recents animation. 6125 */ finishRecentsAnimation(boolean toRecents, boolean shouldPip, @Nullable Runnable onFinishComplete)6126 public void finishRecentsAnimation(boolean toRecents, boolean shouldPip, 6127 @Nullable Runnable onFinishComplete) { 6128 finishRecentsAnimation(toRecents, shouldPip, false, onFinishComplete); 6129 } 6130 /** 6131 * NOTE: Whatever value gets passed through to the toRecents param may need to also be set on 6132 * {@link #mRecentsAnimationController#setWillFinishToHome}. 6133 */ finishRecentsAnimation(boolean toRecents, boolean shouldPip, boolean allAppTargetsAreTranslucent, @Nullable Runnable onFinishComplete)6134 public void finishRecentsAnimation(boolean toRecents, boolean shouldPip, 6135 boolean allAppTargetsAreTranslucent, @Nullable Runnable onFinishComplete) { 6136 Log.d(TAG, "finishRecentsAnimation - mRecentsAnimationController: " 6137 + mRecentsAnimationController); 6138 // TODO(b/197232424#comment#10) Move this back into onRecentsAnimationComplete(). Maybe? 6139 cleanupRemoteTargets(); 6140 6141 if (mRecentsAnimationController == null) { 6142 if (onFinishComplete != null) { 6143 onFinishComplete.run(); 6144 } 6145 return; 6146 } 6147 6148 final boolean sendUserLeaveHint = toRecents && shouldPip; 6149 if (sendUserLeaveHint && !com.android.wm.shell.Flags.enablePip2()) { 6150 // Notify the SysUI to use fade-in animation when entering PiP from live tile. 6151 // Note: PiP2 handles entering differently, so skip if enable_pip2=true. 6152 final SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(getContext()); 6153 systemUiProxy.setPipAnimationTypeToAlpha(); 6154 systemUiProxy.setShelfHeight(true, mContainer.getDeviceProfile().hotseatBarSizePx); 6155 // Transaction to hide the task to avoid flicker for entering PiP from split-screen. 6156 // See also {@link AbsSwipeUpHandler#maybeFinishSwipeToHome}. 6157 PictureInPictureSurfaceTransaction tx = 6158 new PictureInPictureSurfaceTransaction.Builder() 6159 .setAlpha(0f) 6160 .build(); 6161 tx.setShouldDisableCanAffectSystemUiFlags(false); 6162 int[] taskIds = TopTaskTracker.INSTANCE.get(getContext()).getRunningSplitTaskIds(); 6163 for (int taskId : taskIds) { 6164 mRecentsAnimationController.setFinishTaskTransaction(taskId, 6165 tx, null /* overlay */); 6166 } 6167 } 6168 mRecentsAnimationController.finish(toRecents, allAppTargetsAreTranslucent, () -> { 6169 if (onFinishComplete != null) { 6170 onFinishComplete.run(); 6171 } 6172 onRecentsAnimationComplete(); 6173 }, sendUserLeaveHint); 6174 } 6175 6176 /** 6177 * Called when a running recents animation has finished or canceled. 6178 */ onRecentsAnimationComplete()6179 public void onRecentsAnimationComplete() { 6180 Log.d(TAG, "onRecentsAnimationComplete " 6181 + "- mRecentsAnimationController: " + mRecentsAnimationController 6182 + ", mSideTaskLaunchCallback: " + mSideTaskLaunchCallback); 6183 // At this point, the recents animation is not running and if the animation was canceled 6184 // by a display rotation then reset this state to show the screenshot 6185 setRunningTaskViewShowScreenshot(true); 6186 // After we finish the recents animation, the current task id should be correctly 6187 // reset so that when the task is launched from Overview later, it goes through the 6188 // flow of starting a new task instead of finishing recents animation to app. A 6189 // typical example of this is (1) user swipes up from app to Overview (2) user 6190 // taps on QSB (3) user goes back to Overview and launch the most recent task. 6191 setCurrentTask(-1); 6192 mRecentsAnimationController = null; 6193 mSplitSelectStateController.setRecentsAnimationRunning(false); 6194 executeSideTaskLaunchCallback(); 6195 if (enableOverviewBackgroundWallpaperBlur()) { 6196 mBlurUtils.setDrawLiveTileBelowRecents(false); 6197 } 6198 } 6199 setDisallowScrollToClearAll(boolean disallowScrollToClearAll)6200 public void setDisallowScrollToClearAll(boolean disallowScrollToClearAll) { 6201 if (mDisallowScrollToClearAll != disallowScrollToClearAll) { 6202 mDisallowScrollToClearAll = disallowScrollToClearAll; 6203 updateMinAndMaxScrollX(); 6204 } 6205 } 6206 /** 6207 * Update the value of [mDisallowScrollToAddDesk] 6208 */ setDisallowScrollToAddDesk(boolean disallowScrollToAddDesk)6209 public void setDisallowScrollToAddDesk(boolean disallowScrollToAddDesk) { 6210 if (mDisallowScrollToAddDesk != disallowScrollToAddDesk) { 6211 mDisallowScrollToAddDesk = disallowScrollToAddDesk; 6212 updateMinAndMaxScrollX(); 6213 } 6214 } 6215 6216 6217 6218 /** 6219 * Updates page scroll synchronously after measure and layout child views. 6220 */ 6221 @SuppressLint("WrongCall") updateScrollSynchronously()6222 public void updateScrollSynchronously() { 6223 // onMeasure is needed to update child's measured width which is used in scroll calculation, 6224 // in case TaskView sizes has changed when being focused/unfocused. 6225 onMeasure(makeMeasureSpec(getMeasuredWidth(), EXACTLY), 6226 makeMeasureSpec(getMeasuredHeight(), EXACTLY)); 6227 onLayout(false /* changed */, getLeft(), getTop(), getRight(), getBottom()); 6228 updateMinAndMaxScrollX(); 6229 } 6230 6231 @Override getChildGap(int fromIndex, int toIndex)6232 protected int getChildGap(int fromIndex, int toIndex) { 6233 int clearAllIndex = indexOfChild(mClearAllButton); 6234 return fromIndex == clearAllIndex || toIndex == clearAllIndex 6235 ? getClearAllExtraPageSpacing() : 0; 6236 } 6237 getClearAllExtraPageSpacing()6238 protected int getClearAllExtraPageSpacing() { 6239 return showAsGrid() 6240 ? Math.max(mContainer.getDeviceProfile().overviewGridSideMargin - mPageSpacing, 0) 6241 : 0; 6242 } 6243 6244 @Override updateMinAndMaxScrollX()6245 protected void updateMinAndMaxScrollX() { 6246 super.updateMinAndMaxScrollX(); 6247 if (DEBUG) { 6248 Log.d(TAG, "updateMinAndMaxScrollX - mMinScroll: " + mMinScroll); 6249 Log.d(TAG, "updateMinAndMaxScrollX - mMaxScroll: " + mMaxScroll); 6250 } 6251 } 6252 6253 @Override computeMinScroll()6254 protected int computeMinScroll() { 6255 if (!hasTaskViews()) { 6256 return super.computeMinScroll(); 6257 } 6258 6259 return getScrollForPage(mIsRtl ? getLastViewIndex() : getFirstViewIndex()); 6260 } 6261 6262 @Override computeMaxScroll()6263 protected int computeMaxScroll() { 6264 if (!hasTaskViews()) { 6265 return super.computeMaxScroll(); 6266 } 6267 6268 return getScrollForPage(mIsRtl ? getFirstViewIndex() : getLastViewIndex()); 6269 } 6270 getFirstViewIndex()6271 private int getFirstViewIndex() { 6272 final View firstView; 6273 if (mShowAsGridLastOnLayout) { 6274 // For grid Overview, it always start if a large tile (focused task or desktop task) if 6275 // they exist, otherwise it start with the first task. 6276 TaskView firstLargeTaskView = mUtils.getFirstLargeTaskView(); 6277 if (firstLargeTaskView != null) { 6278 firstView = firstLargeTaskView; 6279 } else { 6280 firstView = mUtils.getFirstSmallTaskView(); 6281 } 6282 } else { 6283 firstView = mUtils.getFirstTaskViewInCarousel( 6284 /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress > 0); 6285 } 6286 return indexOfChild(firstView); 6287 } 6288 getLastViewIndex()6289 private int getLastViewIndex() { 6290 final View lastView; 6291 if (!mDisallowScrollToClearAll) { 6292 // When ClearAllButton is present, it always end with ClearAllButton. 6293 lastView = mClearAllButton; 6294 } else if (mShowAsGridLastOnLayout) { 6295 // When ClearAllButton is absent, for the grid Overview, it always end with a grid task 6296 // if they exist, otherwise it ends with a large tile (focused task or desktop task). 6297 TaskView lastGridTaskView = getLastGridTaskView(); 6298 if (lastGridTaskView != null) { 6299 lastView = lastGridTaskView; 6300 } else { 6301 lastView = mUtils.getLastLargeTaskView(); 6302 } 6303 } else { 6304 lastView = mUtils.getLastTaskViewInCarousel( 6305 /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress > 0); 6306 } 6307 return indexOfChild(lastView); 6308 } 6309 6310 /** 6311 * Returns page scroll of ClearAllButton. 6312 */ getClearAllScroll()6313 public int getClearAllScroll() { 6314 return getScrollForPage(indexOfChild(mClearAllButton)); 6315 } 6316 6317 @Override getPageScrolls(int[] outPageScrolls, boolean layoutChildren, ComputePageScrollsLogic scrollLogic)6318 protected boolean getPageScrolls(int[] outPageScrolls, boolean layoutChildren, 6319 ComputePageScrollsLogic scrollLogic) { 6320 int[] newPageScrolls = new int[outPageScrolls.length]; 6321 super.getPageScrolls(newPageScrolls, layoutChildren, scrollLogic); 6322 boolean showAsFullscreen = showAsFullscreen(); 6323 boolean showAsGrid = showAsGrid(); 6324 6325 // Align ClearAllButton to the left (RTL) or right (non-RTL), which is different from other 6326 // TaskViews. This must be called after laying out ClearAllButton. 6327 if (layoutChildren) { 6328 int clearAllWidthDiff = getPagedOrientationHandler().getPrimaryValue(mTaskWidth, 6329 mTaskHeight) - getPagedOrientationHandler().getPrimarySize(mClearAllButton); 6330 mClearAllButton.setScrollOffsetPrimary(mIsRtl ? clearAllWidthDiff : -clearAllWidthDiff); 6331 } 6332 6333 int[] oldPageScrolls = Arrays.copyOf(outPageScrolls, outPageScrolls.length); 6334 int clearAllIndex = indexOfChild(mClearAllButton); 6335 int clearAllScroll = 0; 6336 int clearAllWidth = getPagedOrientationHandler().getPrimarySize(mClearAllButton); 6337 if (clearAllIndex != -1 && clearAllIndex < outPageScrolls.length) { 6338 float scrollDiff = mClearAllButton.getScrollAdjustment(showAsFullscreen, showAsGrid); 6339 clearAllScroll = newPageScrolls[clearAllIndex] + Math.round(scrollDiff); 6340 outPageScrolls[clearAllIndex] = clearAllScroll; 6341 } 6342 6343 int lastTaskScroll = getLastTaskScroll(clearAllScroll, clearAllWidth); 6344 getTaskViews().forEachWithIndexInParent((index, taskView) -> { 6345 float scrollDiff = taskView.getScrollAdjustment(showAsGrid); 6346 int pageScroll = newPageScrolls[index] + Math.round(scrollDiff); 6347 if ((mIsRtl && pageScroll < lastTaskScroll) 6348 || (!mIsRtl && pageScroll > lastTaskScroll)) { 6349 pageScroll = lastTaskScroll; 6350 } 6351 outPageScrolls[index] = pageScroll; 6352 if (DEBUG) { 6353 Log.d(TAG, 6354 "getPageScrolls - outPageScrolls[" + index + "]: " + outPageScrolls[index]); 6355 } 6356 }); 6357 6358 int addDesktopButtonIndex = indexOfChild(mAddDesktopButton); 6359 if (addDesktopButtonIndex >= 0 && addDesktopButtonIndex < outPageScrolls.length) { 6360 int firstViewIndex = getFirstViewIndex(); 6361 if (firstViewIndex >= 0 && firstViewIndex < outPageScrolls.length) { 6362 // If we can scroll to [AddDesktopButton], make its page scroll equal to 6363 // the first [TaskView]. Otherwise, make its page scroll out of range of 6364 // [minScroll, maxScroll]. 6365 if (!mDisallowScrollToAddDesk) { 6366 outPageScrolls[addDesktopButtonIndex] = outPageScrolls[firstViewIndex]; 6367 } else { 6368 outPageScrolls[addDesktopButtonIndex] = 6369 outPageScrolls[firstViewIndex] + (mIsRtl ? 1 : -1); 6370 } 6371 } 6372 6373 if (DEBUG) { 6374 Log.d(TAG, "getPageScrolls - addDesktopButtonScroll: " 6375 + outPageScrolls[addDesktopButtonIndex]); 6376 } 6377 } 6378 if (DEBUG) { 6379 Log.d(TAG, "getPageScrolls - clearAllScroll: " + clearAllScroll); 6380 } 6381 return !Arrays.equals(oldPageScrolls, outPageScrolls); 6382 } 6383 6384 @Override getChildOffset(int index)6385 protected int getChildOffset(int index) { 6386 int childOffset = super.getChildOffset(index); 6387 View child = getChildAt(index); 6388 if (child instanceof TaskView) { 6389 childOffset += ((TaskView) child).getOffsetAdjustment(showAsGrid()); 6390 } else if (child instanceof ClearAllButton) { 6391 childOffset += ((ClearAllButton) child).getOffsetAdjustment(mOverviewFullscreenEnabled, 6392 showAsGrid()); 6393 } 6394 return childOffset; 6395 } 6396 6397 @Override getChildVisibleSize(int childIndex)6398 protected int getChildVisibleSize(int childIndex) { 6399 final TaskView taskView = getTaskViewAt(childIndex); 6400 if (taskView == null) { 6401 return super.getChildVisibleSize(childIndex); 6402 } 6403 return (int) (super.getChildVisibleSize(childIndex) * taskView.getSizeAdjustment( 6404 showAsFullscreen())); 6405 } 6406 getClearAllButton()6407 public ClearAllButton getClearAllButton() { 6408 return mClearAllButton; 6409 } 6410 6411 @Nullable getAddDeskButton()6412 public AddDesktopButton getAddDeskButton() { 6413 return mAddDesktopButton; 6414 } 6415 6416 /** 6417 * @return How many pixels the running task is offset on the currently laid out dominant axis. 6418 */ getScrollOffset()6419 public int getScrollOffset() { 6420 return getScrollOffset(getRunningTaskIndex()); 6421 } 6422 6423 /** 6424 * Returns how many pixels the running task is offset on the currently laid out dominant axis 6425 * specifically during a Keyboard task focus. 6426 */ getScrollOffsetForKeyboardTaskFocus()6427 public int getScrollOffsetForKeyboardTaskFocus() { 6428 if (!isKeyboardTaskFocusPending()) { 6429 return getScrollOffset(getRunningTaskIndex()); 6430 } 6431 return getPagedOrientationHandler().getPrimaryScroll(this) 6432 - getScrollForPage(mKeyboardTaskFocusIndex) 6433 + getScrollOffset(getRunningTaskIndex()); 6434 } 6435 6436 /** 6437 * Sets whether or not we should clamp the scroll offset. 6438 * This is used to avoid x-axis movement when swiping up transient taskbar. 6439 * Should only be set at the beginning and end of the gesture, otherwise a jump may occur. 6440 * 6441 * @param clampScrollOffset When true, we clamp the scroll to 0 before the clamp threshold is 6442 * met. 6443 */ setClampScrollOffset(boolean clampScrollOffset)6444 public void setClampScrollOffset(boolean clampScrollOffset) { 6445 mShouldClampScrollOffset = clampScrollOffset; 6446 } 6447 6448 /** 6449 * Returns how many pixels the page is offset on the currently laid out dominant axis. 6450 */ getScrollOffset(int pageIndex)6451 public int getScrollOffset(int pageIndex) { 6452 int unclampedOffset = getUnclampedScrollOffset(pageIndex); 6453 if (!mShouldClampScrollOffset) { 6454 return unclampedOffset; 6455 } 6456 if (Math.abs(unclampedOffset) < mClampedScrollOffsetBound) { 6457 return 0; 6458 } 6459 return unclampedOffset 6460 - Math.round(Math.signum(unclampedOffset) * mClampedScrollOffsetBound); 6461 } 6462 6463 /** 6464 * Returns how many pixels the page is offset on the currently laid out dominant axis. 6465 */ getUnclampedScrollOffset(int pageIndex)6466 private int getUnclampedScrollOffset(int pageIndex) { 6467 if (pageIndex == INVALID_PAGE) { 6468 return 0; 6469 } 6470 // Don't dampen the scroll (due to overscroll) if the adjacent tasks are offscreen, so that 6471 // the page can move freely given there's no visual indication why it shouldn't. 6472 int overScrollShift = mAdjacentPageHorizontalOffset > 0 6473 ? (int) Utilities.mapRange( 6474 mAdjacentPageHorizontalOffset, 6475 getOverScrollShift(), 6476 getUndampedOverScrollShift()) 6477 : getOverScrollShift(); 6478 return getScrollForPage(pageIndex) - getPagedOrientationHandler().getPrimaryScroll(this) 6479 + overScrollShift + getOffsetFromScrollPosition(pageIndex); 6480 } 6481 6482 /** 6483 * Returns how many pixels the page is offset from its scroll position. 6484 */ getOffsetFromScrollPosition(int pageIndex)6485 private int getOffsetFromScrollPosition(int pageIndex) { 6486 return getOffsetFromScrollPosition(pageIndex, mUtils.getTopRowIdArray(), 6487 mUtils.getBottomRowIdArray()); 6488 } 6489 getOffsetFromScrollPosition( int pageIndex, IntArray topRowIdArray, IntArray bottomRowIdArray)6490 private int getOffsetFromScrollPosition( 6491 int pageIndex, IntArray topRowIdArray, IntArray bottomRowIdArray) { 6492 if (!showAsGrid()) { 6493 return 0; 6494 } 6495 6496 TaskView taskView = getTaskViewAt(pageIndex); 6497 if (taskView == null) { 6498 return 0; 6499 } 6500 6501 TaskView lastGridTaskView = getLastGridTaskView(topRowIdArray, bottomRowIdArray); 6502 if (lastGridTaskView == null) { 6503 return 0; 6504 } 6505 6506 if (getScrollForPage(pageIndex) != getScrollForPage(indexOfChild(lastGridTaskView))) { 6507 return 0; 6508 } 6509 6510 // Check distance from lastGridTaskView to taskView. 6511 int lastGridTaskViewPosition = 6512 getPositionInRow(lastGridTaskView, topRowIdArray, bottomRowIdArray); 6513 int taskViewPosition = getPositionInRow(taskView, topRowIdArray, bottomRowIdArray); 6514 int gridTaskSizeAndSpacing = mLastComputedGridTaskSize.width() + mPageSpacing; 6515 int positionDiff = gridTaskSizeAndSpacing * (lastGridTaskViewPosition - taskViewPosition); 6516 6517 int taskEnd = getLastTaskEnd() + (mIsRtl ? positionDiff : -positionDiff); 6518 int normalTaskEnd = mIsRtl 6519 ? mLastComputedGridTaskSize.left 6520 : mLastComputedGridTaskSize.right; 6521 return taskEnd - normalTaskEnd; 6522 } 6523 getLastTaskEnd()6524 private int getLastTaskEnd() { 6525 return mIsRtl 6526 ? mLastComputedGridSize.left + mPageSpacing + mClearAllShortTotalWidthTranslation 6527 : mLastComputedGridSize.right - mPageSpacing - mClearAllShortTotalWidthTranslation; 6528 } 6529 getPositionInRow( TaskView taskView, IntArray topRowIdArray, IntArray bottomRowIdArray)6530 private int getPositionInRow( 6531 TaskView taskView, IntArray topRowIdArray, IntArray bottomRowIdArray) { 6532 int position = topRowIdArray.indexOf(taskView.getTaskViewId()); 6533 return position != -1 ? position : bottomRowIdArray.indexOf(taskView.getTaskViewId()); 6534 } 6535 6536 /** 6537 * @return true if the task in on the bottom of the grid 6538 */ isOnGridBottomRow(TaskView taskView)6539 public boolean isOnGridBottomRow(TaskView taskView) { 6540 return showAsGrid() 6541 && !mTopRowIdSet.contains(taskView.getTaskViewId()) 6542 && !taskView.isLargeTile(); 6543 } 6544 getEventDispatcher(float navbarRotation)6545 public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) { 6546 float degreesRotated; 6547 if (navbarRotation == 0) { 6548 degreesRotated = getPagedOrientationHandler().getDegreesRotated(); 6549 } else { 6550 degreesRotated = -navbarRotation; 6551 } 6552 if (degreesRotated == 0) { 6553 return super::onTouchEvent; 6554 } 6555 6556 // At this point the event coordinates have already been transformed, so we need to 6557 // undo that transformation since PagedView also accommodates for the transformation via 6558 // PagedOrientationHandler 6559 return e -> { 6560 if (navbarRotation != 0 6561 && mOrientationState.isMultipleOrientationSupportedByDevice() 6562 && !mOrientationState.getOrientationHandler().isLayoutNaturalToLauncher()) { 6563 mOrientationState.flipVertical(e); 6564 super.onTouchEvent(e); 6565 mOrientationState.flipVertical(e); 6566 return; 6567 } 6568 mOrientationState.transformEvent(-degreesRotated, e, true); 6569 super.onTouchEvent(e); 6570 mOrientationState.transformEvent(-degreesRotated, e, false); 6571 }; 6572 } 6573 6574 private void updateEnabledOverlays() { 6575 if (enableRefactorTaskThumbnail()) { 6576 Set<Integer> fullyVisibleTaskIds = new HashSet<>(); 6577 for (TaskView taskView : getTaskViews()) { 6578 if (isTaskViewFullyVisible(taskView)) { 6579 fullyVisibleTaskIds.addAll(taskView.getTaskIdSet()); 6580 } 6581 } 6582 mRecentsViewModel.updateTasksFullyVisible(fullyVisibleTaskIds); 6583 } else { 6584 TaskView focusedTaskView = getFocusedTaskView(); 6585 for (TaskView taskView : getTaskViews()) { 6586 if (taskView == focusedTaskView) { 6587 continue; 6588 } 6589 taskView.setOverlayEnabled(mOverlayEnabled && isTaskViewFullyVisible(taskView)); 6590 } 6591 // Focus task overlay should be enabled and refreshed at last 6592 if (focusedTaskView != null) { 6593 focusedTaskView.setOverlayEnabled( 6594 mOverlayEnabled && isTaskViewFullyVisible(focusedTaskView)); 6595 } 6596 } 6597 } 6598 6599 public void setOverlayEnabled(boolean overlayEnabled) { 6600 if (mOverlayEnabled != overlayEnabled) { 6601 mOverlayEnabled = overlayEnabled; 6602 updateEnabledOverlays(); 6603 6604 if (enableRefactorTaskThumbnail()) { 6605 mRecentsViewModel.setOverlayEnabled(overlayEnabled); 6606 } 6607 } 6608 } 6609 6610 public void setOverviewGridEnabled(boolean overviewGridEnabled) { 6611 if (mOverviewGridEnabled != overviewGridEnabled) { 6612 mOverviewGridEnabled = overviewGridEnabled; 6613 updateActionsViewFocusedScroll(); 6614 // Request layout to ensure scroll position is recalculated with updated mGridProgress. 6615 requestLayout(); 6616 } 6617 } 6618 6619 public void setOverviewFullscreenEnabled(boolean overviewFullscreenEnabled) { 6620 if (mOverviewFullscreenEnabled != overviewFullscreenEnabled) { 6621 mOverviewFullscreenEnabled = overviewFullscreenEnabled; 6622 // Request layout to ensure scroll position is recalculated with updated 6623 // mFullscreenProgress. 6624 requestLayout(); 6625 } 6626 } 6627 6628 /** 6629 * Update whether RecentsView is in select mode. Should be enabled before transitioning to 6630 * select mode, and only disabled after transitioning from select mode. 6631 */ 6632 public void setOverviewSelectEnabled(boolean overviewSelectEnabled) { 6633 if (mOverviewSelectEnabled != overviewSelectEnabled) { 6634 mOverviewSelectEnabled = overviewSelectEnabled; 6635 updatePivots(); 6636 if (!mOverviewSelectEnabled) { 6637 setSelectedTask(INVALID_TASK_ID); 6638 } 6639 } 6640 } 6641 6642 /** 6643 * Switch the current running task view to static snapshot mode, 6644 * capturing the snapshot at the same time. 6645 */ 6646 public void switchToScreenshot(Runnable onFinishRunnable) { 6647 if (mRecentsAnimationController == null) { 6648 if (onFinishRunnable != null) { 6649 onFinishRunnable.run(); 6650 } 6651 return; 6652 } 6653 6654 TaskView taskView = getRunningTaskView(); 6655 if (taskView == null) { 6656 onFinishRunnable.run(); 6657 return; 6658 } 6659 6660 Map<Integer, ThumbnailData> updatedThumbnails = mUtils.screenshotTasks(taskView); 6661 if (enableRefactorTaskThumbnail()) { 6662 mHelper.switchToScreenshot(taskView, updatedThumbnails, onFinishRunnable); 6663 } else { 6664 setRunningTaskViewShowScreenshot(true, updatedThumbnails); 6665 ViewUtils.postFrameDrawn(taskView, onFinishRunnable); 6666 } 6667 } 6668 6669 /** 6670 * Switch the current running task view to static snapshot mode, using the 6671 * provided thumbnail data as the snapshot. 6672 * TODO(b/195609063) Consolidate this method w/ the one above, except this thumbnail data comes 6673 * from gesture state, which is a larger change of it having to keep track of multiple tasks. 6674 * OR. Maybe it doesn't need to pass in a thumbnail and we can use the exact same flow as above 6675 */ 6676 public void switchToScreenshot(@Nullable HashMap<Integer, ThumbnailData> thumbnailDatas, 6677 Runnable onFinishRunnable) { 6678 final TaskView taskView = getRunningTaskView(); 6679 if (taskView != null) { 6680 if (enableRefactorTaskThumbnail()) { 6681 mHelper.switchToScreenshot(taskView, thumbnailDatas, onFinishRunnable); 6682 } else { 6683 taskView.setShouldShowScreenshot(true, thumbnailDatas); 6684 ViewUtils.postFrameDrawn(taskView, onFinishRunnable); 6685 } 6686 } else { 6687 onFinishRunnable.run(); 6688 } 6689 } 6690 6691 /** 6692 * The current task is fully modal (modalness = 1) when it is shown on its own in a modal 6693 * way. Modalness 0 means the task is shown in context with all the other tasks. 6694 */ 6695 private void setTaskModalness(float modalness) { 6696 mTaskModalness = modalness; 6697 updatePageOffsets(); 6698 if (getSelectedTaskView() != null) { 6699 if (enableGridOnlyOverview()) { 6700 for (TaskView taskView : getTaskViews()) { 6701 taskView.setModalness(modalness); 6702 } 6703 } else { 6704 getSelectedTaskView().setModalness(modalness); 6705 } 6706 } else if (getCurrentPageTaskView() != null) { 6707 getCurrentPageTaskView().setModalness(modalness); 6708 } 6709 // Only show actions view when it's modal for in-place landscape mode. 6710 boolean inPlaceLandscape = !mOrientationState.isRecentsActivityRotationAllowed() 6711 && mOrientationState.getTouchRotation() != ROTATION_0; 6712 mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION, modalness < 1 && inPlaceLandscape); 6713 } 6714 6715 @Nullable 6716 protected DepthController getDepthController() { 6717 return null; 6718 } 6719 6720 @Nullable 6721 protected DesktopRecentsTransitionController getDesktopRecentsController() { 6722 return mDesktopRecentsTransitionController; 6723 } 6724 6725 /** Enables or disables modal state for RecentsView */ 6726 public abstract void setModalStateEnabled(int taskId, boolean animate); 6727 6728 public TaskOverlayFactory getTaskOverlayFactory() { 6729 return mTaskOverlayFactory; 6730 } 6731 6732 public BaseContainerInterface getSizeStrategy() { 6733 return mSizeStrategy; 6734 } 6735 6736 6737 /** 6738 * Returns the container interface 6739 */ 6740 protected abstract BaseContainerInterface<STATE_TYPE, ?> getContainerInterface(int displayId); 6741 6742 /** 6743 * Set all the task views to color tint scrim mode, dimming or tinting them all. Allows the 6744 * tasks to be dimmed while other elements in the recents view are left alone. 6745 */ 6746 public void showForegroundScrim(boolean show) { 6747 if (!show && mColorTint == 0) { 6748 if (mTintingAnimator != null) { 6749 mTintingAnimator.cancel(); 6750 mTintingAnimator = null; 6751 } 6752 return; 6753 } 6754 6755 mTintingAnimator = ObjectAnimator.ofFloat(this, COLOR_TINT, 6756 show ? FOREGROUND_SCRIM_TINT : 0f); 6757 mTintingAnimator.setAutoCancel(true); 6758 mTintingAnimator.start(); 6759 } 6760 6761 /** Tint the RecentsView and TaskViews in to simulate a scrim. */ 6762 private void setColorTint(float tintAmount) { 6763 mColorTint = tintAmount; 6764 6765 for (TaskView taskView : getTaskViews()) { 6766 taskView.setColorTint(mColorTint, mTintingColor); 6767 } 6768 6769 Drawable scrimBg = mContainer.getScrimView().getBackground(); 6770 if (scrimBg != null) { 6771 if (tintAmount == 0f) { 6772 scrimBg.setTintList(null); 6773 } else { 6774 scrimBg.setTintBlendMode(BlendMode.SRC_OVER); 6775 scrimBg.setTint( 6776 ColorUtils.setAlphaComponent(mTintingColor, (int) (255 * tintAmount))); 6777 } 6778 } 6779 } 6780 6781 private float getColorTint() { 6782 return mColorTint; 6783 } 6784 6785 /** Returns {@code true} if the overview tasks are displayed as a grid. */ 6786 public boolean showAsGrid() { 6787 return mOverviewGridEnabled || (mCurrentGestureEndTarget != null 6788 && mSizeStrategy.stateFromGestureEndTarget(mCurrentGestureEndTarget) 6789 .displayOverviewTasksAsGrid(mContainer.getDeviceProfile())); 6790 } 6791 6792 protected boolean showAsFullscreen() { 6793 return mOverviewFullscreenEnabled 6794 && mCurrentGestureEndTarget != GestureState.GestureEndTarget.RECENTS; 6795 } 6796 6797 public void cleanupRemoteTargets() { 6798 Log.d(TAG, "cleanupRemoteTargets - mRemoteTargetHandles: " + Arrays.toString( 6799 mRemoteTargetHandles)); 6800 mRemoteTargetHandles = null; 6801 } 6802 6803 /** 6804 * Used to register callbacks for when our empty message state changes. 6805 * 6806 * @see #setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener) 6807 * @see #updateEmptyMessage() 6808 */ 6809 public interface OnEmptyMessageUpdatedListener { 6810 /** @param isEmpty Whether RecentsView is empty (i.e. has no children) */ 6811 void onEmptyMessageUpdated(boolean isEmpty); 6812 } 6813 6814 /** 6815 * Adds a listener for scroll changes 6816 */ 6817 public void addOnScrollChangedListener(OnScrollChangedListener listener) { 6818 mScrollListeners.add(listener); 6819 } 6820 6821 /** 6822 * Removes a previously added scroll change listener 6823 */ 6824 public void removeOnScrollChangedListener(OnScrollChangedListener listener) { 6825 mScrollListeners.remove(listener); 6826 } 6827 6828 /** 6829 * @return Corner radius in pixel value for PiP window, which is updated via 6830 * {@link #mIPipAnimationListener} 6831 */ 6832 public int getPipCornerRadius() { 6833 return mPipCornerRadius; 6834 } 6835 6836 /** 6837 * @return Shadow radius in pixel value for PiP window, which is updated via 6838 * {@link #mIPipAnimationListener} 6839 */ 6840 public int getPipShadowRadius() { 6841 return mPipShadowRadius; 6842 } 6843 6844 @Override 6845 public boolean scrollLeft() { 6846 if (!showAsGrid()) { 6847 return super.scrollLeft(); 6848 } 6849 6850 int targetPage = getNextPage(); 6851 if (targetPage >= 0) { 6852 // Find the next page that is not fully visible. 6853 TaskView taskView = getTaskViewAt(targetPage); 6854 while ((taskView == null || isTaskViewFullyVisible(taskView)) && targetPage - 1 >= 0) { 6855 taskView = getTaskViewAt(--targetPage); 6856 } 6857 // Target a scroll where targetPage is on left of screen but still fully visible. 6858 int normalTaskEnd = mIsRtl 6859 ? mLastComputedGridTaskSize.left 6860 : mLastComputedGridTaskSize.right; 6861 int targetScroll = getScrollForPage(targetPage) + normalTaskEnd - getLastTaskEnd(); 6862 // Find a page that is close to targetScroll while not over it. 6863 while (targetPage - 1 >= 0 6864 && (mIsRtl 6865 ? getScrollForPage(targetPage - 1) < targetScroll 6866 : getScrollForPage(targetPage - 1) > targetScroll)) { 6867 targetPage--; 6868 } 6869 snapToPage(targetPage); 6870 return true; 6871 } 6872 6873 return mAllowOverScroll; 6874 } 6875 6876 @Override 6877 public boolean scrollRight() { 6878 if (!showAsGrid()) { 6879 return super.scrollRight(); 6880 } 6881 6882 int targetPage = getNextPage(); 6883 if (targetPage < getChildCount()) { 6884 // Find the next page that is not fully visible. 6885 TaskView taskView = getTaskViewAt(targetPage); 6886 while ((taskView != null && isTaskViewFullyVisible(taskView)) 6887 && targetPage + 1 < getChildCount()) { 6888 taskView = getTaskViewAt(++targetPage); 6889 } 6890 snapToPage(targetPage); 6891 return true; 6892 } 6893 return mAllowOverScroll; 6894 } 6895 6896 @Override 6897 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 6898 super.onScrollChanged(l, t, oldl, oldt); 6899 dispatchScrollChanged(); 6900 } 6901 6902 /** 6903 * Prepares this RecentsView to scroll properly for an upcoming child view focus request from 6904 * keyboard quick switching 6905 */ 6906 public void setKeyboardTaskFocusIndex(int taskIndex) { 6907 mKeyboardTaskFocusIndex = taskIndex; 6908 } 6909 6910 /** Returns whether this RecentsView will be scrolling to a child view for a focus request */ 6911 public boolean isKeyboardTaskFocusPending() { 6912 return mKeyboardTaskFocusIndex != INVALID_PAGE; 6913 } 6914 6915 private boolean isKeyboardTaskFocusPendingForChild(View child) { 6916 return isKeyboardTaskFocusPending() && mKeyboardTaskFocusIndex == indexOfChild(child); 6917 } 6918 6919 @Override 6920 protected int getSnapAnimationDuration() { 6921 return isKeyboardTaskFocusPending() 6922 ? mKeyboardTaskFocusSnapAnimationDuration : super.getSnapAnimationDuration(); 6923 } 6924 6925 @Override 6926 protected void onVelocityValuesUpdated() { 6927 super.onVelocityValuesUpdated(); 6928 mKeyboardTaskFocusSnapAnimationDuration = 6929 getResources().getInteger(R.integer.config_keyboardTaskFocusSnapAnimationDuration); 6930 } 6931 6932 @Override 6933 protected boolean shouldHandleRequestChildFocus(View child) { 6934 // If we are already scrolling to a task view and we aren't focusing to this child from 6935 // keyboard quick switch, then the focus request has already been handled 6936 return mScroller.isFinished() || isKeyboardTaskFocusPendingForChild(child); 6937 } 6938 6939 @Override 6940 public void requestChildFocus(View child, View focused) { 6941 if (isKeyboardTaskFocusPendingForChild(child)) { 6942 updateGridProperties(); 6943 updateScrollSynchronously(); 6944 } 6945 super.requestChildFocus(child, focused); 6946 } 6947 6948 private void dispatchScrollChanged() { 6949 runActionOnRemoteHandles(remoteTargetHandle -> 6950 remoteTargetHandle.getTaskViewSimulator().setScroll(getScrollOffset())); 6951 for (int i = mScrollListeners.size() - 1; i >= 0; i--) { 6952 mScrollListeners.get(i).onScrollChanged(); 6953 } 6954 } 6955 6956 private static class PinnedStackAnimationListener<T extends RecentsViewContainer> extends 6957 IPipAnimationListener.Stub { 6958 @Nullable 6959 private T mActivity; 6960 @Nullable 6961 private RecentsView mRecentsView; 6962 6963 public void setActivityAndRecentsView(@Nullable T activity, 6964 @Nullable RecentsView recentsView) { 6965 mActivity = activity; 6966 mRecentsView = recentsView; 6967 } 6968 6969 @Override 6970 public void onPipAnimationStarted() { 6971 MAIN_EXECUTOR.execute(() -> { 6972 // Needed for activities that auto-enter PiP, which will not trigger a remote 6973 // animation to be created 6974 if (mActivity != null) { 6975 mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS); 6976 } 6977 }); 6978 } 6979 6980 @Override 6981 public void onPipResourceDimensionsChanged(int cornerRadius, int shadowRadius) { 6982 if (mRecentsView != null) { 6983 mRecentsView.mPipCornerRadius = cornerRadius; 6984 mRecentsView.mPipShadowRadius = shadowRadius; 6985 } 6986 } 6987 6988 @Override 6989 public void onExpandPip() { 6990 MAIN_EXECUTOR.execute(() -> { 6991 if (mRecentsView == null 6992 || mRecentsView.mSizeStrategy.getTaskbarController() == null) { 6993 return; 6994 } 6995 // Hide the task bar when leaving PiP to prevent it from flickering once 6996 // the app settles in full-screen mode. 6997 mRecentsView.mSizeStrategy.getTaskbarController().onExpandPip(); 6998 }); 6999 } 7000 } 7001 7002 @Override 7003 public void onCanCreateDesksChanged(boolean canCreateDesks) { 7004 // TODO: b/389209338 - update the AddDesktopButton's visibility on this. 7005 } 7006 7007 @Override 7008 public void onDeskAdded(int displayId, int deskId) { 7009 // Ignore desk changes that don't belong to this display. 7010 if (displayId != mContainer.getDisplay().getDisplayId()) { 7011 return; 7012 } 7013 7014 if (mUtils.getDesktopTaskViewForDeskId(deskId) != null) { 7015 Log.e(TAG, "A task view for this desk has already been added."); 7016 return; 7017 } 7018 7019 TaskView currentTaskView = getTaskViewAt(mCurrentPage); 7020 7021 // We assume that a newly added desk is always empty and gets added to the left of the 7022 // `AddNewDesktopButton`. 7023 DesktopTaskView desktopTaskView = 7024 (DesktopTaskView) getTaskViewFromPool(TaskViewType.DESKTOP); 7025 desktopTaskView.bind(new DesktopTask(deskId, displayId, new ArrayList<>()), 7026 mOrientationState, mTaskOverlayFactory); 7027 7028 Objects.requireNonNull(mAddDesktopButton); 7029 final int insertionIndex = 1 + indexOfChild(mAddDesktopButton); 7030 addView(desktopTaskView, insertionIndex); 7031 7032 updateTaskSize(); 7033 mUtils.updateChildTaskOrientations(); 7034 updateScrollSynchronously(); 7035 7036 // Set Current Page based on the stored TaskView. 7037 if (currentTaskView != null) { 7038 setCurrentPage(indexOfChild(currentTaskView)); 7039 } 7040 } 7041 7042 @Override 7043 public void onDeskRemoved(int displayId, int deskId) { 7044 // Ignore desk changes that don't belong to this display. 7045 if (displayId != mContainer.getDisplay().getDisplayId()) { 7046 return; 7047 } 7048 7049 // We need to distinguish between desk removals that are triggered from outside of overview 7050 // vs. the ones that were initiated from overview by dismissing the corresponding desktop 7051 // task view. 7052 var taskView = mUtils.getDesktopTaskViewForDeskId(deskId); 7053 if (taskView != null) { 7054 dismissTaskView(taskView, true, true); 7055 } 7056 } 7057 7058 @Override 7059 public void onActiveDeskChanged(int displayId, int newActiveDesk, int oldActiveDesk) { 7060 // TODO: b/400870600 - We may need to add code here to special case when an empty desk gets 7061 // activated, since `RemoteDesktopLaunchTransitionRunner` doesn't always get triggered. 7062 } 7063 7064 /** Get the color used for foreground scrimming the RecentsView for sharing. */ 7065 public static int getForegroundScrimDimColor(Context context) { 7066 return context.getColor(R.color.overview_foreground_scrim_color); 7067 } 7068 7069 /** Get the RecentsAnimationController */ 7070 @Nullable 7071 public RecentsAnimationController getRecentsAnimationController() { 7072 return mRecentsAnimationController; 7073 } 7074 7075 @Nullable 7076 public SplitInstructionsView getSplitInstructionsView() { 7077 return mSplitSelectStateController.getSplitInstructionsView(); 7078 } 7079 7080 /** Update the current activity locus id to show the enabled state of Overview */ 7081 public void updateLocusId() { 7082 String locusId = "Overview"; 7083 7084 if (mOverviewStateEnabled && mContainer.isStarted()) { 7085 locusId += "|ENABLED"; 7086 } else { 7087 locusId += "|DISABLED"; 7088 } 7089 7090 final LocusId id = new LocusId(locusId); 7091 // Set locus context is a binder call, don't want it to happen during a transition 7092 UI_HELPER_EXECUTOR.post(() -> mContainer.setLocusContext(id, Bundle.EMPTY)); 7093 } 7094 7095 /** 7096 * Moves the provided task into desktop mode, and invoke {@code successCallback} if succeeded. 7097 */ 7098 public void moveTaskToDesktop(TaskContainer taskContainer, 7099 DesktopModeTransitionSource transitionSource, 7100 Runnable successCallback) { 7101 if (!DesktopModeStatus.canEnterDesktopMode(mContext)) { 7102 return; 7103 } 7104 switchToScreenshot(() -> finishRecentsAnimation(/* toRecents= */true, /* shouldPip= */false, 7105 () -> moveTaskToDesktopInternal(taskContainer, successCallback, transitionSource))); 7106 } 7107 7108 private void moveTaskToDesktopInternal(TaskContainer taskContainer, 7109 Runnable successCallback, DesktopModeTransitionSource transitionSource) { 7110 if (mDesktopRecentsTransitionController == null) { 7111 return; 7112 } 7113 7114 mDesktopRecentsTransitionController.moveToDesktop(taskContainer, transitionSource, 7115 successCallback); 7116 } 7117 7118 /** 7119 * Move the provided task into external display and invoke {@code successCallback} if succeeded. 7120 */ 7121 public void moveTaskToExternalDisplay(TaskContainer taskContainer, Runnable successCallback) { 7122 if (!DesktopModeStatus.canEnterDesktopMode(mContext)) { 7123 return; 7124 } 7125 switchToScreenshot(() -> finishRecentsAnimation(/* toRecents= */true, /* shouldPip= */false, 7126 () -> moveTaskToDesktopInternal(taskContainer, successCallback))); 7127 } 7128 7129 private void moveTaskToDesktopInternal(TaskContainer taskContainer, Runnable successCallback) { 7130 if (mDesktopRecentsTransitionController == null) { 7131 return; 7132 } 7133 mDesktopRecentsTransitionController.moveToExternalDisplay(taskContainer.getTask().key.id); 7134 successCallback.run(); 7135 } 7136 7137 7138 // Logs when the orientation of Overview changes. We log both real and fake orientation changes. 7139 private void logOrientationChanged() { 7140 // Only log when Overview is showing. 7141 if (mOverviewStateEnabled) { 7142 mContainer.getStatsLogManager() 7143 .logger() 7144 .withContainerInfo( 7145 LauncherAtom.ContainerInfo.newBuilder() 7146 .setTaskSwitcherContainer( 7147 LauncherAtom.TaskSwitcherContainer.newBuilder() 7148 .setOrientationHandler( 7149 getPagedOrientationHandler() 7150 .getHandlerTypeForLogging())) 7151 .build()) 7152 .log(LAUNCHER_OVERVIEW_ORIENTATION_CHANGED); 7153 } 7154 } 7155 7156 private int getFontWeight() { 7157 int fontWeightAdjustment = getResources().getConfiguration().fontWeightAdjustment; 7158 if (fontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) { 7159 return Typeface.Builder.NORMAL_WEIGHT + fontWeightAdjustment; 7160 } 7161 return Typeface.Builder.NORMAL_WEIGHT; 7162 } 7163 7164 /** 7165 * Creates the spring animations which run as a task settles back into its place in overview. 7166 * 7167 * <p>When a task dismiss is cancelled, the task will return to its original position via a 7168 * spring animation. As it passes the threshold of its settling state, its neighbors will 7169 * spring in response to the perceived impact of the settling task. 7170 */ 7171 public SpringAnimation createTaskDismissSettlingSpringAnimation(TaskView draggedTaskView, 7172 float velocity, boolean isDismissing, int dismissLength, 7173 Function0<Unit> onEndRunnable) { 7174 return mDismissUtils.createTaskDismissSettlingSpringAnimation(draggedTaskView, velocity, 7175 isDismissing, dismissLength, onEndRunnable); 7176 } 7177 7178 /** 7179 * Animates RecentsView's scale to the provided value, using spring animations. 7180 */ 7181 public SpringAnimation animateRecentsScale(float scale) { 7182 return mDismissUtils.animateRecentsScale(scale); 7183 } 7184 7185 public interface TaskLaunchListener { 7186 void onTaskLaunched(); 7187 } 7188 7189 /** 7190 * Sets whether the remote animation targets should draw below the recents view. 7191 * 7192 * @param drawBelowRecents whether the surface should draw below Recents. 7193 * @param remoteTargetHandles collection of remoteTargetHandles in Recents. 7194 */ 7195 public void setDrawBelowRecents(boolean drawBelowRecents, 7196 RemoteTargetHandle[] remoteTargetHandles) { 7197 mBlurUtils.setDrawBelowRecents(drawBelowRecents, remoteTargetHandles); 7198 } 7199 } 7200