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 androidx.dynamicanimation.animation.DynamicAnimation.MIN_VISIBLE_CHANGE_PIXELS; 20 21 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS; 22 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS; 23 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; 24 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; 25 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; 26 import static com.android.launcher3.Utilities.EDGE_NAV_BAR; 27 import static com.android.launcher3.Utilities.squaredHypot; 28 import static com.android.launcher3.Utilities.squaredTouchSlop; 29 import static com.android.launcher3.anim.Interpolators.ACCEL; 30 import static com.android.launcher3.anim.Interpolators.ACCEL_2; 31 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN; 32 import static com.android.launcher3.anim.Interpolators.LINEAR; 33 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; 34 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; 35 import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS; 36 import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP; 37 import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON; 38 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW; 39 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId; 40 41 import android.animation.Animator; 42 import android.animation.AnimatorSet; 43 import android.animation.LayoutTransition; 44 import android.animation.LayoutTransition.TransitionListener; 45 import android.animation.ObjectAnimator; 46 import android.animation.TimeInterpolator; 47 import android.animation.ValueAnimator; 48 import android.annotation.TargetApi; 49 import android.app.ActivityManager; 50 import android.content.ComponentName; 51 import android.content.Context; 52 import android.content.Intent; 53 import android.graphics.Canvas; 54 import android.graphics.Matrix; 55 import android.graphics.Point; 56 import android.graphics.Rect; 57 import android.graphics.RectF; 58 import android.graphics.Typeface; 59 import android.graphics.drawable.Drawable; 60 import android.os.Build; 61 import android.os.Handler; 62 import android.text.Layout; 63 import android.text.StaticLayout; 64 import android.text.TextPaint; 65 import android.util.AttributeSet; 66 import android.util.FloatProperty; 67 import android.util.SparseBooleanArray; 68 import android.view.HapticFeedbackConstants; 69 import android.view.KeyEvent; 70 import android.view.LayoutInflater; 71 import android.view.MotionEvent; 72 import android.view.View; 73 import android.view.ViewDebug; 74 import android.view.ViewGroup; 75 import android.view.accessibility.AccessibilityEvent; 76 import android.view.accessibility.AccessibilityNodeInfo; 77 import android.widget.ListView; 78 79 import androidx.annotation.Nullable; 80 import androidx.dynamicanimation.animation.SpringForce; 81 82 import com.android.launcher3.BaseActivity; 83 import com.android.launcher3.DeviceProfile; 84 import com.android.launcher3.Insettable; 85 import com.android.launcher3.InvariantDeviceProfile; 86 import com.android.launcher3.LauncherState; 87 import com.android.launcher3.PagedView; 88 import com.android.launcher3.R; 89 import com.android.launcher3.Utilities; 90 import com.android.launcher3.anim.AnimatorPlaybackController; 91 import com.android.launcher3.anim.PropertyListBuilder; 92 import com.android.launcher3.anim.SpringObjectAnimator; 93 import com.android.launcher3.config.FeatureFlags; 94 import com.android.launcher3.graphics.RotationMode; 95 import com.android.launcher3.userevent.nano.LauncherLogProto; 96 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; 97 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; 98 import com.android.launcher3.util.OverScroller; 99 import com.android.launcher3.util.PendingAnimation; 100 import com.android.launcher3.util.Themes; 101 import com.android.launcher3.util.ViewPool; 102 import com.android.quickstep.RecentsAnimationWrapper; 103 import com.android.quickstep.RecentsModel; 104 import com.android.quickstep.RecentsModel.TaskThumbnailChangeListener; 105 import com.android.quickstep.TaskThumbnailCache; 106 import com.android.quickstep.TaskUtils; 107 import com.android.quickstep.util.ClipAnimationHelper; 108 import com.android.systemui.shared.recents.model.Task; 109 import com.android.systemui.shared.recents.model.ThumbnailData; 110 import com.android.systemui.shared.system.ActivityManagerWrapper; 111 import com.android.systemui.shared.system.BackgroundExecutor; 112 import com.android.systemui.shared.system.LauncherEventUtil; 113 import com.android.systemui.shared.system.PackageManagerWrapper; 114 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; 115 import com.android.systemui.shared.system.TaskStackChangeListener; 116 117 import java.util.ArrayList; 118 import java.util.function.Consumer; 119 120 /** 121 * A list of recent tasks. 122 */ 123 @TargetApi(Build.VERSION_CODES.P) 124 public abstract class RecentsView<T extends BaseActivity> extends PagedView implements Insettable, 125 TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback, 126 InvariantDeviceProfile.OnIDPChangeListener, TaskThumbnailChangeListener { 127 128 private static final String TAG = RecentsView.class.getSimpleName(); 129 130 public static final FloatProperty<RecentsView> CONTENT_ALPHA = 131 new FloatProperty<RecentsView>("contentAlpha") { 132 @Override 133 public void setValue(RecentsView view, float v) { 134 view.setContentAlpha(v); 135 } 136 137 @Override 138 public Float get(RecentsView view) { 139 return view.getContentAlpha(); 140 } 141 }; 142 143 public static final FloatProperty<RecentsView> FULLSCREEN_PROGRESS = 144 new FloatProperty<RecentsView>("fullscreenProgress") { 145 @Override 146 public void setValue(RecentsView recentsView, float v) { 147 recentsView.setFullscreenProgress(v); 148 } 149 150 @Override 151 public Float get(RecentsView recentsView) { 152 return recentsView.mFullscreenProgress; 153 } 154 }; 155 156 protected RecentsAnimationWrapper mRecentsAnimationWrapper; 157 protected ClipAnimationHelper mClipAnimationHelper; 158 protected SyncRtSurfaceTransactionApplierCompat mSyncTransactionApplier; 159 protected int mTaskWidth; 160 protected int mTaskHeight; 161 protected boolean mEnableDrawingLiveTile = false; 162 protected final Rect mTempRect = new Rect(); 163 protected final RectF mTempRectF = new RectF(); 164 165 private static final int DISMISS_TASK_DURATION = 300; 166 private static final int ADDITION_TASK_DURATION = 200; 167 // The threshold at which we update the SystemUI flags when animating from the task into the app 168 public static final float UPDATE_SYSUI_FLAGS_THRESHOLD = 0.85f; 169 170 protected final T mActivity; 171 private final float mFastFlingVelocity; 172 private final RecentsModel mModel; 173 private final int mTaskTopMargin; 174 private final ClearAllButton mClearAllButton; 175 private final Rect mClearAllButtonDeadZoneRect = new Rect(); 176 private final Rect mTaskViewDeadZoneRect = new Rect(); 177 protected final ClipAnimationHelper mTempClipAnimationHelper; 178 179 private final ScrollState mScrollState = new ScrollState(); 180 // Keeps track of the previously known visible tasks for purposes of loading/unloading task data 181 private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray(); 182 183 private final InvariantDeviceProfile mIdp; 184 185 private final ViewPool<TaskView> mTaskViewPool; 186 187 private boolean mDwbToastShown; 188 private boolean mDisallowScrollToClearAll; 189 private boolean mOverlayEnabled; 190 private boolean mFreezeViewVisibility; 191 192 /** 193 * TODO: Call reloadIdNeeded in onTaskStackChanged. 194 */ 195 private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() { 196 @Override 197 public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { 198 if (!mHandleTaskStackChanges) { 199 return; 200 } 201 // Check this is for the right user 202 if (!checkCurrentOrManagedUserId(userId, getContext())) { 203 return; 204 } 205 206 // Remove the task immediately from the task list 207 TaskView taskView = getTaskView(taskId); 208 if (taskView != null) { 209 removeView(taskView); 210 } 211 } 212 213 @Override 214 public void onActivityUnpinned() { 215 if (!mHandleTaskStackChanges) { 216 return; 217 } 218 219 reloadIfNeeded(); 220 enableLayoutTransitions(); 221 } 222 223 @Override 224 public void onTaskRemoved(int taskId) { 225 if (!mHandleTaskStackChanges) { 226 return; 227 } 228 229 BackgroundExecutor.get().submit(() -> { 230 TaskView taskView = getTaskView(taskId); 231 if (taskView == null) { 232 return; 233 } 234 Handler handler = taskView.getHandler(); 235 if (handler == null) { 236 return; 237 } 238 239 // TODO: Add callbacks from AM reflecting adding/removing from the recents list, and 240 // remove all these checks 241 Task.TaskKey taskKey = taskView.getTask().key; 242 if (PackageManagerWrapper.getInstance().getActivityInfo(taskKey.getComponent(), 243 taskKey.userId) == null) { 244 // The package was uninstalled 245 handler.post(() -> 246 dismissTask(taskView, true /* animate */, false /* removeTask */)); 247 } else { 248 mModel.findTaskWithId(taskKey.id, (key) -> { 249 if (key == null) { 250 // The task was removed from the recents list 251 handler.post(() -> dismissTask(taskView, true /* animate */, 252 false /* removeTask */)); 253 } 254 }); 255 } 256 }); 257 } 258 259 @Override 260 public void onPinnedStackAnimationStarted() { 261 // Needed for activities that auto-enter PiP, which will not trigger a remote 262 // animation to be created 263 mActivity.clearForceInvisibleFlag(STATE_HANDLER_INVISIBILITY_FLAGS); 264 } 265 }; 266 267 // Used to keep track of the last requested task list id, so that we do not request to load the 268 // tasks again if we have already requested it and the task list has not changed 269 private int mTaskListChangeId = -1; 270 271 // Only valid until the launcher state changes to NORMAL 272 private int mRunningTaskId = -1; 273 private boolean mRunningTaskTileHidden; 274 private Task mTmpRunningTask; 275 276 private boolean mRunningTaskIconScaledDown = false; 277 278 private boolean mOverviewStateEnabled; 279 private boolean mHandleTaskStackChanges; 280 private boolean mSwipeDownShouldLaunchApp; 281 private boolean mTouchDownToStartHome; 282 private final float mSquaredTouchSlop; 283 private int mDownX; 284 private int mDownY; 285 286 private PendingAnimation mPendingAnimation; 287 private LayoutTransition mLayoutTransition; 288 289 @ViewDebug.ExportedProperty(category = "launcher") 290 private float mContentAlpha = 1; 291 @ViewDebug.ExportedProperty(category = "launcher") 292 private float mFullscreenProgress = 0; 293 294 // Keeps track of task id whose visual state should not be reset 295 private int mIgnoreResetTaskId = -1; 296 297 // Variables for empty state 298 private final Drawable mEmptyIcon; 299 private final CharSequence mEmptyMessage; 300 private final TextPaint mEmptyMessagePaint; 301 private final Point mLastMeasureSize = new Point(); 302 private final int mEmptyMessagePadding; 303 private boolean mShowEmptyMessage; 304 private Layout mEmptyTextLayout; 305 private LiveTileOverlay mLiveTileOverlay; 306 307 private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener = 308 (inMultiWindowMode) -> { 309 if (!inMultiWindowMode && mOverviewStateEnabled) { 310 // TODO: Re-enable layout transitions for addition of the unpinned task 311 reloadIfNeeded(); 312 } 313 }; 314 RecentsView(Context context, AttributeSet attrs, int defStyleAttr)315 public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) { 316 super(context, attrs, defStyleAttr); 317 setPageSpacing(getResources().getDimensionPixelSize(R.dimen.recents_page_spacing)); 318 setEnableFreeScroll(true); 319 320 mFastFlingVelocity = getResources() 321 .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity); 322 mActivity = (T) BaseActivity.fromContext(context); 323 mModel = RecentsModel.INSTANCE.get(context); 324 mIdp = InvariantDeviceProfile.INSTANCE.get(context); 325 mTempClipAnimationHelper = new ClipAnimationHelper(context); 326 327 mClearAllButton = (ClearAllButton) LayoutInflater.from(context) 328 .inflate(R.layout.overview_clear_all_button, this, false); 329 mClearAllButton.setOnClickListener(this::dismissAllTasks); 330 331 mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */, 332 10 /* initial size */); 333 334 mIsRtl = !Utilities.isRtl(getResources()); 335 setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR); 336 mTaskTopMargin = getResources() 337 .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin); 338 mSquaredTouchSlop = squaredTouchSlop(context); 339 340 mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents); 341 mEmptyIcon.setCallback(this); 342 mEmptyMessage = context.getText(R.string.recents_empty_message); 343 mEmptyMessagePaint = new TextPaint(); 344 mEmptyMessagePaint.setColor(Themes.getAttrColor(context, android.R.attr.textColorPrimary)); 345 mEmptyMessagePaint.setTextSize(getResources() 346 .getDimension(R.dimen.recents_empty_message_text_size)); 347 mEmptyMessagePaint.setTypeface(Typeface.create(Themes.getDefaultBodyFont(context), 348 Typeface.NORMAL)); 349 mEmptyMessagePadding = getResources() 350 .getDimensionPixelSize(R.dimen.recents_empty_message_text_padding); 351 setWillNotDraw(false); 352 updateEmptyMessage(); 353 354 // Initialize quickstep specific cache params here, as this is constructed only once 355 mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5); 356 } 357 getScroller()358 public OverScroller getScroller() { 359 return mScroller; 360 } 361 isRtl()362 public boolean isRtl() { 363 return mIsRtl; 364 } 365 366 @Override onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData)367 public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) { 368 if (mHandleTaskStackChanges) { 369 TaskView taskView = getTaskView(taskId); 370 if (taskView != null) { 371 Task task = taskView.getTask(); 372 taskView.getThumbnail().setThumbnail(task, thumbnailData); 373 return task; 374 } 375 } 376 return null; 377 } 378 updateThumbnail(int taskId, ThumbnailData thumbnailData)379 public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData) { 380 TaskView taskView = getTaskView(taskId); 381 if (taskView != null) { 382 taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData); 383 } 384 return taskView; 385 } 386 387 @Override onWindowVisibilityChanged(int visibility)388 protected void onWindowVisibilityChanged(int visibility) { 389 super.onWindowVisibilityChanged(visibility); 390 updateTaskStackListenerState(); 391 } 392 393 @Override onIdpChanged(int changeFlags, InvariantDeviceProfile idp)394 public void onIdpChanged(int changeFlags, InvariantDeviceProfile idp) { 395 if ((changeFlags & CHANGE_FLAG_ICON_PARAMS) == 0) { 396 return; 397 } 398 mModel.getIconCache().clear(); 399 reset(); 400 } 401 402 @Override onAttachedToWindow()403 protected void onAttachedToWindow() { 404 super.onAttachedToWindow(); 405 updateTaskStackListenerState(); 406 mModel.getThumbnailCache().getHighResLoadingState().addCallback(this); 407 mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener); 408 ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener); 409 mSyncTransactionApplier = new SyncRtSurfaceTransactionApplierCompat(this); 410 RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this); 411 mIdp.addOnChangeListener(this); 412 } 413 414 @Override onDetachedFromWindow()415 protected void onDetachedFromWindow() { 416 super.onDetachedFromWindow(); 417 updateTaskStackListenerState(); 418 mModel.getThumbnailCache().getHighResLoadingState().removeCallback(this); 419 mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener); 420 ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener); 421 mSyncTransactionApplier = null; 422 RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this); 423 mIdp.removeOnChangeListener(this); 424 } 425 426 @Override onViewRemoved(View child)427 public void onViewRemoved(View child) { 428 super.onViewRemoved(child); 429 430 // Clear the task data for the removed child if it was visible 431 if (child != mClearAllButton) { 432 TaskView taskView = (TaskView) child; 433 mHasVisibleTaskData.delete(taskView.getTask().key.id); 434 mTaskViewPool.recycle(taskView); 435 } 436 } 437 isTaskViewVisible(TaskView tv)438 public boolean isTaskViewVisible(TaskView tv) { 439 // For now, just check if it's the active task or an adjacent task 440 return Math.abs(indexOfChild(tv) - getNextPage()) <= 1; 441 } 442 getTaskView(int taskId)443 public TaskView getTaskView(int taskId) { 444 for (int i = 0; i < getTaskViewCount(); i++) { 445 TaskView tv = (TaskView) getChildAt(i); 446 if (tv.getTask() != null && tv.getTask().key != null && tv.getTask().key.id == taskId) { 447 return tv; 448 } 449 } 450 return null; 451 } 452 setOverviewStateEnabled(boolean enabled)453 public void setOverviewStateEnabled(boolean enabled) { 454 mOverviewStateEnabled = enabled; 455 updateTaskStackListenerState(); 456 } 457 onDigitalWellbeingToastShown()458 public void onDigitalWellbeingToastShown() { 459 if (!mDwbToastShown) { 460 mDwbToastShown = true; 461 mActivity.getUserEventDispatcher().logActionTip( 462 LauncherEventUtil.VISIBLE, 463 LauncherLogProto.TipType.DWB_TOAST); 464 } 465 } 466 467 @Override onPageEndTransition()468 protected void onPageEndTransition() { 469 super.onPageEndTransition(); 470 if (getNextPage() > 0) { 471 setSwipeDownShouldLaunchApp(true); 472 } 473 } 474 475 @Override onTouchEvent(MotionEvent ev)476 public boolean onTouchEvent(MotionEvent ev) { 477 super.onTouchEvent(ev); 478 final int x = (int) ev.getX(); 479 final int y = (int) ev.getY(); 480 switch (ev.getAction()) { 481 case MotionEvent.ACTION_UP: 482 if (mTouchDownToStartHome) { 483 startHome(); 484 } 485 mTouchDownToStartHome = false; 486 break; 487 case MotionEvent.ACTION_CANCEL: 488 mTouchDownToStartHome = false; 489 break; 490 case MotionEvent.ACTION_MOVE: 491 // Passing the touch slop will not allow dismiss to home 492 if (mTouchDownToStartHome && 493 (isHandlingTouch() || 494 squaredHypot(mDownX - x, mDownY - y) > mSquaredTouchSlop)) { 495 mTouchDownToStartHome = false; 496 } 497 break; 498 case MotionEvent.ACTION_DOWN: 499 // Touch down anywhere but the deadzone around the visible clear all button and 500 // between the task views will start home on touch up 501 if (!isHandlingTouch()) { 502 if (mShowEmptyMessage) { 503 mTouchDownToStartHome = true; 504 } else { 505 updateDeadZoneRects(); 506 final boolean clearAllButtonDeadZoneConsumed = 507 mClearAllButton.getAlpha() == 1 508 && mClearAllButtonDeadZoneRect.contains(x, y); 509 final boolean cameFromNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0; 510 if (!clearAllButtonDeadZoneConsumed && !cameFromNavBar 511 && !mTaskViewDeadZoneRect.contains(x + getScrollX(), y)) { 512 mTouchDownToStartHome = true; 513 } 514 } 515 } 516 mDownX = x; 517 mDownY = y; 518 break; 519 } 520 521 522 // Do not let touch escape to siblings below this view. 523 return isHandlingTouch() || shouldStealTouchFromSiblingsBelow(ev); 524 } 525 shouldStealTouchFromSiblingsBelow(MotionEvent ev)526 protected boolean shouldStealTouchFromSiblingsBelow(MotionEvent ev) { 527 return true; 528 } 529 applyLoadPlan(ArrayList<Task> tasks)530 private void applyLoadPlan(ArrayList<Task> tasks) { 531 if (mPendingAnimation != null) { 532 mPendingAnimation.addEndListener((onEndListener) -> applyLoadPlan(tasks)); 533 return; 534 } 535 536 if (tasks == null || tasks.isEmpty()) { 537 removeAllViews(); 538 onTaskStackUpdated(); 539 return; 540 } 541 542 int oldChildCount = getChildCount(); 543 544 // Unload existing visible task data 545 unloadVisibleTaskData(); 546 547 TaskView ignoreRestTaskView = 548 mIgnoreResetTaskId == -1 ? null : getTaskView(mIgnoreResetTaskId); 549 550 final int requiredTaskCount = tasks.size(); 551 if (getTaskViewCount() != requiredTaskCount) { 552 if (oldChildCount > 0) { 553 removeView(mClearAllButton); 554 } 555 for (int i = getChildCount(); i < requiredTaskCount; i++) { 556 addView(mTaskViewPool.getView()); 557 } 558 while (getChildCount() > requiredTaskCount) { 559 removeView(getChildAt(getChildCount() - 1)); 560 } 561 if (requiredTaskCount > 0) { 562 addView(mClearAllButton); 563 } 564 } 565 566 // Rebind and reset all task views 567 for (int i = requiredTaskCount - 1; i >= 0; i--) { 568 final int pageIndex = requiredTaskCount - i - 1; 569 final Task task = tasks.get(i); 570 final TaskView taskView = (TaskView) getChildAt(pageIndex); 571 taskView.bind(task); 572 } 573 TaskView runningTaskView = getRunningTaskView(); 574 if (runningTaskView != null) { 575 setCurrentPage(indexOfChild(runningTaskView)); 576 } 577 578 if (mIgnoreResetTaskId != -1 && getTaskView(mIgnoreResetTaskId) != ignoreRestTaskView) { 579 // If the taskView mapping is changing, do not preserve the visuals. Since we are 580 // mostly preserving the first task, and new taskViews are added to the end, it should 581 // generally map to the same task. 582 mIgnoreResetTaskId = -1; 583 } 584 resetTaskVisuals(); 585 onTaskStackUpdated(); 586 updateEnabledOverlays(); 587 } 588 getTaskViewCount()589 public int getTaskViewCount() { 590 // Account for the clear all button. 591 int childCount = getChildCount(); 592 return childCount == 0 ? 0 : childCount - 1; 593 } 594 onTaskStackUpdated()595 protected void onTaskStackUpdated() { } 596 resetTaskVisuals()597 public void resetTaskVisuals() { 598 for (int i = getTaskViewCount() - 1; i >= 0; i--) { 599 TaskView taskView = (TaskView) getChildAt(i); 600 if (mIgnoreResetTaskId != taskView.getTask().key.id) { 601 taskView.resetVisualProperties(); 602 } 603 } 604 if (mRunningTaskTileHidden) { 605 setRunningTaskHidden(mRunningTaskTileHidden); 606 } 607 608 // Force apply the scale. 609 if (mIgnoreResetTaskId != mRunningTaskId) { 610 applyRunningTaskIconScale(); 611 } 612 613 updateCurveProperties(); 614 // Update the set of visible task's data 615 loadVisibleTaskData(); 616 } 617 setFullscreenProgress(float fullscreenProgress)618 public void setFullscreenProgress(float fullscreenProgress) { 619 mFullscreenProgress = fullscreenProgress; 620 int taskCount = getTaskViewCount(); 621 for (int i = 0; i < taskCount; i++) { 622 getTaskViewAt(i).setFullscreenProgress(mFullscreenProgress); 623 } 624 } 625 updateTaskStackListenerState()626 private void updateTaskStackListenerState() { 627 boolean handleTaskStackChanges = mOverviewStateEnabled && isAttachedToWindow() 628 && getWindowVisibility() == VISIBLE; 629 if (handleTaskStackChanges != mHandleTaskStackChanges) { 630 mHandleTaskStackChanges = handleTaskStackChanges; 631 if (handleTaskStackChanges) { 632 reloadIfNeeded(); 633 } 634 } 635 } 636 637 @Override setInsets(Rect insets)638 public void setInsets(Rect insets) { 639 mInsets.set(insets); 640 DeviceProfile dp = mActivity.getDeviceProfile(); 641 getTaskSize(dp, mTempRect); 642 mTaskWidth = mTempRect.width(); 643 mTaskHeight = mTempRect.height(); 644 645 mTempRect.top -= mTaskTopMargin; 646 setPadding(mTempRect.left - mInsets.left, mTempRect.top - mInsets.top, 647 dp.widthPx - mInsets.right - mTempRect.right, 648 dp.heightPx - mInsets.bottom - mTempRect.bottom); 649 } 650 getTaskSize(DeviceProfile dp, Rect outRect)651 protected abstract void getTaskSize(DeviceProfile dp, Rect outRect); 652 getTaskSize(Rect outRect)653 public void getTaskSize(Rect outRect) { 654 getTaskSize(mActivity.getDeviceProfile(), outRect); 655 } 656 657 @Override computeScrollHelper()658 protected boolean computeScrollHelper() { 659 boolean scrolling = super.computeScrollHelper(); 660 boolean isFlingingFast = false; 661 updateCurveProperties(); 662 if (scrolling || isHandlingTouch()) { 663 if (scrolling) { 664 // Check if we are flinging quickly to disable high res thumbnail loading 665 isFlingingFast = mScroller.getCurrVelocity() > mFastFlingVelocity; 666 } 667 668 // After scrolling, update the visible task's data 669 loadVisibleTaskData(); 670 } 671 672 // Update the high res thumbnail loader state 673 mModel.getThumbnailCache().getHighResLoadingState().setFlingingFast(isFlingingFast); 674 return scrolling; 675 } 676 677 /** 678 * Scales and adjusts translation of adjacent pages as if on a curved carousel. 679 */ updateCurveProperties()680 public void updateCurveProperties() { 681 if (getPageCount() == 0 || getPageAt(0).getMeasuredWidth() == 0) { 682 return; 683 } 684 int scrollX = getScrollX(); 685 final int halfPageWidth = getNormalChildWidth() / 2; 686 final int screenCenter = mInsets.left + getPaddingLeft() + scrollX + halfPageWidth; 687 final int halfScreenWidth = getMeasuredWidth() / 2; 688 final int pageSpacing = mPageSpacing; 689 mScrollState.scrollFromEdge = mIsRtl ? scrollX : (mMaxScrollX - scrollX); 690 691 final int pageCount = getPageCount(); 692 for (int i = 0; i < pageCount; i++) { 693 View page = getPageAt(i); 694 float pageCenter = page.getLeft() + page.getTranslationX() + halfPageWidth; 695 float distanceFromScreenCenter = screenCenter - pageCenter; 696 float distanceToReachEdge = halfScreenWidth + halfPageWidth + pageSpacing; 697 mScrollState.linearInterpolation = Math.min(1, 698 Math.abs(distanceFromScreenCenter) / distanceToReachEdge); 699 ((PageCallbacks) page).onPageScroll(mScrollState); 700 } 701 } 702 703 /** 704 * Iterates through all thet asks, and loads the associated task data for newly visible tasks, 705 * and unloads the associated task data for tasks that are no longer visible. 706 */ loadVisibleTaskData()707 public void loadVisibleTaskData() { 708 if (!mOverviewStateEnabled || mTaskListChangeId == -1) { 709 // Skip loading visible task data if we've already left the overview state, or if the 710 // task list hasn't been loaded yet (the task views will not reflect the task list) 711 return; 712 } 713 714 int centerPageIndex = getPageNearestToCenterOfScreen(); 715 int numChildren = getTaskViewCount(); 716 int lower = Math.max(0, centerPageIndex - 2); 717 int upper = Math.min(centerPageIndex + 2, numChildren - 1); 718 719 // Update the task data for the in/visible children 720 for (int i = 0; i < numChildren; i++) { 721 TaskView taskView = (TaskView) getChildAt(i); 722 Task task = taskView.getTask(); 723 boolean visible = lower <= i && i <= upper; 724 if (visible) { 725 if (task == mTmpRunningTask) { 726 // Skip loading if this is the task that we are animating into 727 continue; 728 } 729 if (!mHasVisibleTaskData.get(task.key.id)) { 730 taskView.onTaskListVisibilityChanged(true /* visible */); 731 } 732 mHasVisibleTaskData.put(task.key.id, visible); 733 } else { 734 if (mHasVisibleTaskData.get(task.key.id)) { 735 taskView.onTaskListVisibilityChanged(false /* visible */); 736 } 737 mHasVisibleTaskData.delete(task.key.id); 738 } 739 } 740 } 741 742 /** 743 * Unloads any associated data from the currently visible tasks 744 */ unloadVisibleTaskData()745 private void unloadVisibleTaskData() { 746 for (int i = 0; i < mHasVisibleTaskData.size(); i++) { 747 if (mHasVisibleTaskData.valueAt(i)) { 748 TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i)); 749 if (taskView != null) { 750 taskView.onTaskListVisibilityChanged(false /* visible */); 751 } 752 } 753 } 754 mHasVisibleTaskData.clear(); 755 } 756 757 @Override onHighResLoadingStateChanged(boolean enabled)758 public void onHighResLoadingStateChanged(boolean enabled) { 759 // Whenever the high res loading state changes, poke each of the visible tasks to see if 760 // they want to updated their thumbnail state 761 for (int i = 0; i < mHasVisibleTaskData.size(); i++) { 762 if (mHasVisibleTaskData.valueAt(i)) { 763 TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i)); 764 if (taskView != null) { 765 // Poke the view again, which will trigger it to load high res if the state 766 // is enabled 767 taskView.onTaskListVisibilityChanged(true /* visible */); 768 } 769 } 770 } 771 } 772 startHome()773 public abstract void startHome(); 774 reset()775 public void reset() { 776 setCurrentTask(-1); 777 mIgnoreResetTaskId = -1; 778 mTaskListChangeId = -1; 779 780 mRecentsAnimationWrapper = null; 781 mClipAnimationHelper = null; 782 783 unloadVisibleTaskData(); 784 setCurrentPage(0); 785 mDwbToastShown = false; 786 mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 0); 787 } 788 getRunningTaskView()789 public @Nullable TaskView getRunningTaskView() { 790 return getTaskView(mRunningTaskId); 791 } 792 getRunningTaskIndex()793 public int getRunningTaskIndex() { 794 TaskView tv = getRunningTaskView(); 795 return tv == null ? -1 : indexOfChild(tv); 796 } 797 798 /** 799 * Reloads the view if anything in recents changed. 800 */ reloadIfNeeded()801 public void reloadIfNeeded() { 802 if (!mModel.isTaskListValid(mTaskListChangeId)) { 803 mTaskListChangeId = mModel.getTasks(this::applyLoadPlan); 804 } 805 } 806 807 /** 808 * Called when a gesture from an app is starting. 809 */ onGestureAnimationStart(int runningTaskId)810 public void onGestureAnimationStart(int runningTaskId) { 811 // This needs to be called before the other states are set since it can create the task view 812 showCurrentTask(runningTaskId); 813 setEnableFreeScroll(false); 814 setEnableDrawingLiveTile(false); 815 setRunningTaskHidden(true); 816 setRunningTaskIconScaledDown(true); 817 } 818 819 /** 820 * Called only when a swipe-up gesture from an app has completed. Only called after 821 * {@link #onGestureAnimationStart} and {@link #onGestureAnimationEnd()}. 822 */ onSwipeUpAnimationSuccess()823 public void onSwipeUpAnimationSuccess() { 824 if (getRunningTaskView() != null) { 825 float startProgress = ENABLE_QUICKSTEP_LIVE_TILE.get() 826 ? mLiveTileOverlay.cancelIconAnimation() 827 : 0f; 828 animateUpRunningTaskIconScale(startProgress); 829 } 830 setSwipeDownShouldLaunchApp(true); 831 } 832 833 /** 834 * Called when a gesture from an app has finished. 835 */ onGestureAnimationEnd()836 public void onGestureAnimationEnd() { 837 setEnableFreeScroll(true); 838 setEnableDrawingLiveTile(true); 839 setOnScrollChangeListener(null); 840 setRunningTaskViewShowScreenshot(true); 841 setRunningTaskHidden(false); 842 animateUpRunningTaskIconScale(); 843 } 844 845 /** 846 * Creates a task view (if necessary) to represent the task with the {@param runningTaskId}. 847 * 848 * All subsequent calls to reload will keep the task as the first item until {@link #reset()} 849 * is called. Also scrolls the view to this task. 850 */ showCurrentTask(int runningTaskId)851 public void showCurrentTask(int runningTaskId) { 852 if (getChildCount() == 0) { 853 // Add an empty view for now until the task plan is loaded and applied 854 final TaskView taskView = mTaskViewPool.getView(); 855 addView(taskView); 856 addView(mClearAllButton); 857 858 // The temporary running task is only used for the duration between the start of the 859 // gesture and the task list is loaded and applied 860 mTmpRunningTask = new Task(new Task.TaskKey(runningTaskId, 0, new Intent(), 861 new ComponentName(getContext(), getClass()), 0, 0), null, null, "", "", 0, 0, 862 false, true, false, false, new ActivityManager.TaskDescription(), 0, 863 new ComponentName("", ""), false); 864 taskView.bind(mTmpRunningTask); 865 } 866 867 boolean runningTaskTileHidden = mRunningTaskTileHidden; 868 setCurrentTask(runningTaskId); 869 setCurrentPage(getRunningTaskIndex()); 870 setRunningTaskViewShowScreenshot(false); 871 setRunningTaskHidden(runningTaskTileHidden); 872 873 // Reload the task list 874 mTaskListChangeId = mModel.getTasks(this::applyLoadPlan); 875 } 876 877 /** 878 * Sets the running task id, cleaning up the old running task if necessary. 879 * @param runningTaskId 880 */ setCurrentTask(int runningTaskId)881 public void setCurrentTask(int runningTaskId) { 882 if (mRunningTaskId == runningTaskId) { 883 return; 884 } 885 886 if (mRunningTaskId != -1) { 887 // Reset the state on the old running task view 888 setRunningTaskIconScaledDown(false); 889 setRunningTaskViewShowScreenshot(true); 890 setRunningTaskHidden(false); 891 } 892 mRunningTaskId = runningTaskId; 893 } 894 895 /** 896 * Hides the tile associated with {@link #mRunningTaskId} 897 */ setRunningTaskHidden(boolean isHidden)898 public void setRunningTaskHidden(boolean isHidden) { 899 mRunningTaskTileHidden = isHidden; 900 TaskView runningTask = getRunningTaskView(); 901 if (runningTask != null) { 902 runningTask.setStableAlpha(isHidden ? 0 : mContentAlpha); 903 } 904 } 905 setRunningTaskViewShowScreenshot(boolean showScreenshot)906 private void setRunningTaskViewShowScreenshot(boolean showScreenshot) { 907 if (ENABLE_QUICKSTEP_LIVE_TILE.get()) { 908 TaskView runningTaskView = getRunningTaskView(); 909 if (runningTaskView != null) { 910 runningTaskView.setShowScreenshot(showScreenshot); 911 } 912 } 913 } 914 showNextTask()915 public void showNextTask() { 916 TaskView runningTaskView = getRunningTaskView(); 917 if (runningTaskView == null) { 918 // Launch the first task 919 if (getTaskViewCount() > 0) { 920 getTaskViewAt(0).launchTask(true /* animate */); 921 } 922 } else { 923 TaskView nextTaskView = getNextTaskView(); 924 if (nextTaskView != null) { 925 nextTaskView.launchTask(true /* animate */); 926 } else { 927 runningTaskView.launchTask(true /* animate */); 928 } 929 } 930 } 931 setRunningTaskIconScaledDown(boolean isScaledDown)932 public void setRunningTaskIconScaledDown(boolean isScaledDown) { 933 if (mRunningTaskIconScaledDown != isScaledDown) { 934 mRunningTaskIconScaledDown = isScaledDown; 935 applyRunningTaskIconScale(); 936 } 937 } 938 applyRunningTaskIconScale()939 private void applyRunningTaskIconScale() { 940 TaskView firstTask = getRunningTaskView(); 941 if (firstTask != null) { 942 firstTask.setIconScaleAndDim(mRunningTaskIconScaledDown ? 0 : 1); 943 } 944 } 945 animateUpRunningTaskIconScale()946 public void animateUpRunningTaskIconScale() { 947 animateUpRunningTaskIconScale(0); 948 } 949 animateUpRunningTaskIconScale(float startProgress)950 public void animateUpRunningTaskIconScale(float startProgress) { 951 mRunningTaskIconScaledDown = false; 952 TaskView firstTask = getRunningTaskView(); 953 if (firstTask != null) { 954 firstTask.animateIconScaleAndDimIntoView(); 955 firstTask.setIconScaleAnimStartProgress(startProgress); 956 } 957 } 958 enableLayoutTransitions()959 private void enableLayoutTransitions() { 960 if (mLayoutTransition == null) { 961 mLayoutTransition = new LayoutTransition(); 962 mLayoutTransition.enableTransitionType(LayoutTransition.APPEARING); 963 mLayoutTransition.setDuration(ADDITION_TASK_DURATION); 964 mLayoutTransition.setStartDelay(LayoutTransition.APPEARING, 0); 965 966 mLayoutTransition.addTransitionListener(new TransitionListener() { 967 @Override 968 public void startTransition(LayoutTransition transition, ViewGroup viewGroup, 969 View view, int i) { 970 } 971 972 @Override 973 public void endTransition(LayoutTransition transition, ViewGroup viewGroup, 974 View view, int i) { 975 // When the unpinned task is added, snap to first page and disable transitions 976 if (view instanceof TaskView) { 977 snapToPage(0); 978 disableLayoutTransitions(); 979 } 980 981 } 982 }); 983 } 984 setLayoutTransition(mLayoutTransition); 985 } 986 disableLayoutTransitions()987 private void disableLayoutTransitions() { 988 setLayoutTransition(null); 989 } 990 setSwipeDownShouldLaunchApp(boolean swipeDownShouldLaunchApp)991 public void setSwipeDownShouldLaunchApp(boolean swipeDownShouldLaunchApp) { 992 mSwipeDownShouldLaunchApp = swipeDownShouldLaunchApp; 993 } 994 shouldSwipeDownLaunchApp()995 public boolean shouldSwipeDownLaunchApp() { 996 return mSwipeDownShouldLaunchApp; 997 } 998 999 public interface PageCallbacks { 1000 1001 /** 1002 * Updates the page UI based on scroll params. 1003 */ onPageScroll(ScrollState scrollState)1004 default void onPageScroll(ScrollState scrollState) {} 1005 } 1006 1007 public static class ScrollState { 1008 1009 /** 1010 * The progress from 0 to 1, where 0 is the center 1011 * of the screen and 1 is the edge of the screen. 1012 */ 1013 public float linearInterpolation; 1014 1015 /** 1016 * The amount by which all the content is scrolled relative to the end of the list. 1017 */ 1018 public float scrollFromEdge; 1019 } 1020 setIgnoreResetTask(int taskId)1021 public void setIgnoreResetTask(int taskId) { 1022 mIgnoreResetTaskId = taskId; 1023 } 1024 clearIgnoreResetTask(int taskId)1025 public void clearIgnoreResetTask(int taskId) { 1026 if (mIgnoreResetTaskId == taskId) { 1027 mIgnoreResetTaskId = -1; 1028 } 1029 } 1030 addDismissedTaskAnimations(View taskView, AnimatorSet anim, long duration)1031 private void addDismissedTaskAnimations(View taskView, AnimatorSet anim, long duration) { 1032 addAnim(ObjectAnimator.ofFloat(taskView, ALPHA, 0), duration, ACCEL_2, anim); 1033 if (QUICKSTEP_SPRINGS.get() && taskView instanceof TaskView) 1034 addAnim(new SpringObjectAnimator<>(taskView, VIEW_TRANSLATE_Y, 1035 MIN_VISIBLE_CHANGE_PIXELS, SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY, 1036 SpringForce.STIFFNESS_MEDIUM, 1037 0, -taskView.getHeight()), 1038 duration, LINEAR, anim); 1039 else { 1040 addAnim(ObjectAnimator.ofFloat(taskView, TRANSLATION_Y, -taskView.getHeight()), 1041 duration, LINEAR, anim); 1042 } 1043 } 1044 removeTask(Task task, int index, PendingAnimation.OnEndListener onEndListener, boolean shouldLog)1045 private void removeTask(Task task, int index, PendingAnimation.OnEndListener onEndListener, 1046 boolean shouldLog) { 1047 if (task != null) { 1048 ActivityManagerWrapper.getInstance().removeTask(task.key.id); 1049 if (shouldLog) { 1050 mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss( 1051 onEndListener.logAction, Direction.UP, index, 1052 TaskUtils.getLaunchComponentKeyForTask(task.key)); 1053 } 1054 } 1055 } 1056 createTaskDismissAnimation(TaskView taskView, boolean animateTaskView, boolean shouldRemoveTask, long duration)1057 public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView, 1058 boolean shouldRemoveTask, long duration) { 1059 if (mPendingAnimation != null) { 1060 mPendingAnimation.finish(false, Touch.SWIPE); 1061 } 1062 AnimatorSet anim = new AnimatorSet(); 1063 PendingAnimation pendingAnimation = new PendingAnimation(anim); 1064 1065 int count = getPageCount(); 1066 if (count == 0) { 1067 return pendingAnimation; 1068 } 1069 1070 int[] oldScroll = new int[count]; 1071 getPageScrolls(oldScroll, false, SIMPLE_SCROLL_LOGIC); 1072 1073 int[] newScroll = new int[count]; 1074 getPageScrolls(newScroll, false, (v) -> v.getVisibility() != GONE && v != taskView); 1075 1076 int taskCount = getTaskViewCount(); 1077 int scrollDiffPerPage = 0; 1078 if (count > 1) { 1079 scrollDiffPerPage = Math.abs(oldScroll[1] - oldScroll[0]); 1080 } 1081 int draggedIndex = indexOfChild(taskView); 1082 1083 boolean needsCurveUpdates = false; 1084 for (int i = 0; i < count; i++) { 1085 View child = getChildAt(i); 1086 if (child == taskView) { 1087 if (animateTaskView) { 1088 addDismissedTaskAnimations(taskView, anim, duration); 1089 } 1090 } else { 1091 // If we just take newScroll - oldScroll, everything to the right of dragged task 1092 // translates to the left. We need to offset this in some cases: 1093 // - In RTL, add page offset to all pages, since we want pages to move to the right 1094 // Additionally, add a page offset if: 1095 // - Current page is rightmost page (leftmost for RTL) 1096 // - Dragging an adjacent page on the left side (right side for RTL) 1097 int offset = mIsRtl ? scrollDiffPerPage : 0; 1098 if (mCurrentPage == draggedIndex) { 1099 int lastPage = taskCount - 1; 1100 if (mCurrentPage == lastPage) { 1101 offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage; 1102 } 1103 } else { 1104 // Dragging an adjacent page. 1105 int negativeAdjacent = mCurrentPage - 1; // (Right in RTL, left in LTR) 1106 if (draggedIndex == negativeAdjacent) { 1107 offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage; 1108 } 1109 } 1110 int scrollDiff = newScroll[i] - oldScroll[i] + offset; 1111 if (scrollDiff != 0) { 1112 if (QUICKSTEP_SPRINGS.get() && child instanceof TaskView) { 1113 addAnim(new SpringObjectAnimator<>(child, VIEW_TRANSLATE_X, 1114 MIN_VISIBLE_CHANGE_PIXELS, SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY, 1115 SpringForce.STIFFNESS_MEDIUM, 1116 0, scrollDiff), duration, ACCEL, anim); 1117 } else { 1118 addAnim(ObjectAnimator.ofFloat(child, TRANSLATION_X, scrollDiff), duration, 1119 ACCEL, anim); 1120 } 1121 1122 needsCurveUpdates = true; 1123 } 1124 } 1125 } 1126 1127 if (needsCurveUpdates) { 1128 ValueAnimator va = ValueAnimator.ofFloat(0, 1); 1129 va.addUpdateListener((a) -> updateCurveProperties()); 1130 anim.play(va); 1131 } 1132 1133 // Add a tiny bit of translation Z, so that it draws on top of other views 1134 if (animateTaskView) { 1135 taskView.setTranslationZ(0.1f); 1136 } 1137 1138 mPendingAnimation = pendingAnimation; 1139 mPendingAnimation.addEndListener(new Consumer<PendingAnimation.OnEndListener>() { 1140 @Override 1141 public void accept(PendingAnimation.OnEndListener onEndListener) { 1142 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && 1143 taskView.isRunningTask() && onEndListener.isSuccess) { 1144 finishRecentsAnimation(true /* toHome */, () -> onEnd(onEndListener)); 1145 } else { 1146 onEnd(onEndListener); 1147 } 1148 } 1149 1150 private void onEnd(PendingAnimation.OnEndListener onEndListener) { 1151 if (onEndListener.isSuccess) { 1152 if (shouldRemoveTask) { 1153 removeTask(taskView.getTask(), draggedIndex, onEndListener, true); 1154 } 1155 1156 int pageToSnapTo = mCurrentPage; 1157 if (draggedIndex < pageToSnapTo || 1158 pageToSnapTo == (getTaskViewCount() - 1)) { 1159 pageToSnapTo -= 1; 1160 } 1161 removeView(taskView); 1162 1163 if (getTaskViewCount() == 0) { 1164 removeView(mClearAllButton); 1165 startHome(); 1166 } else { 1167 snapToPageImmediately(pageToSnapTo); 1168 } 1169 } 1170 resetTaskVisuals(); 1171 mPendingAnimation = null; 1172 } 1173 }); 1174 return pendingAnimation; 1175 } 1176 createAllTasksDismissAnimation(long duration)1177 public PendingAnimation createAllTasksDismissAnimation(long duration) { 1178 if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) { 1179 throw new IllegalStateException("Another pending animation is still running"); 1180 } 1181 AnimatorSet anim = new AnimatorSet(); 1182 PendingAnimation pendingAnimation = new PendingAnimation(anim); 1183 1184 int count = getTaskViewCount(); 1185 for (int i = 0; i < count; i++) { 1186 addDismissedTaskAnimations(getChildAt(i), anim, duration); 1187 } 1188 1189 mPendingAnimation = pendingAnimation; 1190 mPendingAnimation.addEndListener((onEndListener) -> { 1191 if (onEndListener.isSuccess) { 1192 // Remove all the task views now 1193 ActivityManagerWrapper.getInstance().removeAllRecentTasks(); 1194 removeAllViews(); 1195 startHome(); 1196 } 1197 mPendingAnimation = null; 1198 }); 1199 return pendingAnimation; 1200 } 1201 addAnim(Animator anim, long duration, TimeInterpolator interpolator, AnimatorSet set)1202 private static void addAnim(Animator anim, long duration, 1203 TimeInterpolator interpolator, AnimatorSet set) { 1204 anim.setDuration(duration).setInterpolator(interpolator); 1205 set.play(anim); 1206 } 1207 snapToPageRelative(int pageCount, int delta, boolean cycle)1208 private boolean snapToPageRelative(int pageCount, int delta, boolean cycle) { 1209 if (pageCount == 0) { 1210 return false; 1211 } 1212 final int newPageUnbound = getNextPage() + delta; 1213 if (!cycle && (newPageUnbound < 0 || newPageUnbound >= pageCount)) { 1214 return false; 1215 } 1216 snapToPage((newPageUnbound + pageCount) % pageCount); 1217 getChildAt(getNextPage()).requestFocus(); 1218 return true; 1219 } 1220 runDismissAnimation(PendingAnimation pendingAnim)1221 private void runDismissAnimation(PendingAnimation pendingAnim) { 1222 AnimatorPlaybackController controller = AnimatorPlaybackController.wrap( 1223 pendingAnim.anim, DISMISS_TASK_DURATION); 1224 controller.dispatchOnStart(); 1225 controller.setEndAction(() -> pendingAnim.finish(true, Touch.SWIPE)); 1226 controller.getAnimationPlayer().setInterpolator(FAST_OUT_SLOW_IN); 1227 controller.start(); 1228 } 1229 dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask)1230 public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) { 1231 runDismissAnimation(createTaskDismissAnimation(taskView, animateTaskView, removeTask, 1232 DISMISS_TASK_DURATION)); 1233 } 1234 1235 @SuppressWarnings("unused") dismissAllTasks(View view)1236 private void dismissAllTasks(View view) { 1237 runDismissAnimation(createAllTasksDismissAnimation(DISMISS_TASK_DURATION)); 1238 mActivity.getUserEventDispatcher().logActionOnControl(TAP, CLEAR_ALL_BUTTON); 1239 } 1240 dismissCurrentTask()1241 private void dismissCurrentTask() { 1242 TaskView taskView = getTaskView(getNextPage()); 1243 if (taskView != null) { 1244 dismissTask(taskView, true /*animateTaskView*/, true /*removeTask*/); 1245 } 1246 } 1247 1248 @Override dispatchKeyEvent(KeyEvent event)1249 public boolean dispatchKeyEvent(KeyEvent event) { 1250 if (event.getAction() == KeyEvent.ACTION_DOWN) { 1251 switch (event.getKeyCode()) { 1252 case KeyEvent.KEYCODE_TAB: 1253 return snapToPageRelative(getTaskViewCount(), event.isShiftPressed() ? -1 : 1, 1254 event.isAltPressed() /* cycle */); 1255 case KeyEvent.KEYCODE_DPAD_RIGHT: 1256 return snapToPageRelative(getPageCount(), mIsRtl ? -1 : 1, false /* cycle */); 1257 case KeyEvent.KEYCODE_DPAD_LEFT: 1258 return snapToPageRelative(getPageCount(), mIsRtl ? 1 : -1, false /* cycle */); 1259 case KeyEvent.KEYCODE_DEL: 1260 case KeyEvent.KEYCODE_FORWARD_DEL: 1261 dismissCurrentTask(); 1262 return true; 1263 case KeyEvent.KEYCODE_NUMPAD_DOT: 1264 if (event.isAltPressed()) { 1265 // Numpad DEL pressed while holding Alt. 1266 dismissCurrentTask(); 1267 return true; 1268 } 1269 } 1270 } 1271 return super.dispatchKeyEvent(event); 1272 } 1273 1274 @Override onFocusChanged(boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect)1275 protected void onFocusChanged(boolean gainFocus, int direction, 1276 @Nullable Rect previouslyFocusedRect) { 1277 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 1278 if (gainFocus && getChildCount() > 0) { 1279 switch (direction) { 1280 case FOCUS_FORWARD: 1281 setCurrentPage(0); 1282 break; 1283 case FOCUS_BACKWARD: 1284 case FOCUS_RIGHT: 1285 case FOCUS_LEFT: 1286 setCurrentPage(getChildCount() - 1); 1287 break; 1288 } 1289 } 1290 } 1291 getContentAlpha()1292 public float getContentAlpha() { 1293 return mContentAlpha; 1294 } 1295 setContentAlpha(float alpha)1296 public void setContentAlpha(float alpha) { 1297 if (alpha == mContentAlpha) { 1298 return; 1299 } 1300 alpha = Utilities.boundToRange(alpha, 0, 1); 1301 mContentAlpha = alpha; 1302 for (int i = getTaskViewCount() - 1; i >= 0; i--) { 1303 TaskView child = getTaskViewAt(i); 1304 if (!mRunningTaskTileHidden || child.getTask().key.id != mRunningTaskId) { 1305 child.setStableAlpha(alpha); 1306 } 1307 } 1308 mClearAllButton.setContentAlpha(mContentAlpha); 1309 1310 int alphaInt = Math.round(alpha * 255); 1311 mEmptyMessagePaint.setAlpha(alphaInt); 1312 mEmptyIcon.setAlpha(alphaInt); 1313 1314 if (alpha > 0) { 1315 setVisibility(VISIBLE); 1316 } else if (!mFreezeViewVisibility) { 1317 setVisibility(GONE); 1318 } 1319 } 1320 1321 /** 1322 * Freezes the view visibility change. When frozen, the view will not change its visibility 1323 * to gone due to alpha changes. 1324 */ setFreezeViewVisibility(boolean freezeViewVisibility)1325 public void setFreezeViewVisibility(boolean freezeViewVisibility) { 1326 if (mFreezeViewVisibility != freezeViewVisibility) { 1327 mFreezeViewVisibility = freezeViewVisibility; 1328 1329 if (!mFreezeViewVisibility) { 1330 setVisibility(mContentAlpha > 0 ? VISIBLE : GONE); 1331 } 1332 } 1333 } 1334 1335 @Override onViewAdded(View child)1336 public void onViewAdded(View child) { 1337 super.onViewAdded(child); 1338 child.setAlpha(mContentAlpha); 1339 } 1340 1341 /** 1342 * @return The most recent task that is older than the currently running task. If there is 1343 * currently no running task or there is no task older than it, then return null. 1344 */ 1345 @Nullable getNextTaskView()1346 public TaskView getNextTaskView() { 1347 TaskView runningTaskView = getRunningTaskView(); 1348 if (runningTaskView == null) { 1349 return null; 1350 } 1351 return getTaskViewAt(indexOfChild(runningTaskView) + 1); 1352 } 1353 getTaskViewAt(int index)1354 public TaskView getTaskViewAt(int index) { 1355 View child = getChildAt(index); 1356 return child == mClearAllButton ? null : (TaskView) child; 1357 } 1358 updateEmptyMessage()1359 public void updateEmptyMessage() { 1360 boolean isEmpty = getChildCount() == 0; 1361 boolean hasSizeChanged = mLastMeasureSize.x != getWidth() 1362 || mLastMeasureSize.y != getHeight(); 1363 if (isEmpty == mShowEmptyMessage && !hasSizeChanged) { 1364 return; 1365 } 1366 setContentDescription(isEmpty ? mEmptyMessage : ""); 1367 mShowEmptyMessage = isEmpty; 1368 updateEmptyStateUi(hasSizeChanged); 1369 invalidate(); 1370 } 1371 1372 @Override onLayout(boolean changed, int left, int top, int right, int bottom)1373 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1374 super.onLayout(changed, left, top, right, bottom); 1375 updateEmptyStateUi(changed); 1376 1377 // Set the pivot points to match the task preview center 1378 setPivotY(((mInsets.top + getPaddingTop() + mTaskTopMargin) 1379 + (getHeight() - mInsets.bottom - getPaddingBottom())) / 2); 1380 setPivotX(((mInsets.left + getPaddingLeft()) 1381 + (getWidth() - mInsets.right - getPaddingRight())) / 2); 1382 } 1383 updateDeadZoneRects()1384 private void updateDeadZoneRects() { 1385 // Get the deadzone rect surrounding the clear all button to not dismiss overview to home 1386 mClearAllButtonDeadZoneRect.setEmpty(); 1387 if (mClearAllButton.getWidth() > 0) { 1388 int verticalMargin = getResources() 1389 .getDimensionPixelSize(R.dimen.recents_clear_all_deadzone_vertical_margin); 1390 mClearAllButton.getHitRect(mClearAllButtonDeadZoneRect); 1391 mClearAllButtonDeadZoneRect.inset(-getPaddingRight() / 2, -verticalMargin); 1392 } 1393 1394 // Get the deadzone rect between the task views 1395 mTaskViewDeadZoneRect.setEmpty(); 1396 int count = getTaskViewCount(); 1397 if (count > 0) { 1398 final View taskView = getTaskViewAt(0); 1399 getTaskViewAt(count - 1).getHitRect(mTaskViewDeadZoneRect); 1400 mTaskViewDeadZoneRect.union(taskView.getLeft(), taskView.getTop(), taskView.getRight(), 1401 taskView.getBottom()); 1402 } 1403 } 1404 updateEmptyStateUi(boolean sizeChanged)1405 private void updateEmptyStateUi(boolean sizeChanged) { 1406 boolean hasValidSize = getWidth() > 0 && getHeight() > 0; 1407 if (sizeChanged && hasValidSize) { 1408 mEmptyTextLayout = null; 1409 mLastMeasureSize.set(getWidth(), getHeight()); 1410 } 1411 1412 if (mShowEmptyMessage && hasValidSize && mEmptyTextLayout == null) { 1413 int availableWidth = mLastMeasureSize.x - mEmptyMessagePadding - mEmptyMessagePadding; 1414 mEmptyTextLayout = StaticLayout.Builder.obtain(mEmptyMessage, 0, mEmptyMessage.length(), 1415 mEmptyMessagePaint, availableWidth) 1416 .setAlignment(Layout.Alignment.ALIGN_CENTER) 1417 .build(); 1418 int totalHeight = mEmptyTextLayout.getHeight() 1419 + mEmptyMessagePadding + mEmptyIcon.getIntrinsicHeight(); 1420 1421 int top = (mLastMeasureSize.y - totalHeight) / 2; 1422 int left = (mLastMeasureSize.x - mEmptyIcon.getIntrinsicWidth()) / 2; 1423 mEmptyIcon.setBounds(left, top, left + mEmptyIcon.getIntrinsicWidth(), 1424 top + mEmptyIcon.getIntrinsicHeight()); 1425 } 1426 } 1427 1428 @Override verifyDrawable(Drawable who)1429 protected boolean verifyDrawable(Drawable who) { 1430 return super.verifyDrawable(who) || (mShowEmptyMessage && who == mEmptyIcon); 1431 } 1432 maybeDrawEmptyMessage(Canvas canvas)1433 protected void maybeDrawEmptyMessage(Canvas canvas) { 1434 if (mShowEmptyMessage && mEmptyTextLayout != null) { 1435 // Offset to center in the visible (non-padded) part of RecentsView 1436 mTempRect.set(mInsets.left + getPaddingLeft(), mInsets.top + getPaddingTop(), 1437 mInsets.right + getPaddingRight(), mInsets.bottom + getPaddingBottom()); 1438 canvas.save(); 1439 canvas.translate(getScrollX() + (mTempRect.left - mTempRect.right) / 2, 1440 (mTempRect.top - mTempRect.bottom) / 2); 1441 mEmptyIcon.draw(canvas); 1442 canvas.translate(mEmptyMessagePadding, 1443 mEmptyIcon.getBounds().bottom + mEmptyMessagePadding); 1444 mEmptyTextLayout.draw(canvas); 1445 canvas.restore(); 1446 } 1447 } 1448 1449 /** 1450 * Animate adjacent tasks off screen while scaling up. 1451 * 1452 * If launching one of the adjacent tasks, parallax the center task and other adjacent task 1453 * to the right. 1454 */ createAdjacentPageAnimForTaskLaunch( TaskView tv, ClipAnimationHelper clipAnimationHelper)1455 public AnimatorSet createAdjacentPageAnimForTaskLaunch( 1456 TaskView tv, ClipAnimationHelper clipAnimationHelper) { 1457 AnimatorSet anim = new AnimatorSet(); 1458 1459 int taskIndex = indexOfChild(tv); 1460 int centerTaskIndex = getCurrentPage(); 1461 boolean launchingCenterTask = taskIndex == centerTaskIndex; 1462 1463 LauncherState.ScaleAndTranslation toScaleAndTranslation = clipAnimationHelper 1464 .getScaleAndTranslation(); 1465 float toScale = toScaleAndTranslation.scale; 1466 float toTranslationY = toScaleAndTranslation.translationY; 1467 if (launchingCenterTask) { 1468 RecentsView recentsView = tv.getRecentsView(); 1469 anim.play(ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, toScale)); 1470 anim.play(ObjectAnimator.ofFloat(recentsView, TRANSLATION_Y, toTranslationY)); 1471 anim.play(ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS, 1)); 1472 } else { 1473 // We are launching an adjacent task, so parallax the center and other adjacent task. 1474 float displacementX = tv.getWidth() * (toScale - tv.getCurveScale()); 1475 anim.play(ObjectAnimator.ofFloat(getPageAt(centerTaskIndex), TRANSLATION_X, 1476 mIsRtl ? -displacementX : displacementX)); 1477 1478 int otherAdjacentTaskIndex = centerTaskIndex + (centerTaskIndex - taskIndex); 1479 if (otherAdjacentTaskIndex >= 0 && otherAdjacentTaskIndex < getPageCount()) { 1480 anim.play(new PropertyListBuilder() 1481 .translationX(mIsRtl ? -displacementX : displacementX) 1482 .scale(1) 1483 .build(getPageAt(otherAdjacentTaskIndex))); 1484 } 1485 } 1486 return anim; 1487 } 1488 createTaskLauncherAnimation(TaskView tv, long duration)1489 public PendingAnimation createTaskLauncherAnimation(TaskView tv, long duration) { 1490 if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) { 1491 throw new IllegalStateException("Another pending animation is still running"); 1492 } 1493 1494 int count = getChildCount(); 1495 if (count == 0) { 1496 return new PendingAnimation(new AnimatorSet()); 1497 } 1498 1499 int targetSysUiFlags = tv.getThumbnail().getSysUiStatusNavFlags(); 1500 final boolean[] passedOverviewThreshold = new boolean[] {false}; 1501 ValueAnimator progressAnim = ValueAnimator.ofFloat(0, 1); 1502 progressAnim.setInterpolator(LINEAR); 1503 progressAnim.addUpdateListener(animator -> { 1504 // Once we pass a certain threshold, update the sysui flags to match the target 1505 // tasks' flags 1506 mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, 1507 animator.getAnimatedFraction() > UPDATE_SYSUI_FLAGS_THRESHOLD 1508 ? targetSysUiFlags 1509 : 0); 1510 1511 onTaskLaunchAnimationUpdate(animator.getAnimatedFraction(), tv); 1512 1513 // Passing the threshold from taskview to fullscreen app will vibrate 1514 final boolean passed = animator.getAnimatedFraction() >= 1515 SUCCESS_TRANSITION_PROGRESS; 1516 if (passed != passedOverviewThreshold[0]) { 1517 passedOverviewThreshold[0] = passed; 1518 performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, 1519 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); 1520 } 1521 }); 1522 1523 ClipAnimationHelper clipAnimationHelper = new ClipAnimationHelper(mActivity); 1524 clipAnimationHelper.fromTaskThumbnailView(tv.getThumbnail(), this); 1525 clipAnimationHelper.prepareAnimation(mActivity.getDeviceProfile(), true /* isOpening */); 1526 AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv, clipAnimationHelper); 1527 anim.play(progressAnim); 1528 anim.setDuration(duration); 1529 1530 Consumer<Boolean> onTaskLaunchFinish = this::onTaskLaunched; 1531 1532 mPendingAnimation = new PendingAnimation(anim); 1533 mPendingAnimation.addEndListener((onEndListener) -> { 1534 if (onEndListener.isSuccess) { 1535 Consumer<Boolean> onLaunchResult = (result) -> { 1536 onTaskLaunchFinish.accept(result); 1537 if (!result) { 1538 tv.notifyTaskLaunchFailed(TAG); 1539 } 1540 }; 1541 tv.launchTask(false, onLaunchResult, getHandler()); 1542 Task task = tv.getTask(); 1543 if (task != null) { 1544 mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss( 1545 onEndListener.logAction, Direction.DOWN, indexOfChild(tv), 1546 TaskUtils.getLaunchComponentKeyForTask(task.key)); 1547 } 1548 } else { 1549 onTaskLaunchFinish.accept(false); 1550 } 1551 mPendingAnimation = null; 1552 }); 1553 return mPendingAnimation; 1554 } 1555 onTaskLaunchAnimationUpdate(float progress, TaskView tv)1556 protected void onTaskLaunchAnimationUpdate(float progress, TaskView tv) { 1557 } 1558 shouldUseMultiWindowTaskSizeStrategy()1559 public abstract boolean shouldUseMultiWindowTaskSizeStrategy(); 1560 onTaskLaunched(boolean success)1561 protected void onTaskLaunched(boolean success) { 1562 if (success) { 1563 resetTaskVisuals(); 1564 } 1565 } 1566 1567 @Override notifyPageSwitchListener(int prevPage)1568 protected void notifyPageSwitchListener(int prevPage) { 1569 super.notifyPageSwitchListener(prevPage); 1570 loadVisibleTaskData(); 1571 updateEnabledOverlays(); 1572 } 1573 1574 @Override getCurrentPageDescription()1575 protected String getCurrentPageDescription() { 1576 return ""; 1577 } 1578 1579 @Override addChildrenForAccessibility(ArrayList<View> outChildren)1580 public void addChildrenForAccessibility(ArrayList<View> outChildren) { 1581 // Add children in reverse order 1582 for (int i = getChildCount() - 1; i >= 0; --i) { 1583 outChildren.add(getChildAt(i)); 1584 } 1585 } 1586 1587 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)1588 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 1589 super.onInitializeAccessibilityNodeInfo(info); 1590 final AccessibilityNodeInfo.CollectionInfo 1591 collectionInfo = AccessibilityNodeInfo.CollectionInfo.obtain( 1592 1, getTaskViewCount(), false, 1593 AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_NONE); 1594 info.setCollectionInfo(collectionInfo); 1595 } 1596 1597 @Override onInitializeAccessibilityEvent(AccessibilityEvent event)1598 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 1599 super.onInitializeAccessibilityEvent(event); 1600 1601 final int taskViewCount = getTaskViewCount(); 1602 event.setScrollable(taskViewCount > 0); 1603 1604 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 1605 final int[] visibleTasks = getVisibleChildrenRange(); 1606 event.setFromIndex(taskViewCount - visibleTasks[1] - 1); 1607 event.setToIndex(taskViewCount - visibleTasks[0] - 1); 1608 event.setItemCount(taskViewCount); 1609 } 1610 } 1611 1612 @Override getAccessibilityClassName()1613 public CharSequence getAccessibilityClassName() { 1614 // To hear position-in-list related feedback from Talkback. 1615 return ListView.class.getName(); 1616 } 1617 1618 @Override isPageOrderFlipped()1619 protected boolean isPageOrderFlipped() { 1620 return true; 1621 } 1622 setEnableDrawingLiveTile(boolean enableDrawingLiveTile)1623 public void setEnableDrawingLiveTile(boolean enableDrawingLiveTile) { 1624 mEnableDrawingLiveTile = enableDrawingLiveTile; 1625 } 1626 redrawLiveTile(boolean mightNeedToRefill)1627 public void redrawLiveTile(boolean mightNeedToRefill) { } 1628 setRecentsAnimationWrapper(RecentsAnimationWrapper recentsAnimationWrapper)1629 public void setRecentsAnimationWrapper(RecentsAnimationWrapper recentsAnimationWrapper) { 1630 mRecentsAnimationWrapper = recentsAnimationWrapper; 1631 } 1632 setClipAnimationHelper(ClipAnimationHelper clipAnimationHelper)1633 public void setClipAnimationHelper(ClipAnimationHelper clipAnimationHelper) { 1634 mClipAnimationHelper = clipAnimationHelper; 1635 } 1636 setLiveTileOverlay(LiveTileOverlay liveTileOverlay)1637 public void setLiveTileOverlay(LiveTileOverlay liveTileOverlay) { 1638 mLiveTileOverlay = liveTileOverlay; 1639 } 1640 updateLiveTileIcon(Drawable icon)1641 public void updateLiveTileIcon(Drawable icon) { 1642 if (mLiveTileOverlay != null) { 1643 mLiveTileOverlay.setIcon(icon); 1644 } 1645 } 1646 finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete)1647 public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) { 1648 if (mRecentsAnimationWrapper == null) { 1649 if (onFinishComplete != null) { 1650 onFinishComplete.run(); 1651 } 1652 return; 1653 } 1654 1655 mRecentsAnimationWrapper.finish(toRecents, onFinishComplete); 1656 } 1657 setDisallowScrollToClearAll(boolean disallowScrollToClearAll)1658 public void setDisallowScrollToClearAll(boolean disallowScrollToClearAll) { 1659 if (mDisallowScrollToClearAll != disallowScrollToClearAll) { 1660 mDisallowScrollToClearAll = disallowScrollToClearAll; 1661 updateMinAndMaxScrollX(); 1662 } 1663 } 1664 1665 @Override computeMinScrollX()1666 protected int computeMinScrollX() { 1667 if (mIsRtl && mDisallowScrollToClearAll) { 1668 // We aren't showing the clear all button, so use the leftmost task as the min scroll. 1669 return getScrollForPage(getTaskViewCount() - 1); 1670 } 1671 return super.computeMinScrollX(); 1672 } 1673 1674 @Override computeMaxScrollX()1675 protected int computeMaxScrollX() { 1676 if (!mIsRtl && mDisallowScrollToClearAll) { 1677 // We aren't showing the clear all button, so use the rightmost task as the max scroll. 1678 return getScrollForPage(getTaskViewCount() - 1); 1679 } 1680 return super.computeMaxScrollX(); 1681 } 1682 getClearAllButton()1683 public ClearAllButton getClearAllButton() { 1684 return mClearAllButton; 1685 } 1686 1687 /** 1688 * @return How many pixels the running task is offset on the x-axis due to the current scrollX. 1689 */ getScrollOffset()1690 public float getScrollOffset() { 1691 int startScroll = getScrollForPage(getRunningTaskIndex()); 1692 int offsetX = startScroll - getScrollX(); 1693 offsetX *= getScaleX(); 1694 return offsetX; 1695 } 1696 getEventDispatcher(RotationMode rotationMode)1697 public Consumer<MotionEvent> getEventDispatcher(RotationMode rotationMode) { 1698 if (rotationMode.isTransposed) { 1699 Matrix transform = new Matrix(); 1700 transform.setRotate(-rotationMode.surfaceRotation); 1701 1702 if (getWidth() > 0 && getHeight() > 0) { 1703 float scale = ((float) getWidth()) / getHeight(); 1704 transform.postScale(scale, 1 / scale); 1705 } 1706 1707 Matrix inverse = new Matrix(); 1708 transform.invert(inverse); 1709 return e -> { 1710 e.transform(transform); 1711 super.onTouchEvent(e); 1712 e.transform(inverse); 1713 }; 1714 } else { 1715 return super::onTouchEvent; 1716 } 1717 } 1718 getTempClipAnimationHelper()1719 public ClipAnimationHelper getTempClipAnimationHelper() { 1720 return mTempClipAnimationHelper; 1721 } 1722 updateEnabledOverlays()1723 private void updateEnabledOverlays() { 1724 int overlayEnabledPage = mOverlayEnabled ? getNextPage() : -1; 1725 int taskCount = getTaskViewCount(); 1726 for (int i = 0; i < taskCount; i++) { 1727 ((TaskView) getChildAt(i)).setOverlayEnabled(i == overlayEnabledPage); 1728 } 1729 } 1730 setOverlayEnabled(boolean overlayEnabled)1731 public void setOverlayEnabled(boolean overlayEnabled) { 1732 if (mOverlayEnabled != overlayEnabled) { 1733 mOverlayEnabled = overlayEnabled; 1734 updateEnabledOverlays(); 1735 } 1736 } 1737 } 1738