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