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.view.Surface.ROTATION_0; 20 import static android.view.View.MeasureSpec.EXACTLY; 21 import static android.view.View.MeasureSpec.makeMeasureSpec; 22 23 import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU; 24 import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType; 25 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS; 26 import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS; 27 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA; 28 import static com.android.launcher3.LauncherState.BACKGROUND_APP; 29 import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION; 30 import static com.android.launcher3.Utilities.EDGE_NAV_BAR; 31 import static com.android.launcher3.Utilities.mapToRange; 32 import static com.android.launcher3.Utilities.squaredHypot; 33 import static com.android.launcher3.Utilities.squaredTouchSlop; 34 import static com.android.launcher3.anim.Interpolators.ACCEL; 35 import static com.android.launcher3.anim.Interpolators.ACCEL_0_5; 36 import static com.android.launcher3.anim.Interpolators.ACCEL_0_75; 37 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL; 38 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; 39 import static com.android.launcher3.anim.Interpolators.LINEAR; 40 import static com.android.launcher3.anim.Interpolators.clampToProgress; 41 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; 42 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL; 43 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP; 44 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN; 45 import static com.android.launcher3.statehandlers.DepthController.DEPTH; 46 import static com.android.launcher3.touch.PagedOrientationHandler.CANVAS_TRANSLATE; 47 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 48 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; 49 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK; 50 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId; 51 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION; 52 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS; 53 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS; 54 55 import android.animation.Animator; 56 import android.animation.AnimatorListenerAdapter; 57 import android.animation.AnimatorSet; 58 import android.animation.LayoutTransition; 59 import android.animation.LayoutTransition.TransitionListener; 60 import android.animation.ObjectAnimator; 61 import android.animation.PropertyValuesHolder; 62 import android.animation.ValueAnimator; 63 import android.annotation.TargetApi; 64 import android.app.ActivityManager.RunningTaskInfo; 65 import android.content.Context; 66 import android.content.LocusId; 67 import android.content.res.Configuration; 68 import android.graphics.BlendMode; 69 import android.graphics.Canvas; 70 import android.graphics.Color; 71 import android.graphics.Matrix; 72 import android.graphics.Point; 73 import android.graphics.PointF; 74 import android.graphics.Rect; 75 import android.graphics.RectF; 76 import android.graphics.Typeface; 77 import android.graphics.drawable.Drawable; 78 import android.os.Build; 79 import android.os.Bundle; 80 import android.os.UserHandle; 81 import android.text.Layout; 82 import android.text.StaticLayout; 83 import android.text.TextPaint; 84 import android.util.AttributeSet; 85 import android.util.FloatProperty; 86 import android.util.SparseBooleanArray; 87 import android.view.Gravity; 88 import android.view.HapticFeedbackConstants; 89 import android.view.KeyEvent; 90 import android.view.LayoutInflater; 91 import android.view.MotionEvent; 92 import android.view.View; 93 import android.view.ViewDebug; 94 import android.view.ViewGroup; 95 import android.view.ViewTreeObserver.OnScrollChangedListener; 96 import android.view.accessibility.AccessibilityEvent; 97 import android.view.accessibility.AccessibilityNodeInfo; 98 import android.view.animation.Interpolator; 99 import android.widget.FrameLayout; 100 import android.widget.ListView; 101 import android.widget.OverScroller; 102 103 import androidx.annotation.Nullable; 104 import androidx.annotation.UiThread; 105 import androidx.core.graphics.ColorUtils; 106 107 import com.android.launcher3.BaseActivity; 108 import com.android.launcher3.BaseActivity.MultiWindowModeChangedListener; 109 import com.android.launcher3.DeviceProfile; 110 import com.android.launcher3.Insettable; 111 import com.android.launcher3.InvariantDeviceProfile; 112 import com.android.launcher3.PagedView; 113 import com.android.launcher3.R; 114 import com.android.launcher3.Utilities; 115 import com.android.launcher3.anim.AnimationSuccessListener; 116 import com.android.launcher3.anim.AnimatorListeners; 117 import com.android.launcher3.anim.AnimatorPlaybackController; 118 import com.android.launcher3.anim.PendingAnimation; 119 import com.android.launcher3.anim.SpringProperty; 120 import com.android.launcher3.compat.AccessibilityManagerCompat; 121 import com.android.launcher3.config.FeatureFlags; 122 import com.android.launcher3.icons.cache.HandlerRunnable; 123 import com.android.launcher3.statehandlers.DepthController; 124 import com.android.launcher3.statemanager.BaseState; 125 import com.android.launcher3.statemanager.StatefulActivity; 126 import com.android.launcher3.touch.OverScroll; 127 import com.android.launcher3.touch.PagedOrientationHandler; 128 import com.android.launcher3.util.DynamicResource; 129 import com.android.launcher3.util.IntSet; 130 import com.android.launcher3.util.MultiValueAlpha; 131 import com.android.launcher3.util.ResourceBasedOverride.Overrides; 132 import com.android.launcher3.util.SplitConfigurationOptions; 133 import com.android.launcher3.util.RunnableList; 134 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; 135 import com.android.launcher3.util.Themes; 136 import com.android.launcher3.util.TranslateEdgeEffect; 137 import com.android.launcher3.util.ViewPool; 138 import com.android.quickstep.AnimatedFloat; 139 import com.android.quickstep.BaseActivityInterface; 140 import com.android.quickstep.GestureState; 141 import com.android.quickstep.RecentsAnimationController; 142 import com.android.quickstep.RecentsAnimationTargets; 143 import com.android.quickstep.RecentsModel; 144 import com.android.quickstep.RecentsModel.TaskVisualsChangeListener; 145 import com.android.quickstep.RemoteAnimationTargets; 146 import com.android.quickstep.SystemUiProxy; 147 import com.android.quickstep.TaskOverlayFactory; 148 import com.android.quickstep.TaskThumbnailCache; 149 import com.android.quickstep.TaskViewUtils; 150 import com.android.quickstep.ViewUtils; 151 import com.android.quickstep.util.LayoutUtils; 152 import com.android.quickstep.util.RecentsOrientedState; 153 import com.android.quickstep.util.SplitScreenBounds; 154 import com.android.quickstep.util.SplitSelectStateController; 155 import com.android.quickstep.util.SurfaceTransactionApplier; 156 import com.android.quickstep.util.TaskViewSimulator; 157 import com.android.quickstep.util.TransformParams; 158 import com.android.systemui.plugins.ResourceProvider; 159 import com.android.systemui.shared.recents.model.Task; 160 import com.android.systemui.shared.recents.model.Task.TaskKey; 161 import com.android.systemui.shared.recents.model.ThumbnailData; 162 import com.android.systemui.shared.system.ActivityManagerWrapper; 163 import com.android.systemui.shared.system.PackageManagerWrapper; 164 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 165 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; 166 import com.android.systemui.shared.system.TaskStackChangeListener; 167 import com.android.systemui.shared.system.TaskStackChangeListeners; 168 import com.android.wm.shell.pip.IPipAnimationListener; 169 170 import java.util.ArrayList; 171 import java.util.List; 172 import java.util.function.Consumer; 173 174 /** 175 * A list of recent tasks. 176 */ 177 @TargetApi(Build.VERSION_CODES.R) 178 public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_TYPE>, 179 STATE_TYPE extends BaseState<STATE_TYPE>> extends PagedView implements Insettable, 180 TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback, 181 TaskVisualsChangeListener, SplitScreenBounds.OnChangeListener { 182 183 // TODO(b/184899234): We use this timeout to wait a fixed period after switching to the 184 // screenshot when dismissing the current live task to ensure the app can try and get stopped. 185 private static final int REMOVE_TASK_WAIT_FOR_APP_STOP_MS = 100; 186 187 public static final FloatProperty<RecentsView> CONTENT_ALPHA = 188 new FloatProperty<RecentsView>("contentAlpha") { 189 @Override 190 public void setValue(RecentsView view, float v) { 191 view.setContentAlpha(v); 192 } 193 194 @Override 195 public Float get(RecentsView view) { 196 return view.getContentAlpha(); 197 } 198 }; 199 200 public static final FloatProperty<RecentsView> FULLSCREEN_PROGRESS = 201 new FloatProperty<RecentsView>("fullscreenProgress") { 202 @Override 203 public void setValue(RecentsView recentsView, float v) { 204 recentsView.setFullscreenProgress(v); 205 } 206 207 @Override 208 public Float get(RecentsView recentsView) { 209 return recentsView.mFullscreenProgress; 210 } 211 }; 212 213 public static final FloatProperty<RecentsView> TASK_MODALNESS = 214 new FloatProperty<RecentsView>("taskModalness") { 215 @Override 216 public void setValue(RecentsView recentsView, float v) { 217 recentsView.setTaskModalness(v); 218 } 219 220 @Override 221 public Float get(RecentsView recentsView) { 222 return recentsView.mTaskModalness; 223 } 224 }; 225 226 public static final FloatProperty<RecentsView> ADJACENT_PAGE_HORIZONTAL_OFFSET = 227 new FloatProperty<RecentsView>("adjacentPageHorizontalOffset") { 228 @Override 229 public void setValue(RecentsView recentsView, float v) { 230 if (recentsView.mAdjacentPageHorizontalOffset != v) { 231 recentsView.mAdjacentPageHorizontalOffset = v; 232 recentsView.updatePageOffsets(); 233 } 234 } 235 236 @Override 237 public Float get(RecentsView recentsView) { 238 return recentsView.mAdjacentPageHorizontalOffset; 239 } 240 }; 241 242 /** 243 * Can be used to tint the color of the RecentsView to simulate a scrim that can views 244 * excluded from. Really should be a proper scrim. 245 * TODO(b/187528071): Remove this and replace with a real scrim. 246 */ 247 private static final FloatProperty<RecentsView> COLOR_TINT = 248 new FloatProperty<RecentsView>("colorTint") { 249 @Override 250 public void setValue(RecentsView recentsView, float v) { 251 recentsView.setColorTint(v); 252 } 253 254 @Override 255 public Float get(RecentsView recentsView) { 256 return recentsView.getColorTint(); 257 } 258 }; 259 260 /** 261 * Even though {@link TaskView} has distinct offsetTranslationX/Y and resistance property, they 262 * are currently both used to apply secondary translation. Should their use cases change to be 263 * more specific, we'd want to create a similar FloatProperty just for a TaskView's 264 * offsetX/Y property 265 */ 266 public static final FloatProperty<RecentsView> TASK_SECONDARY_TRANSLATION = 267 new FloatProperty<RecentsView>("taskSecondaryTranslation") { 268 @Override 269 public void setValue(RecentsView recentsView, float v) { 270 recentsView.setTaskViewsResistanceTranslation(v); 271 } 272 273 @Override 274 public Float get(RecentsView recentsView) { 275 return recentsView.mTaskViewsSecondaryTranslation; 276 } 277 }; 278 279 /** 280 * Even though {@link TaskView} has distinct offsetTranslationX/Y and resistance property, they 281 * are currently both used to apply secondary translation. Should their use cases change to be 282 * more specific, we'd want to create a similar FloatProperty just for a TaskView's 283 * offsetX/Y property 284 */ 285 public static final FloatProperty<RecentsView> TASK_PRIMARY_SPLIT_TRANSLATION = 286 new FloatProperty<RecentsView>("taskPrimarySplitTranslation") { 287 @Override 288 public void setValue(RecentsView recentsView, float v) { 289 recentsView.setTaskViewsPrimarySplitTranslation(v); 290 } 291 292 @Override 293 public Float get(RecentsView recentsView) { 294 return recentsView.mTaskViewsPrimarySplitTranslation; 295 } 296 }; 297 298 public static final FloatProperty<RecentsView> TASK_SECONDARY_SPLIT_TRANSLATION = 299 new FloatProperty<RecentsView>("taskSecondarySplitTranslation") { 300 @Override 301 public void setValue(RecentsView recentsView, float v) { 302 recentsView.setTaskViewsSecondarySplitTranslation(v); 303 } 304 305 @Override 306 public Float get(RecentsView recentsView) { 307 return recentsView.mTaskViewsSecondarySplitTranslation; 308 } 309 }; 310 311 /** Same as normal SCALE_PROPERTY, but also updates page offsets that depend on this scale. */ 312 public static final FloatProperty<RecentsView> RECENTS_SCALE_PROPERTY = 313 new FloatProperty<RecentsView>("recentsScale") { 314 @Override 315 public void setValue(RecentsView view, float scale) { 316 view.setScaleX(scale); 317 view.setScaleY(scale); 318 view.mLastComputedTaskStartPushOutDistance = null; 319 view.mLastComputedTaskEndPushOutDistance = null; 320 view.mLiveTileTaskViewSimulator.recentsViewScale.value = scale; 321 view.setTaskViewsResistanceTranslation(view.mTaskViewsSecondaryTranslation); 322 view.updatePageOffsets(); 323 } 324 325 @Override 326 public Float get(RecentsView view) { 327 return view.getScaleX(); 328 } 329 }; 330 331 public static final FloatProperty<RecentsView> RECENTS_GRID_PROGRESS = 332 new FloatProperty<RecentsView>("recentsGrid") { 333 @Override 334 public void setValue(RecentsView view, float gridProgress) { 335 view.setGridProgress(gridProgress); 336 } 337 338 @Override 339 public Float get(RecentsView view) { 340 return view.mGridProgress; 341 } 342 }; 343 344 // OverScroll constants 345 private static final int OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION = 270; 346 347 private static final int DISMISS_TASK_DURATION = 300; 348 private static final int ADDITION_TASK_DURATION = 200; 349 private static final float INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.55f; 350 private static final float ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET = 0.05f; 351 352 protected final RecentsOrientedState mOrientationState; 353 protected final BaseActivityInterface<STATE_TYPE, ACTIVITY_TYPE> mSizeStrategy; 354 protected RecentsAnimationController mRecentsAnimationController; 355 protected SurfaceTransactionApplier mSyncTransactionApplier; 356 protected int mTaskWidth; 357 protected int mTaskHeight; 358 protected final TransformParams mLiveTileParams = new TransformParams(); 359 protected final TaskViewSimulator mLiveTileTaskViewSimulator; 360 protected final Rect mLastComputedTaskSize = new Rect(); 361 protected final Rect mLastComputedGridSize = new Rect(); 362 protected final Rect mLastComputedGridTaskSize = new Rect(); 363 // How much a task that is directly offscreen will be pushed out due to RecentsView scale/pivot. 364 protected Float mLastComputedTaskStartPushOutDistance = null; 365 protected Float mLastComputedTaskEndPushOutDistance = null; 366 protected boolean mEnableDrawingLiveTile = false; 367 protected final Rect mTempRect = new Rect(); 368 protected final RectF mTempRectF = new RectF(); 369 private final PointF mTempPointF = new PointF(); 370 private final float[] mTempFloat = new float[1]; 371 private final List<OnScrollChangedListener> mScrollListeners = new ArrayList<>(); 372 373 // The threshold at which we update the SystemUI flags when animating from the task into the app 374 public static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.85f; 375 376 protected final ACTIVITY_TYPE mActivity; 377 private final float mFastFlingVelocity; 378 private final RecentsModel mModel; 379 private final int mRowSpacing; 380 private final int mGridSideMargin; 381 private final ClearAllButton mClearAllButton; 382 private final Rect mClearAllButtonDeadZoneRect = new Rect(); 383 private final Rect mTaskViewDeadZoneRect = new Rect(); 384 /** 385 * Reflects if Recents is currently in the middle of a gesture 386 */ 387 private boolean mGestureActive; 388 389 // Keeps track of the previously known visible tasks for purposes of loading/unloading task data 390 private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray(); 391 392 private final InvariantDeviceProfile mIdp; 393 394 private final ViewPool<TaskView> mTaskViewPool; 395 396 private final TaskOverlayFactory mTaskOverlayFactory; 397 398 protected boolean mDisallowScrollToClearAll; 399 private boolean mOverlayEnabled; 400 protected boolean mFreezeViewVisibility; 401 private boolean mOverviewGridEnabled; 402 private boolean mOverviewFullscreenEnabled; 403 404 private float mAdjacentPageHorizontalOffset = 0; 405 protected float mTaskViewsSecondaryTranslation = 0; 406 protected float mTaskViewsPrimarySplitTranslation = 0; 407 protected float mTaskViewsSecondarySplitTranslation = 0; 408 // Progress from 0 to 1 where 0 is a carousel and 1 is a 2 row grid. 409 private float mGridProgress = 0; 410 private final IntSet mTopRowIdSet = new IntSet(); 411 412 // The GestureEndTarget that is still in progress. 413 protected GestureState.GestureEndTarget mCurrentGestureEndTarget; 414 415 // TODO(b/187528071): Remove these and replace with a real scrim. 416 private float mColorTint; 417 private final int mTintingColor; 418 private ObjectAnimator mTintingAnimator; 419 420 private int mOverScrollShift = 0; 421 422 /** 423 * TODO: Call reloadIdNeeded in onTaskStackChanged. 424 */ 425 private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() { 426 @Override 427 public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { 428 if (!mHandleTaskStackChanges) { 429 return; 430 } 431 // Check this is for the right user 432 if (!checkCurrentOrManagedUserId(userId, getContext())) { 433 return; 434 } 435 436 // Remove the task immediately from the task list 437 TaskView taskView = getTaskView(taskId); 438 if (taskView != null) { 439 removeView(taskView); 440 } 441 } 442 443 @Override 444 public void onActivityUnpinned() { 445 if (!mHandleTaskStackChanges) { 446 return; 447 } 448 449 reloadIfNeeded(); 450 enableLayoutTransitions(); 451 } 452 453 @Override 454 public void onTaskRemoved(int taskId) { 455 if (!mHandleTaskStackChanges) { 456 return; 457 } 458 459 TaskView taskView = getTaskView(taskId); 460 if (taskView == null) { 461 return; 462 } 463 Task.TaskKey taskKey = taskView.getTask().key; 464 UI_HELPER_EXECUTOR.execute(new HandlerRunnable<>( 465 UI_HELPER_EXECUTOR.getHandler(), 466 () -> PackageManagerWrapper.getInstance() 467 .getActivityInfo(taskKey.getComponent(), taskKey.userId) == null, 468 MAIN_EXECUTOR, 469 apkRemoved -> { 470 if (apkRemoved) { 471 dismissTask(taskId); 472 } else { 473 mModel.isTaskRemoved(taskKey.id, taskRemoved -> { 474 if (taskRemoved) { 475 dismissTask(taskId); 476 } 477 }); 478 } 479 })); 480 } 481 }; 482 483 private final PinnedStackAnimationListener mIPipAnimationListener = 484 new PinnedStackAnimationListener(); 485 private int mPipCornerRadius; 486 487 // Used to keep track of the last requested task list id, so that we do not request to load the 488 // tasks again if we have already requested it and the task list has not changed 489 private int mTaskListChangeId = -1; 490 491 // Only valid until the launcher state changes to NORMAL 492 protected int mRunningTaskId = -1; 493 protected boolean mRunningTaskTileHidden; 494 private Task mTmpRunningTask; 495 protected int mFocusedTaskId = -1; 496 private float mFocusedTaskRatio; 497 498 private boolean mRunningTaskIconScaledDown = false; 499 private boolean mRunningTaskShowScreenshot = false; 500 501 private boolean mOverviewStateEnabled; 502 private boolean mHandleTaskStackChanges; 503 private boolean mSwipeDownShouldLaunchApp; 504 private boolean mTouchDownToStartHome; 505 private final float mSquaredTouchSlop; 506 private int mDownX; 507 private int mDownY; 508 509 private PendingAnimation mPendingAnimation; 510 private LayoutTransition mLayoutTransition; 511 512 @ViewDebug.ExportedProperty(category = "launcher") 513 protected float mContentAlpha = 1; 514 @ViewDebug.ExportedProperty(category = "launcher") 515 protected float mFullscreenProgress = 0; 516 /** 517 * How modal is the current task to be displayed, 1 means the task is fully modal and no other 518 * tasks are show. 0 means the task is displays in context in the list with other tasks. 519 */ 520 @ViewDebug.ExportedProperty(category = "launcher") 521 protected float mTaskModalness = 0; 522 523 // Keeps track of task id whose visual state should not be reset 524 private int mIgnoreResetTaskId = -1; 525 526 // Variables for empty state 527 private final Drawable mEmptyIcon; 528 private final CharSequence mEmptyMessage; 529 private final TextPaint mEmptyMessagePaint; 530 private final Point mLastMeasureSize = new Point(); 531 private final int mEmptyMessagePadding; 532 private boolean mShowEmptyMessage; 533 private OnEmptyMessageUpdatedListener mOnEmptyMessageUpdatedListener; 534 private Layout mEmptyTextLayout; 535 536 /** 537 * Placeholder view indicating where the first split screen selected app will be placed 538 */ 539 private SplitPlaceholderView mSplitPlaceholderView; 540 /** 541 * The first task that split screen selection was initiated with. When split select state is 542 * initialized, we create a 543 * {@link #createTaskDismissAnimation(TaskView, boolean, boolean, long)} for this TaskView but 544 * don't actually remove the task since the user might back out. As such, we also ensure this 545 * View doesn't go back into the {@link #mTaskViewPool}, see {@link #onViewRemoved(View)} 546 */ 547 private TaskView mSplitHiddenTaskView; 548 /** 549 * Keeps track of the index of the TaskView that split screen was initialized with so we know 550 * where to insert it back into list of taskViews in case user backs out of entering split 551 * screen. 552 * NOTE: This index is the index while {@link #mSplitHiddenTaskView} was a child of recentsView, 553 * this doesn't get adjusted to reflect the new child count after the taskView is dismissed/ 554 * removed from recentsView 555 */ 556 private int mSplitHiddenTaskViewIndex; 557 558 // Keeps track of the index where the first TaskView should be 559 private int mTaskViewStartIndex = 0; 560 private OverviewActionsView mActionsView; 561 562 private MultiWindowModeChangedListener mMultiWindowModeChangedListener = 563 new MultiWindowModeChangedListener() { 564 @Override 565 public void onMultiWindowModeChanged(boolean inMultiWindowMode) { 566 mOrientationState.setMultiWindowMode(inMultiWindowMode); 567 setLayoutRotation(mOrientationState.getTouchRotation(), 568 mOrientationState.getDisplayRotation()); 569 updateChildTaskOrientations(); 570 if (!inMultiWindowMode && mOverviewStateEnabled) { 571 // TODO: Re-enable layout transitions for addition of the unpinned task 572 reloadIfNeeded(); 573 } 574 } 575 }; 576 577 private RunnableList mSideTaskLaunchCallback; 578 RecentsView(Context context, AttributeSet attrs, int defStyleAttr, BaseActivityInterface sizeStrategy)579 public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, 580 BaseActivityInterface sizeStrategy) { 581 super(context, attrs, defStyleAttr); 582 setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing)); 583 setEnableFreeScroll(true); 584 mSizeStrategy = sizeStrategy; 585 mActivity = BaseActivity.fromContext(context); 586 mOrientationState = new RecentsOrientedState( 587 context, mSizeStrategy, this::animateRecentsRotationInPlace); 588 final int rotation = mActivity.getDisplay().getRotation(); 589 mOrientationState.setRecentsRotation(rotation); 590 591 mFastFlingVelocity = getResources() 592 .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity); 593 mModel = RecentsModel.INSTANCE.get(context); 594 mIdp = InvariantDeviceProfile.INSTANCE.get(context); 595 596 mClearAllButton = (ClearAllButton) LayoutInflater.from(context) 597 .inflate(R.layout.overview_clear_all_button, this, false); 598 mClearAllButton.setOnClickListener(this::dismissAllTasks); 599 mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */, 600 10 /* initial size */); 601 602 mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources()); 603 setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR); 604 mRowSpacing = getResources().getDimensionPixelSize(R.dimen.overview_grid_row_spacing); 605 mGridSideMargin = getResources().getDimensionPixelSize(R.dimen.overview_grid_side_margin); 606 mSquaredTouchSlop = squaredTouchSlop(context); 607 608 mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents); 609 mEmptyIcon.setCallback(this); 610 mEmptyMessage = context.getText(R.string.recents_empty_message); 611 mEmptyMessagePaint = new TextPaint(); 612 mEmptyMessagePaint.setColor(Themes.getAttrColor(context, android.R.attr.textColorPrimary)); 613 mEmptyMessagePaint.setTextSize(getResources() 614 .getDimension(R.dimen.recents_empty_message_text_size)); 615 mEmptyMessagePaint.setTypeface(Typeface.create(Themes.getDefaultBodyFont(context), 616 Typeface.NORMAL)); 617 mEmptyMessagePaint.setAntiAlias(true); 618 mEmptyMessagePadding = getResources() 619 .getDimensionPixelSize(R.dimen.recents_empty_message_text_padding); 620 setWillNotDraw(false); 621 updateEmptyMessage(); 622 mOrientationHandler = mOrientationState.getOrientationHandler(); 623 624 mTaskOverlayFactory = Overrides.getObject( 625 TaskOverlayFactory.class, 626 context.getApplicationContext(), 627 R.string.task_overlay_factory_class); 628 629 // Initialize quickstep specific cache params here, as this is constructed only once 630 mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5); 631 632 mLiveTileTaskViewSimulator = new TaskViewSimulator(getContext(), getSizeStrategy(), 633 true /* isForLiveTile */); 634 mLiveTileTaskViewSimulator.recentsViewScale.value = 1; 635 mLiveTileTaskViewSimulator.setOrientationState(mOrientationState); 636 mLiveTileTaskViewSimulator.setDrawsBelowRecents(true); 637 638 mTintingColor = getForegroundScrimDimColor(context); 639 } 640 getScroller()641 public OverScroller getScroller() { 642 return mScroller; 643 } 644 isRtl()645 public boolean isRtl() { 646 return mIsRtl; 647 } 648 649 @Override initEdgeEffect()650 protected void initEdgeEffect() { 651 mEdgeGlowLeft = new TranslateEdgeEffect(getContext()); 652 mEdgeGlowRight = new TranslateEdgeEffect(getContext()); 653 } 654 655 @Override drawEdgeEffect(Canvas canvas)656 protected void drawEdgeEffect(Canvas canvas) { 657 // Do not draw edge effect 658 } 659 660 @Override dispatchDraw(Canvas canvas)661 protected void dispatchDraw(Canvas canvas) { 662 // Draw overscroll 663 if (mAllowOverScroll && (!mEdgeGlowRight.isFinished() || !mEdgeGlowLeft.isFinished())) { 664 final int restoreCount = canvas.save(); 665 666 int primarySize = mOrientationHandler.getPrimaryValue(getWidth(), getHeight()); 667 int scroll = OverScroll.dampedScroll(getUndampedOverScrollShift(), primarySize); 668 mOrientationHandler.set(canvas, CANVAS_TRANSLATE, scroll); 669 670 if (mOverScrollShift != scroll) { 671 mOverScrollShift = scroll; 672 dispatchScrollChanged(); 673 } 674 675 super.dispatchDraw(canvas); 676 canvas.restoreToCount(restoreCount); 677 } else { 678 if (mOverScrollShift != 0) { 679 mOverScrollShift = 0; 680 dispatchScrollChanged(); 681 } 682 super.dispatchDraw(canvas); 683 } 684 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile 685 && mLiveTileParams.getTargetSet() != null) { 686 redrawLiveTile(); 687 } 688 } 689 getUndampedOverScrollShift()690 private float getUndampedOverScrollShift() { 691 final int width = getWidth(); 692 final int height = getHeight(); 693 int primarySize = mOrientationHandler.getPrimaryValue(width, height); 694 int secondarySize = mOrientationHandler.getSecondaryValue(width, height); 695 696 float effectiveShift = 0; 697 if (!mEdgeGlowLeft.isFinished()) { 698 mEdgeGlowLeft.setSize(secondarySize, primarySize); 699 if (((TranslateEdgeEffect) mEdgeGlowLeft).getTranslationShift(mTempFloat)) { 700 effectiveShift = mTempFloat[0]; 701 postInvalidateOnAnimation(); 702 } 703 } 704 if (!mEdgeGlowRight.isFinished()) { 705 mEdgeGlowRight.setSize(secondarySize, primarySize); 706 if (((TranslateEdgeEffect) mEdgeGlowRight).getTranslationShift(mTempFloat)) { 707 effectiveShift -= mTempFloat[0]; 708 postInvalidateOnAnimation(); 709 } 710 } 711 712 return effectiveShift * primarySize; 713 } 714 715 /** 716 * Returns the view shift due to overscroll 717 */ getOverScrollShift()718 public int getOverScrollShift() { 719 return mOverScrollShift; 720 } 721 722 @Override onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData)723 public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) { 724 if (mHandleTaskStackChanges) { 725 TaskView taskView = getTaskView(taskId); 726 if (taskView != null) { 727 Task task = taskView.getTask(); 728 taskView.getThumbnail().setThumbnail(task, thumbnailData); 729 return task; 730 } 731 } 732 return null; 733 } 734 735 @Override onTaskIconChanged(String pkg, UserHandle user)736 public void onTaskIconChanged(String pkg, UserHandle user) { 737 for (int i = 0; i < getTaskViewCount(); i++) { 738 TaskView tv = getTaskViewAt(i); 739 Task task = tv.getTask(); 740 if (task != null && task.key != null && pkg.equals(task.key.getPackageName()) 741 && task.key.userId == user.getIdentifier()) { 742 task.icon = null; 743 if (tv.getIconView().getDrawable() != null) { 744 tv.onTaskListVisibilityChanged(true /* visible */); 745 } 746 } 747 } 748 } 749 750 /** 751 * Update the thumbnail of the task. 752 * @param refreshNow Refresh immediately if it's true. 753 */ updateThumbnail(int taskId, ThumbnailData thumbnailData, boolean refreshNow)754 public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData, boolean refreshNow) { 755 TaskView taskView = getTaskView(taskId); 756 if (taskView != null) { 757 taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData, refreshNow); 758 } 759 return taskView; 760 } 761 762 @Override onWindowVisibilityChanged(int visibility)763 protected void onWindowVisibilityChanged(int visibility) { 764 super.onWindowVisibilityChanged(visibility); 765 updateTaskStackListenerState(); 766 } 767 init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView)768 public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) { 769 mActionsView = actionsView; 770 mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0); 771 mSplitPlaceholderView = splitPlaceholderView; 772 } 773 getSplitPlaceholder()774 public SplitPlaceholderView getSplitPlaceholder() { 775 return mSplitPlaceholderView; 776 } 777 isSplitSelectionActive()778 public boolean isSplitSelectionActive() { 779 return mSplitPlaceholderView.getSplitController().isSplitSelectActive(); 780 } 781 782 @Override onAttachedToWindow()783 protected void onAttachedToWindow() { 784 super.onAttachedToWindow(); 785 updateTaskStackListenerState(); 786 mModel.getThumbnailCache().getHighResLoadingState().addCallback(this); 787 mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener); 788 TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener); 789 mSyncTransactionApplier = new SurfaceTransactionApplier(this); 790 mLiveTileParams.setSyncTransactionApplier(mSyncTransactionApplier); 791 RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this); 792 mIPipAnimationListener.setActivityAndRecentsView(mActivity, this); 793 SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener( 794 mIPipAnimationListener); 795 mOrientationState.initListeners(); 796 SplitScreenBounds.INSTANCE.addOnChangeListener(this); 797 mTaskOverlayFactory.initListeners(); 798 } 799 800 @Override onDetachedFromWindow()801 protected void onDetachedFromWindow() { 802 super.onDetachedFromWindow(); 803 updateTaskStackListenerState(); 804 mModel.getThumbnailCache().getHighResLoadingState().removeCallback(this); 805 mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener); 806 TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener); 807 mSyncTransactionApplier = null; 808 mLiveTileParams.setSyncTransactionApplier(null); 809 executeSideTaskLaunchCallback(); 810 RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this); 811 SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null); 812 SplitScreenBounds.INSTANCE.removeOnChangeListener(this); 813 mIPipAnimationListener.setActivityAndRecentsView(null, null); 814 mOrientationState.destroyListeners(); 815 mTaskOverlayFactory.removeListeners(); 816 } 817 818 @Override onViewRemoved(View child)819 public void onViewRemoved(View child) { 820 super.onViewRemoved(child); 821 822 // Clear the task data for the removed child if it was visible unless it's the initial 823 // taskview for entering split screen, we only pretend to dismiss the task 824 if (child instanceof TaskView && child != mSplitHiddenTaskView) { 825 TaskView taskView = (TaskView) child; 826 mHasVisibleTaskData.delete(taskView.getTask().key.id); 827 mTaskViewPool.recycle(taskView); 828 mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0); 829 } 830 updateTaskStartIndex(child); 831 } 832 833 @Override onViewAdded(View child)834 public void onViewAdded(View child) { 835 super.onViewAdded(child); 836 child.setAlpha(mContentAlpha); 837 // RecentsView is set to RTL in the constructor when system is using LTR. Here we set the 838 // child direction back to match system settings. 839 child.setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_LTR : View.LAYOUT_DIRECTION_RTL); 840 updateTaskStartIndex(child); 841 mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, false); 842 updateEmptyMessage(); 843 } 844 845 @Override draw(Canvas canvas)846 public void draw(Canvas canvas) { 847 maybeDrawEmptyMessage(canvas); 848 super.draw(canvas); 849 } 850 addSideTaskLaunchCallback(RunnableList callback)851 public void addSideTaskLaunchCallback(RunnableList callback) { 852 if (mSideTaskLaunchCallback == null) { 853 mSideTaskLaunchCallback = new RunnableList(); 854 } 855 mSideTaskLaunchCallback.add(callback::executeAllAndDestroy); 856 } 857 executeSideTaskLaunchCallback()858 private void executeSideTaskLaunchCallback() { 859 if (mSideTaskLaunchCallback != null) { 860 mSideTaskLaunchCallback.executeAllAndDestroy(); 861 mSideTaskLaunchCallback = null; 862 } 863 } 864 launchSideTaskInLiveTileModeForRestartedApp(int taskId)865 public void launchSideTaskInLiveTileModeForRestartedApp(int taskId) { 866 if (mRunningTaskId != -1 && mRunningTaskId == taskId) { 867 RemoteAnimationTargets targets = getLiveTileParams().getTargetSet(); 868 if (targets != null && targets.findTask(taskId) != null) { 869 launchSideTaskInLiveTileMode(taskId, targets.apps, targets.wallpapers, 870 targets.nonApps); 871 } 872 } 873 } 874 launchSideTaskInLiveTileMode(int taskId, RemoteAnimationTargetCompat[] apps, RemoteAnimationTargetCompat[] wallpaper, RemoteAnimationTargetCompat[] nonApps)875 public void launchSideTaskInLiveTileMode(int taskId, RemoteAnimationTargetCompat[] apps, 876 RemoteAnimationTargetCompat[] wallpaper, RemoteAnimationTargetCompat[] nonApps) { 877 AnimatorSet anim = new AnimatorSet(); 878 TaskView taskView = getTaskView(taskId); 879 if (taskView == null || !isTaskViewVisible(taskView)) { 880 // TODO: Refine this animation. 881 SurfaceTransactionApplier surfaceApplier = 882 new SurfaceTransactionApplier(mActivity.getDragLayer()); 883 ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1); 884 appAnimator.setDuration(RECENTS_LAUNCH_DURATION); 885 appAnimator.setInterpolator(ACCEL_DEACCEL); 886 appAnimator.addUpdateListener(valueAnimator -> { 887 float percent = valueAnimator.getAnimatedFraction(); 888 SurfaceParams.Builder builder = new SurfaceParams.Builder( 889 apps[apps.length - 1].leash); 890 Matrix matrix = new Matrix(); 891 matrix.postScale(percent, percent); 892 matrix.postTranslate(mActivity.getDeviceProfile().widthPx * (1 - percent) / 2, 893 mActivity.getDeviceProfile().heightPx * (1 - percent) / 2); 894 builder.withAlpha(percent).withMatrix(matrix); 895 surfaceApplier.scheduleApply(builder.build()); 896 }); 897 anim.play(appAnimator); 898 anim.addListener(new AnimatorListenerAdapter() { 899 @Override 900 public void onAnimationEnd(Animator animation) { 901 finishRecentsAnimation(false /* toRecents */, null); 902 } 903 }); 904 } else { 905 TaskViewUtils.composeRecentsLaunchAnimator(anim, taskView, apps, wallpaper, nonApps, 906 true /* launcherClosing */, mActivity.getStateManager(), this, 907 getDepthController()); 908 } 909 anim.start(); 910 } 911 updateTaskStartIndex(View affectingView)912 private void updateTaskStartIndex(View affectingView) { 913 if (!(affectingView instanceof TaskView) && !(affectingView instanceof ClearAllButton)) { 914 int childCount = getChildCount(); 915 916 mTaskViewStartIndex = 0; 917 while (mTaskViewStartIndex < childCount 918 && !(getChildAt(mTaskViewStartIndex) instanceof TaskView)) { 919 mTaskViewStartIndex++; 920 } 921 } 922 } 923 isTaskViewVisible(TaskView tv)924 public boolean isTaskViewVisible(TaskView tv) { 925 if (showAsGrid()) { 926 int screenStart = mOrientationHandler.getPrimaryScroll(this); 927 int screenEnd = screenStart + mOrientationHandler.getMeasuredSize(this); 928 return isTaskViewWithinBounds(tv, screenStart, screenEnd); 929 } else { 930 // For now, just check if it's the active task or an adjacent task 931 return Math.abs(indexOfChild(tv) - getNextPage()) <= 1; 932 } 933 } 934 isTaskViewWithinBounds(TaskView tv, int start, int end)935 private boolean isTaskViewWithinBounds(TaskView tv, int start, int end) { 936 int taskStart = mOrientationHandler.getChildStart(tv) + (int) tv.getOffsetAdjustment( 937 showAsFullscreen(), showAsGrid()); 938 int taskSize = (int) (mOrientationHandler.getMeasuredSize(tv) * tv.getSizeAdjustment( 939 showAsFullscreen())); 940 int taskEnd = taskStart + taskSize; 941 return (taskStart >= start && taskStart <= end) || (taskEnd >= start 942 && taskEnd <= end); 943 } 944 getTaskView(int taskId)945 public TaskView getTaskView(int taskId) { 946 for (int i = 0; i < getTaskViewCount(); i++) { 947 TaskView taskView = getTaskViewAt(i); 948 if (taskView.hasTaskId(taskId)) { 949 return taskView; 950 } 951 } 952 return null; 953 } 954 setOverviewStateEnabled(boolean enabled)955 public void setOverviewStateEnabled(boolean enabled) { 956 mOverviewStateEnabled = enabled; 957 updateTaskStackListenerState(); 958 mOrientationState.setRotationWatcherEnabled(enabled); 959 if (!enabled) { 960 // Reset the running task when leaving overview since it can still have a reference to 961 // its thumbnail 962 mTmpRunningTask = null; 963 if (mSplitPlaceholderView.getSplitController().isSplitSelectActive()) { 964 cancelSplitSelect(false); 965 } 966 } 967 updateLocusId(); 968 } 969 970 /** 971 * Whether the Clear All button is hidden or fully visible. Used to determine if center 972 * displayed page is a task or the Clear All button. 973 * 974 * @return True = Clear All button not fully visible, center page is a task. False = Clear All 975 * button fully visible, center page is Clear All button. 976 */ isClearAllHidden()977 public boolean isClearAllHidden() { 978 return mClearAllButton.getAlpha() != 1f; 979 } 980 981 @Override onPageBeginTransition()982 protected void onPageBeginTransition() { 983 super.onPageBeginTransition(); 984 mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, true); 985 } 986 987 @Override onPageEndTransition()988 protected void onPageEndTransition() { 989 super.onPageEndTransition(); 990 if (isClearAllHidden()) { 991 mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false); 992 } 993 if (getNextPage() > 0) { 994 setSwipeDownShouldLaunchApp(true); 995 } 996 } 997 998 @Override onTouchEvent(MotionEvent ev)999 public boolean onTouchEvent(MotionEvent ev) { 1000 super.onTouchEvent(ev); 1001 1002 if (showAsGrid()) { 1003 int taskCount = getTaskViewCount(); 1004 for (int i = 0; i < taskCount; i++) { 1005 TaskView taskView = getTaskViewAt(i); 1006 if (isTaskViewVisible(taskView) && taskView.offerTouchToChildren(ev)) { 1007 // Keep consuming events to pass to delegate 1008 return true; 1009 } 1010 } 1011 } else { 1012 TaskView taskView = getCurrentPageTaskView(); 1013 if (taskView != null && taskView.offerTouchToChildren(ev)) { 1014 // Keep consuming events to pass to delegate 1015 return true; 1016 } 1017 } 1018 1019 final int x = (int) ev.getX(); 1020 final int y = (int) ev.getY(); 1021 switch (ev.getAction()) { 1022 case MotionEvent.ACTION_UP: 1023 if (mTouchDownToStartHome) { 1024 startHome(); 1025 } 1026 mTouchDownToStartHome = false; 1027 break; 1028 case MotionEvent.ACTION_CANCEL: 1029 mTouchDownToStartHome = false; 1030 break; 1031 case MotionEvent.ACTION_MOVE: 1032 // Passing the touch slop will not allow dismiss to home 1033 if (mTouchDownToStartHome && 1034 (isHandlingTouch() || 1035 squaredHypot(mDownX - x, mDownY - y) > mSquaredTouchSlop)) { 1036 mTouchDownToStartHome = false; 1037 } 1038 break; 1039 case MotionEvent.ACTION_DOWN: 1040 // Touch down anywhere but the deadzone around the visible clear all button and 1041 // between the task views will start home on touch up 1042 if (!isHandlingTouch() && !isModal()) { 1043 if (mShowEmptyMessage) { 1044 mTouchDownToStartHome = true; 1045 } else { 1046 updateDeadZoneRects(); 1047 final boolean clearAllButtonDeadZoneConsumed = 1048 mClearAllButton.getAlpha() == 1 1049 && mClearAllButtonDeadZoneRect.contains(x, y); 1050 final boolean cameFromNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0; 1051 if (!clearAllButtonDeadZoneConsumed && !cameFromNavBar 1052 && !mTaskViewDeadZoneRect.contains(x + getScrollX(), y)) { 1053 mTouchDownToStartHome = true; 1054 } 1055 } 1056 } 1057 mDownX = x; 1058 mDownY = y; 1059 break; 1060 } 1061 1062 return isHandlingTouch(); 1063 } 1064 1065 @Override onNotSnappingToPageInFreeScroll()1066 protected void onNotSnappingToPageInFreeScroll() { 1067 int finalPos = mScroller.getFinalX(); 1068 if (!showAsGrid() && finalPos > mMinScroll && finalPos < mMaxScroll) { 1069 int firstPageScroll = getScrollForPage(!mIsRtl ? 0 : getPageCount() - 1); 1070 int lastPageScroll = getScrollForPage(!mIsRtl ? getPageCount() - 1 : 0); 1071 1072 // If scrolling ends in the half of the added space that is closer to 1073 // the end, settle to the end. Otherwise snap to the nearest page. 1074 // If flinging past one of the ends, don't change the velocity as it 1075 // will get stopped at the end anyway. 1076 int pageSnapped = finalPos < (firstPageScroll + mMinScroll) / 2 1077 ? mMinScroll 1078 : finalPos > (lastPageScroll + mMaxScroll) / 2 1079 ? mMaxScroll 1080 : getScrollForPage(mNextPage); 1081 1082 mScroller.setFinalX(pageSnapped); 1083 // Ensure the scroll/snap doesn't happen too fast; 1084 int extraScrollDuration = OVERSCROLL_PAGE_SNAP_ANIMATION_DURATION 1085 - mScroller.getDuration(); 1086 if (extraScrollDuration > 0) { 1087 mScroller.extendDuration(extraScrollDuration); 1088 } 1089 } 1090 } 1091 1092 @Override determineScrollingStart(MotionEvent ev, float touchSlopScale)1093 protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) { 1094 // Enables swiping to the left or right only if the task overlay is not modal. 1095 if (!isModal()) { 1096 super.determineScrollingStart(ev, touchSlopScale); 1097 } 1098 } 1099 applyLoadPlan(ArrayList<Task> tasks)1100 protected void applyLoadPlan(ArrayList<Task> tasks) { 1101 if (mPendingAnimation != null) { 1102 mPendingAnimation.addEndListener(success -> applyLoadPlan(tasks)); 1103 return; 1104 } 1105 1106 if (tasks == null || tasks.isEmpty()) { 1107 removeTasksViewsAndClearAllButton(); 1108 onTaskStackUpdated(); 1109 return; 1110 } 1111 1112 int currentTaskId = -1; 1113 TaskView currentTaskView = getTaskViewAtByAbsoluteIndex(mCurrentPage); 1114 if (currentTaskView != null) { 1115 currentTaskId = currentTaskView.getTask().key.id; 1116 } 1117 1118 // Unload existing visible task data 1119 unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); 1120 1121 TaskView ignoreResetTaskView = 1122 mIgnoreResetTaskId == -1 ? null : getTaskView(mIgnoreResetTaskId); 1123 1124 final int requiredTaskCount = tasks.size(); 1125 if (getTaskViewCount() != requiredTaskCount) { 1126 if (indexOfChild(mClearAllButton) != -1) { 1127 removeView(mClearAllButton); 1128 } 1129 for (int i = getTaskViewCount(); i < requiredTaskCount; i++) { 1130 addView(mTaskViewPool.getView()); 1131 } 1132 while (getTaskViewCount() > requiredTaskCount) { 1133 removeView(getChildAt(getChildCount() - 1)); 1134 } 1135 if (requiredTaskCount > 0) { 1136 addView(mClearAllButton); 1137 } 1138 } 1139 1140 // Rebind and reset all task views 1141 for (int i = requiredTaskCount - 1; i >= 0; i--) { 1142 final int pageIndex = requiredTaskCount - i - 1 + mTaskViewStartIndex; 1143 final Task task = tasks.get(i); 1144 final TaskView taskView = (TaskView) getChildAt(pageIndex); 1145 taskView.bind(task, mOrientationState); 1146 } 1147 updateTaskSize(); 1148 1149 if (mNextPage == INVALID_PAGE) { 1150 // Set the current page to the running task, but not if settling on new task. 1151 TaskView runningTaskView = getRunningTaskView(); 1152 if (runningTaskView != null) { 1153 setCurrentPage(indexOfChild(runningTaskView)); 1154 } else if (getTaskViewCount() > 0) { 1155 setCurrentPage(indexOfChild(getTaskViewAt(0))); 1156 } 1157 } else if (currentTaskId != -1) { 1158 currentTaskView = getTaskView(currentTaskId); 1159 if (currentTaskView != null) { 1160 setCurrentPage(indexOfChild(currentTaskView)); 1161 } 1162 } 1163 1164 if (mIgnoreResetTaskId != -1 && getTaskView(mIgnoreResetTaskId) != ignoreResetTaskView) { 1165 // If the taskView mapping is changing, do not preserve the visuals. Since we are 1166 // mostly preserving the first task, and new taskViews are added to the end, it should 1167 // generally map to the same task. 1168 mIgnoreResetTaskId = -1; 1169 } 1170 resetTaskVisuals(); 1171 onTaskStackUpdated(); 1172 updateEnabledOverlays(); 1173 } 1174 isModal()1175 private boolean isModal() { 1176 return mTaskModalness > 0; 1177 } 1178 isLoadingTasks()1179 public boolean isLoadingTasks() { 1180 return mModel.isLoadingTasksInBackground(); 1181 } 1182 removeTasksViewsAndClearAllButton()1183 private void removeTasksViewsAndClearAllButton() { 1184 for (int i = getTaskViewCount() - 1; i >= 0; i--) { 1185 removeView(getTaskViewAt(i)); 1186 } 1187 if (indexOfChild(mClearAllButton) != -1) { 1188 removeView(mClearAllButton); 1189 } 1190 } 1191 getTaskViewCount()1192 public int getTaskViewCount() { 1193 int taskViewCount = getChildCount() - mTaskViewStartIndex; 1194 if (indexOfChild(mClearAllButton) != -1) { 1195 taskViewCount--; 1196 } 1197 return taskViewCount; 1198 } 1199 onTaskStackUpdated()1200 protected void onTaskStackUpdated() { 1201 // Lazily update the empty message only when the task stack is reapplied 1202 updateEmptyMessage(); 1203 } 1204 resetTaskVisuals()1205 public void resetTaskVisuals() { 1206 for (int i = getTaskViewCount() - 1; i >= 0; i--) { 1207 TaskView taskView = getTaskViewAt(i); 1208 if (mIgnoreResetTaskId != taskView.getTask().key.id) { 1209 taskView.resetViewTransforms(); 1210 taskView.setStableAlpha(mContentAlpha); 1211 taskView.setFullscreenProgress(mFullscreenProgress); 1212 taskView.setModalness(mTaskModalness); 1213 } 1214 } 1215 if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { 1216 // Since we reuse the same mLiveTileTaskViewSimulator in the RecentsView, we need 1217 // to reset the params after it settles in Overview from swipe up so that we don't 1218 // render with obsolete param values. 1219 mLiveTileTaskViewSimulator.taskPrimaryTranslation.value = 0; 1220 mLiveTileTaskViewSimulator.taskSecondaryTranslation.value = 0; 1221 mLiveTileTaskViewSimulator.fullScreenProgress.value = 0; 1222 mLiveTileTaskViewSimulator.recentsViewScale.value = 1; 1223 1224 // Similar to setRunningTaskHidden below, reapply the state before runningTaskView is 1225 // null. 1226 if (!mRunningTaskShowScreenshot) { 1227 setRunningTaskViewShowScreenshot(mRunningTaskShowScreenshot); 1228 } 1229 } 1230 if (mRunningTaskTileHidden) { 1231 setRunningTaskHidden(mRunningTaskTileHidden); 1232 } 1233 1234 // Force apply the scale. 1235 if (mIgnoreResetTaskId != mRunningTaskId) { 1236 applyRunningTaskIconScale(); 1237 } 1238 1239 updateCurveProperties(); 1240 // Update the set of visible task's data 1241 loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); 1242 setTaskModalness(0); 1243 setColorTint(0); 1244 } 1245 setFullscreenProgress(float fullscreenProgress)1246 public void setFullscreenProgress(float fullscreenProgress) { 1247 mFullscreenProgress = fullscreenProgress; 1248 int taskCount = getTaskViewCount(); 1249 for (int i = 0; i < taskCount; i++) { 1250 getTaskViewAt(i).setFullscreenProgress(mFullscreenProgress); 1251 } 1252 mClearAllButton.setFullscreenProgress(fullscreenProgress); 1253 1254 // Fade out the actions view quickly (0.1 range) 1255 mActionsView.getFullscreenAlpha().setValue( 1256 mapToRange(fullscreenProgress, 0, 0.1f, 1f, 0f, LINEAR)); 1257 } 1258 updateTaskStackListenerState()1259 private void updateTaskStackListenerState() { 1260 boolean handleTaskStackChanges = mOverviewStateEnabled && isAttachedToWindow() 1261 && getWindowVisibility() == VISIBLE; 1262 if (handleTaskStackChanges != mHandleTaskStackChanges) { 1263 mHandleTaskStackChanges = handleTaskStackChanges; 1264 if (handleTaskStackChanges) { 1265 reloadIfNeeded(); 1266 } 1267 } 1268 } 1269 1270 @Override setInsets(Rect insets)1271 public void setInsets(Rect insets) { 1272 mInsets.set(insets); 1273 1274 // Update DeviceProfile dependant state. 1275 DeviceProfile dp = mActivity.getDeviceProfile(); 1276 setOverviewGridEnabled( 1277 mActivity.getStateManager().getState().displayOverviewTasksAsGrid(dp)); 1278 1279 // Propagate DeviceProfile change event. 1280 mLiveTileTaskViewSimulator.setDp(dp); 1281 mActionsView.setDp(dp); 1282 mOrientationState.setDeviceProfile(dp); 1283 1284 // Update RecentsView adn TaskView's DeviceProfile dependent layout. 1285 updateOrientationHandler(); 1286 } 1287 updateOrientationHandler()1288 private void updateOrientationHandler() { 1289 updateOrientationHandler(true); 1290 } 1291 updateOrientationHandler(boolean forceRecreateDragLayerControllers)1292 private void updateOrientationHandler(boolean forceRecreateDragLayerControllers) { 1293 // Handle orientation changes. 1294 PagedOrientationHandler oldOrientationHandler = mOrientationHandler; 1295 mOrientationHandler = mOrientationState.getOrientationHandler(); 1296 1297 mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources()); 1298 setLayoutDirection(mIsRtl 1299 ? View.LAYOUT_DIRECTION_RTL 1300 : View.LAYOUT_DIRECTION_LTR); 1301 mClearAllButton.setLayoutDirection(mIsRtl 1302 ? View.LAYOUT_DIRECTION_LTR 1303 : View.LAYOUT_DIRECTION_RTL); 1304 mClearAllButton.setRotation(mOrientationHandler.getDegreesRotated()); 1305 1306 if (forceRecreateDragLayerControllers 1307 || !mOrientationHandler.equals(oldOrientationHandler)) { 1308 // Changed orientations, update controllers so they intercept accordingly. 1309 mActivity.getDragLayer().recreateControllers(); 1310 } 1311 1312 boolean isInLandscape = mOrientationState.getTouchRotation() != ROTATION_0 1313 || mOrientationState.getRecentsActivityRotation() != ROTATION_0; 1314 mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION, 1315 !mOrientationState.canRecentsActivityRotate() && isInLandscape); 1316 1317 // Update TaskView's DeviceProfile dependent layout. 1318 updateChildTaskOrientations(); 1319 1320 // Recalculate DeviceProfile dependent layout. 1321 updateSizeAndPadding(); 1322 1323 requestLayout(); 1324 // Reapply the current page to update page scrolls. 1325 setCurrentPage(mCurrentPage); 1326 } 1327 1328 // Update task size and padding that are dependent on DeviceProfile and insets. updateSizeAndPadding()1329 private void updateSizeAndPadding() { 1330 DeviceProfile dp = mActivity.getDeviceProfile(); 1331 getTaskSize(mTempRect); 1332 mTaskWidth = mTempRect.width(); 1333 mTaskHeight = mTempRect.height(); 1334 1335 mTempRect.top -= dp.overviewTaskThumbnailTopMarginPx; 1336 setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top, 1337 dp.widthPx - mInsets.right - mTempRect.right, 1338 dp.heightPx - mInsets.bottom - mTempRect.bottom); 1339 1340 mSizeStrategy.calculateGridSize(mActivity, mActivity.getDeviceProfile(), 1341 mLastComputedGridSize); 1342 mSizeStrategy.calculateGridTaskSize(mActivity, mActivity.getDeviceProfile(), 1343 mLastComputedGridTaskSize, mOrientationHandler); 1344 1345 // Force TaskView to update size from thumbnail 1346 updateTaskSize(); 1347 1348 // Update ActionsView position 1349 if (mActionsView != null) { 1350 FrameLayout.LayoutParams layoutParams = 1351 (FrameLayout.LayoutParams) mActionsView.getLayoutParams(); 1352 if (dp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) { 1353 layoutParams.gravity = Gravity.BOTTOM; 1354 layoutParams.bottomMargin = 1355 dp.heightPx - mInsets.bottom - mLastComputedGridSize.bottom; 1356 layoutParams.leftMargin = mLastComputedTaskSize.left; 1357 layoutParams.rightMargin = dp.widthPx - mLastComputedTaskSize.right; 1358 // When in modal state, remove bottom margin to avoid covering content. 1359 mActionsView.setModalTransformY(layoutParams.bottomMargin); 1360 } else { 1361 layoutParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM; 1362 layoutParams.bottomMargin = 0; 1363 layoutParams.leftMargin = 0; 1364 layoutParams.rightMargin = 0; 1365 mActionsView.setModalTransformY(0); 1366 } 1367 mActionsView.setLayoutParams(layoutParams); 1368 } 1369 } 1370 1371 /** 1372 * Updates TaskView scaling and translation required to support variable width. 1373 */ updateTaskSize()1374 private void updateTaskSize() { 1375 final int taskCount = getTaskViewCount(); 1376 if (taskCount == 0) { 1377 return; 1378 } 1379 1380 float accumulatedTranslationX = 0; 1381 for (int i = 0; i < taskCount; i++) { 1382 TaskView taskView = getTaskViewAt(i); 1383 taskView.updateTaskSize(); 1384 taskView.getPrimaryFullscreenTranslationProperty().set(taskView, 1385 accumulatedTranslationX); 1386 taskView.getSecondaryFullscreenTranslationProperty().set(taskView, 0f); 1387 // Compensate space caused by TaskView scaling. 1388 float widthDiff = 1389 taskView.getLayoutParams().width * (1 - taskView.getFullscreenScale()); 1390 accumulatedTranslationX += mIsRtl ? widthDiff : -widthDiff; 1391 } 1392 1393 mClearAllButton.setFullscreenTranslationPrimary(accumulatedTranslationX); 1394 1395 updateGridProperties(); 1396 } 1397 getTaskSize(Rect outRect)1398 public void getTaskSize(Rect outRect) { 1399 mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), outRect, 1400 mOrientationHandler); 1401 mLastComputedTaskSize.set(outRect); 1402 } 1403 1404 /** 1405 * Returns the size of task selected to enter modal state. 1406 */ getSelectedTaskSize()1407 public Point getSelectedTaskSize() { 1408 mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), mTempRect, 1409 mOrientationHandler); 1410 int taskWidth = mTempRect.width(); 1411 int taskHeight = mTempRect.height(); 1412 if (mFocusedTaskId != -1) { 1413 int boxLength = Math.max(taskWidth, taskHeight); 1414 if (mFocusedTaskRatio > 1) { 1415 taskWidth = boxLength; 1416 taskHeight = (int) (boxLength / mFocusedTaskRatio); 1417 } else { 1418 taskWidth = (int) (boxLength * mFocusedTaskRatio); 1419 taskHeight = boxLength; 1420 } 1421 } 1422 return new Point(taskWidth, taskHeight); 1423 } 1424 1425 /** Gets the last computed task size */ getLastComputedTaskSize()1426 public Rect getLastComputedTaskSize() { 1427 return mLastComputedTaskSize; 1428 } 1429 getLastComputedGridTaskSize()1430 public Rect getLastComputedGridTaskSize() { 1431 return mLastComputedGridTaskSize; 1432 } 1433 1434 /** Gets the task size for modal state. */ getModalTaskSize(Rect outRect)1435 public void getModalTaskSize(Rect outRect) { 1436 mSizeStrategy.calculateModalTaskSize(mActivity, mActivity.getDeviceProfile(), outRect); 1437 } 1438 1439 @Override computeScrollHelper()1440 protected boolean computeScrollHelper() { 1441 boolean scrolling = super.computeScrollHelper(); 1442 boolean isFlingingFast = false; 1443 updateCurveProperties(); 1444 if (scrolling || isHandlingTouch()) { 1445 if (scrolling) { 1446 // Check if we are flinging quickly to disable high res thumbnail loading 1447 isFlingingFast = mScroller.getCurrVelocity() > mFastFlingVelocity; 1448 } 1449 1450 // After scrolling, update the visible task's data 1451 loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); 1452 1453 // After scrolling, update ActionsView's visibility. 1454 TaskView focusedTaskView = getFocusedTaskView(); 1455 if (focusedTaskView != null) { 1456 float scrollDiff = Math.abs(getScrollForPage(indexOfChild(focusedTaskView)) 1457 - mOrientationHandler.getPrimaryScroll(this)); 1458 float delta = (mGridSideMargin - scrollDiff) / (float) mGridSideMargin; 1459 mActionsView.getScrollAlpha().setValue(Utilities.boundToRange(delta, 0, 1)); 1460 } 1461 } 1462 1463 // Update the high res thumbnail loader state 1464 mModel.getThumbnailCache().getHighResLoadingState().setFlingingFast(isFlingingFast); 1465 return scrolling; 1466 } 1467 1468 /** 1469 * Scales and adjusts translation of adjacent pages as if on a curved carousel. 1470 */ updateCurveProperties()1471 public void updateCurveProperties() { 1472 if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) { 1473 return; 1474 } 1475 int scroll = mOrientationHandler.getPrimaryScroll(this); 1476 mClearAllButton.onRecentsViewScroll(scroll, mOverviewGridEnabled); 1477 } 1478 1479 @Override getDestinationPage(int scaledScroll)1480 protected int getDestinationPage(int scaledScroll) { 1481 if (!(mActivity.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get())) { 1482 return super.getDestinationPage(scaledScroll); 1483 } 1484 1485 final int childCount = getChildCount(); 1486 if (mPageScrolls == null || childCount != mPageScrolls.length) { 1487 return -1; 1488 } 1489 1490 // When in tablet with variable task width, return the page which scroll is closest to 1491 // screenStart instead of page nearest to center of screen. 1492 int minDistanceFromScreenStart = Integer.MAX_VALUE; 1493 int minDistanceFromScreenStartIndex = -1; 1494 for (int i = 0; i < childCount; ++i) { 1495 int distanceFromScreenStart = Math.abs(mPageScrolls[i] - scaledScroll); 1496 if (distanceFromScreenStart < minDistanceFromScreenStart) { 1497 minDistanceFromScreenStart = distanceFromScreenStart; 1498 minDistanceFromScreenStartIndex = i; 1499 } 1500 } 1501 return minDistanceFromScreenStartIndex; 1502 } 1503 1504 /** 1505 * Iterates through all the tasks, and loads the associated task data for newly visible tasks, 1506 * and unloads the associated task data for tasks that are no longer visible. 1507 */ loadVisibleTaskData(@askView.TaskDataChanges int dataChanges)1508 public void loadVisibleTaskData(@TaskView.TaskDataChanges int dataChanges) { 1509 if (!mOverviewStateEnabled || mTaskListChangeId == -1) { 1510 // Skip loading visible task data if we've already left the overview state, or if the 1511 // task list hasn't been loaded yet (the task views will not reflect the task list) 1512 return; 1513 } 1514 1515 int lower = 0; 1516 int upper = 0; 1517 int visibleStart = 0; 1518 int visibleEnd = 0; 1519 if (showAsGrid()) { 1520 int screenStart = mOrientationHandler.getPrimaryScroll(this); 1521 int pageOrientedSize = mOrientationHandler.getMeasuredSize(this); 1522 int halfScreenSize = pageOrientedSize / 2; 1523 // Use +/- 50% screen width as visible area. 1524 visibleStart = screenStart - halfScreenSize; 1525 visibleEnd = screenStart + pageOrientedSize + halfScreenSize; 1526 } else { 1527 int centerPageIndex = getPageNearestToCenterOfScreen(); 1528 int numChildren = getChildCount(); 1529 lower = Math.max(0, centerPageIndex - 2); 1530 upper = Math.min(centerPageIndex + 2, numChildren - 1); 1531 } 1532 1533 // Update the task data for the in/visible children 1534 for (int i = 0; i < getTaskViewCount(); i++) { 1535 TaskView taskView = getTaskViewAt(i); 1536 Task task = taskView.getTask(); 1537 int index = indexOfChild(taskView); 1538 boolean visible; 1539 if (showAsGrid()) { 1540 visible = isTaskViewWithinBounds(taskView, visibleStart, visibleEnd); 1541 } else { 1542 visible = lower <= index && index <= upper; 1543 } 1544 if (visible) { 1545 if (task == mTmpRunningTask) { 1546 // Skip loading if this is the task that we are animating into 1547 continue; 1548 } 1549 if (!mHasVisibleTaskData.get(task.key.id)) { 1550 // Ignore thumbnail update if it's current running task during the gesture 1551 // We snapshot at end of gesture, it will update then 1552 int changes = dataChanges; 1553 if (taskView == getRunningTaskView() && mGestureActive) { 1554 changes &= ~TaskView.FLAG_UPDATE_THUMBNAIL; 1555 } 1556 taskView.onTaskListVisibilityChanged(true /* visible */, changes); 1557 } 1558 mHasVisibleTaskData.put(task.key.id, visible); 1559 } else { 1560 if (mHasVisibleTaskData.get(task.key.id)) { 1561 taskView.onTaskListVisibilityChanged(false /* visible */, dataChanges); 1562 } 1563 mHasVisibleTaskData.delete(task.key.id); 1564 } 1565 } 1566 } 1567 1568 /** 1569 * Unloads any associated data from the currently visible tasks 1570 */ unloadVisibleTaskData(@askView.TaskDataChanges int dataChanges)1571 private void unloadVisibleTaskData(@TaskView.TaskDataChanges int dataChanges) { 1572 for (int i = 0; i < mHasVisibleTaskData.size(); i++) { 1573 if (mHasVisibleTaskData.valueAt(i)) { 1574 TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i)); 1575 if (taskView != null) { 1576 taskView.onTaskListVisibilityChanged(false /* visible */, dataChanges); 1577 } 1578 } 1579 } 1580 mHasVisibleTaskData.clear(); 1581 } 1582 1583 @Override onHighResLoadingStateChanged(boolean enabled)1584 public void onHighResLoadingStateChanged(boolean enabled) { 1585 // Whenever the high res loading state changes, poke each of the visible tasks to see if 1586 // they want to updated their thumbnail state 1587 for (int i = 0; i < mHasVisibleTaskData.size(); i++) { 1588 if (mHasVisibleTaskData.valueAt(i)) { 1589 TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i)); 1590 if (taskView != null) { 1591 // Poke the view again, which will trigger it to load high res if the state 1592 // is enabled 1593 taskView.onTaskListVisibilityChanged(true /* visible */); 1594 } 1595 } 1596 } 1597 } 1598 startHome()1599 public abstract void startHome(); 1600 1601 /** `true` if there is a +1 space available in overview. */ hasRecentsExtraCard()1602 public boolean hasRecentsExtraCard() { 1603 return false; 1604 } 1605 reset()1606 public void reset() { 1607 setCurrentTask(-1); 1608 mIgnoreResetTaskId = -1; 1609 mTaskListChangeId = -1; 1610 mFocusedTaskId = -1; 1611 1612 if (mRecentsAnimationController != null) { 1613 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile) { 1614 // We are still drawing the live tile, finish it now to clean up. 1615 finishRecentsAnimation(true /* toRecents */, null); 1616 } else { 1617 mRecentsAnimationController = null; 1618 } 1619 } 1620 setEnableDrawingLiveTile(false); 1621 mLiveTileParams.setTargetSet(null); 1622 mLiveTileTaskViewSimulator.setDrawsBelowRecents(true); 1623 1624 // These are relatively expensive and don't need to be done this frame (RecentsView isn't 1625 // visible anyway), so defer by a frame to get off the critical path, e.g. app to home. 1626 post(() -> { 1627 unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); 1628 setCurrentPage(0); 1629 LayoutUtils.setViewEnabled(mActionsView, true); 1630 if (mOrientationState.setGestureActive(false)) { 1631 updateOrientationHandler(/* forceRecreateDragLayerControllers = */ false); 1632 } 1633 }); 1634 } 1635 getRunningTaskId()1636 public int getRunningTaskId() { 1637 return mRunningTaskId; 1638 } 1639 getRunningTaskView()1640 public @Nullable TaskView getRunningTaskView() { 1641 return getTaskView(mRunningTaskId); 1642 } 1643 getRunningTaskIndex()1644 public int getRunningTaskIndex() { 1645 return getTaskIndexForId(mRunningTaskId); 1646 } 1647 getFocusedTaskView()1648 public @Nullable TaskView getFocusedTaskView() { 1649 return getTaskView(mFocusedTaskId); 1650 } 1651 1652 /** 1653 * Returns the width to height ratio of the focused {@link TaskView}. 1654 */ getFocusedTaskRatio()1655 public float getFocusedTaskRatio() { 1656 return mFocusedTaskRatio; 1657 } 1658 1659 /** 1660 * Get the index of the task view whose id matches {@param taskId}. 1661 * @return -1 if there is no task view for the task id, else the index of the task view. 1662 */ getTaskIndexForId(int taskId)1663 public int getTaskIndexForId(int taskId) { 1664 TaskView tv = getTaskView(taskId); 1665 return tv == null ? -1 : indexOfChild(tv); 1666 } 1667 getTaskViewStartIndex()1668 public int getTaskViewStartIndex() { 1669 return mTaskViewStartIndex; 1670 } 1671 1672 /** 1673 * Reloads the view if anything in recents changed. 1674 */ reloadIfNeeded()1675 public void reloadIfNeeded() { 1676 if (!mModel.isTaskListValid(mTaskListChangeId)) { 1677 mTaskListChangeId = mModel.getTasks(this::applyLoadPlan); 1678 } 1679 } 1680 1681 /** 1682 * Called when a gesture from an app is starting. 1683 */ onGestureAnimationStart(RunningTaskInfo runningTaskInfo)1684 public void onGestureAnimationStart(RunningTaskInfo runningTaskInfo) { 1685 mGestureActive = true; 1686 // This needs to be called before the other states are set since it can create the task view 1687 if (mOrientationState.setGestureActive(true)) { 1688 updateOrientationHandler(); 1689 } 1690 1691 showCurrentTask(runningTaskInfo); 1692 setEnableFreeScroll(false); 1693 setEnableDrawingLiveTile(false); 1694 setRunningTaskHidden(true); 1695 setRunningTaskIconScaledDown(true); 1696 } 1697 1698 /** 1699 * Called only when a swipe-up gesture from an app has completed. Only called after 1700 * {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}. 1701 */ onSwipeUpAnimationSuccess()1702 public void onSwipeUpAnimationSuccess() { 1703 if (getRunningTaskView() != null) { 1704 animateUpRunningTaskIconScale(); 1705 } 1706 setSwipeDownShouldLaunchApp(true); 1707 } 1708 animateRecentsRotationInPlace(int newRotation)1709 private void animateRecentsRotationInPlace(int newRotation) { 1710 if (mOrientationState.canRecentsActivityRotate()) { 1711 // Let system take care of the rotation 1712 return; 1713 } 1714 AnimatorSet pa = setRecentsChangedOrientation(true); 1715 pa.addListener(AnimatorListeners.forSuccessCallback(() -> { 1716 setLayoutRotation(newRotation, mOrientationState.getDisplayRotation()); 1717 mActivity.getDragLayer().recreateControllers(); 1718 setRecentsChangedOrientation(false).start(); 1719 })); 1720 pa.start(); 1721 } 1722 setRecentsChangedOrientation(boolean fadeInChildren)1723 public AnimatorSet setRecentsChangedOrientation(boolean fadeInChildren) { 1724 getRunningTaskIndex(); 1725 int runningIndex = getCurrentPage(); 1726 AnimatorSet as = new AnimatorSet(); 1727 for (int i = 0; i < getTaskViewCount(); i++) { 1728 if (runningIndex == i) { 1729 continue; 1730 } 1731 View taskView = getTaskViewAt(i); 1732 as.play(ObjectAnimator.ofFloat(taskView, View.ALPHA, fadeInChildren ? 0 : 1)); 1733 } 1734 return as; 1735 } 1736 1737 updateChildTaskOrientations()1738 private void updateChildTaskOrientations() { 1739 for (int i = 0; i < getTaskViewCount(); i++) { 1740 getTaskViewAt(i).setOrientationState(mOrientationState); 1741 } 1742 TaskMenuView tv = (TaskMenuView) getTopOpenViewWithType(mActivity, TYPE_TASK_MENU); 1743 if (tv != null) { 1744 tv.onRotationChanged(); 1745 } 1746 } 1747 1748 /** 1749 * Called when a gesture from an app has finished, and an end target has been determined. 1750 */ onPrepareGestureEndAnimation( @ullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget)1751 public void onPrepareGestureEndAnimation( 1752 @Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget) { 1753 if (mSizeStrategy.stateFromGestureEndTarget(endTarget) 1754 .displayOverviewTasksAsGrid(mActivity.getDeviceProfile())) { 1755 if (animatorSet == null) { 1756 setGridProgress(1); 1757 } else { 1758 animatorSet.play(ObjectAnimator.ofFloat(this, RECENTS_GRID_PROGRESS, 1)); 1759 } 1760 } 1761 mCurrentGestureEndTarget = endTarget; 1762 if (endTarget == GestureState.GestureEndTarget.NEW_TASK 1763 || endTarget == GestureState.GestureEndTarget.LAST_TASK) { 1764 // When switching to tasks in quick switch, ensures the snapped page's scroll maintain 1765 // invariant between quick switch and overview, to ensure a smooth animation transition. 1766 updateGridProperties(); 1767 } else if (endTarget == GestureState.GestureEndTarget.RECENTS) { 1768 setEnableFreeScroll(true); 1769 } 1770 } 1771 1772 /** 1773 * Called when a gesture from an app has finished, and the animation to the target has ended. 1774 */ onGestureAnimationEnd()1775 public void onGestureAnimationEnd() { 1776 mGestureActive = false; 1777 if (mOrientationState.setGestureActive(false)) { 1778 updateOrientationHandler(); 1779 } 1780 1781 setEnableFreeScroll(true); 1782 setEnableDrawingLiveTile(mCurrentGestureEndTarget == GestureState.GestureEndTarget.RECENTS); 1783 if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) { 1784 setRunningTaskViewShowScreenshot(true); 1785 } 1786 setRunningTaskHidden(false); 1787 animateUpRunningTaskIconScale(); 1788 1789 if (mCurrentGestureEndTarget == GestureState.GestureEndTarget.RECENTS 1790 && (!showAsGrid() || getFocusedTaskView() != null)) { 1791 animateActionsViewIn(); 1792 } 1793 1794 mCurrentGestureEndTarget = null; 1795 1796 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mActivity.getDeviceProfile().isMultiWindowMode) { 1797 switchToScreenshot( 1798 () -> finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, 1799 null)); 1800 } 1801 } 1802 1803 /** 1804 * Returns true if we should add a stub taskView for the running task id 1805 */ shouldAddStubTaskView(RunningTaskInfo runningTaskInfo)1806 protected boolean shouldAddStubTaskView(RunningTaskInfo runningTaskInfo) { 1807 return runningTaskInfo != null && getTaskView(runningTaskInfo.taskId) == null; 1808 } 1809 1810 /** 1811 * Creates a task view (if necessary) to represent the task with the {@param runningTaskId}. 1812 * 1813 * All subsequent calls to reload will keep the task as the first item until {@link #reset()} 1814 * is called. Also scrolls the view to this task. 1815 */ showCurrentTask(RunningTaskInfo runningTaskInfo)1816 public void showCurrentTask(RunningTaskInfo runningTaskInfo) { 1817 if (shouldAddStubTaskView(runningTaskInfo)) { 1818 boolean wasEmpty = getChildCount() == 0; 1819 // Add an empty view for now until the task plan is loaded and applied 1820 final TaskView taskView = mTaskViewPool.getView(); 1821 addView(taskView, mTaskViewStartIndex); 1822 if (wasEmpty) { 1823 addView(mClearAllButton); 1824 } 1825 // The temporary running task is only used for the duration between the start of the 1826 // gesture and the task list is loaded and applied 1827 mTmpRunningTask = Task.from(new TaskKey(runningTaskInfo), runningTaskInfo, false); 1828 taskView.bind(mTmpRunningTask, mOrientationState); 1829 1830 // Measure and layout immediately so that the scroll values is updated instantly 1831 // as the user might be quick-switching 1832 measure(makeMeasureSpec(getMeasuredWidth(), EXACTLY), 1833 makeMeasureSpec(getMeasuredHeight(), EXACTLY)); 1834 layout(getLeft(), getTop(), getRight(), getBottom()); 1835 } 1836 1837 boolean runningTaskTileHidden = mRunningTaskTileHidden; 1838 int runningTaskId = runningTaskInfo == null ? -1 : runningTaskInfo.taskId; 1839 setCurrentTask(runningTaskId); 1840 if (mActivity.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()) { 1841 setFocusedTask(runningTaskId); 1842 } 1843 setCurrentPage(getRunningTaskIndex()); 1844 setRunningTaskViewShowScreenshot(false); 1845 setRunningTaskHidden(runningTaskTileHidden); 1846 // Update task size after setting current task. 1847 updateTaskSize(); 1848 1849 // Reload the task list 1850 mTaskListChangeId = mModel.getTasks(this::applyLoadPlan); 1851 } 1852 1853 /** 1854 * Sets the running task id, cleaning up the old running task if necessary. 1855 */ setCurrentTask(int runningTaskId)1856 public void setCurrentTask(int runningTaskId) { 1857 if (mRunningTaskId == runningTaskId) { 1858 return; 1859 } 1860 1861 if (mRunningTaskId != -1) { 1862 // Reset the state on the old running task view 1863 setRunningTaskIconScaledDown(false); 1864 setRunningTaskViewShowScreenshot(true); 1865 setRunningTaskHidden(false); 1866 } 1867 mRunningTaskId = runningTaskId; 1868 } 1869 1870 /** 1871 * Sets the focused task id and store the width to height ratio of the focused task. 1872 */ setFocusedTask(int focusedTaskId)1873 protected void setFocusedTask(int focusedTaskId) { 1874 mFocusedTaskId = focusedTaskId; 1875 mFocusedTaskRatio = 1876 mLastComputedTaskSize.width() / (float) mLastComputedTaskSize.height(); 1877 } 1878 1879 /** 1880 * Hides the tile associated with {@link #mRunningTaskId} 1881 */ setRunningTaskHidden(boolean isHidden)1882 public void setRunningTaskHidden(boolean isHidden) { 1883 mRunningTaskTileHidden = isHidden; 1884 TaskView runningTask = getRunningTaskView(); 1885 if (runningTask != null) { 1886 runningTask.setStableAlpha(isHidden ? 0 : mContentAlpha); 1887 if (!isHidden) { 1888 AccessibilityManagerCompat.sendCustomAccessibilityEvent(runningTask, 1889 AccessibilityEvent.TYPE_VIEW_FOCUSED, null); 1890 } 1891 } 1892 } 1893 setRunningTaskViewShowScreenshot(boolean showScreenshot)1894 private void setRunningTaskViewShowScreenshot(boolean showScreenshot) { 1895 if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { 1896 mRunningTaskShowScreenshot = showScreenshot; 1897 TaskView runningTaskView = getRunningTaskView(); 1898 if (runningTaskView != null) { 1899 runningTaskView.setShowScreenshot(mRunningTaskShowScreenshot); 1900 } 1901 } 1902 } 1903 setRunningTaskIconScaledDown(boolean isScaledDown)1904 public void setRunningTaskIconScaledDown(boolean isScaledDown) { 1905 if (mRunningTaskIconScaledDown != isScaledDown) { 1906 mRunningTaskIconScaledDown = isScaledDown; 1907 applyRunningTaskIconScale(); 1908 } 1909 } 1910 isTaskIconScaledDown(TaskView taskView)1911 public boolean isTaskIconScaledDown(TaskView taskView) { 1912 return mRunningTaskIconScaledDown && getRunningTaskView() == taskView; 1913 } 1914 applyRunningTaskIconScale()1915 private void applyRunningTaskIconScale() { 1916 TaskView firstTask = getRunningTaskView(); 1917 if (firstTask != null) { 1918 firstTask.setIconScaleAndDim(mRunningTaskIconScaledDown ? 0 : 1); 1919 } 1920 } 1921 animateActionsViewIn()1922 private void animateActionsViewIn() { 1923 ObjectAnimator anim = ObjectAnimator.ofFloat( 1924 mActionsView.getVisibilityAlpha(), MultiValueAlpha.VALUE, 0, 1); 1925 anim.setDuration(TaskView.SCALE_ICON_DURATION); 1926 anim.start(); 1927 } 1928 animateActionsViewOut()1929 private void animateActionsViewOut() { 1930 ObjectAnimator anim = ObjectAnimator.ofFloat( 1931 mActionsView.getVisibilityAlpha(), MultiValueAlpha.VALUE, 1, 0); 1932 anim.setDuration(TaskView.SCALE_ICON_DURATION); 1933 anim.start(); 1934 } 1935 animateUpRunningTaskIconScale()1936 public void animateUpRunningTaskIconScale() { 1937 mRunningTaskIconScaledDown = false; 1938 TaskView firstTask = getRunningTaskView(); 1939 if (firstTask != null) { 1940 firstTask.setIconScaleAnimStartProgress(0f); 1941 firstTask.animateIconScaleAndDimIntoView(); 1942 } 1943 } 1944 1945 /** Updates TaskView and ClearAllButtion scaling and translation required to turn into grid 1946 * layout. 1947 * This method is used when no task dismissal has occurred. 1948 */ updateGridProperties()1949 private void updateGridProperties() { 1950 updateGridProperties(false); 1951 } 1952 1953 /** 1954 * Updates TaskView and ClearAllButton scaling and translation required to turn into grid 1955 * layout. 1956 * This method only calculates the potential position and depends on {@link #setGridProgress} to 1957 * apply the actual scaling and translation. 1958 * 1959 * @param isTaskDismissal indicates if update was called due to task dismissal 1960 */ updateGridProperties(boolean isTaskDismissal)1961 private void updateGridProperties(boolean isTaskDismissal) { 1962 int taskCount = getTaskViewCount(); 1963 if (taskCount == 0) { 1964 return; 1965 } 1966 1967 final int boxLength = Math.max(mLastComputedGridTaskSize.width(), 1968 mLastComputedGridTaskSize.height()); 1969 int taskTopMargin = mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx; 1970 1971 /* 1972 * taskGridVerticalDiff is used to position the top of a task in the top row of the grid 1973 * heightOffset is the vertical space one grid task takes + space between top and 1974 * bottom row 1975 * Summed together they provide the top position for bottom row of grid tasks 1976 */ 1977 final float taskGridVerticalDiff = 1978 mLastComputedGridTaskSize.top - mLastComputedTaskSize.top; 1979 final float heightOffset = (boxLength + taskTopMargin) + mRowSpacing; 1980 1981 int topRowWidth = 0; 1982 int bottomRowWidth = 0; 1983 float topAccumulatedTranslationX = 0; 1984 float bottomAccumulatedTranslationX = 0; 1985 1986 // Contains whether the child index is in top or bottom of grid (for non-focused task) 1987 // Different from mTopRowIdSet, which contains the taskId of what task is in top row 1988 IntSet topSet = new IntSet(); 1989 IntSet bottomSet = new IntSet(); 1990 1991 // Horizontal grid translation for each task 1992 float[] gridTranslations = new float[taskCount]; 1993 1994 int focusedTaskIndex = Integer.MAX_VALUE; 1995 int focusedTaskShift = 0; 1996 int focusedTaskWidthAndSpacing = 0; 1997 int snappedTaskRowWidth = 0; 1998 int snappedPage = getNextPage(); 1999 TaskView snappedTaskView = getTaskViewAtByAbsoluteIndex(snappedPage); 2000 2001 if (!isTaskDismissal) { 2002 mTopRowIdSet.clear(); 2003 } 2004 for (int i = 0; i < taskCount; i++) { 2005 TaskView taskView = getTaskViewAt(i); 2006 int taskWidthAndSpacing = taskView.getLayoutParams().width + mPageSpacing; 2007 // Evenly distribute tasks between rows unless rearranging due to task dismissal, in 2008 // which case keep tasks in their respective rows. For the running task, don't join 2009 // the grid. 2010 if (taskView.isFocusedTask()) { 2011 topRowWidth += taskWidthAndSpacing; 2012 bottomRowWidth += taskWidthAndSpacing; 2013 2014 focusedTaskIndex = i; 2015 focusedTaskWidthAndSpacing = taskWidthAndSpacing; 2016 gridTranslations[i] += focusedTaskShift; 2017 gridTranslations[i] += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing; 2018 2019 // Center view vertically in case it's from different orientation. 2020 taskView.setGridTranslationY((mLastComputedTaskSize.height() + taskTopMargin 2021 - taskView.getLayoutParams().height) / 2f); 2022 2023 if (taskView == snappedTaskView) { 2024 // If focused task is snapped, the row width is just task width and spacing. 2025 snappedTaskRowWidth = taskWidthAndSpacing; 2026 } 2027 } else { 2028 if (i > focusedTaskIndex) { 2029 // For tasks after the focused task, shift by focused task's width and spacing. 2030 gridTranslations[i] += 2031 mIsRtl ? focusedTaskWidthAndSpacing : -focusedTaskWidthAndSpacing; 2032 } else { 2033 // For task before the focused task, accumulate the width and spacing to 2034 // calculate the distance focused task need to shift. 2035 focusedTaskShift += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing; 2036 } 2037 int taskId = taskView.getTask().key.id; 2038 boolean isTopRow = isTaskDismissal ? mTopRowIdSet.contains(taskId) 2039 : topRowWidth <= bottomRowWidth; 2040 if (isTopRow) { 2041 topRowWidth += taskWidthAndSpacing; 2042 topSet.add(i); 2043 mTopRowIdSet.add(taskId); 2044 2045 taskView.setGridTranslationY(taskGridVerticalDiff); 2046 2047 // Move horizontally into empty space. 2048 float widthOffset = 0; 2049 for (int j = i - 1; !topSet.contains(j) && j >= 0; j--) { 2050 if (j == focusedTaskIndex) { 2051 continue; 2052 } 2053 widthOffset += getTaskViewAt(j).getLayoutParams().width + mPageSpacing; 2054 } 2055 2056 float currentTaskTranslationX = mIsRtl ? widthOffset : -widthOffset; 2057 gridTranslations[i] += topAccumulatedTranslationX + currentTaskTranslationX; 2058 topAccumulatedTranslationX += currentTaskTranslationX; 2059 } else { 2060 bottomRowWidth += taskWidthAndSpacing; 2061 bottomSet.add(i); 2062 2063 // Move into bottom row. 2064 taskView.setGridTranslationY(heightOffset + taskGridVerticalDiff); 2065 2066 // Move horizontally into empty space. 2067 float widthOffset = 0; 2068 for (int j = i - 1; !bottomSet.contains(j) && j >= 0; j--) { 2069 if (j == focusedTaskIndex) { 2070 continue; 2071 } 2072 widthOffset += getTaskViewAt(j).getLayoutParams().width + mPageSpacing; 2073 } 2074 2075 float currentTaskTranslationX = mIsRtl ? widthOffset : -widthOffset; 2076 gridTranslations[i] += bottomAccumulatedTranslationX + currentTaskTranslationX; 2077 bottomAccumulatedTranslationX += currentTaskTranslationX; 2078 } 2079 if (taskView == snappedTaskView) { 2080 snappedTaskRowWidth = isTopRow ? topRowWidth : bottomRowWidth; 2081 } 2082 } 2083 } 2084 2085 // We need to maintain snapped task's page scroll invariant between quick switch and 2086 // overview, so we sure snapped task's grid translation is 0, and add a non-fullscreen 2087 // translationX that is the same as snapped task's full scroll adjustment. 2088 float snappedTaskFullscreenScrollAdjustment = 0; 2089 float snappedTaskGridTranslationX = 0; 2090 if (snappedTaskView != null) { 2091 snappedTaskFullscreenScrollAdjustment = snappedTaskView.getScrollAdjustment( 2092 /*fullscreenEnabled=*/true, /*gridEnabled=*/false); 2093 snappedTaskGridTranslationX = gridTranslations[snappedPage - mTaskViewStartIndex]; 2094 } 2095 2096 for (int i = 0; i < taskCount; i++) { 2097 TaskView taskView = getTaskViewAt(i); 2098 taskView.setGridTranslationX(gridTranslations[i] - snappedTaskGridTranslationX); 2099 taskView.getPrimaryNonFullscreenTranslationProperty().set(taskView, 2100 snappedTaskFullscreenScrollAdjustment); 2101 taskView.getSecondaryNonFullscreenTranslationProperty().set(taskView, 0f); 2102 } 2103 2104 // Use the accumulated translation of the row containing the last task. 2105 float clearAllAccumulatedTranslation = topSet.contains(taskCount - 1) 2106 ? topAccumulatedTranslationX : bottomAccumulatedTranslationX; 2107 2108 // If the last task is on the shorter row, ClearAllButton will embed into the shorter row 2109 // which is not what we want. Compensate the width difference of the 2 rows in that case. 2110 float shorterRowCompensation = 0; 2111 if (topRowWidth <= bottomRowWidth) { 2112 if (topSet.contains(taskCount - 1)) { 2113 shorterRowCompensation = bottomRowWidth - topRowWidth; 2114 } 2115 } else { 2116 if (bottomSet.contains(taskCount - 1)) { 2117 shorterRowCompensation = topRowWidth - bottomRowWidth; 2118 } 2119 } 2120 float clearAllShorterRowCompensation = 2121 mIsRtl ? -shorterRowCompensation : shorterRowCompensation; 2122 2123 // If the total width is shorter than one grid's width, move ClearAllButton further away 2124 // accordingly. Update longRowWidth if ClearAllButton has been moved. 2125 float clearAllShortTotalCompensation = 0; 2126 int longRowWidth = Math.max(topRowWidth, bottomRowWidth); 2127 if (longRowWidth < mLastComputedGridSize.width()) { 2128 float shortTotalCompensation = mLastComputedGridSize.width() - longRowWidth; 2129 clearAllShortTotalCompensation = 2130 mIsRtl ? -shortTotalCompensation : shortTotalCompensation; 2131 longRowWidth = mLastComputedGridSize.width(); 2132 } 2133 2134 float clearAllTotalTranslationX = 2135 clearAllAccumulatedTranslation + clearAllShorterRowCompensation 2136 + clearAllShortTotalCompensation + snappedTaskFullscreenScrollAdjustment; 2137 if (focusedTaskIndex < taskCount) { 2138 // Shift by focused task's width and spacing if a task is focused. 2139 clearAllTotalTranslationX += 2140 mIsRtl ? focusedTaskWidthAndSpacing : -focusedTaskWidthAndSpacing; 2141 } 2142 2143 // Make sure there are enough space between snapped page and ClearAllButton, for the case 2144 // of swiping up after quick switch. 2145 if (snappedTaskView != null) { 2146 int distanceFromClearAll = longRowWidth - snappedTaskRowWidth; 2147 int minimumDistance = 2148 mLastComputedGridSize.width() - snappedTaskView.getLayoutParams().width; 2149 if (distanceFromClearAll < minimumDistance) { 2150 int distanceDifference = minimumDistance - distanceFromClearAll; 2151 clearAllTotalTranslationX += mIsRtl ? -distanceDifference : distanceDifference; 2152 } 2153 } 2154 2155 mClearAllButton.setGridTranslationPrimary( 2156 clearAllTotalTranslationX - snappedTaskGridTranslationX); 2157 mClearAllButton.setGridScrollOffset( 2158 mIsRtl ? mLastComputedTaskSize.left - mLastComputedGridSize.left 2159 : mLastComputedTaskSize.right - mLastComputedGridSize.right); 2160 2161 setGridProgress(mGridProgress); 2162 } 2163 isSameGridRow(TaskView taskView1, TaskView taskView2)2164 private boolean isSameGridRow(TaskView taskView1, TaskView taskView2) { 2165 if (taskView1 == null || taskView2 == null) { 2166 return false; 2167 } 2168 int taskId1 = taskView1.getTask().key.id; 2169 int taskId2 = taskView2.getTask().key.id; 2170 if (taskId1 == mFocusedTaskId || taskId2 == mFocusedTaskId) { 2171 return false; 2172 } 2173 return (mTopRowIdSet.contains(taskId1) && mTopRowIdSet.contains(taskId2)) || ( 2174 !mTopRowIdSet.contains(taskId1) && !mTopRowIdSet.contains(taskId2)); 2175 } 2176 2177 /** 2178 * Moves TaskView and ClearAllButton between carousel and 2 row grid. 2179 * 2180 * @param gridProgress 0 = carousel; 1 = 2 row grid. 2181 */ setGridProgress(float gridProgress)2182 private void setGridProgress(float gridProgress) { 2183 int taskCount = getTaskViewCount(); 2184 if (taskCount == 0) { 2185 return; 2186 } 2187 2188 mGridProgress = gridProgress; 2189 2190 for (int i = 0; i < taskCount; i++) { 2191 getTaskViewAt(i).setGridProgress(gridProgress); 2192 } 2193 mClearAllButton.setGridProgress(gridProgress); 2194 } 2195 enableLayoutTransitions()2196 private void enableLayoutTransitions() { 2197 if (mLayoutTransition == null) { 2198 mLayoutTransition = new LayoutTransition(); 2199 mLayoutTransition.enableTransitionType(LayoutTransition.APPEARING); 2200 mLayoutTransition.setDuration(ADDITION_TASK_DURATION); 2201 mLayoutTransition.setStartDelay(LayoutTransition.APPEARING, 0); 2202 2203 mLayoutTransition.addTransitionListener(new TransitionListener() { 2204 @Override 2205 public void startTransition(LayoutTransition transition, ViewGroup viewGroup, 2206 View view, int i) { 2207 } 2208 2209 @Override 2210 public void endTransition(LayoutTransition transition, ViewGroup viewGroup, 2211 View view, int i) { 2212 // When the unpinned task is added, snap to first page and disable transitions 2213 if (view instanceof TaskView) { 2214 snapToPage(0); 2215 setLayoutTransition(null); 2216 } 2217 2218 } 2219 }); 2220 } 2221 setLayoutTransition(mLayoutTransition); 2222 } 2223 setSwipeDownShouldLaunchApp(boolean swipeDownShouldLaunchApp)2224 public void setSwipeDownShouldLaunchApp(boolean swipeDownShouldLaunchApp) { 2225 mSwipeDownShouldLaunchApp = swipeDownShouldLaunchApp; 2226 } 2227 shouldSwipeDownLaunchApp()2228 public boolean shouldSwipeDownLaunchApp() { 2229 return mSwipeDownShouldLaunchApp; 2230 } 2231 setIgnoreResetTask(int taskId)2232 public void setIgnoreResetTask(int taskId) { 2233 mIgnoreResetTaskId = taskId; 2234 } 2235 clearIgnoreResetTask(int taskId)2236 public void clearIgnoreResetTask(int taskId) { 2237 if (mIgnoreResetTaskId == taskId) { 2238 mIgnoreResetTaskId = -1; 2239 } 2240 } 2241 addDismissedTaskAnimations(TaskView taskView, long duration, PendingAnimation anim)2242 private void addDismissedTaskAnimations(TaskView taskView, long duration, 2243 PendingAnimation anim) { 2244 // Use setFloat instead of setViewAlpha as we want to keep the view visible even when it's 2245 // alpha is set to 0 so that it can be recycled in the view pool properly 2246 anim.setFloat(taskView, VIEW_ALPHA, 0, clampToProgress(ACCEL, 0, 0.5f)); 2247 SplitSelectStateController splitController = mSplitPlaceholderView.getSplitController(); 2248 2249 ResourceProvider rp = DynamicResource.provider(mActivity); 2250 SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_START) 2251 .setDampingRatio(rp.getFloat(R.dimen.dismiss_task_trans_y_damping_ratio)) 2252 .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_y_stiffness)); 2253 FloatProperty<TaskView> dismissingTaskViewTranslate = 2254 taskView.getSecondaryDissmissTranslationProperty(); 2255 // TODO(b/186800707) translate entire grid size distance 2256 int translateDistance = mOrientationHandler.getSecondaryDimension(taskView); 2257 int positiveNegativeFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor(); 2258 if (splitController.isSplitSelectActive()) { 2259 // Have the task translate towards whatever side was just pinned 2260 int dir = mOrientationHandler.getSplitTaskViewDismissDirection(splitController 2261 .getActiveSplitPositionOption(), mActivity.getDeviceProfile()); 2262 switch (dir) { 2263 case PagedOrientationHandler.SPLIT_TRANSLATE_SECONDARY_NEGATIVE: 2264 dismissingTaskViewTranslate = taskView 2265 .getSecondaryDissmissTranslationProperty(); 2266 positiveNegativeFactor = -1; 2267 break; 2268 2269 case PagedOrientationHandler.SPLIT_TRANSLATE_PRIMARY_POSITIVE: 2270 dismissingTaskViewTranslate = taskView.getPrimaryDismissTranslationProperty(); 2271 positiveNegativeFactor = 1; 2272 break; 2273 2274 case PagedOrientationHandler.SPLIT_TRANSLATE_PRIMARY_NEGATIVE: 2275 dismissingTaskViewTranslate = taskView.getPrimaryDismissTranslationProperty(); 2276 positiveNegativeFactor = -1; 2277 break; 2278 default: 2279 throw new IllegalStateException("Invalid split task translation: " + dir); 2280 } 2281 } 2282 // Double translation distance so dismissal drag is the full height, as we only animate 2283 // the drag for the first half of the progress. 2284 anim.add(ObjectAnimator.ofFloat(taskView, dismissingTaskViewTranslate, 2285 positiveNegativeFactor * translateDistance * 2).setDuration(duration), LINEAR, sp); 2286 2287 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile 2288 && taskView.isRunningTask()) { 2289 anim.addOnFrameCallback(() -> { 2290 mLiveTileTaskViewSimulator.taskSecondaryTranslation.value = 2291 mOrientationHandler.getSecondaryValue( 2292 taskView.getTranslationX(), 2293 taskView.getTranslationY()); 2294 redrawLiveTile(); 2295 }); 2296 } 2297 } 2298 createTaskDismissAnimation(TaskView taskView, boolean animateTaskView, boolean shouldRemoveTask, long duration)2299 public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView, 2300 boolean shouldRemoveTask, long duration) { 2301 if (mPendingAnimation != null) { 2302 mPendingAnimation.createPlaybackController().dispatchOnCancel().dispatchOnEnd(); 2303 } 2304 PendingAnimation anim = new PendingAnimation(duration); 2305 2306 int count = getPageCount(); 2307 if (count == 0) { 2308 return anim; 2309 } 2310 2311 int[] oldScroll = new int[count]; 2312 int[] newScroll = new int[count]; 2313 getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC); 2314 getPageScrolls(newScroll, false, (v) -> v.getVisibility() != GONE && v != taskView); 2315 int taskCount = getTaskViewCount(); 2316 int scrollDiffPerPage = 0; 2317 if (count > 1) { 2318 scrollDiffPerPage = Math.abs(oldScroll[1] - oldScroll[0]); 2319 } 2320 int draggedIndex = indexOfChild(taskView); 2321 2322 boolean isFocusedTaskDismissed = taskView.getTask().key.id == mFocusedTaskId; 2323 if (isFocusedTaskDismissed && showAsGrid()) { 2324 anim.setFloat(mActionsView, VIEW_ALPHA, 0, clampToProgress(ACCEL_0_5, 0, 0.5f)); 2325 } 2326 float dismissedTaskWidth = taskView.getLayoutParams().width + mPageSpacing; 2327 boolean needsCurveUpdates = false; 2328 for (int i = 0; i < count; i++) { 2329 View child = getChildAt(i); 2330 if (child == taskView) { 2331 if (animateTaskView) { 2332 addDismissedTaskAnimations(taskView, duration, anim); 2333 } 2334 } else if (!showAsGrid()) { 2335 // Compute scroll offsets from task dismissal for animation. 2336 // If we just take newScroll - oldScroll, everything to the right of dragged task 2337 // translates to the left. We need to offset this in some cases: 2338 // - In RTL, add page offset to all pages, since we want pages to move to the right 2339 // Additionally, add a page offset if: 2340 // - Current page is rightmost page (leftmost for RTL) 2341 // - Dragging an adjacent page on the left side (right side for RTL) 2342 int offset = mIsRtl ? scrollDiffPerPage : 0; 2343 if (mCurrentPage == draggedIndex) { 2344 int lastPage = taskCount - 1; 2345 if (mCurrentPage == lastPage) { 2346 offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage; 2347 } 2348 } else { 2349 // Dragging an adjacent page. 2350 int negativeAdjacent = mCurrentPage - 1; // (Right in RTL, left in LTR) 2351 if (draggedIndex == negativeAdjacent) { 2352 offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage; 2353 } 2354 } 2355 2356 int scrollDiff = newScroll[i] - oldScroll[i] + offset; 2357 if (scrollDiff != 0) { 2358 FloatProperty translationProperty = child instanceof TaskView 2359 ? ((TaskView) child).getPrimaryDismissTranslationProperty() 2360 : mOrientationHandler.getPrimaryViewTranslate(); 2361 2362 float additionalDismissDuration = 2363 ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET * Math.abs( 2364 i - draggedIndex); 2365 anim.setFloat(child, translationProperty, scrollDiff, clampToProgress(LINEAR, 2366 Utilities.boundToRange(INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET 2367 + additionalDismissDuration, 0f, 1f), 1)); 2368 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile 2369 && child instanceof TaskView 2370 && ((TaskView) child).isRunningTask()) { 2371 anim.addOnFrameCallback(() -> { 2372 mLiveTileTaskViewSimulator.taskPrimaryTranslation.value = 2373 mOrientationHandler.getPrimaryValue(child.getTranslationX(), 2374 child.getTranslationY()); 2375 redrawLiveTile(); 2376 }); 2377 } 2378 needsCurveUpdates = true; 2379 } 2380 } else if (child instanceof TaskView) { 2381 // Animate task with index >= dismissed index and in the same row as the 2382 // dismissed index, or if the dismissed task was the focused task. Offset 2383 // successive task dismissal durations for a staggered effect. 2384 if (isFocusedTaskDismissed || (i >= draggedIndex && isSameGridRow((TaskView) child, 2385 taskView))) { 2386 FloatProperty translationProperty = 2387 ((TaskView) child).getPrimaryDismissTranslationProperty(); 2388 float additionalDismissDuration = 2389 ADDITIONAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET * Math.abs( 2390 i - draggedIndex); 2391 anim.setFloat(child, translationProperty, 2392 !mIsRtl ? -dismissedTaskWidth : dismissedTaskWidth, 2393 clampToProgress(LINEAR, Utilities.boundToRange( 2394 INITIAL_DISMISS_TRANSLATION_INTERPOLATION_OFFSET 2395 + additionalDismissDuration, 0f, 1f), 1)); 2396 } 2397 } 2398 } 2399 2400 if (needsCurveUpdates) { 2401 anim.addOnFrameCallback(this::updateCurveProperties); 2402 } 2403 2404 // Add a tiny bit of translation Z, so that it draws on top of other views 2405 if (animateTaskView) { 2406 taskView.setTranslationZ(0.1f); 2407 } 2408 2409 mPendingAnimation = anim; 2410 mPendingAnimation.addEndListener(new Consumer<Boolean>() { 2411 @Override 2412 public void accept(Boolean success) { 2413 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile 2414 && taskView.isRunningTask() && success) { 2415 finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, 2416 () -> onEnd(success)); 2417 } else { 2418 onEnd(success); 2419 } 2420 } 2421 2422 @SuppressWarnings("WrongCall") 2423 private void onEnd(boolean success) { 2424 if (success) { 2425 if (shouldRemoveTask) { 2426 if (taskView.getTask() != null) { 2427 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && taskView.isRunningTask()) { 2428 finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, 2429 () -> removeTaskInternal(taskView)); 2430 } else { 2431 removeTaskInternal(taskView); 2432 } 2433 mActivity.getStatsLogManager().logger() 2434 .withItemInfo(taskView.getItemInfo()) 2435 .log(LAUNCHER_TASK_DISMISS_SWIPE_UP); 2436 } 2437 } 2438 2439 // Reset task translations as they may have updated via animations in 2440 // createTaskDismissAnimation 2441 resetTaskVisuals(); 2442 2443 int pageToSnapTo = mCurrentPage; 2444 // Snap to start if focused task was dismissed, as after quick switch it could 2445 // be at any page but the focused task always displays at the start. 2446 if (taskView.getTask().key.id == mFocusedTaskId) { 2447 pageToSnapTo = mTaskViewStartIndex; 2448 } else if (draggedIndex < pageToSnapTo || pageToSnapTo == (getTaskViewCount() 2449 - 1)) { 2450 pageToSnapTo -= 1; 2451 } 2452 removeViewInLayout(taskView); 2453 2454 if (getTaskViewCount() == 0) { 2455 removeViewInLayout(mClearAllButton); 2456 startHome(); 2457 } else { 2458 snapToPageImmediately(pageToSnapTo); 2459 dispatchScrollChanged(); 2460 // Grid got messed up, reapply. 2461 updateGridProperties(true); 2462 if (showAsGrid() && getFocusedTaskView() == null 2463 && mActionsView.getVisibilityAlpha().getValue() == 1) { 2464 animateActionsViewOut(); 2465 } 2466 } 2467 // Update the layout synchronously so that the position of next view is 2468 // immediately available. 2469 onLayout(false /* changed */, getLeft(), getTop(), getRight(), getBottom()); 2470 } 2471 onDismissAnimationEnds(); 2472 mPendingAnimation = null; 2473 } 2474 }); 2475 return anim; 2476 } 2477 removeTaskInternal(TaskView taskView)2478 private void removeTaskInternal(TaskView taskView) { 2479 UI_HELPER_EXECUTOR.getHandler().postDelayed(() -> 2480 ActivityManagerWrapper.getInstance().removeTask( 2481 taskView.getTask().key.id), 2482 REMOVE_TASK_WAIT_FOR_APP_STOP_MS); 2483 } 2484 2485 /** 2486 * @return {@code true} if one of the task thumbnails would intersect/overlap with the 2487 * {@link #mSplitPlaceholderView} 2488 */ shouldShiftThumbnailsForSplitSelect(@plitConfigurationOptions.StagePosition int stagePosition)2489 public boolean shouldShiftThumbnailsForSplitSelect(@SplitConfigurationOptions.StagePosition 2490 int stagePosition) { 2491 if (!mActivity.getDeviceProfile().isTablet) { 2492 // Never enough space on phones 2493 return true; 2494 } else if (!mActivity.getDeviceProfile().isLandscape) { 2495 return false; 2496 } 2497 2498 Rect splitBounds = new Rect(); 2499 float placeholderSize = getResources().getDimension(R.dimen.split_placeholder_size); 2500 // This acts as a best approximation on where the splitplaceholder view would be, 2501 // doesn't need to be exact necessarily. This also doesn't need to take translations 2502 // into account since placeholder view is not translated 2503 if (stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT) { 2504 splitBounds.set((int) (getWidth() - placeholderSize), 0, getWidth(), getHeight()); 2505 } else { 2506 splitBounds.set(0, 0, (int) (placeholderSize), getHeight()); 2507 } 2508 Rect taskBounds = new Rect(); 2509 int taskCount = getTaskViewCount(); 2510 for (int i = 0; i < taskCount; i++) { 2511 TaskView taskView = getTaskViewAt(i); 2512 if (taskView == mSplitHiddenTaskView && taskView != getFocusedTaskView()) { 2513 // Case where the hidden task view would have overlapped w/ placeholder, 2514 // but because it's going to hide we don't care 2515 // TODO (b/187312247) edge case for thumbnails that are off screen but scroll on 2516 continue; 2517 } 2518 taskView.getBoundsOnScreen(taskBounds); 2519 if (Rect.intersects(taskBounds, splitBounds)) { 2520 return true; 2521 } 2522 } 2523 return false; 2524 } 2525 onDismissAnimationEnds()2526 protected void onDismissAnimationEnds() { 2527 } 2528 createAllTasksDismissAnimation(long duration)2529 public PendingAnimation createAllTasksDismissAnimation(long duration) { 2530 if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) { 2531 throw new IllegalStateException("Another pending animation is still running"); 2532 } 2533 PendingAnimation anim = new PendingAnimation(duration); 2534 2535 int count = getTaskViewCount(); 2536 for (int i = 0; i < count; i++) { 2537 addDismissedTaskAnimations(getTaskViewAt(i), duration, anim); 2538 } 2539 2540 mPendingAnimation = anim; 2541 mPendingAnimation.addEndListener(isSuccess -> { 2542 if (isSuccess) { 2543 // Remove all the task views now 2544 finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, () -> { 2545 UI_HELPER_EXECUTOR.getHandler().postDelayed( 2546 ActivityManagerWrapper.getInstance()::removeAllRecentTasks, 2547 REMOVE_TASK_WAIT_FOR_APP_STOP_MS); 2548 removeTasksViewsAndClearAllButton(); 2549 startHome(); 2550 }); 2551 } 2552 mPendingAnimation = null; 2553 }); 2554 return anim; 2555 } 2556 snapToPageRelative(int pageCount, int delta, boolean cycle)2557 private boolean snapToPageRelative(int pageCount, int delta, boolean cycle) { 2558 if (pageCount == 0) { 2559 return false; 2560 } 2561 final int newPageUnbound = getNextPage() + delta; 2562 if (!cycle && (newPageUnbound < 0 || newPageUnbound >= pageCount)) { 2563 return false; 2564 } 2565 snapToPage((newPageUnbound + pageCount) % pageCount); 2566 getChildAt(getNextPage()).requestFocus(); 2567 return true; 2568 } 2569 runDismissAnimation(PendingAnimation pendingAnim)2570 private void runDismissAnimation(PendingAnimation pendingAnim) { 2571 AnimatorPlaybackController controller = pendingAnim.createPlaybackController(); 2572 controller.dispatchOnStart(); 2573 controller.getAnimationPlayer().setInterpolator(FAST_OUT_SLOW_IN); 2574 controller.start(); 2575 } 2576 2577 @UiThread dismissTask(int taskId)2578 private void dismissTask(int taskId) { 2579 TaskView taskView = getTaskView(taskId); 2580 if (taskView == null) { 2581 return; 2582 } 2583 dismissTask(taskView, true /* animate */, false /* removeTask */); 2584 } 2585 dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask)2586 public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) { 2587 runDismissAnimation(createTaskDismissAnimation(taskView, animateTaskView, removeTask, 2588 DISMISS_TASK_DURATION)); 2589 } 2590 2591 @SuppressWarnings("unused") dismissAllTasks(View view)2592 private void dismissAllTasks(View view) { 2593 runDismissAnimation(createAllTasksDismissAnimation(DISMISS_TASK_DURATION)); 2594 mActivity.getStatsLogManager().logger().log(LAUNCHER_TASK_CLEAR_ALL); 2595 } 2596 dismissCurrentTask()2597 private void dismissCurrentTask() { 2598 TaskView taskView = getNextPageTaskView(); 2599 if (taskView != null) { 2600 dismissTask(taskView, true /*animateTaskView*/, true /*removeTask*/); 2601 } 2602 } 2603 2604 @Override dispatchKeyEvent(KeyEvent event)2605 public boolean dispatchKeyEvent(KeyEvent event) { 2606 if (event.getAction() == KeyEvent.ACTION_DOWN) { 2607 switch (event.getKeyCode()) { 2608 case KeyEvent.KEYCODE_TAB: 2609 return snapToPageRelative(getTaskViewCount(), event.isShiftPressed() ? -1 : 1, 2610 event.isAltPressed() /* cycle */); 2611 case KeyEvent.KEYCODE_DPAD_RIGHT: 2612 return snapToPageRelative(getPageCount(), mIsRtl ? -1 : 1, false /* cycle */); 2613 case KeyEvent.KEYCODE_DPAD_LEFT: 2614 return snapToPageRelative(getPageCount(), mIsRtl ? 1 : -1, false /* cycle */); 2615 case KeyEvent.KEYCODE_DEL: 2616 case KeyEvent.KEYCODE_FORWARD_DEL: 2617 dismissCurrentTask(); 2618 return true; 2619 case KeyEvent.KEYCODE_NUMPAD_DOT: 2620 if (event.isAltPressed()) { 2621 // Numpad DEL pressed while holding Alt. 2622 dismissCurrentTask(); 2623 return true; 2624 } 2625 } 2626 } 2627 return super.dispatchKeyEvent(event); 2628 } 2629 2630 @Override onFocusChanged(boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect)2631 protected void onFocusChanged(boolean gainFocus, int direction, 2632 @Nullable Rect previouslyFocusedRect) { 2633 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 2634 if (gainFocus && getChildCount() > 0) { 2635 switch (direction) { 2636 case FOCUS_FORWARD: 2637 setCurrentPage(0); 2638 break; 2639 case FOCUS_BACKWARD: 2640 case FOCUS_RIGHT: 2641 case FOCUS_LEFT: 2642 setCurrentPage(getChildCount() - 1); 2643 break; 2644 } 2645 } 2646 } 2647 getContentAlpha()2648 public float getContentAlpha() { 2649 return mContentAlpha; 2650 } 2651 setContentAlpha(float alpha)2652 public void setContentAlpha(float alpha) { 2653 if (alpha == mContentAlpha) { 2654 return; 2655 } 2656 alpha = Utilities.boundToRange(alpha, 0, 1); 2657 mContentAlpha = alpha; 2658 for (int i = getTaskViewCount() - 1; i >= 0; i--) { 2659 TaskView child = getTaskViewAt(i); 2660 if (!mRunningTaskTileHidden || child.getTask().key.id != mRunningTaskId) { 2661 child.setStableAlpha(alpha); 2662 } 2663 } 2664 mClearAllButton.setContentAlpha(mContentAlpha); 2665 int alphaInt = Math.round(alpha * 255); 2666 mEmptyMessagePaint.setAlpha(alphaInt); 2667 mEmptyIcon.setAlpha(alphaInt); 2668 mActionsView.getContentAlpha().setValue(mContentAlpha); 2669 2670 if (alpha > 0) { 2671 setVisibility(VISIBLE); 2672 } else if (!mFreezeViewVisibility) { 2673 setVisibility(INVISIBLE); 2674 } 2675 } 2676 2677 /** 2678 * Freezes the view visibility change. When frozen, the view will not change its visibility 2679 * to gone due to alpha changes. 2680 */ setFreezeViewVisibility(boolean freezeViewVisibility)2681 public void setFreezeViewVisibility(boolean freezeViewVisibility) { 2682 if (mFreezeViewVisibility != freezeViewVisibility) { 2683 mFreezeViewVisibility = freezeViewVisibility; 2684 if (!mFreezeViewVisibility) { 2685 setVisibility(mContentAlpha > 0 ? VISIBLE : INVISIBLE); 2686 } 2687 } 2688 } 2689 2690 @Override setVisibility(int visibility)2691 public void setVisibility(int visibility) { 2692 super.setVisibility(visibility); 2693 if (mActionsView != null) { 2694 mActionsView.updateHiddenFlags(HIDDEN_NO_RECENTS, visibility != VISIBLE); 2695 if (visibility != VISIBLE) { 2696 mActionsView.updateDisabledFlags(OverviewActionsView.DISABLED_SCROLLING, false); 2697 } 2698 } 2699 } 2700 2701 @Override onConfigurationChanged(Configuration newConfig)2702 protected void onConfigurationChanged(Configuration newConfig) { 2703 super.onConfigurationChanged(newConfig); 2704 updateRecentsRotation(); 2705 } 2706 2707 /** 2708 * Updates {@link RecentsOrientedState}'s cached RecentsView rotation. 2709 */ updateRecentsRotation()2710 public void updateRecentsRotation() { 2711 final int rotation = mActivity.getDisplay().getRotation(); 2712 mOrientationState.setRecentsRotation(rotation); 2713 } 2714 setLayoutRotation(int touchRotation, int displayRotation)2715 public void setLayoutRotation(int touchRotation, int displayRotation) { 2716 if (mOrientationState.update(touchRotation, displayRotation)) { 2717 updateOrientationHandler(); 2718 } 2719 } 2720 getPagedViewOrientedState()2721 public RecentsOrientedState getPagedViewOrientedState() { 2722 return mOrientationState; 2723 } 2724 getPagedOrientationHandler()2725 public PagedOrientationHandler getPagedOrientationHandler() { 2726 return mOrientationHandler; 2727 } 2728 2729 @Nullable getNextTaskView()2730 public TaskView getNextTaskView() { 2731 return getTaskViewAtByAbsoluteIndex(getRunningTaskIndex() + 1); 2732 } 2733 2734 @Nullable getCurrentPageTaskView()2735 public TaskView getCurrentPageTaskView() { 2736 return getTaskViewAtByAbsoluteIndex(getCurrentPage()); 2737 } 2738 2739 @Nullable getNextPageTaskView()2740 public TaskView getNextPageTaskView() { 2741 return getTaskViewAtByAbsoluteIndex(getNextPage()); 2742 } 2743 2744 @Nullable getTaskViewNearestToCenterOfScreen()2745 public TaskView getTaskViewNearestToCenterOfScreen() { 2746 return getTaskViewAtByAbsoluteIndex(getPageNearestToCenterOfScreen()); 2747 } 2748 2749 /** 2750 * Returns null instead of indexOutOfBoundsError when index is not in range 2751 */ 2752 @Nullable getTaskViewAt(int index)2753 public TaskView getTaskViewAt(int index) { 2754 return getTaskViewAtByAbsoluteIndex(index + mTaskViewStartIndex); 2755 } 2756 2757 @Nullable getTaskViewAtByAbsoluteIndex(int index)2758 private TaskView getTaskViewAtByAbsoluteIndex(int index) { 2759 if (index < getChildCount() && index >= 0) { 2760 View child = getChildAt(index); 2761 return child instanceof TaskView ? (TaskView) child : null; 2762 } 2763 return null; 2764 } 2765 setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener listener)2766 public void setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener listener) { 2767 mOnEmptyMessageUpdatedListener = listener; 2768 } 2769 updateEmptyMessage()2770 public void updateEmptyMessage() { 2771 boolean isEmpty = getTaskViewCount() == 0; 2772 boolean hasSizeChanged = mLastMeasureSize.x != getWidth() 2773 || mLastMeasureSize.y != getHeight(); 2774 if (isEmpty == mShowEmptyMessage && !hasSizeChanged) { 2775 return; 2776 } 2777 setContentDescription(isEmpty ? mEmptyMessage : ""); 2778 mShowEmptyMessage = isEmpty; 2779 updateEmptyStateUi(hasSizeChanged); 2780 invalidate(); 2781 2782 if (mOnEmptyMessageUpdatedListener != null) { 2783 mOnEmptyMessageUpdatedListener.onEmptyMessageUpdated(mShowEmptyMessage); 2784 } 2785 } 2786 2787 @Override onLayout(boolean changed, int left, int top, int right, int bottom)2788 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 2789 super.onLayout(changed, left, top, right, bottom); 2790 2791 updateEmptyStateUi(changed); 2792 2793 // Update the pivots such that when the task is scaled, it fills the full page 2794 getTaskSize(mTempRect); 2795 getPagedViewOrientedState().getFullScreenScaleAndPivot(mTempRect, 2796 mActivity.getDeviceProfile(), mTempPointF); 2797 setPivotX(mTempPointF.x); 2798 setPivotY(mTempPointF.y); 2799 setTaskModalness(mTaskModalness); 2800 mLastComputedTaskStartPushOutDistance = null; 2801 mLastComputedTaskEndPushOutDistance = null; 2802 updatePageOffsets(); 2803 setImportantForAccessibility(isModal() ? IMPORTANT_FOR_ACCESSIBILITY_NO 2804 : IMPORTANT_FOR_ACCESSIBILITY_AUTO); 2805 } 2806 updatePageOffsets()2807 private void updatePageOffsets() { 2808 float offset = mAdjacentPageHorizontalOffset; 2809 float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness); 2810 int count = getChildCount(); 2811 2812 TaskView runningTask = mRunningTaskId == -1 || !mRunningTaskTileHidden 2813 ? null : getTaskView(mRunningTaskId); 2814 int midpoint = runningTask == null ? -1 : indexOfChild(runningTask); 2815 int modalMidpoint = getCurrentPage(); 2816 2817 float midpointOffsetSize = 0; 2818 float leftOffsetSize = midpoint - 1 >= 0 2819 ? getHorizontalOffsetSize(midpoint - 1, midpoint, offset) 2820 : 0; 2821 float rightOffsetSize = midpoint + 1 < count 2822 ? getHorizontalOffsetSize(midpoint + 1, midpoint, offset) 2823 : 0; 2824 2825 boolean showAsGrid = showAsGrid(); 2826 float modalMidpointOffsetSize = 0; 2827 float modalLeftOffsetSize = 0; 2828 float modalRightOffsetSize = 0; 2829 float gridOffsetSize = 0; 2830 2831 if (showAsGrid) { 2832 // In grid, we only focus the task on the side. The reference index used for offset 2833 // calculation is the task directly next to the focus task in the grid. 2834 int referenceIndex = modalMidpoint == 0 ? 1 : 0; 2835 gridOffsetSize = referenceIndex < count 2836 ? getHorizontalOffsetSize(referenceIndex, modalMidpoint, modalOffset) 2837 : 0; 2838 } else { 2839 modalLeftOffsetSize = modalMidpoint - 1 >= 0 2840 ? getHorizontalOffsetSize(modalMidpoint - 1, modalMidpoint, modalOffset) 2841 : 0; 2842 modalRightOffsetSize = modalMidpoint + 1 < count 2843 ? getHorizontalOffsetSize(modalMidpoint + 1, modalMidpoint, modalOffset) 2844 : 0; 2845 } 2846 2847 for (int i = 0; i < count; i++) { 2848 float translation = i == midpoint 2849 ? midpointOffsetSize 2850 : i < midpoint 2851 ? leftOffsetSize 2852 : rightOffsetSize; 2853 float modalTranslation = i == modalMidpoint 2854 ? modalMidpointOffsetSize 2855 : showAsGrid 2856 ? gridOffsetSize 2857 : i < modalMidpoint ? modalLeftOffsetSize : modalRightOffsetSize; 2858 float totalTranslation = translation + modalTranslation; 2859 View child = getChildAt(i); 2860 FloatProperty translationProperty = child instanceof TaskView 2861 ? ((TaskView) child).getPrimaryTaskOffsetTranslationProperty() 2862 : mOrientationHandler.getPrimaryViewTranslate(); 2863 translationProperty.set(child, totalTranslation); 2864 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile 2865 && i == getRunningTaskIndex()) { 2866 mLiveTileTaskViewSimulator.taskPrimaryTranslation.value = totalTranslation; 2867 redrawLiveTile(); 2868 } 2869 } 2870 updateCurveProperties(); 2871 } 2872 2873 /** 2874 * Computes the child position with persistent translation considered (see 2875 * {@link TaskView#getPersistentTranslationX()}. 2876 */ 2877 private void getPersistentChildPosition(int childIndex, int midPointScroll, RectF outRect) { 2878 View child = getChildAt(childIndex); 2879 outRect.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom()); 2880 if (child instanceof TaskView) { 2881 TaskView taskView = (TaskView) child; 2882 outRect.offset(taskView.getPersistentTranslationX(), 2883 taskView.getPersistentTranslationY()); 2884 outRect.top += mActivity.getDeviceProfile().overviewTaskThumbnailTopMarginPx; 2885 } 2886 outRect.offset(mOrientationHandler.getPrimaryValue(-midPointScroll, 0), 2887 mOrientationHandler.getSecondaryValue(-midPointScroll, 0)); 2888 } 2889 2890 /** 2891 * Computes the distance to offset the given child such that it is completely offscreen when 2892 * translating away from the given midpoint. 2893 * @param offsetProgress From 0 to 1 where 0 means no offset and 1 means offset offscreen. 2894 */ 2895 private float getHorizontalOffsetSize(int childIndex, int midpointIndex, float offsetProgress) { 2896 if (offsetProgress == 0) { 2897 // Don't bother calculating everything below if we won't offset anyway. 2898 return 0; 2899 } 2900 2901 // First, get the position of the task relative to the midpoint. If there is no midpoint 2902 // then we just use the normal (centered) task position. 2903 RectF taskPosition = mTempRectF; 2904 // Whether the task should be shifted to start direction (i.e. left edge for portrait, top 2905 // edge for landscape/seascape). 2906 boolean isStartShift; 2907 if (midpointIndex > -1) { 2908 // When there is a midpoint reference task, adjacent tasks have less distance to travel 2909 // to reach offscreen. Offset the task position to the task's starting point. 2910 int midpointScroll = getScrollForPage(midpointIndex); 2911 getPersistentChildPosition(midpointIndex, midpointScroll, taskPosition); 2912 float midpointStart = mOrientationHandler.getStart(taskPosition); 2913 2914 getPersistentChildPosition(childIndex, midpointScroll, taskPosition); 2915 // Assume child does not overlap with midPointChild. 2916 isStartShift = mOrientationHandler.getStart(taskPosition) < midpointStart; 2917 } else { 2918 // Position the task at scroll position. 2919 getPersistentChildPosition(childIndex, getScrollForPage(childIndex), taskPosition); 2920 isStartShift = mIsRtl; 2921 } 2922 2923 // Next, calculate the distance to move the task off screen. We also need to account for 2924 // RecentsView scale, because it moves tasks based on its pivot. To do this, we move the 2925 // task position to where it would be offscreen at scale = 1 (computed above), then we 2926 // apply the scale via getMatrix() to determine how much that moves the task from its 2927 // desired position, and adjust the computed distance accordingly. 2928 float distanceToOffscreen; 2929 if (isStartShift) { 2930 float desiredStart = -mOrientationHandler.getPrimarySize(taskPosition); 2931 distanceToOffscreen = -mOrientationHandler.getEnd(taskPosition); 2932 if (mLastComputedTaskStartPushOutDistance == null) { 2933 taskPosition.offsetTo( 2934 mOrientationHandler.getPrimaryValue(desiredStart, 0f), 2935 mOrientationHandler.getSecondaryValue(desiredStart, 0f)); 2936 getMatrix().mapRect(taskPosition); 2937 mLastComputedTaskStartPushOutDistance = mOrientationHandler.getEnd(taskPosition) 2938 / mOrientationHandler.getPrimaryScale(this); 2939 } 2940 distanceToOffscreen -= mLastComputedTaskStartPushOutDistance; 2941 } else { 2942 float desiredStart = mOrientationHandler.getPrimarySize(this); 2943 distanceToOffscreen = desiredStart - mOrientationHandler.getStart(taskPosition); 2944 if (mLastComputedTaskEndPushOutDistance == null) { 2945 taskPosition.offsetTo( 2946 mOrientationHandler.getPrimaryValue(desiredStart, 0f), 2947 mOrientationHandler.getSecondaryValue(desiredStart, 0f)); 2948 getMatrix().mapRect(taskPosition); 2949 mLastComputedTaskEndPushOutDistance = (mOrientationHandler.getStart(taskPosition) 2950 - desiredStart) / mOrientationHandler.getPrimaryScale(this); 2951 } 2952 distanceToOffscreen -= mLastComputedTaskEndPushOutDistance; 2953 } 2954 return distanceToOffscreen * offsetProgress; 2955 } 2956 2957 protected void setTaskViewsResistanceTranslation(float translation) { 2958 mTaskViewsSecondaryTranslation = translation; 2959 for (int i = 0; i < getTaskViewCount(); i++) { 2960 TaskView task = getTaskViewAt(i); 2961 task.getTaskResistanceTranslationProperty().set(task, translation / getScaleY()); 2962 } 2963 mLiveTileTaskViewSimulator.recentsViewSecondaryTranslation.value = translation; 2964 } 2965 2966 protected void setTaskViewsPrimarySplitTranslation(float translation) { 2967 mTaskViewsPrimarySplitTranslation = translation; 2968 for (int i = 0; i < getTaskViewCount(); i++) { 2969 TaskView task = getTaskViewAt(i); 2970 task.getPrimarySplitTranslationProperty().set(task, translation); 2971 } 2972 } 2973 2974 protected void setTaskViewsSecondarySplitTranslation(float translation) { 2975 mTaskViewsSecondarySplitTranslation = translation; 2976 for (int i = 0; i < getTaskViewCount(); i++) { 2977 TaskView task = getTaskViewAt(i); 2978 task.getSecondarySplitTranslationProperty().set(task, translation); 2979 } 2980 } 2981 2982 /** 2983 * Resets the visuals when exit modal state. 2984 */ 2985 public void resetModalVisuals() { 2986 TaskView taskView = getCurrentPageTaskView(); 2987 if (taskView != null) { 2988 taskView.getThumbnail().getTaskOverlay().resetModalVisuals(); 2989 } 2990 } 2991 2992 public void initiateSplitSelect(TaskView taskView, SplitPositionOption splitPositionOption) { 2993 mSplitHiddenTaskView = taskView; 2994 SplitSelectStateController splitController = mSplitPlaceholderView.getSplitController(); 2995 Rect initialBounds = new Rect(taskView.getLeft(), taskView.getTop(), taskView.getRight(), 2996 taskView.getBottom()); 2997 splitController.setInitialTaskSelect(taskView, splitPositionOption, initialBounds); 2998 mSplitHiddenTaskViewIndex = indexOfChild(taskView); 2999 mSplitPlaceholderView.setLayoutParams( 3000 splitController.getLayoutParamsForActivePosition(getResources(), 3001 mActivity.getDeviceProfile())); 3002 mSplitPlaceholderView.setIcon(taskView.getIconView()); 3003 } 3004 3005 public PendingAnimation createSplitSelectInitAnimation() { 3006 int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext()); 3007 return createTaskDismissAnimation(mSplitHiddenTaskView, true, false, duration); 3008 } 3009 3010 public void confirmSplitSelect(TaskView taskView) { 3011 mSplitPlaceholderView.getSplitController().setSecondTaskId(taskView); 3012 resetTaskVisuals(); 3013 setTranslationY(0); 3014 } 3015 3016 public PendingAnimation cancelSplitSelect(boolean animate) { 3017 SplitSelectStateController splitController = mSplitPlaceholderView.getSplitController(); 3018 SplitPositionOption splitOption = splitController.getActiveSplitPositionOption(); 3019 Rect initialBounds = splitController.getInitialBounds(); 3020 splitController.resetState(); 3021 int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext()); 3022 PendingAnimation pendingAnim = new PendingAnimation(duration); 3023 if (!animate) { 3024 resetFromSplitSelectionState(); 3025 return pendingAnim; 3026 } 3027 3028 addViewInLayout(mSplitHiddenTaskView, mSplitHiddenTaskViewIndex, 3029 mSplitHiddenTaskView.getLayoutParams()); 3030 mSplitHiddenTaskView.setAlpha(0); 3031 int[] oldScroll = new int[getChildCount()]; 3032 getPageScrolls(oldScroll, false, 3033 view -> view.getVisibility() != GONE && view != mSplitHiddenTaskView); 3034 3035 int[] newScroll = new int[getChildCount()]; 3036 getPageScrolls(newScroll, false, SIMPLE_SCROLL_LOGIC); 3037 3038 boolean needsCurveUpdates = false; 3039 for (int i = mSplitHiddenTaskViewIndex; i >= 0; i--) { 3040 View child = getChildAt(i); 3041 if (child == mSplitHiddenTaskView) { 3042 TaskView taskView = (TaskView) child; 3043 3044 int dir = mOrientationHandler.getSplitTaskViewDismissDirection(splitOption, 3045 mActivity.getDeviceProfile()); 3046 FloatProperty<TaskView> dismissingTaskViewTranslate; 3047 Rect hiddenBounds = new Rect(taskView.getLeft(), taskView.getTop(), 3048 taskView.getRight(), taskView.getBottom()); 3049 int distanceDelta = 0; 3050 if (dir == PagedOrientationHandler.SPLIT_TRANSLATE_SECONDARY_NEGATIVE) { 3051 dismissingTaskViewTranslate = taskView 3052 .getSecondaryDissmissTranslationProperty(); 3053 distanceDelta = initialBounds.top - hiddenBounds.top; 3054 taskView.layout(initialBounds.left, hiddenBounds.top, initialBounds.right, 3055 hiddenBounds.bottom); 3056 } else { 3057 dismissingTaskViewTranslate = taskView 3058 .getPrimaryDismissTranslationProperty(); 3059 distanceDelta = initialBounds.left - hiddenBounds.left; 3060 taskView.layout(hiddenBounds.left, initialBounds.top, hiddenBounds.right, 3061 initialBounds.bottom); 3062 if (dir == PagedOrientationHandler.SPLIT_TRANSLATE_PRIMARY_POSITIVE) { 3063 distanceDelta *= -1; 3064 } 3065 } 3066 pendingAnim.add(ObjectAnimator.ofFloat(mSplitHiddenTaskView, 3067 dismissingTaskViewTranslate, 3068 distanceDelta)); 3069 pendingAnim.add(ObjectAnimator.ofFloat(mSplitHiddenTaskView, ALPHA, 1)); 3070 } else { 3071 // If insertion is on last index (furthest from clear all), we directly add the view 3072 // else we translate all views to the right of insertion index further right, 3073 // ignore views to left 3074 if (showAsGrid()) { 3075 // TODO(b/186800707) handle more elegantly for grid 3076 continue; 3077 } 3078 int scrollDiff = newScroll[i] - oldScroll[i]; 3079 if (scrollDiff != 0) { 3080 FloatProperty translationProperty = child instanceof TaskView 3081 ? ((TaskView) child).getPrimaryDismissTranslationProperty() 3082 : mOrientationHandler.getPrimaryViewTranslate(); 3083 3084 ResourceProvider rp = DynamicResource.provider(mActivity); 3085 SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_END) 3086 .setDampingRatio( 3087 rp.getFloat(R.dimen.dismiss_task_trans_x_damping_ratio)) 3088 .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_x_stiffness)); 3089 pendingAnim.add(ObjectAnimator.ofFloat(child, translationProperty, scrollDiff) 3090 .setDuration(duration), ACCEL, sp); 3091 needsCurveUpdates = true; 3092 } 3093 } 3094 } 3095 3096 if (needsCurveUpdates) { 3097 pendingAnim.addOnFrameCallback(this::updateCurveProperties); 3098 } 3099 3100 pendingAnim.addListener(new AnimationSuccessListener() { 3101 @Override 3102 public void onAnimationSuccess(Animator animator) { 3103 // TODO(b/186800707) Figure out how to undo for grid view 3104 // Need to handle cases where dismissed task is 3105 // * Top Row 3106 // * Bottom Row 3107 // * Focused Task 3108 updateGridProperties(); 3109 resetFromSplitSelectionState(); 3110 } 3111 }); 3112 3113 return pendingAnim; 3114 } 3115 3116 private void resetFromSplitSelectionState() { 3117 mSplitHiddenTaskView.setTranslationY(0); 3118 if (!showAsGrid()) { 3119 // TODO(b/186800707) 3120 int pageToSnapTo = mCurrentPage; 3121 if (mSplitHiddenTaskViewIndex <= pageToSnapTo) { 3122 pageToSnapTo += 1; 3123 } else { 3124 pageToSnapTo = mSplitHiddenTaskViewIndex; 3125 } 3126 snapToPageImmediately(pageToSnapTo); 3127 } 3128 onLayout(false /* changed */, getLeft(), getTop(), getRight(), getBottom()); 3129 resetTaskVisuals(); 3130 mSplitHiddenTaskView = null; 3131 mSplitHiddenTaskViewIndex = -1; 3132 } 3133 3134 private void updateDeadZoneRects() { 3135 // Get the deadzone rect surrounding the clear all button to not dismiss overview to home 3136 mClearAllButtonDeadZoneRect.setEmpty(); 3137 if (mClearAllButton.getWidth() > 0) { 3138 int verticalMargin = getResources() 3139 .getDimensionPixelSize(R.dimen.recents_clear_all_deadzone_vertical_margin); 3140 mClearAllButton.getHitRect(mClearAllButtonDeadZoneRect); 3141 mClearAllButtonDeadZoneRect.inset(-getPaddingRight() / 2, -verticalMargin); 3142 } 3143 3144 // Get the deadzone rect between the task views 3145 mTaskViewDeadZoneRect.setEmpty(); 3146 int count = getTaskViewCount(); 3147 if (count > 0) { 3148 final View taskView = getTaskViewAt(0); 3149 getTaskViewAt(count - 1).getHitRect(mTaskViewDeadZoneRect); 3150 mTaskViewDeadZoneRect.union(taskView.getLeft(), taskView.getTop(), taskView.getRight(), 3151 taskView.getBottom()); 3152 } 3153 } 3154 3155 private void updateEmptyStateUi(boolean sizeChanged) { 3156 boolean hasValidSize = getWidth() > 0 && getHeight() > 0; 3157 if (sizeChanged && hasValidSize) { 3158 mEmptyTextLayout = null; 3159 mLastMeasureSize.set(getWidth(), getHeight()); 3160 } 3161 3162 if (mShowEmptyMessage && hasValidSize && mEmptyTextLayout == null) { 3163 int availableWidth = mLastMeasureSize.x - mEmptyMessagePadding - mEmptyMessagePadding; 3164 mEmptyTextLayout = StaticLayout.Builder.obtain(mEmptyMessage, 0, mEmptyMessage.length(), 3165 mEmptyMessagePaint, availableWidth) 3166 .setAlignment(Layout.Alignment.ALIGN_CENTER) 3167 .build(); 3168 int totalHeight = mEmptyTextLayout.getHeight() 3169 + mEmptyMessagePadding + mEmptyIcon.getIntrinsicHeight(); 3170 3171 int top = (mLastMeasureSize.y - totalHeight) / 2; 3172 int left = (mLastMeasureSize.x - mEmptyIcon.getIntrinsicWidth()) / 2; 3173 mEmptyIcon.setBounds(left, top, left + mEmptyIcon.getIntrinsicWidth(), 3174 top + mEmptyIcon.getIntrinsicHeight()); 3175 } 3176 } 3177 3178 @Override 3179 protected boolean verifyDrawable(Drawable who) { 3180 return super.verifyDrawable(who) || (mShowEmptyMessage && who == mEmptyIcon); 3181 } 3182 3183 protected void maybeDrawEmptyMessage(Canvas canvas) { 3184 if (mShowEmptyMessage && mEmptyTextLayout != null) { 3185 // Offset to center in the visible (non-padded) part of RecentsView 3186 mTempRect.set(mInsets.left + getPaddingLeft(), mInsets.top + getPaddingTop(), 3187 mInsets.right + getPaddingRight(), mInsets.bottom + getPaddingBottom()); 3188 canvas.save(); 3189 canvas.translate(getScrollX() + (mTempRect.left - mTempRect.right) / 2, 3190 (mTempRect.top - mTempRect.bottom) / 2); 3191 mEmptyIcon.draw(canvas); 3192 canvas.translate(mEmptyMessagePadding, 3193 mEmptyIcon.getBounds().bottom + mEmptyMessagePadding); 3194 mEmptyTextLayout.draw(canvas); 3195 canvas.restore(); 3196 } 3197 } 3198 3199 /** 3200 * Animate adjacent tasks off screen while scaling up. 3201 * 3202 * If launching one of the adjacent tasks, parallax the center task and other adjacent task 3203 * to the right. 3204 */ 3205 public AnimatorSet createAdjacentPageAnimForTaskLaunch(TaskView tv) { 3206 AnimatorSet anim = new AnimatorSet(); 3207 3208 int taskIndex = indexOfChild(tv); 3209 int centerTaskIndex = getCurrentPage(); 3210 boolean launchingCenterTask = taskIndex == centerTaskIndex; 3211 3212 float toScale = getMaxScaleForFullScreen(); 3213 RecentsView recentsView = tv.getRecentsView(); 3214 if (launchingCenterTask) { 3215 anim.play(ObjectAnimator.ofFloat(recentsView, RECENTS_SCALE_PROPERTY, toScale)); 3216 anim.play(ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS, 1)); 3217 } else { 3218 // We are launching an adjacent task, so parallax the center and other adjacent task. 3219 float displacementX = tv.getWidth() * (toScale - 1f); 3220 float primaryTranslation = mIsRtl ? -displacementX : displacementX; 3221 anim.play(ObjectAnimator.ofFloat(getPageAt(centerTaskIndex), 3222 mOrientationHandler.getPrimaryViewTranslate(), primaryTranslation)); 3223 int runningTaskIndex = recentsView.getRunningTaskIndex(); 3224 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && runningTaskIndex != -1 3225 && runningTaskIndex != taskIndex) { 3226 anim.play(ObjectAnimator.ofFloat( 3227 recentsView.getLiveTileTaskViewSimulator().taskPrimaryTranslation, 3228 AnimatedFloat.VALUE, 3229 primaryTranslation)); 3230 } 3231 3232 int otherAdjacentTaskIndex = centerTaskIndex + (centerTaskIndex - taskIndex); 3233 if (otherAdjacentTaskIndex >= 0 && otherAdjacentTaskIndex < getPageCount()) { 3234 PropertyValuesHolder[] properties = new PropertyValuesHolder[3]; 3235 properties[0] = PropertyValuesHolder.ofFloat( 3236 mOrientationHandler.getPrimaryViewTranslate(), primaryTranslation); 3237 properties[1] = PropertyValuesHolder.ofFloat(View.SCALE_X, 1); 3238 properties[2] = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1); 3239 3240 anim.play(ObjectAnimator.ofPropertyValuesHolder(getPageAt(otherAdjacentTaskIndex), 3241 properties)); 3242 } 3243 } 3244 return anim; 3245 } 3246 3247 /** 3248 * Returns the scale up required on the view, so that it coves the screen completely 3249 */ 3250 public float getMaxScaleForFullScreen() { 3251 getTaskSize(mTempRect); 3252 return getPagedViewOrientedState().getFullScreenScaleAndPivot( 3253 mTempRect, mActivity.getDeviceProfile(), mTempPointF); 3254 } 3255 3256 public PendingAnimation createTaskLaunchAnimation( 3257 TaskView tv, long duration, Interpolator interpolator) { 3258 if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) { 3259 throw new IllegalStateException("Another pending animation is still running"); 3260 } 3261 3262 int count = getTaskViewCount(); 3263 if (count == 0) { 3264 return new PendingAnimation(duration); 3265 } 3266 3267 // When swiping down from overview to tasks, ensures the snapped page's scroll maintain 3268 // invariant between quick switch and overview, to ensure a smooth animation transition. 3269 updateGridProperties(); 3270 updateScrollSynchronously(); 3271 3272 int targetSysUiFlags = tv.getThumbnail().getSysUiStatusNavFlags(); 3273 final boolean[] passedOverviewThreshold = new boolean[] {false}; 3274 ValueAnimator progressAnim = ValueAnimator.ofFloat(0, 1); 3275 progressAnim.addUpdateListener(animator -> { 3276 // Once we pass a certain threshold, update the sysui flags to match the target 3277 // tasks' flags 3278 if (animator.getAnimatedFraction() > UPDATE_SYSUI_FLAGS_THRESHOLD) { 3279 mActivity.getSystemUiController().updateUiState( 3280 UI_STATE_FULLSCREEN_TASK, targetSysUiFlags); 3281 } else { 3282 mActivity.getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, 0); 3283 } 3284 3285 // Passing the threshold from taskview to fullscreen app will vibrate 3286 final boolean passed = animator.getAnimatedFraction() >= 3287 SUCCESS_TRANSITION_PROGRESS; 3288 if (passed != passedOverviewThreshold[0]) { 3289 passedOverviewThreshold[0] = passed; 3290 performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, 3291 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); 3292 } 3293 }); 3294 3295 AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv); 3296 3297 DepthController depthController = getDepthController(); 3298 if (depthController != null) { 3299 ObjectAnimator depthAnimator = ObjectAnimator.ofFloat(depthController, DEPTH, 3300 BACKGROUND_APP.getDepth(mActivity)); 3301 anim.play(depthAnimator); 3302 } anim.play(progressAnim)3303 anim.play(progressAnim); anim.setInterpolator(interpolator)3304 anim.setInterpolator(interpolator); 3305 3306 mPendingAnimation = new PendingAnimation(duration); mPendingAnimation.add(anim)3307 mPendingAnimation.add(anim); 3308 if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { mLiveTileTaskViewSimulator.addOverviewToAppAnim(mPendingAnimation, interpolator)3309 mLiveTileTaskViewSimulator.addOverviewToAppAnim(mPendingAnimation, interpolator); mPendingAnimation.addOnFrameCallback(this::redrawLiveTile)3310 mPendingAnimation.addOnFrameCallback(this::redrawLiveTile); 3311 } 3312 mPendingAnimation.addEndListener(isSuccess -> { 3313 if (isSuccess) { 3314 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && tv.isRunningTask()) { 3315 finishRecentsAnimation(false /* toRecents */, null); 3316 onTaskLaunchAnimationEnd(true /* success */); 3317 } else { 3318 tv.launchTask(this::onTaskLaunchAnimationEnd); 3319 } 3320 Task task = tv.getTask(); 3321 if (task != null) { 3322 mActivity.getStatsLogManager().logger().withItemInfo(tv.getItemInfo()) 3323 .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN); 3324 } 3325 } else { 3326 onTaskLaunchAnimationEnd(false); 3327 } 3328 mPendingAnimation = null; 3329 }); 3330 return mPendingAnimation; 3331 } 3332 onTaskLaunchAnimationEnd(boolean success)3333 protected void onTaskLaunchAnimationEnd(boolean success) { 3334 if (success) { 3335 resetTaskVisuals(); 3336 } 3337 } 3338 3339 @Override notifyPageSwitchListener(int prevPage)3340 protected void notifyPageSwitchListener(int prevPage) { 3341 super.notifyPageSwitchListener(prevPage); 3342 loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL); 3343 updateEnabledOverlays(); 3344 } 3345 3346 @Override getCurrentPageDescription()3347 protected String getCurrentPageDescription() { 3348 return ""; 3349 } 3350 3351 @Override addChildrenForAccessibility(ArrayList<View> outChildren)3352 public void addChildrenForAccessibility(ArrayList<View> outChildren) { 3353 // Add children in reverse order 3354 for (int i = getChildCount() - 1; i >= 0; --i) { 3355 outChildren.add(getChildAt(i)); 3356 } 3357 } 3358 3359 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)3360 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 3361 super.onInitializeAccessibilityNodeInfo(info); 3362 final AccessibilityNodeInfo.CollectionInfo 3363 collectionInfo = AccessibilityNodeInfo.CollectionInfo.obtain( 3364 1, getTaskViewCount(), false, 3365 AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_NONE); 3366 info.setCollectionInfo(collectionInfo); 3367 } 3368 3369 @Override onInitializeAccessibilityEvent(AccessibilityEvent event)3370 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 3371 super.onInitializeAccessibilityEvent(event); 3372 3373 final int taskViewCount = getTaskViewCount(); 3374 event.setScrollable(taskViewCount > 0); 3375 3376 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 3377 final int[] visibleTasks = getVisibleChildrenRange(); 3378 event.setFromIndex(taskViewCount - visibleTasks[1]); 3379 event.setToIndex(taskViewCount - visibleTasks[0]); 3380 event.setItemCount(taskViewCount); 3381 } 3382 } 3383 3384 @Override getAccessibilityClassName()3385 public CharSequence getAccessibilityClassName() { 3386 // To hear position-in-list related feedback from Talkback. 3387 return ListView.class.getName(); 3388 } 3389 3390 @Override isPageOrderFlipped()3391 protected boolean isPageOrderFlipped() { 3392 return true; 3393 } 3394 setEnableDrawingLiveTile(boolean enableDrawingLiveTile)3395 public void setEnableDrawingLiveTile(boolean enableDrawingLiveTile) { 3396 mEnableDrawingLiveTile = enableDrawingLiveTile; 3397 } 3398 redrawLiveTile()3399 public void redrawLiveTile() { 3400 if (mLiveTileParams.getTargetSet() != null) { 3401 mLiveTileTaskViewSimulator.apply(mLiveTileParams); 3402 } 3403 } 3404 getLiveTileTaskViewSimulator()3405 public TaskViewSimulator getLiveTileTaskViewSimulator() { 3406 return mLiveTileTaskViewSimulator; 3407 } 3408 getLiveTileParams()3409 public TransformParams getLiveTileParams() { 3410 return mLiveTileParams; 3411 } 3412 3413 // TODO: To be removed in a follow up CL setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController, RecentsAnimationTargets recentsAnimationTargets)3414 public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController, 3415 RecentsAnimationTargets recentsAnimationTargets) { 3416 mRecentsAnimationController = recentsAnimationController; 3417 if (recentsAnimationTargets != null && recentsAnimationTargets.apps.length > 0) { 3418 if (mSyncTransactionApplier != null) { 3419 recentsAnimationTargets.addReleaseCheck(mSyncTransactionApplier); 3420 } 3421 mLiveTileTaskViewSimulator.setPreview( 3422 recentsAnimationTargets.apps[recentsAnimationTargets.apps.length - 1]); 3423 mLiveTileParams.setTargetSet(recentsAnimationTargets); 3424 } 3425 } 3426 finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete)3427 public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) { 3428 finishRecentsAnimation(toRecents, true /* shouldPip */, onFinishComplete); 3429 } 3430 finishRecentsAnimation(boolean toRecents, boolean shouldPip, Runnable onFinishComplete)3431 public void finishRecentsAnimation(boolean toRecents, boolean shouldPip, 3432 Runnable onFinishComplete) { 3433 if (!toRecents && ENABLE_QUICKSTEP_LIVE_TILE.get()) { 3434 // Reset the minimized state since we force-toggled the minimized state when entering 3435 // overview, but never actually finished the recents animation. This is a catch all for 3436 // cases where we haven't already reset it. 3437 SystemUiProxy p = SystemUiProxy.INSTANCE.getNoCreate(); 3438 if (p != null) { 3439 p.setSplitScreenMinimized(false); 3440 } 3441 } 3442 3443 if (mRecentsAnimationController == null) { 3444 if (onFinishComplete != null) { 3445 onFinishComplete.run(); 3446 } 3447 return; 3448 } 3449 3450 final boolean sendUserLeaveHint = toRecents && shouldPip; 3451 if (sendUserLeaveHint) { 3452 // Notify the SysUI to use fade-in animation when entering PiP from live tile. 3453 final SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(getContext()); 3454 systemUiProxy.notifySwipeToHomeFinished(); 3455 systemUiProxy.setShelfHeight(true, mActivity.getDeviceProfile().hotseatBarSizePx); 3456 } 3457 mRecentsAnimationController.finish(toRecents, () -> { 3458 if (onFinishComplete != null) { 3459 onFinishComplete.run(); 3460 } 3461 onRecentsAnimationComplete(); 3462 }, sendUserLeaveHint); 3463 } 3464 3465 /** 3466 * Called when a running recents animation has finished or canceled. 3467 */ onRecentsAnimationComplete()3468 public void onRecentsAnimationComplete() { 3469 // At this point, the recents animation is not running and if the animation was canceled 3470 // by a display rotation then reset this state to show the screenshot 3471 setRunningTaskViewShowScreenshot(true); 3472 // After we finish the recents animation, the current task id should be correctly 3473 // reset so that when the task is launched from Overview later, it goes through the 3474 // flow of starting a new task instead of finishing recents animation to app. A 3475 // typical example of this is (1) user swipes up from app to Overview (2) user 3476 // taps on QSB (3) user goes back to Overview and launch the most recent task. 3477 setCurrentTask(-1); 3478 mRecentsAnimationController = null; 3479 executeSideTaskLaunchCallback(); 3480 } 3481 setDisallowScrollToClearAll(boolean disallowScrollToClearAll)3482 public void setDisallowScrollToClearAll(boolean disallowScrollToClearAll) { 3483 if (mDisallowScrollToClearAll != disallowScrollToClearAll) { 3484 mDisallowScrollToClearAll = disallowScrollToClearAll; 3485 updateMinAndMaxScrollX(); 3486 } 3487 } 3488 3489 /** 3490 * Updates page scroll synchronously and layout child views. 3491 */ updateScrollSynchronously()3492 public void updateScrollSynchronously() { 3493 onLayout(false /* changed */, getLeft(), getTop(), getRight(), getBottom()); 3494 updateMinAndMaxScrollX(); 3495 } 3496 3497 @Override computeMinScroll()3498 protected int computeMinScroll() { 3499 if (getTaskViewCount() > 0) { 3500 if (mIsRtl) { 3501 // If we aren't showing the clear all button, use the rightmost task as the min 3502 // scroll. 3503 return getScrollForPage(mDisallowScrollToClearAll ? indexOfChild( 3504 getTaskViewAt(getTaskViewCount() - 1)) : indexOfChild(mClearAllButton)); 3505 } else { 3506 TaskView focusedTaskView = showAsGrid() ? getFocusedTaskView() : null; 3507 return getScrollForPage(focusedTaskView != null ? indexOfChild(focusedTaskView) 3508 : mTaskViewStartIndex); 3509 } 3510 } 3511 return super.computeMinScroll(); 3512 } 3513 3514 @Override computeMaxScroll()3515 protected int computeMaxScroll() { 3516 if (getTaskViewCount() > 0) { 3517 if (mIsRtl) { 3518 TaskView focusedTaskView = showAsGrid() ? getFocusedTaskView() : null; 3519 return getScrollForPage(focusedTaskView != null ? indexOfChild(focusedTaskView) 3520 : mTaskViewStartIndex); 3521 } else { 3522 // If we aren't showing the clear all button, use the leftmost task as the min 3523 // scroll. 3524 return getScrollForPage(mDisallowScrollToClearAll ? indexOfChild( 3525 getTaskViewAt(getTaskViewCount() - 1)) : indexOfChild(mClearAllButton)); 3526 } 3527 } 3528 return super.computeMaxScroll(); 3529 } 3530 3531 /** 3532 * Returns page scroll of ClearAllButton. 3533 */ getClearAllScroll()3534 public int getClearAllScroll() { 3535 return getScrollForPage(indexOfChild(mClearAllButton)); 3536 } 3537 3538 @Override getPageScrolls(int[] outPageScrolls, boolean layoutChildren, ComputePageScrollsLogic scrollLogic)3539 protected boolean getPageScrolls(int[] outPageScrolls, boolean layoutChildren, 3540 ComputePageScrollsLogic scrollLogic) { 3541 int[] newPageScrolls = new int[outPageScrolls.length]; 3542 super.getPageScrolls(newPageScrolls, layoutChildren, scrollLogic); 3543 boolean showAsFullscreen = showAsFullscreen(); 3544 boolean showAsGrid = showAsGrid(); 3545 3546 // Align ClearAllButton to the left (RTL) or right (non-RTL), which is different from other 3547 // TaskViews. This must be called after laying out ClearAllButton. 3548 if (layoutChildren) { 3549 int clearAllWidthDiff = mOrientationHandler.getPrimaryValue(mTaskWidth, mTaskHeight) 3550 - mOrientationHandler.getPrimarySize(mClearAllButton); 3551 mClearAllButton.setScrollOffsetPrimary(mIsRtl ? clearAllWidthDiff : -clearAllWidthDiff); 3552 } 3553 3554 boolean pageScrollChanged = false; 3555 final int childCount = getChildCount(); 3556 for (int i = 0; i < childCount; i++) { 3557 View child = getChildAt(i); 3558 float scrollDiff = 0; 3559 if (child instanceof TaskView) { 3560 scrollDiff = ((TaskView) child).getScrollAdjustment(showAsFullscreen, showAsGrid); 3561 } else if (child instanceof ClearAllButton) { 3562 scrollDiff = ((ClearAllButton) child).getScrollAdjustment(showAsFullscreen, 3563 showAsGrid); 3564 } 3565 3566 final int pageScroll = newPageScrolls[i] + (int) scrollDiff; 3567 if (outPageScrolls[i] != pageScroll) { 3568 pageScrollChanged = true; 3569 outPageScrolls[i] = pageScroll; 3570 } 3571 } 3572 return pageScrollChanged; 3573 } 3574 3575 @Override getChildOffset(int index)3576 protected int getChildOffset(int index) { 3577 int childOffset = super.getChildOffset(index); 3578 View child = getChildAt(index); 3579 if (child instanceof TaskView) { 3580 childOffset += ((TaskView) child).getOffsetAdjustment(showAsFullscreen(), 3581 showAsGrid()); 3582 } else if (child instanceof ClearAllButton) { 3583 childOffset += ((ClearAllButton) child).getOffsetAdjustment(mOverviewFullscreenEnabled, 3584 showAsGrid()); 3585 } 3586 return childOffset; 3587 } 3588 3589 @Override getChildVisibleSize(int index)3590 protected int getChildVisibleSize(int index) { 3591 final TaskView taskView = getTaskViewAtByAbsoluteIndex(index); 3592 if (taskView == null) { 3593 return super.getChildVisibleSize(index); 3594 } 3595 return (int) (super.getChildVisibleSize(index) * taskView.getSizeAdjustment( 3596 showAsFullscreen())); 3597 } 3598 getClearAllButton()3599 public ClearAllButton getClearAllButton() { 3600 return mClearAllButton; 3601 } 3602 3603 /** 3604 * @return How many pixels the running task is offset on the currently laid out dominant axis. 3605 */ getScrollOffset()3606 public int getScrollOffset() { 3607 return getScrollOffset(getRunningTaskIndex()); 3608 } 3609 3610 /** 3611 * Returns how many pixels the page is offset on the currently laid out dominant axis. 3612 */ getScrollOffset(int pageIndex)3613 public int getScrollOffset(int pageIndex) { 3614 if (pageIndex == -1) { 3615 return 0; 3616 } 3617 3618 int overScrollShift = getOverScrollShift(); 3619 if (mAdjacentPageHorizontalOffset > 0) { 3620 // Don't dampen the scroll (due to overscroll) if the adjacent tasks are offscreen, so 3621 // that the page can move freely given there's no visual indication why it shouldn't. 3622 overScrollShift = (int) Utilities.mapRange(mAdjacentPageHorizontalOffset, 3623 overScrollShift, getUndampedOverScrollShift()); 3624 } 3625 return getScrollForPage(pageIndex) - mOrientationHandler.getPrimaryScroll(this) 3626 + overScrollShift; 3627 } 3628 3629 /** 3630 * Returns how many pixels the task is offset on the currently laid out secondary axis 3631 * according to {@link #mGridProgress}. 3632 */ getGridTranslationSecondary(int pageIndex)3633 public float getGridTranslationSecondary(int pageIndex) { 3634 TaskView taskView = getTaskViewAtByAbsoluteIndex(pageIndex); 3635 if (taskView == null) { 3636 return 0; 3637 } 3638 3639 return mOrientationHandler.getSecondaryValue(taskView.getGridTranslationX(), 3640 taskView.getGridTranslationY()); 3641 } 3642 getEventDispatcher(float navbarRotation)3643 public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) { 3644 float degreesRotated; 3645 if (navbarRotation == 0) { 3646 degreesRotated = mOrientationHandler.getDegreesRotated(); 3647 } else { 3648 degreesRotated = -navbarRotation; 3649 } 3650 if (degreesRotated == 0) { 3651 return super::onTouchEvent; 3652 } 3653 3654 // At this point the event coordinates have already been transformed, so we need to 3655 // undo that transformation since PagedView also accommodates for the transformation via 3656 // PagedOrientationHandler 3657 return e -> { 3658 if (navbarRotation != 0 3659 && mOrientationState.isMultipleOrientationSupportedByDevice() 3660 && !mOrientationState.getOrientationHandler().isLayoutNaturalToLauncher()) { 3661 mOrientationState.flipVertical(e); 3662 super.onTouchEvent(e); 3663 mOrientationState.flipVertical(e); 3664 return; 3665 } 3666 mOrientationState.transformEvent(-degreesRotated, e, true); 3667 super.onTouchEvent(e); 3668 mOrientationState.transformEvent(-degreesRotated, e, false); 3669 }; 3670 } 3671 3672 private void updateEnabledOverlays() { 3673 int overlayEnabledPage = mOverlayEnabled ? getNextPage() : -1; 3674 int taskCount = getTaskViewCount(); 3675 for (int i = mTaskViewStartIndex; i < mTaskViewStartIndex + taskCount; i++) { 3676 getTaskViewAtByAbsoluteIndex(i).setOverlayEnabled(i == overlayEnabledPage); 3677 } 3678 } 3679 3680 public void setOverlayEnabled(boolean overlayEnabled) { 3681 if (mOverlayEnabled != overlayEnabled) { 3682 mOverlayEnabled = overlayEnabled; 3683 updateEnabledOverlays(); 3684 } 3685 } 3686 3687 public void setOverviewGridEnabled(boolean overviewGridEnabled) { 3688 if (mOverviewGridEnabled != overviewGridEnabled) { 3689 mOverviewGridEnabled = overviewGridEnabled; 3690 // Request layout to ensure scroll position is recalculated with updated mGridProgress. 3691 requestLayout(); 3692 } 3693 } 3694 3695 public void setOverviewFullscreenEnabled(boolean overviewFullscreenEnabled) { 3696 if (mOverviewFullscreenEnabled != overviewFullscreenEnabled) { 3697 mOverviewFullscreenEnabled = overviewFullscreenEnabled; 3698 // Request layout to ensure scroll position is recalculated with updated 3699 // mFullscreenProgress. 3700 requestLayout(); 3701 } 3702 } 3703 3704 /** 3705 * Switch the current running task view to static snapshot mode, 3706 * capturing the snapshot at the same time. 3707 */ 3708 public void switchToScreenshot(Runnable onFinishRunnable) { 3709 if (mRecentsAnimationController == null) { 3710 if (onFinishRunnable != null) { 3711 onFinishRunnable.run(); 3712 } 3713 return; 3714 } 3715 switchToScreenshot(mRunningTaskId == -1 ? null 3716 : mRecentsAnimationController.screenshotTask(mRunningTaskId), onFinishRunnable); 3717 } 3718 3719 /** 3720 * Switch the current running task view to static snapshot mode, using the 3721 * provided thumbnail data as the snapshot. 3722 */ 3723 public void switchToScreenshot(ThumbnailData thumbnailData, Runnable onFinishRunnable) { 3724 TaskView taskView = getRunningTaskView(); 3725 if (taskView != null) { 3726 taskView.setShowScreenshot(true); 3727 if (thumbnailData != null) { 3728 taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData); 3729 } else { 3730 taskView.getThumbnail().refresh(); 3731 } 3732 ViewUtils.postFrameDrawn(taskView, onFinishRunnable); 3733 } else { 3734 onFinishRunnable.run(); 3735 } 3736 } 3737 3738 /** 3739 * The current task is fully modal (modalness = 1) when it is shown on its own in a modal 3740 * way. Modalness 0 means the task is shown in context with all the other tasks. 3741 */ 3742 private void setTaskModalness(float modalness) { 3743 mTaskModalness = modalness; 3744 updatePageOffsets(); 3745 if (getCurrentPageTaskView() != null) { 3746 getCurrentPageTaskView().setModalness(modalness); 3747 } 3748 // Only show actions view when it's modal for in-place landscape mode. 3749 boolean inPlaceLandscape = !mOrientationState.canRecentsActivityRotate() 3750 && mOrientationState.getTouchRotation() != ROTATION_0; 3751 mActionsView.updateHiddenFlags(HIDDEN_NON_ZERO_ROTATION, modalness < 1 && inPlaceLandscape); 3752 mActionsView.setTaskModalness(modalness); 3753 } 3754 3755 @Nullable 3756 protected DepthController getDepthController() { 3757 return null; 3758 } 3759 3760 @Override 3761 public void onSecondaryWindowBoundsChanged() { 3762 // Invalidate the task view size 3763 setInsets(mInsets); 3764 } 3765 3766 /** 3767 * Enables or disables modal state for RecentsView 3768 * @param isModalState 3769 */ 3770 public void setModalStateEnabled(boolean isModalState) { } 3771 3772 public TaskOverlayFactory getTaskOverlayFactory() { 3773 return mTaskOverlayFactory; 3774 } 3775 3776 public BaseActivityInterface getSizeStrategy() { 3777 return mSizeStrategy; 3778 } 3779 3780 /** 3781 * Set all the task views to color tint scrim mode, dimming or tinting them all. Allows the 3782 * tasks to be dimmed while other elements in the recents view are left alone. 3783 */ 3784 public void showForegroundScrim(boolean show) { 3785 if (!show && mColorTint == 0) { 3786 if (mTintingAnimator != null) { 3787 mTintingAnimator.cancel(); 3788 mTintingAnimator = null; 3789 } 3790 return; 3791 } 3792 3793 mTintingAnimator = ObjectAnimator.ofFloat(this, COLOR_TINT, show ? 0.5f : 0f); 3794 mTintingAnimator.setAutoCancel(true); 3795 mTintingAnimator.start(); 3796 } 3797 3798 /** Tint the RecentsView and TaskViews in to simulate a scrim. */ 3799 // TODO(b/187528071): Replace this tinting with a scrim on top of RecentsView 3800 private void setColorTint(float tintAmount) { 3801 mColorTint = tintAmount; 3802 3803 for (int i = 0; i < getTaskViewCount(); i++) { 3804 getTaskViewAt(i).setColorTint(mColorTint, mTintingColor); 3805 } 3806 3807 Drawable scrimBg = mActivity.getScrimView().getBackground(); 3808 if (scrimBg != null) { 3809 if (tintAmount == 0f) { 3810 scrimBg.setTintList(null); 3811 } else { 3812 scrimBg.setTintBlendMode(BlendMode.SRC_OVER); 3813 scrimBg.setTint( 3814 ColorUtils.setAlphaComponent(mTintingColor, (int) (255 * tintAmount))); 3815 } 3816 } 3817 } 3818 3819 private float getColorTint() { 3820 return mColorTint; 3821 } 3822 3823 /** Returns {@code true} if the overview tasks are displayed as a grid. */ 3824 public boolean showAsGrid() { 3825 return mOverviewGridEnabled || (mCurrentGestureEndTarget != null 3826 && mSizeStrategy.stateFromGestureEndTarget( 3827 mCurrentGestureEndTarget).displayOverviewTasksAsGrid(mActivity.getDeviceProfile())); 3828 } 3829 3830 private boolean showAsFullscreen() { 3831 return mOverviewFullscreenEnabled 3832 && mCurrentGestureEndTarget != GestureState.GestureEndTarget.RECENTS; 3833 } 3834 3835 public boolean shouldShowOverviewActionsForState(STATE_TYPE state) { 3836 return !state.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()) 3837 || getFocusedTaskView() != null; 3838 } 3839 3840 /** 3841 * Used to register callbacks for when our empty message state changes. 3842 * 3843 * @see #setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener) 3844 * @see #updateEmptyMessage() 3845 */ 3846 public interface OnEmptyMessageUpdatedListener { 3847 /** @param isEmpty Whether RecentsView is empty (i.e. has no children) */ 3848 void onEmptyMessageUpdated(boolean isEmpty); 3849 } 3850 3851 /** 3852 * Adds a listener for scroll changes 3853 */ 3854 public void addOnScrollChangedListener(OnScrollChangedListener listener) { 3855 mScrollListeners.add(listener); 3856 } 3857 3858 /** 3859 * Removes a previously added scroll change listener 3860 */ 3861 public void removeOnScrollChangedListener(OnScrollChangedListener listener) { 3862 mScrollListeners.remove(listener); 3863 } 3864 3865 /** 3866 * @return Corner radius in pixel value for PiP window, which is updated via 3867 * {@link #mIPipAnimationListener} 3868 */ 3869 public int getPipCornerRadius() { 3870 return mPipCornerRadius; 3871 } 3872 3873 @Override 3874 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 3875 super.onScrollChanged(l, t, oldl, oldt); 3876 dispatchScrollChanged(); 3877 } 3878 3879 private void dispatchScrollChanged() { 3880 mLiveTileTaskViewSimulator.setScroll(getScrollOffset()); 3881 for (int i = mScrollListeners.size() - 1; i >= 0; i--) { 3882 mScrollListeners.get(i).onScrollChanged(); 3883 } 3884 } 3885 3886 private static class PinnedStackAnimationListener<T extends BaseActivity> extends 3887 IPipAnimationListener.Stub { 3888 private T mActivity; 3889 private RecentsView mRecentsView; 3890 3891 public void setActivityAndRecentsView(T activity, RecentsView recentsView) { 3892 mActivity = activity; 3893 mRecentsView = recentsView; 3894 } 3895 3896 @Override 3897 public void onPipAnimationStarted() { 3898 MAIN_EXECUTOR.execute(() -> { 3899 // Needed for activities that auto-enter PiP, which will not trigger a remote 3900 // animation to be created 3901 if (mActivity != null) { 3902 mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS); 3903 } 3904 }); 3905 } 3906 3907 @Override 3908 public void onPipCornerRadiusChanged(int cornerRadius) { 3909 if (mRecentsView != null) { 3910 mRecentsView.mPipCornerRadius = cornerRadius; 3911 } 3912 } 3913 } 3914 3915 /** Get the color used for foreground scrimming the RecentsView for sharing. */ 3916 public static int getForegroundScrimDimColor(Context context) { 3917 int baseColor = Themes.getAttrColor(context, R.attr.overviewScrimColor); 3918 // The Black blending is temporary until we have the proper color token. 3919 return ColorUtils.blendARGB(Color.BLACK, baseColor, 0.25f); 3920 } 3921 3922 /** Get the RecentsAnimationController */ 3923 @Nullable 3924 public RecentsAnimationController getRecentsAnimationController() { 3925 return mRecentsAnimationController; 3926 } 3927 3928 /** Update the current activity locus id to show the enabled state of Overview */ 3929 public void updateLocusId() { 3930 String locusId = "Overview"; 3931 3932 if (mOverviewStateEnabled && mActivity.isStarted()) { 3933 locusId += "|ENABLED"; 3934 } else { 3935 locusId += "|DISABLED"; 3936 } 3937 3938 final LocusId id = new LocusId(locusId); 3939 // Set locus context is a binder call, don't want it to happen during a transition 3940 UI_HELPER_EXECUTOR.post(() -> mActivity.setLocusContext(id, Bundle.EMPTY)); 3941 } 3942 } 3943