1 /* 2 * Copyright (C) 2014 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.systemui.recents.views; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ValueAnimator; 22 import android.annotation.IntDef; 23 import android.content.ComponentName; 24 import android.content.Context; 25 import android.content.res.Configuration; 26 import android.content.res.Resources; 27 import android.graphics.Rect; 28 import android.os.Bundle; 29 import android.util.ArrayMap; 30 import android.util.ArraySet; 31 import android.util.MutableBoolean; 32 import android.view.LayoutInflater; 33 import android.view.MotionEvent; 34 import android.view.View; 35 import android.view.ViewDebug; 36 import android.view.ViewGroup; 37 import android.view.accessibility.AccessibilityEvent; 38 import android.view.accessibility.AccessibilityNodeInfo; 39 import android.widget.FrameLayout; 40 import android.widget.ScrollView; 41 42 import com.android.internal.logging.MetricsLogger; 43 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 44 import com.android.systemui.Interpolators; 45 import com.android.systemui.R; 46 import com.android.systemui.recents.LegacyRecentsImpl; 47 import com.android.systemui.recents.RecentsActivity; 48 import com.android.systemui.recents.RecentsActivityLaunchState; 49 import com.android.systemui.recents.RecentsConfiguration; 50 import com.android.systemui.recents.RecentsDebugFlags; 51 import com.android.systemui.recents.RecentsImpl; 52 import com.android.systemui.recents.events.EventBus; 53 import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent; 54 import com.android.systemui.recents.events.activity.ConfigurationChangedEvent; 55 import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted; 56 import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent; 57 import com.android.systemui.recents.events.activity.HideRecentsEvent; 58 import com.android.systemui.recents.events.activity.HideStackActionButtonEvent; 59 import com.android.systemui.recents.events.activity.LaunchMostRecentTaskRequestEvent; 60 import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent; 61 import com.android.systemui.recents.events.activity.LaunchTaskEvent; 62 import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent; 63 import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent; 64 import com.android.systemui.recents.events.activity.PackagesChangedEvent; 65 import com.android.systemui.recents.events.activity.ShowEmptyViewEvent; 66 import com.android.systemui.recents.events.activity.ShowStackActionButtonEvent; 67 import com.android.systemui.recents.events.component.ActivityPinnedEvent; 68 import com.android.systemui.recents.events.component.ExpandPipEvent; 69 import com.android.systemui.recents.events.component.HidePipMenuEvent; 70 import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent; 71 import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent; 72 import com.android.systemui.recents.events.ui.DeleteTaskDataEvent; 73 import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent; 74 import com.android.systemui.recents.events.ui.DismissTaskViewEvent; 75 import com.android.systemui.recents.events.ui.RecentsGrowingEvent; 76 import com.android.systemui.recents.events.ui.TaskViewDismissedEvent; 77 import com.android.systemui.recents.events.ui.UserInteractionEvent; 78 import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent; 79 import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent; 80 import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; 81 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; 82 import com.android.systemui.recents.events.ui.focus.DismissFocusedTaskViewEvent; 83 import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent; 84 import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent; 85 import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent; 86 import com.android.systemui.recents.misc.DozeTrigger; 87 import com.android.systemui.recents.misc.ReferenceCountedTrigger; 88 import com.android.systemui.recents.misc.SystemServicesProxy; 89 import com.android.systemui.recents.utilities.AnimationProps; 90 import com.android.systemui.recents.utilities.Utilities; 91 import com.android.systemui.shared.recents.model.Task; 92 import com.android.systemui.recents.model.TaskStack; 93 import com.android.systemui.recents.views.grid.GridTaskView; 94 import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm; 95 import com.android.systemui.recents.views.grid.TaskViewFocusFrame; 96 97 import com.android.systemui.shared.system.ActivityManagerWrapper; 98 import java.io.PrintWriter; 99 import java.lang.annotation.Retention; 100 import java.lang.annotation.RetentionPolicy; 101 import java.util.ArrayList; 102 import java.util.List; 103 104 105 /* The visual representation of a task stack view */ 106 public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks, 107 TaskView.TaskViewCallbacks, TaskStackViewScroller.TaskStackViewScrollerCallbacks, 108 TaskStackLayoutAlgorithm.TaskStackLayoutAlgorithmCallbacks, 109 ViewPool.ViewPoolConsumer<TaskView, Task> { 110 111 private static final String TAG = "TaskStackView"; 112 113 // The thresholds at which to show/hide the stack action button. 114 private static final float SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD = 0.3f; 115 private static final float HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD = 0.3f; 116 117 public static final int DEFAULT_SYNC_STACK_DURATION = 200; 118 public static final int SLOW_SYNC_STACK_DURATION = 250; 119 private static final int DRAG_SCALE_DURATION = 175; 120 static final float DRAG_SCALE_FACTOR = 1.05f; 121 122 private static final int LAUNCH_NEXT_SCROLL_BASE_DURATION = 216; 123 private static final int LAUNCH_NEXT_SCROLL_INCR_DURATION = 32; 124 125 // The actions to perform when resetting to initial state, 126 @Retention(RetentionPolicy.SOURCE) 127 @IntDef({INITIAL_STATE_UPDATE_NONE, INITIAL_STATE_UPDATE_ALL, INITIAL_STATE_UPDATE_LAYOUT_ONLY}) 128 public @interface InitialStateAction {} 129 /** Do not update the stack and layout to the initial state. */ 130 private static final int INITIAL_STATE_UPDATE_NONE = 0; 131 /** Update both the stack and layout to the initial state. */ 132 private static final int INITIAL_STATE_UPDATE_ALL = 1; 133 /** Update only the layout to the initial state. */ 134 private static final int INITIAL_STATE_UPDATE_LAYOUT_ONLY = 2; 135 136 private LayoutInflater mInflater; 137 private TaskStack mStack = new TaskStack(); 138 @ViewDebug.ExportedProperty(deepExport=true, prefix="layout_") 139 TaskStackLayoutAlgorithm mLayoutAlgorithm; 140 // The stable layout algorithm is only used to calculate the task rect with the stable bounds 141 private TaskStackLayoutAlgorithm mStableLayoutAlgorithm; 142 @ViewDebug.ExportedProperty(deepExport=true, prefix="scroller_") 143 private TaskStackViewScroller mStackScroller; 144 @ViewDebug.ExportedProperty(deepExport=true, prefix="touch_") 145 private TaskStackViewTouchHandler mTouchHandler; 146 private TaskStackAnimationHelper mAnimationHelper; 147 private ViewPool<TaskView, Task> mViewPool; 148 149 private ArrayList<TaskView> mTaskViews = new ArrayList<>(); 150 private ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>(); 151 private ArraySet<Task.TaskKey> mIgnoreTasks = new ArraySet<>(); 152 private AnimationProps mDeferredTaskViewLayoutAnimation = null; 153 154 @ViewDebug.ExportedProperty(deepExport=true, prefix="doze_") 155 private DozeTrigger mUIDozeTrigger; 156 @ViewDebug.ExportedProperty(deepExport=true, prefix="focused_task_") 157 private Task mFocusedTask; 158 159 private int mTaskCornerRadiusPx; 160 private int mDividerSize; 161 private int mStartTimerIndicatorDuration; 162 163 @ViewDebug.ExportedProperty(category="recents") 164 private boolean mTaskViewsClipDirty = true; 165 @ViewDebug.ExportedProperty(category="recents") 166 private boolean mEnterAnimationComplete = false; 167 @ViewDebug.ExportedProperty(category="recents") 168 private boolean mStackReloaded = false; 169 @ViewDebug.ExportedProperty(category="recents") 170 private boolean mFinishedLayoutAfterStackReload = false; 171 @ViewDebug.ExportedProperty(category="recents") 172 private boolean mLaunchNextAfterFirstMeasure = false; 173 @ViewDebug.ExportedProperty(category="recents") 174 @InitialStateAction 175 private int mInitialState = INITIAL_STATE_UPDATE_ALL; 176 @ViewDebug.ExportedProperty(category="recents") 177 private boolean mInMeasureLayout = false; 178 @ViewDebug.ExportedProperty(category="recents") 179 boolean mTouchExplorationEnabled; 180 @ViewDebug.ExportedProperty(category="recents") 181 boolean mScreenPinningEnabled; 182 183 // The stable stack bounds are the full bounds that we were measured with from RecentsView 184 @ViewDebug.ExportedProperty(category="recents") 185 private Rect mStableStackBounds = new Rect(); 186 // The current stack bounds are dynamic and may change as the user drags and drops 187 @ViewDebug.ExportedProperty(category="recents") 188 private Rect mStackBounds = new Rect(); 189 // The current window bounds at the point we were measured 190 @ViewDebug.ExportedProperty(category="recents") 191 private Rect mStableWindowRect = new Rect(); 192 // The current window bounds are dynamic and may change as the user drags and drops 193 @ViewDebug.ExportedProperty(category="recents") 194 private Rect mWindowRect = new Rect(); 195 // The current display bounds 196 @ViewDebug.ExportedProperty(category="recents") 197 private Rect mDisplayRect = new Rect(); 198 // The current display orientation 199 @ViewDebug.ExportedProperty(category="recents") 200 private int mDisplayOrientation = Configuration.ORIENTATION_UNDEFINED; 201 202 private Rect mTmpRect = new Rect(); 203 private ArrayMap<Task.TaskKey, TaskView> mTmpTaskViewMap = new ArrayMap<>(); 204 private List<TaskView> mTmpTaskViews = new ArrayList<>(); 205 private TaskViewTransform mTmpTransform = new TaskViewTransform(); 206 private int[] mTmpIntPair = new int[2]; 207 private boolean mResetToInitialStateWhenResized; 208 private int mLastWidth; 209 private int mLastHeight; 210 private boolean mStackActionButtonVisible; 211 212 // Percentage of last ScrollP from the min to max scrollP that lives after configuration changes 213 private float mLastScrollPPercent = -1; 214 215 // We keep track of the task view focused by user interaction and draw a frame around it in the 216 // grid layout. 217 private TaskViewFocusFrame mTaskViewFocusFrame; 218 219 private Task mPrefetchingTask; 220 private final float mFastFlingVelocity; 221 222 // A convenience update listener to request updating clipping of tasks 223 private ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener = 224 new ValueAnimator.AnimatorUpdateListener() { 225 @Override 226 public void onAnimationUpdate(ValueAnimator animation) { 227 if (!mTaskViewsClipDirty) { 228 mTaskViewsClipDirty = true; 229 invalidate(); 230 } 231 } 232 }; 233 234 private DropTarget mStackDropTarget = new DropTarget() { 235 @Override 236 public boolean acceptsDrop(int x, int y, int width, int height, Rect insets, 237 boolean isCurrentTarget) { 238 // This drop target has a fixed bounds and should be checked last, so just fall through 239 // if it is the current target 240 if (!isCurrentTarget) { 241 return mLayoutAlgorithm.mStackRect.contains(x, y); 242 } 243 return false; 244 } 245 }; 246 TaskStackView(Context context)247 public TaskStackView(Context context) { 248 super(context); 249 SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices(); 250 Resources res = context.getResources(); 251 252 // Set the stack first 253 mStack.setCallbacks(this); 254 mViewPool = new ViewPool<>(context, this); 255 mInflater = LayoutInflater.from(context); 256 mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, this); 257 mStableLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, null); 258 mStackScroller = new TaskStackViewScroller(context, this, mLayoutAlgorithm); 259 mTouchHandler = new TaskStackViewTouchHandler(context, this, mStackScroller); 260 mAnimationHelper = new TaskStackAnimationHelper(context, this); 261 mTaskCornerRadiusPx = LegacyRecentsImpl.getConfiguration().isGridEnabled ? 262 res.getDimensionPixelSize(R.dimen.recents_grid_task_view_rounded_corners_radius) : 263 res.getDimensionPixelSize(R.dimen.recents_task_view_rounded_corners_radius); 264 mFastFlingVelocity = res.getDimensionPixelSize(R.dimen.recents_fast_fling_velocity); 265 mDividerSize = ssp.getDockedDividerSize(context); 266 mDisplayOrientation = Utilities.getAppConfiguration(mContext).orientation; 267 mDisplayRect = ssp.getDisplayRect(); 268 mStackActionButtonVisible = false; 269 270 // Create a frame to draw around the focused task view 271 if (LegacyRecentsImpl.getConfiguration().isGridEnabled) { 272 mTaskViewFocusFrame = new TaskViewFocusFrame(mContext, this, 273 mLayoutAlgorithm.mTaskGridLayoutAlgorithm); 274 addView(mTaskViewFocusFrame); 275 getViewTreeObserver().addOnGlobalFocusChangeListener(mTaskViewFocusFrame); 276 } 277 278 int taskBarDismissDozeDelaySeconds = getResources().getInteger( 279 R.integer.recents_task_bar_dismiss_delay_seconds); 280 mUIDozeTrigger = new DozeTrigger(taskBarDismissDozeDelaySeconds, new Runnable() { 281 @Override 282 public void run() { 283 // Show the task bar dismiss buttons 284 List<TaskView> taskViews = getTaskViews(); 285 int taskViewCount = taskViews.size(); 286 for (int i = 0; i < taskViewCount; i++) { 287 TaskView tv = taskViews.get(i); 288 tv.startNoUserInteractionAnimation(); 289 } 290 } 291 }); 292 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); 293 } 294 295 @Override onAttachedToWindow()296 protected void onAttachedToWindow() { 297 EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1); 298 super.onAttachedToWindow(); 299 readSystemFlags(); 300 } 301 302 @Override onDetachedFromWindow()303 protected void onDetachedFromWindow() { 304 super.onDetachedFromWindow(); 305 EventBus.getDefault().unregister(this); 306 } 307 308 /** 309 * Called from RecentsActivity when it is relaunched. 310 */ onReload(boolean isResumingFromVisible)311 void onReload(boolean isResumingFromVisible) { 312 if (!isResumingFromVisible) { 313 // Reset the focused task 314 resetFocusedTask(getFocusedTask()); 315 } 316 317 // Reset the state of each of the task views 318 List<TaskView> taskViews = new ArrayList<>(); 319 taskViews.addAll(getTaskViews()); 320 taskViews.addAll(mViewPool.getViews()); 321 for (int i = taskViews.size() - 1; i >= 0; i--) { 322 taskViews.get(i).onReload(isResumingFromVisible); 323 } 324 325 // Reset the stack state 326 readSystemFlags(); 327 mTaskViewsClipDirty = true; 328 mUIDozeTrigger.stopDozing(); 329 if (!isResumingFromVisible) { 330 mStackScroller.reset(); 331 mStableLayoutAlgorithm.reset(); 332 mLayoutAlgorithm.reset(); 333 mLastScrollPPercent = -1; 334 } 335 336 // Since we always animate to the same place in (the initial state), always reset the stack 337 // to the initial state when resuming 338 mStackReloaded = true; 339 mFinishedLayoutAfterStackReload = false; 340 mLaunchNextAfterFirstMeasure = false; 341 mInitialState = INITIAL_STATE_UPDATE_ALL; 342 requestLayout(); 343 } 344 345 /** 346 * Sets the stack tasks of this TaskStackView from the given TaskStack. 347 */ setTasks(TaskStack stack, boolean allowNotifyStackChanges)348 public void setTasks(TaskStack stack, boolean allowNotifyStackChanges) { 349 boolean isInitialized = mLayoutAlgorithm.isInitialized(); 350 351 // Only notify if we are already initialized, otherwise, everything will pick up all the 352 // new and old tasks when we next layout 353 mStack.setTasks(stack, allowNotifyStackChanges && isInitialized); 354 } 355 356 /** Returns the task stack. */ getStack()357 public TaskStack getStack() { 358 return mStack; 359 } 360 361 /** 362 * Updates this TaskStackView to the initial state. 363 */ updateToInitialState()364 public void updateToInitialState() { 365 mStackScroller.setStackScrollToInitialState(); 366 mLayoutAlgorithm.setTaskOverridesForInitialState(mStack, false /* ignoreScrollToFront */); 367 } 368 369 /** Updates the list of task views */ updateTaskViewsList()370 void updateTaskViewsList() { 371 mTaskViews.clear(); 372 int childCount = getChildCount(); 373 for (int i = 0; i < childCount; i++) { 374 View v = getChildAt(i); 375 if (v instanceof TaskView) { 376 mTaskViews.add((TaskView) v); 377 } 378 } 379 } 380 381 /** Gets the list of task views */ getTaskViews()382 List<TaskView> getTaskViews() { 383 return mTaskViews; 384 } 385 386 /** 387 * Returns the front most task view. 388 */ getFrontMostTaskView()389 private TaskView getFrontMostTaskView() { 390 List<TaskView> taskViews = getTaskViews(); 391 if (taskViews.isEmpty()) { 392 return null; 393 } 394 return taskViews.get(taskViews.size() - 1); 395 } 396 397 /** 398 * Finds the child view given a specific {@param task}. 399 */ getChildViewForTask(Task t)400 public TaskView getChildViewForTask(Task t) { 401 List<TaskView> taskViews = getTaskViews(); 402 int taskViewCount = taskViews.size(); 403 for (int i = 0; i < taskViewCount; i++) { 404 TaskView tv = taskViews.get(i); 405 if (tv.getTask() == t) { 406 return tv; 407 } 408 } 409 return null; 410 } 411 412 /** Returns the stack algorithm for this task stack. */ getStackAlgorithm()413 public TaskStackLayoutAlgorithm getStackAlgorithm() { 414 return mLayoutAlgorithm; 415 } 416 417 /** Returns the grid algorithm for this task stack. */ getGridAlgorithm()418 public TaskGridLayoutAlgorithm getGridAlgorithm() { 419 return mLayoutAlgorithm.mTaskGridLayoutAlgorithm; 420 } 421 422 /** 423 * Returns the touch handler for this task stack. 424 */ getTouchHandler()425 public TaskStackViewTouchHandler getTouchHandler() { 426 return mTouchHandler; 427 } 428 429 /** 430 * Adds a task to the ignored set. 431 */ addIgnoreTask(Task task)432 void addIgnoreTask(Task task) { 433 mIgnoreTasks.add(task.key); 434 } 435 436 /** 437 * Removes a task from the ignored set. 438 */ removeIgnoreTask(Task task)439 void removeIgnoreTask(Task task) { 440 mIgnoreTasks.remove(task.key); 441 } 442 443 /** 444 * Returns whether the specified {@param task} is ignored. 445 */ isIgnoredTask(Task task)446 boolean isIgnoredTask(Task task) { 447 return mIgnoreTasks.contains(task.key); 448 } 449 450 /** 451 * Computes the task transforms at the current stack scroll for all visible tasks. If a valid 452 * target stack scroll is provided (ie. is different than {@param curStackScroll}), then the 453 * visible range includes all tasks at the target stack scroll. This is useful for ensure that 454 * all views necessary for a transition or animation will be visible at the start. 455 * 456 * @param taskTransforms The set of task view transforms to reuse, this list will be sized to 457 * match the size of {@param tasks} 458 * @param tasks The set of tasks for which to generate transforms 459 * @param curStackScroll The current stack scroll 460 * @param targetStackScroll The stack scroll that we anticipate we are going to be scrolling to. 461 * The range of the union of the visible views at the current and 462 * target stack scrolls will be returned. 463 * @param ignoreTasksSet The set of tasks to skip for purposes of calculaing the visible range. 464 * Transforms will still be calculated for the ignore tasks. 465 * @return the front and back most visible task indices (there may be non visible tasks in 466 * between this range) 467 */ computeVisibleTaskTransforms(ArrayList<TaskViewTransform> taskTransforms, ArrayList<Task> tasks, float curStackScroll, float targetStackScroll, ArraySet<Task.TaskKey> ignoreTasksSet, boolean ignoreTaskOverrides)468 int[] computeVisibleTaskTransforms(ArrayList<TaskViewTransform> taskTransforms, 469 ArrayList<Task> tasks, float curStackScroll, float targetStackScroll, 470 ArraySet<Task.TaskKey> ignoreTasksSet, boolean ignoreTaskOverrides) { 471 int taskCount = tasks.size(); 472 int[] visibleTaskRange = mTmpIntPair; 473 visibleTaskRange[0] = -1; 474 visibleTaskRange[1] = -1; 475 boolean useTargetStackScroll = Float.compare(curStackScroll, targetStackScroll) != 0; 476 477 // We can reuse the task transforms where possible to reduce object allocation 478 matchTaskListSize(tasks, taskTransforms); 479 480 // Update the stack transforms 481 TaskViewTransform frontTransform = null; 482 TaskViewTransform frontTransformAtTarget = null; 483 TaskViewTransform transform = null; 484 TaskViewTransform transformAtTarget = null; 485 for (int i = taskCount - 1; i >= 0; i--) { 486 Task task = tasks.get(i); 487 488 // Calculate the current and (if necessary) the target transform for the task 489 transform = mLayoutAlgorithm.getStackTransform(task, curStackScroll, 490 taskTransforms.get(i), frontTransform, ignoreTaskOverrides); 491 if (useTargetStackScroll && !transform.visible) { 492 // If we have a target stack scroll and the task is not currently visible, then we 493 // just update the transform at the new scroll 494 // TODO: Optimize this 495 transformAtTarget = mLayoutAlgorithm.getStackTransform(task, targetStackScroll, 496 new TaskViewTransform(), frontTransformAtTarget); 497 if (transformAtTarget.visible) { 498 transform.copyFrom(transformAtTarget); 499 } 500 } 501 502 // For ignore tasks, only calculate the stack transform and skip the calculation of the 503 // visible stack indices 504 if (ignoreTasksSet.contains(task.key)) { 505 continue; 506 } 507 508 frontTransform = transform; 509 frontTransformAtTarget = transformAtTarget; 510 if (transform.visible) { 511 if (visibleTaskRange[0] < 0) { 512 visibleTaskRange[0] = i; 513 } 514 visibleTaskRange[1] = i; 515 } 516 } 517 return visibleTaskRange; 518 } 519 520 /** 521 * Binds the visible {@link TaskView}s at the given target scroll. 522 */ bindVisibleTaskViews(float targetStackScroll)523 void bindVisibleTaskViews(float targetStackScroll) { 524 bindVisibleTaskViews(targetStackScroll, false /* ignoreTaskOverrides */); 525 } 526 527 /** 528 * Synchronizes the set of children {@link TaskView}s to match the visible set of tasks in the 529 * current {@link TaskStack}. This call does not continue on to update their position to the 530 * computed {@link TaskViewTransform}s of the visible range, but only ensures that they will 531 * be added/removed from the view hierarchy and placed in the correct Z order and initial 532 * position (if not currently on screen). 533 * 534 * @param targetStackScroll If provided, will ensure that the set of visible {@link TaskView}s 535 * includes those visible at the current stack scroll, and all at the 536 * target stack scroll. 537 * @param ignoreTaskOverrides If set, the visible task computation will get the transforms for 538 * tasks at their non-overridden task progress 539 */ bindVisibleTaskViews(float targetStackScroll, boolean ignoreTaskOverrides)540 void bindVisibleTaskViews(float targetStackScroll, boolean ignoreTaskOverrides) { 541 // Get all the task transforms 542 ArrayList<Task> tasks = mStack.getTasks(); 543 int[] visibleTaskRange = computeVisibleTaskTransforms(mCurrentTaskTransforms, tasks, 544 mStackScroller.getStackScroll(), targetStackScroll, mIgnoreTasks, 545 ignoreTaskOverrides); 546 547 // Return all the invisible children to the pool 548 mTmpTaskViewMap.clear(); 549 List<TaskView> taskViews = getTaskViews(); 550 int lastFocusedTaskIndex = -1; 551 int taskViewCount = taskViews.size(); 552 for (int i = taskViewCount - 1; i >= 0; i--) { 553 TaskView tv = taskViews.get(i); 554 Task task = tv.getTask(); 555 556 // Skip ignored tasks 557 if (mIgnoreTasks.contains(task.key)) { 558 continue; 559 } 560 561 // It is possible for the set of lingering TaskViews to differ from the stack if the 562 // stack was updated before the relayout. If the task view is no longer in the stack, 563 // then just return it back to the view pool. 564 int taskIndex = mStack.indexOfTask(task); 565 TaskViewTransform transform = null; 566 if (taskIndex != -1) { 567 transform = mCurrentTaskTransforms.get(taskIndex); 568 } 569 570 if (transform != null && transform.visible) { 571 mTmpTaskViewMap.put(task.key, tv); 572 } else { 573 if (mTouchExplorationEnabled && Utilities.isDescendentAccessibilityFocused(tv)) { 574 lastFocusedTaskIndex = taskIndex; 575 resetFocusedTask(task); 576 } 577 mViewPool.returnViewToPool(tv); 578 } 579 } 580 581 // Pick up all the newly visible children 582 for (int i = tasks.size() - 1; i >= 0; i--) { 583 Task task = tasks.get(i); 584 TaskViewTransform transform = mCurrentTaskTransforms.get(i); 585 586 // Skip ignored tasks 587 if (mIgnoreTasks.contains(task.key)) { 588 continue; 589 } 590 591 // Skip the invisible stack tasks 592 if (!transform.visible) { 593 continue; 594 } 595 596 TaskView tv = mTmpTaskViewMap.get(task.key); 597 if (tv == null) { 598 tv = mViewPool.pickUpViewFromPool(task, task); 599 if (transform.rect.top <= mLayoutAlgorithm.mStackRect.top) { 600 updateTaskViewToTransform(tv, mLayoutAlgorithm.getBackOfStackTransform(), 601 AnimationProps.IMMEDIATE); 602 } else { 603 updateTaskViewToTransform(tv, mLayoutAlgorithm.getFrontOfStackTransform(), 604 AnimationProps.IMMEDIATE); 605 } 606 } else { 607 // Reattach it in the right z order 608 final int taskIndex = mStack.indexOfTask(task); 609 final int insertIndex = findTaskViewInsertIndex(task, taskIndex); 610 if (insertIndex != getTaskViews().indexOf(tv)){ 611 detachViewFromParent(tv); 612 attachViewToParent(tv, insertIndex, tv.getLayoutParams()); 613 updateTaskViewsList(); 614 } 615 } 616 } 617 618 updatePrefetchingTask(tasks, visibleTaskRange[0], visibleTaskRange[1]); 619 620 // Update the focus if the previous focused task was returned to the view pool 621 if (lastFocusedTaskIndex != -1) { 622 int newFocusedTaskIndex = (lastFocusedTaskIndex < visibleTaskRange[1]) 623 ? visibleTaskRange[1] 624 : visibleTaskRange[0]; 625 setFocusedTask(newFocusedTaskIndex, false /* scrollToTask */, 626 true /* requestViewFocus */); 627 TaskView focusedTaskView = getChildViewForTask(mFocusedTask); 628 if (focusedTaskView != null) { 629 focusedTaskView.requestAccessibilityFocus(); 630 } 631 } 632 } 633 634 /** 635 * @see #relayoutTaskViews(AnimationProps, ArrayMap<Task, AnimationProps>, boolean) 636 */ relayoutTaskViews(AnimationProps animation)637 public void relayoutTaskViews(AnimationProps animation) { 638 relayoutTaskViews(animation, null /* animationOverrides */, 639 false /* ignoreTaskOverrides */); 640 } 641 642 /** 643 * Relayout the the visible {@link TaskView}s to their current transforms as specified by the 644 * {@link TaskStackLayoutAlgorithm} with the given {@param animation}. This call cancels any 645 * animations that are current running on those task views, and will ensure that the children 646 * {@link TaskView}s will match the set of visible tasks in the stack. If a {@link Task} has 647 * an animation provided in {@param animationOverrides}, that will be used instead. 648 */ relayoutTaskViews(AnimationProps animation, ArrayMap<Task, AnimationProps> animationOverrides, boolean ignoreTaskOverrides)649 private void relayoutTaskViews(AnimationProps animation, 650 ArrayMap<Task, AnimationProps> animationOverrides, boolean ignoreTaskOverrides) { 651 // If we had a deferred animation, cancel that 652 cancelDeferredTaskViewLayoutAnimation(); 653 654 // Synchronize the current set of TaskViews 655 bindVisibleTaskViews(mStackScroller.getStackScroll(), ignoreTaskOverrides); 656 657 // Animate them to their final transforms with the given animation 658 List<TaskView> taskViews = getTaskViews(); 659 int taskViewCount = taskViews.size(); 660 for (int i = 0; i < taskViewCount; i++) { 661 TaskView tv = taskViews.get(i); 662 Task task = tv.getTask(); 663 664 if (mIgnoreTasks.contains(task.key)) { 665 continue; 666 } 667 668 int taskIndex = mStack.indexOfTask(task); 669 TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex); 670 if (animationOverrides != null && animationOverrides.containsKey(task)) { 671 animation = animationOverrides.get(task); 672 } 673 674 updateTaskViewToTransform(tv, transform, animation); 675 } 676 } 677 678 /** 679 * Posts an update to synchronize the {@link TaskView}s with the stack on the next frame. 680 */ relayoutTaskViewsOnNextFrame(AnimationProps animation)681 void relayoutTaskViewsOnNextFrame(AnimationProps animation) { 682 mDeferredTaskViewLayoutAnimation = animation; 683 invalidate(); 684 } 685 686 /** 687 * Called to update a specific {@link TaskView} to a given {@link TaskViewTransform} with a 688 * given set of {@link AnimationProps} properties. 689 */ updateTaskViewToTransform(TaskView taskView, TaskViewTransform transform, AnimationProps animation)690 public void updateTaskViewToTransform(TaskView taskView, TaskViewTransform transform, 691 AnimationProps animation) { 692 if (taskView.isAnimatingTo(transform)) { 693 return; 694 } 695 taskView.cancelTransformAnimation(); 696 taskView.updateViewPropertiesToTaskTransform(transform, animation, 697 mRequestUpdateClippingListener); 698 } 699 700 /** 701 * Returns the current task transforms of all tasks, falling back to the stack layout if there 702 * is no {@link TaskView} for the task. 703 */ getCurrentTaskTransforms(ArrayList<Task> tasks, ArrayList<TaskViewTransform> transformsOut)704 public void getCurrentTaskTransforms(ArrayList<Task> tasks, 705 ArrayList<TaskViewTransform> transformsOut) { 706 matchTaskListSize(tasks, transformsOut); 707 int focusState = mLayoutAlgorithm.getFocusState(); 708 for (int i = tasks.size() - 1; i >= 0; i--) { 709 Task task = tasks.get(i); 710 TaskViewTransform transform = transformsOut.get(i); 711 TaskView tv = getChildViewForTask(task); 712 if (tv != null) { 713 transform.fillIn(tv); 714 } else { 715 mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(), 716 focusState, transform, null, true /* forceUpdate */, 717 false /* ignoreTaskOverrides */); 718 } 719 transform.visible = true; 720 } 721 } 722 723 /** 724 * Returns the task transforms for all the tasks in the stack if the stack was at the given 725 * {@param stackScroll} and {@param focusState}. 726 */ getLayoutTaskTransforms(float stackScroll, int focusState, ArrayList<Task> tasks, boolean ignoreTaskOverrides, ArrayList<TaskViewTransform> transformsOut)727 public void getLayoutTaskTransforms(float stackScroll, int focusState, ArrayList<Task> tasks, 728 boolean ignoreTaskOverrides, ArrayList<TaskViewTransform> transformsOut) { 729 matchTaskListSize(tasks, transformsOut); 730 for (int i = tasks.size() - 1; i >= 0; i--) { 731 Task task = tasks.get(i); 732 TaskViewTransform transform = transformsOut.get(i); 733 mLayoutAlgorithm.getStackTransform(task, stackScroll, focusState, transform, null, 734 true /* forceUpdate */, ignoreTaskOverrides); 735 transform.visible = true; 736 } 737 } 738 739 /** 740 * Cancels the next deferred task view layout. 741 */ cancelDeferredTaskViewLayoutAnimation()742 void cancelDeferredTaskViewLayoutAnimation() { 743 mDeferredTaskViewLayoutAnimation = null; 744 } 745 746 /** 747 * Cancels all {@link TaskView} animations. 748 */ cancelAllTaskViewAnimations()749 void cancelAllTaskViewAnimations() { 750 List<TaskView> taskViews = getTaskViews(); 751 for (int i = taskViews.size() - 1; i >= 0; i--) { 752 final TaskView tv = taskViews.get(i); 753 if (!mIgnoreTasks.contains(tv.getTask().key)) { 754 tv.cancelTransformAnimation(); 755 } 756 } 757 } 758 759 /** 760 * Updates the clip for each of the task views from back to front. 761 */ clipTaskViews()762 private void clipTaskViews() { 763 // We never clip task views in grid layout 764 if (LegacyRecentsImpl.getConfiguration().isGridEnabled) { 765 return; 766 } 767 768 // Update the clip on each task child 769 List<TaskView> taskViews = getTaskViews(); 770 TaskView tmpTv = null; 771 TaskView prevVisibleTv = null; 772 int taskViewCount = taskViews.size(); 773 for (int i = 0; i < taskViewCount; i++) { 774 TaskView tv = taskViews.get(i); 775 TaskView frontTv = null; 776 int clipBottom = 0; 777 778 if (isIgnoredTask(tv.getTask())) { 779 // For each of the ignore tasks, update the translationZ of its TaskView to be 780 // between the translationZ of the tasks immediately underneath it 781 if (prevVisibleTv != null) { 782 tv.setTranslationZ(Math.max(tv.getTranslationZ(), 783 prevVisibleTv.getTranslationZ() + 0.1f)); 784 } 785 } 786 787 if (i < (taskViewCount - 1) && tv.shouldClipViewInStack()) { 788 // Find the next view to clip against 789 for (int j = i + 1; j < taskViewCount; j++) { 790 tmpTv = taskViews.get(j); 791 792 if (tmpTv.shouldClipViewInStack()) { 793 frontTv = tmpTv; 794 break; 795 } 796 } 797 798 // Clip against the next view, this is just an approximation since we are 799 // stacked and we can make assumptions about the visibility of the this 800 // task relative to the ones in front of it. 801 if (frontTv != null) { 802 float taskBottom = tv.getBottom(); 803 float frontTaskTop = frontTv.getTop(); 804 if (frontTaskTop < taskBottom) { 805 // Map the stack view space coordinate (the rects) to view space 806 clipBottom = (int) (taskBottom - frontTaskTop) - mTaskCornerRadiusPx; 807 } 808 } 809 } 810 tv.getViewBounds().setClipBottom(clipBottom); 811 tv.mThumbnailView.updateThumbnailVisibility(clipBottom - tv.getPaddingBottom()); 812 prevVisibleTv = tv; 813 } 814 mTaskViewsClipDirty = false; 815 } 816 updateLayoutAlgorithm(boolean boundScrollToNewMinMax)817 public void updateLayoutAlgorithm(boolean boundScrollToNewMinMax) { 818 updateLayoutAlgorithm(boundScrollToNewMinMax, LegacyRecentsImpl.getConfiguration().getLaunchState()); 819 } 820 821 /** 822 * Updates the layout algorithm min and max virtual scroll bounds. 823 */ updateLayoutAlgorithm(boolean boundScrollToNewMinMax, RecentsActivityLaunchState launchState)824 public void updateLayoutAlgorithm(boolean boundScrollToNewMinMax, 825 RecentsActivityLaunchState launchState) { 826 // Compute the min and max scroll values 827 mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState, mLastScrollPPercent); 828 829 if (boundScrollToNewMinMax) { 830 mStackScroller.boundScroll(); 831 } 832 } 833 834 /** 835 * Updates the stack layout to its stable places. 836 */ updateLayoutToStableBounds()837 private void updateLayoutToStableBounds() { 838 mWindowRect.set(mStableWindowRect); 839 mStackBounds.set(mStableStackBounds); 840 mLayoutAlgorithm.setSystemInsets(mStableLayoutAlgorithm.mSystemInsets); 841 mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds); 842 updateLayoutAlgorithm(true /* boundScroll */); 843 } 844 845 /** Returns the scroller. */ getScroller()846 public TaskStackViewScroller getScroller() { 847 return mStackScroller; 848 } 849 850 /** 851 * Sets the focused task to the provided (bounded taskIndex). 852 * 853 * @return whether or not the stack will scroll as a part of this focus change 854 */ setFocusedTask(int taskIndex, boolean scrollToTask, final boolean requestViewFocus)855 public boolean setFocusedTask(int taskIndex, boolean scrollToTask, 856 final boolean requestViewFocus) { 857 return setFocusedTask(taskIndex, scrollToTask, requestViewFocus, 0); 858 } 859 860 /** 861 * Sets the focused task to the provided (bounded focusTaskIndex). 862 * 863 * @return whether or not the stack will scroll as a part of this focus change 864 */ setFocusedTask(int focusTaskIndex, boolean scrollToTask, boolean requestViewFocus, int timerIndicatorDuration)865 public boolean setFocusedTask(int focusTaskIndex, boolean scrollToTask, 866 boolean requestViewFocus, int timerIndicatorDuration) { 867 // Find the next task to focus 868 int newFocusedTaskIndex = mStack.getTaskCount() > 0 ? 869 Utilities.clamp(focusTaskIndex, 0, mStack.getTaskCount() - 1) : -1; 870 final Task newFocusedTask = (newFocusedTaskIndex != -1) ? 871 mStack.getTasks().get(newFocusedTaskIndex) : null; 872 873 // Reset the last focused task state if changed 874 if (mFocusedTask != null) { 875 // Cancel the timer indicator, if applicable 876 if (timerIndicatorDuration > 0) { 877 final TaskView tv = getChildViewForTask(mFocusedTask); 878 if (tv != null) { 879 tv.getHeaderView().cancelFocusTimerIndicator(); 880 } 881 } 882 883 resetFocusedTask(mFocusedTask); 884 } 885 886 boolean willScroll = false; 887 mFocusedTask = newFocusedTask; 888 889 if (newFocusedTask != null) { 890 // Start the timer indicator, if applicable 891 if (timerIndicatorDuration > 0) { 892 final TaskView tv = getChildViewForTask(mFocusedTask); 893 if (tv != null) { 894 tv.getHeaderView().startFocusTimerIndicator(timerIndicatorDuration); 895 } else { 896 // The view is null; set a flag for later 897 mStartTimerIndicatorDuration = timerIndicatorDuration; 898 } 899 } 900 901 if (scrollToTask) { 902 // Cancel any running enter animations at this point when we scroll or change focus 903 if (!mEnterAnimationComplete) { 904 cancelAllTaskViewAnimations(); 905 } 906 907 mLayoutAlgorithm.clearUnfocusedTaskOverrides(); 908 willScroll = mAnimationHelper.startScrollToFocusedTaskAnimation(newFocusedTask, 909 requestViewFocus); 910 if (willScroll) { 911 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED); 912 } 913 } else { 914 // Focus the task view 915 TaskView newFocusedTaskView = getChildViewForTask(newFocusedTask); 916 if (newFocusedTaskView != null) { 917 newFocusedTaskView.setFocusedState(true, requestViewFocus); 918 } 919 } 920 // Any time a task view gets the focus, we move the focus frame around it. 921 if (mTaskViewFocusFrame != null) { 922 mTaskViewFocusFrame.moveGridTaskViewFocus(getChildViewForTask(newFocusedTask)); 923 } 924 } 925 return willScroll; 926 } 927 928 /** 929 * Sets the focused task relative to the currently focused task. 930 * 931 * @param forward whether to go to the next task in the stack (along the curve) or the previous 932 * @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and 933 * if the currently focused task is not a stack task, will set the focus 934 * to the first visible stack task 935 * @param animated determines whether to actually draw the highlight along with the change in 936 * focus. 937 */ setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated)938 public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated) { 939 setRelativeFocusedTask(forward, stackTasksOnly, animated, false, 0); 940 } 941 942 /** 943 * Sets the focused task relative to the currently focused task. 944 * 945 * @param forward whether to go to the next task in the stack (along the curve) or the previous 946 * @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and 947 * if the currently focused task is not a stack task, will set the focus 948 * to the first visible stack task 949 * @param animated determines whether to actually draw the highlight along with the change in 950 * focus. 951 * @param cancelWindowAnimations if set, will attempt to cancel window animations if a scroll 952 * happens. 953 * @param timerIndicatorDuration the duration to initialize the auto-advance timer indicator 954 */ setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated, boolean cancelWindowAnimations, int timerIndicatorDuration)955 public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated, 956 boolean cancelWindowAnimations, int timerIndicatorDuration) { 957 Task focusedTask = getFocusedTask(); 958 int newIndex = mStack.indexOfTask(focusedTask); 959 if (focusedTask != null) { 960 if (stackTasksOnly) { 961 List<Task> tasks = mStack.getTasks(); 962 // Try the next task if it is a stack task 963 int tmpNewIndex = newIndex + (forward ? -1 : 1); 964 if (0 <= tmpNewIndex && tmpNewIndex < tasks.size()) { 965 newIndex = tmpNewIndex; 966 } 967 } else { 968 // No restrictions, lets just move to the new task (looping forward/backwards if 969 // necessary) 970 int taskCount = mStack.getTaskCount(); 971 newIndex = (newIndex + (forward ? -1 : 1) + taskCount) % taskCount; 972 } 973 } else { 974 // We don't have a focused task 975 float stackScroll = mStackScroller.getStackScroll(); 976 ArrayList<Task> tasks = mStack.getTasks(); 977 int taskCount = tasks.size(); 978 if (useGridLayout()) { 979 // For the grid layout, we directly set focus to the most recently used task 980 // no matter we're moving forwards or backwards. 981 newIndex = taskCount - 1; 982 } else { 983 // For the grid layout we pick a proper task to focus, according to the current 984 // stack scroll. 985 if (forward) { 986 // Walk backwards and focus the next task smaller than the current stack scroll 987 for (newIndex = taskCount - 1; newIndex >= 0; newIndex--) { 988 float taskP = mLayoutAlgorithm.getStackScrollForTask(tasks.get(newIndex)); 989 if (Float.compare(taskP, stackScroll) <= 0) { 990 break; 991 } 992 } 993 } else { 994 // Walk forwards and focus the next task larger than the current stack scroll 995 for (newIndex = 0; newIndex < taskCount; newIndex++) { 996 float taskP = mLayoutAlgorithm.getStackScrollForTask(tasks.get(newIndex)); 997 if (Float.compare(taskP, stackScroll) >= 0) { 998 break; 999 } 1000 } 1001 } 1002 } 1003 } 1004 if (newIndex != -1) { 1005 boolean willScroll = setFocusedTask(newIndex, true /* scrollToTask */, 1006 true /* requestViewFocus */, timerIndicatorDuration); 1007 if (willScroll && cancelWindowAnimations) { 1008 // As we iterate to the next/previous task, cancel any current/lagging window 1009 // transition animations 1010 EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null)); 1011 } 1012 } 1013 } 1014 1015 /** 1016 * Resets the focused task. 1017 */ resetFocusedTask(Task task)1018 public void resetFocusedTask(Task task) { 1019 if (task != null) { 1020 TaskView tv = getChildViewForTask(task); 1021 if (tv != null) { 1022 tv.setFocusedState(false, false /* requestViewFocus */); 1023 } 1024 } 1025 if (mTaskViewFocusFrame != null) { 1026 mTaskViewFocusFrame.moveGridTaskViewFocus(null); 1027 } 1028 mFocusedTask = null; 1029 } 1030 1031 /** 1032 * Returns the focused task. 1033 */ getFocusedTask()1034 public Task getFocusedTask() { 1035 return mFocusedTask; 1036 } 1037 1038 /** 1039 * Returns the accessibility focused task. 1040 */ getAccessibilityFocusedTask()1041 Task getAccessibilityFocusedTask() { 1042 List<TaskView> taskViews = getTaskViews(); 1043 int taskViewCount = taskViews.size(); 1044 for (int i = 0; i < taskViewCount; i++) { 1045 TaskView tv = taskViews.get(i); 1046 if (Utilities.isDescendentAccessibilityFocused(tv)) { 1047 return tv.getTask(); 1048 } 1049 } 1050 TaskView frontTv = getFrontMostTaskView(); 1051 if (frontTv != null) { 1052 return frontTv.getTask(); 1053 } 1054 return null; 1055 } 1056 1057 @Override onInitializeAccessibilityEvent(AccessibilityEvent event)1058 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 1059 super.onInitializeAccessibilityEvent(event); 1060 List<TaskView> taskViews = getTaskViews(); 1061 int taskViewCount = taskViews.size(); 1062 if (taskViewCount > 0) { 1063 TaskView backMostTask = taskViews.get(0); 1064 TaskView frontMostTask = taskViews.get(taskViewCount - 1); 1065 event.setFromIndex(mStack.indexOfTask(backMostTask.getTask())); 1066 event.setToIndex(mStack.indexOfTask(frontMostTask.getTask())); 1067 event.setContentDescription(frontMostTask.getTask().title); 1068 } 1069 event.setItemCount(mStack.getTaskCount()); 1070 1071 int stackHeight = mLayoutAlgorithm.mStackRect.height(); 1072 event.setScrollY((int) (mStackScroller.getStackScroll() * stackHeight)); 1073 event.setMaxScrollY((int) (mLayoutAlgorithm.mMaxScrollP * stackHeight)); 1074 } 1075 1076 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)1077 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 1078 super.onInitializeAccessibilityNodeInfo(info); 1079 List<TaskView> taskViews = getTaskViews(); 1080 int taskViewCount = taskViews.size(); 1081 if (taskViewCount > 1) { 1082 // Find the accessibility focused task 1083 Task focusedTask = getAccessibilityFocusedTask(); 1084 info.setScrollable(true); 1085 int focusedTaskIndex = mStack.indexOfTask(focusedTask); 1086 if (focusedTaskIndex > 0 || !mStackActionButtonVisible) { 1087 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); 1088 } 1089 if (0 <= focusedTaskIndex && focusedTaskIndex < mStack.getTaskCount() - 1) { 1090 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); 1091 } 1092 } 1093 } 1094 1095 @Override getAccessibilityClassName()1096 public CharSequence getAccessibilityClassName() { 1097 return ScrollView.class.getName(); 1098 } 1099 1100 @Override performAccessibilityAction(int action, Bundle arguments)1101 public boolean performAccessibilityAction(int action, Bundle arguments) { 1102 if (super.performAccessibilityAction(action, arguments)) { 1103 return true; 1104 } 1105 Task focusedTask = getAccessibilityFocusedTask(); 1106 int taskIndex = mStack.indexOfTask(focusedTask); 1107 if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) { 1108 switch (action) { 1109 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { 1110 setFocusedTask(taskIndex + 1, true /* scrollToTask */, true /* requestViewFocus */, 1111 0); 1112 return true; 1113 } 1114 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { 1115 setFocusedTask(taskIndex - 1, true /* scrollToTask */, true /* requestViewFocus */, 1116 0); 1117 return true; 1118 } 1119 } 1120 } 1121 return false; 1122 } 1123 1124 @Override onInterceptTouchEvent(MotionEvent ev)1125 public boolean onInterceptTouchEvent(MotionEvent ev) { 1126 return mTouchHandler.onInterceptTouchEvent(ev); 1127 } 1128 1129 @Override onTouchEvent(MotionEvent ev)1130 public boolean onTouchEvent(MotionEvent ev) { 1131 return mTouchHandler.onTouchEvent(ev); 1132 } 1133 1134 @Override onGenericMotionEvent(MotionEvent ev)1135 public boolean onGenericMotionEvent(MotionEvent ev) { 1136 return mTouchHandler.onGenericMotionEvent(ev); 1137 } 1138 1139 @Override computeScroll()1140 public void computeScroll() { 1141 if (mStackScroller.computeScroll()) { 1142 // Notify accessibility 1143 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED); 1144 LegacyRecentsImpl.getTaskLoader().getHighResThumbnailLoader().setFlingingFast( 1145 mStackScroller.getScrollVelocity() > mFastFlingVelocity); 1146 } 1147 if (mDeferredTaskViewLayoutAnimation != null) { 1148 relayoutTaskViews(mDeferredTaskViewLayoutAnimation); 1149 mTaskViewsClipDirty = true; 1150 mDeferredTaskViewLayoutAnimation = null; 1151 } 1152 if (mTaskViewsClipDirty) { 1153 clipTaskViews(); 1154 } 1155 mLastScrollPPercent = Utilities.clamp(Utilities.unmapRange(mStackScroller.getStackScroll(), 1156 mLayoutAlgorithm.mMinScrollP, mLayoutAlgorithm.mMaxScrollP), 0, 1); 1157 } 1158 1159 /** 1160 * Computes the maximum number of visible tasks and thumbnails. Requires that 1161 * updateLayoutForStack() is called first. 1162 */ computeStackVisibilityReport()1163 public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() { 1164 return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getTasks()); 1165 } 1166 1167 /** 1168 * Updates the system insets. 1169 */ setSystemInsets(Rect systemInsets)1170 public void setSystemInsets(Rect systemInsets) { 1171 boolean changed = false; 1172 changed |= mStableLayoutAlgorithm.setSystemInsets(systemInsets); 1173 changed |= mLayoutAlgorithm.setSystemInsets(systemInsets); 1174 if (changed) { 1175 requestLayout(); 1176 } 1177 } 1178 1179 /** 1180 * This is called with the full window width and height to allow stack view children to 1181 * perform the full screen transition down. 1182 */ 1183 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)1184 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1185 mInMeasureLayout = true; 1186 int width = MeasureSpec.getSize(widthMeasureSpec); 1187 int height = MeasureSpec.getSize(heightMeasureSpec); 1188 1189 // Update the stable stack bounds, but only update the current stack bounds if the stable 1190 // bounds have changed. This is because we may get spurious measures while dragging where 1191 // our current stack bounds reflect the target drop region. 1192 mLayoutAlgorithm.getTaskStackBounds(mDisplayRect, new Rect(0, 0, width, height), 1193 mLayoutAlgorithm.mSystemInsets.top, mLayoutAlgorithm.mSystemInsets.left, 1194 mLayoutAlgorithm.mSystemInsets.right, mTmpRect); 1195 if (!mTmpRect.equals(mStableStackBounds)) { 1196 mStableStackBounds.set(mTmpRect); 1197 mStackBounds.set(mTmpRect); 1198 mStableWindowRect.set(0, 0, width, height); 1199 mWindowRect.set(0, 0, width, height); 1200 } 1201 1202 // Compute the rects in the stack algorithm 1203 mStableLayoutAlgorithm.initialize(mDisplayRect, mStableWindowRect, mStableStackBounds); 1204 mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds); 1205 updateLayoutAlgorithm(false /* boundScroll */); 1206 1207 // If this is the first layout, then scroll to the front of the stack, then update the 1208 // TaskViews with the stack so that we can lay them out 1209 boolean resetToInitialState = (width != mLastWidth || height != mLastHeight) 1210 && mResetToInitialStateWhenResized; 1211 if (!mFinishedLayoutAfterStackReload || mInitialState != INITIAL_STATE_UPDATE_NONE 1212 || resetToInitialState) { 1213 if (mInitialState != INITIAL_STATE_UPDATE_LAYOUT_ONLY || resetToInitialState) { 1214 updateToInitialState(); 1215 mResetToInitialStateWhenResized = false; 1216 } 1217 if (mFinishedLayoutAfterStackReload) { 1218 mInitialState = INITIAL_STATE_UPDATE_NONE; 1219 } 1220 } 1221 // If we got the launch-next event before the first layout pass, then re-send it after the 1222 // initial state has been updated 1223 if (mLaunchNextAfterFirstMeasure) { 1224 mLaunchNextAfterFirstMeasure = false; 1225 EventBus.getDefault().post(new LaunchNextTaskRequestEvent()); 1226 } 1227 1228 // Rebind all the views, including the ignore ones 1229 bindVisibleTaskViews(mStackScroller.getStackScroll(), false /* ignoreTaskOverrides */); 1230 1231 // Measure each of the TaskViews 1232 mTmpTaskViews.clear(); 1233 mTmpTaskViews.addAll(getTaskViews()); 1234 mTmpTaskViews.addAll(mViewPool.getViews()); 1235 int taskViewCount = mTmpTaskViews.size(); 1236 for (int i = 0; i < taskViewCount; i++) { 1237 measureTaskView(mTmpTaskViews.get(i)); 1238 } 1239 if (mTaskViewFocusFrame != null) { 1240 mTaskViewFocusFrame.measure(); 1241 } 1242 1243 setMeasuredDimension(width, height); 1244 mLastWidth = width; 1245 mLastHeight = height; 1246 mInMeasureLayout = false; 1247 } 1248 1249 /** 1250 * Measures a TaskView. 1251 */ measureTaskView(TaskView tv)1252 private void measureTaskView(TaskView tv) { 1253 Rect padding = new Rect(); 1254 if (tv.getBackground() != null) { 1255 tv.getBackground().getPadding(padding); 1256 } 1257 mTmpRect.set(mStableLayoutAlgorithm.getTaskRect()); 1258 mTmpRect.union(mLayoutAlgorithm.getTaskRect()); 1259 tv.measure( 1260 MeasureSpec.makeMeasureSpec(mTmpRect.width() + padding.left + padding.right, 1261 MeasureSpec.EXACTLY), 1262 MeasureSpec.makeMeasureSpec(mTmpRect.height() + padding.top + padding.bottom, 1263 MeasureSpec.EXACTLY)); 1264 } 1265 1266 @Override onLayout(boolean changed, int left, int top, int right, int bottom)1267 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1268 // Layout each of the TaskViews 1269 mTmpTaskViews.clear(); 1270 mTmpTaskViews.addAll(getTaskViews()); 1271 mTmpTaskViews.addAll(mViewPool.getViews()); 1272 int taskViewCount = mTmpTaskViews.size(); 1273 for (int i = 0; i < taskViewCount; i++) { 1274 layoutTaskView(changed, mTmpTaskViews.get(i)); 1275 } 1276 if (mTaskViewFocusFrame != null) { 1277 mTaskViewFocusFrame.layout(); 1278 } 1279 1280 if (changed) { 1281 if (mStackScroller.isScrollOutOfBounds()) { 1282 mStackScroller.boundScroll(); 1283 } 1284 } 1285 1286 // Relayout all of the task views including the ignored ones 1287 relayoutTaskViews(AnimationProps.IMMEDIATE); 1288 clipTaskViews(); 1289 1290 if (!mFinishedLayoutAfterStackReload) { 1291 // Prepare the task enter animations (this can be called numerous times) 1292 mInitialState = INITIAL_STATE_UPDATE_NONE; 1293 onFirstLayout(); 1294 1295 if (mStackReloaded) { 1296 mFinishedLayoutAfterStackReload = true; 1297 tryStartEnterAnimation(); 1298 } 1299 } 1300 } 1301 1302 /** 1303 * Lays out a TaskView. 1304 */ layoutTaskView(boolean changed, TaskView tv)1305 private void layoutTaskView(boolean changed, TaskView tv) { 1306 if (changed) { 1307 Rect padding = new Rect(); 1308 if (tv.getBackground() != null) { 1309 tv.getBackground().getPadding(padding); 1310 } 1311 mTmpRect.set(mStableLayoutAlgorithm.getTaskRect()); 1312 mTmpRect.union(mLayoutAlgorithm.getTaskRect()); 1313 tv.cancelTransformAnimation(); 1314 tv.layout(mTmpRect.left - padding.left, mTmpRect.top - padding.top, 1315 mTmpRect.right + padding.right, mTmpRect.bottom + padding.bottom); 1316 } else { 1317 // If the layout has not changed, then just lay it out again in-place 1318 tv.layout(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom()); 1319 } 1320 } 1321 1322 /** Handler for the first layout. */ onFirstLayout()1323 void onFirstLayout() { 1324 // Setup the view for the enter animation 1325 mAnimationHelper.prepareForEnterAnimation(); 1326 1327 // Set the task focused state without requesting view focus, and leave the focus animations 1328 // until after the enter-animation 1329 RecentsConfiguration config = LegacyRecentsImpl.getConfiguration(); 1330 RecentsActivityLaunchState launchState = config.getLaunchState(); 1331 1332 // We set the initial focused task view iff the following conditions are satisfied: 1333 // 1. Recents is showing task views in stack layout. 1334 // 2. Recents is launched with ALT + TAB. 1335 boolean setFocusOnFirstLayout = !useGridLayout() || launchState.launchedWithAltTab; 1336 if (setFocusOnFirstLayout) { 1337 int focusedTaskIndex = getInitialFocusTaskIndex(launchState, mStack.getTaskCount(), 1338 useGridLayout()); 1339 if (focusedTaskIndex != -1) { 1340 setFocusedTask(focusedTaskIndex, false /* scrollToTask */, 1341 false /* requestViewFocus */); 1342 } 1343 } 1344 updateStackActionButtonVisibility(); 1345 } 1346 isTouchPointInView(float x, float y, TaskView tv)1347 public boolean isTouchPointInView(float x, float y, TaskView tv) { 1348 mTmpRect.set(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom()); 1349 mTmpRect.offset((int) tv.getTranslationX(), (int) tv.getTranslationY()); 1350 return mTmpRect.contains((int) x, (int) y); 1351 } 1352 1353 /** 1354 * Returns a non-ignored task in the {@param tasks} list that can be used as an achor when 1355 * calculating the scroll position before and after a layout change. 1356 */ findAnchorTask(List<Task> tasks, MutableBoolean isFrontMostTask)1357 public Task findAnchorTask(List<Task> tasks, MutableBoolean isFrontMostTask) { 1358 for (int i = tasks.size() - 1; i >= 0; i--) { 1359 Task task = tasks.get(i); 1360 1361 // Ignore deleting tasks 1362 if (isIgnoredTask(task)) { 1363 if (i == tasks.size() - 1) { 1364 isFrontMostTask.value = true; 1365 } 1366 continue; 1367 } 1368 return task; 1369 } 1370 return null; 1371 } 1372 1373 /**** TaskStackCallbacks Implementation ****/ 1374 1375 @Override onStackTaskAdded(TaskStack stack, Task newTask)1376 public void onStackTaskAdded(TaskStack stack, Task newTask) { 1377 // Update the min/max scroll and animate other task views into their new positions 1378 updateLayoutAlgorithm(true /* boundScroll */); 1379 1380 // Animate all the tasks into place 1381 relayoutTaskViews(!mFinishedLayoutAfterStackReload 1382 ? AnimationProps.IMMEDIATE 1383 : new AnimationProps(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN)); 1384 } 1385 1386 /** 1387 * We expect that the {@link TaskView} associated with the removed task is already hidden. 1388 */ 1389 @Override onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask, AnimationProps animation, boolean fromDockGesture, boolean dismissRecentsIfAllRemoved)1390 public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask, 1391 AnimationProps animation, boolean fromDockGesture, boolean dismissRecentsIfAllRemoved) { 1392 if (mFocusedTask == removedTask) { 1393 resetFocusedTask(removedTask); 1394 } 1395 1396 // Remove the view associated with this task, we can't rely on updateTransforms 1397 // to work here because the task is no longer in the list 1398 TaskView tv = getChildViewForTask(removedTask); 1399 if (tv != null) { 1400 mViewPool.returnViewToPool(tv); 1401 } 1402 1403 // Remove the task from the ignored set 1404 removeIgnoreTask(removedTask); 1405 1406 // If requested, relayout with the given animation 1407 if (animation != null) { 1408 updateLayoutAlgorithm(true /* boundScroll */); 1409 relayoutTaskViews(animation); 1410 } 1411 1412 // Update the new front most task's action button 1413 if (mScreenPinningEnabled && newFrontMostTask != null) { 1414 TaskView frontTv = getChildViewForTask(newFrontMostTask); 1415 if (frontTv != null) { 1416 frontTv.showActionButton(true /* fadeIn */, DEFAULT_SYNC_STACK_DURATION); 1417 } 1418 } 1419 1420 // If there are no remaining tasks, then just close recents 1421 if (mStack.getTaskCount() == 0) { 1422 if (dismissRecentsIfAllRemoved) { 1423 EventBus.getDefault().send(new AllTaskViewsDismissedEvent(fromDockGesture 1424 ? R.string.recents_empty_message 1425 : R.string.recents_empty_message_dismissed_all)); 1426 } else { 1427 EventBus.getDefault().send(new ShowEmptyViewEvent()); 1428 } 1429 } 1430 } 1431 1432 @Override onStackTasksRemoved(TaskStack stack)1433 public void onStackTasksRemoved(TaskStack stack) { 1434 // Reset the focused task 1435 resetFocusedTask(getFocusedTask()); 1436 1437 // Return all the views to the pool 1438 List<TaskView> taskViews = new ArrayList<>(); 1439 taskViews.addAll(getTaskViews()); 1440 for (int i = taskViews.size() - 1; i >= 0; i--) { 1441 mViewPool.returnViewToPool(taskViews.get(i)); 1442 } 1443 1444 // Remove all the ignore tasks 1445 mIgnoreTasks.clear(); 1446 1447 // If there are no remaining tasks, then just close recents 1448 EventBus.getDefault().send(new AllTaskViewsDismissedEvent( 1449 R.string.recents_empty_message_dismissed_all)); 1450 } 1451 1452 @Override onStackTasksUpdated(TaskStack stack)1453 public void onStackTasksUpdated(TaskStack stack) { 1454 if (!mFinishedLayoutAfterStackReload) { 1455 return; 1456 } 1457 1458 // Update the layout and immediately layout 1459 updateLayoutAlgorithm(false /* boundScroll */); 1460 relayoutTaskViews(AnimationProps.IMMEDIATE); 1461 1462 // Rebind all the task views. This will not trigger new resources to be loaded 1463 // unless they have actually changed 1464 List<TaskView> taskViews = getTaskViews(); 1465 int taskViewCount = taskViews.size(); 1466 for (int i = 0; i < taskViewCount; i++) { 1467 TaskView tv = taskViews.get(i); 1468 bindTaskView(tv, tv.getTask()); 1469 } 1470 } 1471 1472 /**** ViewPoolConsumer Implementation ****/ 1473 1474 @Override createView(Context context)1475 public TaskView createView(Context context) { 1476 if (LegacyRecentsImpl.getConfiguration().isGridEnabled) { 1477 return (GridTaskView) mInflater.inflate(R.layout.recents_grid_task_view, this, false); 1478 } else { 1479 return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false); 1480 } 1481 } 1482 1483 @Override onReturnViewToPool(TaskView tv)1484 public void onReturnViewToPool(TaskView tv) { 1485 final Task task = tv.getTask(); 1486 1487 // Unbind the task from the task view 1488 unbindTaskView(tv, task); 1489 1490 // Reset the view properties and view state 1491 tv.clearAccessibilityFocus(); 1492 tv.resetViewProperties(); 1493 tv.setFocusedState(false, false /* requestViewFocus */); 1494 tv.setClipViewInStack(false); 1495 if (mScreenPinningEnabled) { 1496 tv.hideActionButton(false /* fadeOut */, 0 /* duration */, false /* scaleDown */, null); 1497 } 1498 1499 // Detach the view from the hierarchy 1500 detachViewFromParent(tv); 1501 // Update the task views list after removing the task view 1502 updateTaskViewsList(); 1503 } 1504 1505 @Override onPickUpViewFromPool(TaskView tv, Task task, boolean isNewView)1506 public void onPickUpViewFromPool(TaskView tv, Task task, boolean isNewView) { 1507 // Find the index where this task should be placed in the stack 1508 int taskIndex = mStack.indexOfTask(task); 1509 int insertIndex = findTaskViewInsertIndex(task, taskIndex); 1510 1511 // Add/attach the view to the hierarchy 1512 if (isNewView) { 1513 if (mInMeasureLayout) { 1514 // If we are measuring the layout, then just add the view normally as it will be 1515 // laid out during the layout pass 1516 addView(tv, insertIndex); 1517 } else { 1518 // Otherwise, this is from a bindVisibleTaskViews() call outside the measure/layout 1519 // pass, and we should layout the new child ourselves 1520 ViewGroup.LayoutParams params = tv.getLayoutParams(); 1521 if (params == null) { 1522 params = generateDefaultLayoutParams(); 1523 } 1524 addViewInLayout(tv, insertIndex, params, true /* preventRequestLayout */); 1525 measureTaskView(tv); 1526 layoutTaskView(true /* changed */, tv); 1527 } 1528 } else { 1529 attachViewToParent(tv, insertIndex, tv.getLayoutParams()); 1530 } 1531 // Update the task views list after adding the new task view 1532 updateTaskViewsList(); 1533 1534 // Bind the task view to the new task 1535 bindTaskView(tv, task); 1536 1537 // Set the new state for this view, including the callbacks and view clipping 1538 tv.setCallbacks(this); 1539 tv.setTouchEnabled(true); 1540 tv.setClipViewInStack(true); 1541 if (mFocusedTask == task) { 1542 tv.setFocusedState(true, false /* requestViewFocus */); 1543 if (mStartTimerIndicatorDuration > 0) { 1544 // The timer indicator couldn't be started before, so start it now 1545 tv.getHeaderView().startFocusTimerIndicator(mStartTimerIndicatorDuration); 1546 mStartTimerIndicatorDuration = 0; 1547 } 1548 } 1549 1550 // Restore the action button visibility if it is the front most task view 1551 if (mScreenPinningEnabled && tv.getTask() == mStack.getFrontMostTask()) { 1552 tv.showActionButton(false /* fadeIn */, 0 /* fadeInDuration */); 1553 } 1554 } 1555 1556 @Override hasPreferredData(TaskView tv, Task preferredData)1557 public boolean hasPreferredData(TaskView tv, Task preferredData) { 1558 return (tv.getTask() == preferredData); 1559 } 1560 bindTaskView(TaskView tv, Task task)1561 private void bindTaskView(TaskView tv, Task task) { 1562 // Rebind the task and request that this task's data be filled into the TaskView 1563 tv.onTaskBound(task, mTouchExplorationEnabled, mDisplayOrientation, mDisplayRect); 1564 1565 // If the doze trigger has already fired, then update the state for this task view 1566 if (mUIDozeTrigger.isAsleep() || 1567 useGridLayout() || LegacyRecentsImpl.getConfiguration().isLowRamDevice) { 1568 tv.setNoUserInteractionState(); 1569 } 1570 1571 if (task == mPrefetchingTask) { 1572 task.notifyTaskDataLoaded(task.thumbnail, task.icon); 1573 } else { 1574 // Load the task data 1575 LegacyRecentsImpl.getTaskLoader().loadTaskData(task); 1576 } 1577 LegacyRecentsImpl.getTaskLoader().getHighResThumbnailLoader().onTaskVisible(task); 1578 } 1579 unbindTaskView(TaskView tv, Task task)1580 private void unbindTaskView(TaskView tv, Task task) { 1581 if (task != mPrefetchingTask) { 1582 // Report that this task's data is no longer being used 1583 LegacyRecentsImpl.getTaskLoader().unloadTaskData(task); 1584 } 1585 LegacyRecentsImpl.getTaskLoader().getHighResThumbnailLoader().onTaskInvisible(task); 1586 } 1587 updatePrefetchingTask(ArrayList<Task> tasks, int frontIndex, int backIndex)1588 private void updatePrefetchingTask(ArrayList<Task> tasks, int frontIndex, int backIndex) { 1589 Task t = null; 1590 boolean somethingVisible = frontIndex != -1 && backIndex != -1; 1591 if (somethingVisible && frontIndex < tasks.size() - 1) { 1592 t = tasks.get(frontIndex + 1); 1593 } 1594 if (mPrefetchingTask != t) { 1595 if (mPrefetchingTask != null) { 1596 int index = tasks.indexOf(mPrefetchingTask); 1597 if (index < backIndex || index > frontIndex) { 1598 LegacyRecentsImpl.getTaskLoader().unloadTaskData(mPrefetchingTask); 1599 } 1600 } 1601 mPrefetchingTask = t; 1602 if (t != null) { 1603 LegacyRecentsImpl.getTaskLoader().loadTaskData(t); 1604 } 1605 } 1606 } 1607 clearPrefetchingTask()1608 private void clearPrefetchingTask() { 1609 if (mPrefetchingTask != null) { 1610 LegacyRecentsImpl.getTaskLoader().unloadTaskData(mPrefetchingTask); 1611 } 1612 mPrefetchingTask = null; 1613 } 1614 1615 /**** TaskViewCallbacks Implementation ****/ 1616 1617 @Override onTaskViewClipStateChanged(TaskView tv)1618 public void onTaskViewClipStateChanged(TaskView tv) { 1619 if (!mTaskViewsClipDirty) { 1620 mTaskViewsClipDirty = true; 1621 invalidate(); 1622 } 1623 } 1624 1625 /**** TaskStackLayoutAlgorithm.TaskStackLayoutAlgorithmCallbacks ****/ 1626 1627 @Override onFocusStateChanged(int prevFocusState, int curFocusState)1628 public void onFocusStateChanged(int prevFocusState, int curFocusState) { 1629 if (mDeferredTaskViewLayoutAnimation == null) { 1630 mUIDozeTrigger.poke(); 1631 relayoutTaskViewsOnNextFrame(AnimationProps.IMMEDIATE); 1632 } 1633 } 1634 1635 /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/ 1636 1637 @Override onStackScrollChanged(float prevScroll, float curScroll, AnimationProps animation)1638 public void onStackScrollChanged(float prevScroll, float curScroll, AnimationProps animation) { 1639 mUIDozeTrigger.poke(); 1640 if (animation != null) { 1641 relayoutTaskViewsOnNextFrame(animation); 1642 } 1643 1644 // In grid layout, the stack action button always remains visible. 1645 if (mEnterAnimationComplete && !useGridLayout()) { 1646 if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) { 1647 // Show stack button when user drags down to show older tasks on low ram devices 1648 if (mStack.getTaskCount() > 0 && !mStackActionButtonVisible 1649 && mTouchHandler.mIsScrolling && curScroll - prevScroll < 0) { 1650 // Going up 1651 EventBus.getDefault().send( 1652 new ShowStackActionButtonEvent(true /* translate */)); 1653 } 1654 return; 1655 } 1656 if (prevScroll > SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && 1657 curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && 1658 mStack.getTaskCount() > 0) { 1659 EventBus.getDefault().send(new ShowStackActionButtonEvent(true /* translate */)); 1660 } else if (prevScroll < HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && 1661 curScroll >= HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD) { 1662 EventBus.getDefault().send(new HideStackActionButtonEvent()); 1663 } 1664 } 1665 } 1666 1667 /**** EventBus Events ****/ 1668 onBusEvent(PackagesChangedEvent event)1669 public final void onBusEvent(PackagesChangedEvent event) { 1670 // Compute which components need to be removed 1671 ArraySet<ComponentName> removedComponents = mStack.computeComponentsRemoved( 1672 event.packageName, event.userId); 1673 1674 // For other tasks, just remove them directly if they no longer exist 1675 ArrayList<Task> tasks = mStack.getTasks(); 1676 for (int i = tasks.size() - 1; i >= 0; i--) { 1677 final Task t = tasks.get(i); 1678 if (removedComponents.contains(t.key.getComponent())) { 1679 final TaskView tv = getChildViewForTask(t); 1680 if (tv != null) { 1681 // For visible children, defer removing the task until after the animation 1682 tv.dismissTask(); 1683 } else { 1684 // Otherwise, remove the task from the stack immediately 1685 mStack.removeTask(t, AnimationProps.IMMEDIATE, false /* fromDockGesture */); 1686 } 1687 } 1688 } 1689 } 1690 onBusEvent(LaunchTaskEvent event)1691 public final void onBusEvent(LaunchTaskEvent event) { 1692 // Cancel any doze triggers once a task is launched 1693 mUIDozeTrigger.stopDozing(); 1694 } 1695 onBusEvent(LaunchMostRecentTaskRequestEvent event)1696 public final void onBusEvent(LaunchMostRecentTaskRequestEvent event) { 1697 if (mStack.getTaskCount() > 0) { 1698 Task mostRecentTask = mStack.getFrontMostTask(); 1699 launchTask(mostRecentTask); 1700 } 1701 } 1702 onBusEvent(ShowStackActionButtonEvent event)1703 public final void onBusEvent(ShowStackActionButtonEvent event) { 1704 mStackActionButtonVisible = true; 1705 } 1706 onBusEvent(HideStackActionButtonEvent event)1707 public final void onBusEvent(HideStackActionButtonEvent event) { 1708 mStackActionButtonVisible = false; 1709 } 1710 onBusEvent(LaunchNextTaskRequestEvent event)1711 public final void onBusEvent(LaunchNextTaskRequestEvent event) { 1712 if (!mFinishedLayoutAfterStackReload) { 1713 mLaunchNextAfterFirstMeasure = true; 1714 return; 1715 } 1716 1717 if (mStack.getTaskCount() == 0) { 1718 if (RecentsImpl.getLastPipTime() != -1) { 1719 EventBus.getDefault().send(new ExpandPipEvent()); 1720 MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_LAUNCH_PREVIOUS_TASK, 1721 "pip"); 1722 } else { 1723 // If there are no tasks, then just hide recents back to home. 1724 EventBus.getDefault().send(new HideRecentsEvent(false, true)); 1725 } 1726 return; 1727 } 1728 1729 if (!LegacyRecentsImpl.getConfiguration().getLaunchState().launchedFromPipApp 1730 && mStack.isNextLaunchTargetPip(RecentsImpl.getLastPipTime())) { 1731 // If the launch task is in the pinned stack, then expand the PiP now 1732 EventBus.getDefault().send(new ExpandPipEvent()); 1733 MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_LAUNCH_PREVIOUS_TASK, "pip"); 1734 } else { 1735 final Task launchTask = mStack.getNextLaunchTarget(); 1736 if (launchTask != null) { 1737 // Defer launching the task until the PiP menu has been dismissed (if it is 1738 // showing at all) 1739 HidePipMenuEvent hideMenuEvent = new HidePipMenuEvent(); 1740 hideMenuEvent.addPostAnimationCallback(() -> { 1741 launchTask(launchTask); 1742 }); 1743 EventBus.getDefault().send(hideMenuEvent); 1744 MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_LAUNCH_PREVIOUS_TASK, 1745 launchTask.key.getComponent().toString()); 1746 } 1747 } 1748 } 1749 onBusEvent(LaunchTaskStartedEvent event)1750 public final void onBusEvent(LaunchTaskStartedEvent event) { 1751 mAnimationHelper.startLaunchTaskAnimation(event.taskView, event.screenPinningRequested, 1752 event.getAnimationTrigger()); 1753 } 1754 onBusEvent(DismissRecentsToHomeAnimationStarted event)1755 public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) { 1756 // Stop any scrolling 1757 mTouchHandler.cancelNonDismissTaskAnimations(); 1758 mStackScroller.stopScroller(); 1759 mStackScroller.stopBoundScrollAnimation(); 1760 cancelDeferredTaskViewLayoutAnimation(); 1761 1762 // Start the task animations 1763 mAnimationHelper.startExitToHomeAnimation(event.animated, event.getAnimationTrigger()); 1764 1765 // Dismiss the grid task view focus frame 1766 if (mTaskViewFocusFrame != null) { 1767 mTaskViewFocusFrame.moveGridTaskViewFocus(null); 1768 } 1769 } 1770 onBusEvent(DismissFocusedTaskViewEvent event)1771 public final void onBusEvent(DismissFocusedTaskViewEvent event) { 1772 if (mFocusedTask != null) { 1773 if (mTaskViewFocusFrame != null) { 1774 mTaskViewFocusFrame.moveGridTaskViewFocus(null); 1775 } 1776 TaskView tv = getChildViewForTask(mFocusedTask); 1777 if (tv != null) { 1778 tv.dismissTask(); 1779 } 1780 resetFocusedTask(mFocusedTask); 1781 } 1782 } 1783 onBusEvent(DismissTaskViewEvent event)1784 public final void onBusEvent(DismissTaskViewEvent event) { 1785 // For visible children, defer removing the task until after the animation 1786 mAnimationHelper.startDeleteTaskAnimation( 1787 event.taskView, useGridLayout(), event.getAnimationTrigger()); 1788 } 1789 onBusEvent(final DismissAllTaskViewsEvent event)1790 public final void onBusEvent(final DismissAllTaskViewsEvent event) { 1791 // Keep track of the tasks which will have their data removed 1792 ArrayList<Task> tasks = new ArrayList<>(mStack.getTasks()); 1793 mAnimationHelper.startDeleteAllTasksAnimation( 1794 getTaskViews(), useGridLayout(), event.getAnimationTrigger()); 1795 event.addPostAnimationCallback(new Runnable() { 1796 @Override 1797 public void run() { 1798 // Announce for accessibility 1799 announceForAccessibility(getContext().getString( 1800 R.string.accessibility_recents_all_items_dismissed)); 1801 1802 // Remove all tasks and delete the task data for all tasks 1803 mStack.removeAllTasks(true /* notifyStackChanges */); 1804 for (int i = tasks.size() - 1; i >= 0; i--) { 1805 EventBus.getDefault().send(new DeleteTaskDataEvent(tasks.get(i))); 1806 } 1807 1808 MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_DISMISS_ALL); 1809 } 1810 }); 1811 1812 } 1813 onBusEvent(TaskViewDismissedEvent event)1814 public final void onBusEvent(TaskViewDismissedEvent event) { 1815 // Announce for accessibility 1816 announceForAccessibility(getContext().getString( 1817 R.string.accessibility_recents_item_dismissed, event.task.title)); 1818 1819 if (useGridLayout() && event.animation != null) { 1820 event.animation.setListener(new AnimatorListenerAdapter() { 1821 public void onAnimationEnd(Animator animator) { 1822 if (mTaskViewFocusFrame != null) { 1823 // Resize the grid layout task view focus frame 1824 mTaskViewFocusFrame.resize(); 1825 } 1826 } 1827 }); 1828 } 1829 1830 // Remove the task from the stack 1831 mStack.removeTask(event.task, event.animation, false /* fromDockGesture */); 1832 EventBus.getDefault().send(new DeleteTaskDataEvent(event.task)); 1833 if (mStack.getTaskCount() > 0 && LegacyRecentsImpl.getConfiguration().isLowRamDevice) { 1834 EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */)); 1835 } 1836 1837 MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_DISMISS, 1838 event.task.key.getComponent().toString()); 1839 } 1840 onBusEvent(FocusNextTaskViewEvent event)1841 public final void onBusEvent(FocusNextTaskViewEvent event) { 1842 // Stop any scrolling 1843 mStackScroller.stopScroller(); 1844 mStackScroller.stopBoundScrollAnimation(); 1845 1846 setRelativeFocusedTask(true, false /* stackTasksOnly */, true /* animated */, false, 0); 1847 } 1848 onBusEvent(FocusPreviousTaskViewEvent event)1849 public final void onBusEvent(FocusPreviousTaskViewEvent event) { 1850 // Stop any scrolling 1851 mStackScroller.stopScroller(); 1852 mStackScroller.stopBoundScrollAnimation(); 1853 1854 setRelativeFocusedTask(false, false /* stackTasksOnly */, true /* animated */); 1855 } 1856 onBusEvent(NavigateTaskViewEvent event)1857 public final void onBusEvent(NavigateTaskViewEvent event) { 1858 if (useGridLayout()) { 1859 final int taskCount = mStack.getTaskCount(); 1860 final int currentIndex = mStack.indexOfTask(getFocusedTask()); 1861 final int nextIndex = mLayoutAlgorithm.mTaskGridLayoutAlgorithm.navigateFocus(taskCount, 1862 currentIndex, event.direction); 1863 setFocusedTask(nextIndex, false, true); 1864 } else { 1865 switch (event.direction) { 1866 case UP: 1867 EventBus.getDefault().send(new FocusPreviousTaskViewEvent()); 1868 break; 1869 case DOWN: 1870 EventBus.getDefault().send(new FocusNextTaskViewEvent()); 1871 break; 1872 } 1873 } 1874 } 1875 onBusEvent(UserInteractionEvent event)1876 public final void onBusEvent(UserInteractionEvent event) { 1877 // Poke the doze trigger on user interaction 1878 mUIDozeTrigger.poke(); 1879 1880 RecentsDebugFlags debugFlags = LegacyRecentsImpl.getDebugFlags(); 1881 if (mFocusedTask != null) { 1882 TaskView tv = getChildViewForTask(mFocusedTask); 1883 if (tv != null) { 1884 tv.getHeaderView().cancelFocusTimerIndicator(); 1885 } 1886 } 1887 } 1888 onBusEvent(DragStartEvent event)1889 public final void onBusEvent(DragStartEvent event) { 1890 // Ensure that the drag task is not animated 1891 addIgnoreTask(event.task); 1892 1893 // Enlarge the dragged view slightly 1894 float finalScale = event.taskView.getScaleX() * DRAG_SCALE_FACTOR; 1895 mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(), 1896 mTmpTransform, null); 1897 mTmpTransform.scale = finalScale; 1898 mTmpTransform.translationZ = mLayoutAlgorithm.mMaxTranslationZ + 1; 1899 mTmpTransform.dimAlpha = 0f; 1900 updateTaskViewToTransform(event.taskView, mTmpTransform, 1901 new AnimationProps(DRAG_SCALE_DURATION, Interpolators.FAST_OUT_SLOW_IN)); 1902 } 1903 onBusEvent(DragDropTargetChangedEvent event)1904 public final void onBusEvent(DragDropTargetChangedEvent event) { 1905 AnimationProps animation = new AnimationProps(SLOW_SYNC_STACK_DURATION, 1906 Interpolators.FAST_OUT_SLOW_IN); 1907 boolean ignoreTaskOverrides = false; 1908 if (event.dropTarget instanceof DockState) { 1909 // Calculate the new task stack bounds that matches the window size that Recents will 1910 // have after the drop 1911 final DockState dockState = (DockState) event.dropTarget; 1912 Rect systemInsets = new Rect(mStableLayoutAlgorithm.mSystemInsets); 1913 // When docked, the nav bar insets are consumed and the activity is measured without 1914 // insets. However, the window bounds include the insets, so we need to subtract them 1915 // here to make them identical. 1916 int height = getMeasuredHeight(); 1917 height -= systemInsets.bottom; 1918 systemInsets.bottom = 0; 1919 mStackBounds.set(dockState.getDockedTaskStackBounds(mDisplayRect, getMeasuredWidth(), 1920 height, mDividerSize, systemInsets, 1921 mLayoutAlgorithm, getResources(), mWindowRect)); 1922 mLayoutAlgorithm.setSystemInsets(systemInsets); 1923 mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds); 1924 updateLayoutAlgorithm(true /* boundScroll */); 1925 ignoreTaskOverrides = true; 1926 } else { 1927 // Restore the pre-drag task stack bounds, but ensure that we don't layout the dragging 1928 // task view, so add it back to the ignore set after updating the layout 1929 removeIgnoreTask(event.task); 1930 updateLayoutToStableBounds(); 1931 addIgnoreTask(event.task); 1932 } 1933 relayoutTaskViews(animation, null /* animationOverrides */, ignoreTaskOverrides); 1934 } 1935 onBusEvent(final DragEndEvent event)1936 public final void onBusEvent(final DragEndEvent event) { 1937 // We don't handle drops on the dock regions 1938 if (event.dropTarget instanceof DockState) { 1939 // However, we do need to reset the overrides, since the last state of this task stack 1940 // view layout was ignoring task overrides (see DragDropTargetChangedEvent handler) 1941 mLayoutAlgorithm.clearUnfocusedTaskOverrides(); 1942 return; 1943 } 1944 1945 // Restore the task, so that relayout will apply to it below 1946 removeIgnoreTask(event.task); 1947 1948 // Convert the dragging task view back to its final layout-space rect 1949 Utilities.setViewFrameFromTranslation(event.taskView); 1950 1951 // Animate all the tasks into place 1952 ArrayMap<Task, AnimationProps> animationOverrides = new ArrayMap<>(); 1953 animationOverrides.put(event.task, new AnimationProps(SLOW_SYNC_STACK_DURATION, 1954 Interpolators.FAST_OUT_SLOW_IN, 1955 event.getAnimationTrigger().decrementOnAnimationEnd())); 1956 relayoutTaskViews(new AnimationProps(SLOW_SYNC_STACK_DURATION, 1957 Interpolators.FAST_OUT_SLOW_IN)); 1958 event.getAnimationTrigger().increment(); 1959 } 1960 onBusEvent(final DragEndCancelledEvent event)1961 public final void onBusEvent(final DragEndCancelledEvent event) { 1962 // Restore the pre-drag task stack bounds, including the dragging task view 1963 removeIgnoreTask(event.task); 1964 updateLayoutToStableBounds(); 1965 1966 // Convert the dragging task view back to its final layout-space rect 1967 Utilities.setViewFrameFromTranslation(event.taskView); 1968 1969 // Animate all the tasks into place 1970 ArrayMap<Task, AnimationProps> animationOverrides = new ArrayMap<>(); 1971 animationOverrides.put(event.task, new AnimationProps(SLOW_SYNC_STACK_DURATION, 1972 Interpolators.FAST_OUT_SLOW_IN, 1973 event.getAnimationTrigger().decrementOnAnimationEnd())); 1974 relayoutTaskViews(new AnimationProps(SLOW_SYNC_STACK_DURATION, 1975 Interpolators.FAST_OUT_SLOW_IN)); 1976 event.getAnimationTrigger().increment(); 1977 } 1978 onBusEvent(EnterRecentsWindowAnimationCompletedEvent event)1979 public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) { 1980 mEnterAnimationComplete = true; 1981 tryStartEnterAnimation(); 1982 } 1983 tryStartEnterAnimation()1984 private void tryStartEnterAnimation() { 1985 if (!mStackReloaded || !mFinishedLayoutAfterStackReload || !mEnterAnimationComplete) { 1986 return; 1987 } 1988 1989 if (mStack.getTaskCount() > 0) { 1990 // Start the task enter animations 1991 ReferenceCountedTrigger trigger = new ReferenceCountedTrigger(); 1992 mAnimationHelper.startEnterAnimation(trigger); 1993 1994 // Add a runnable to the post animation ref counter to clear all the views 1995 trigger.addLastDecrementRunnable(() -> { 1996 // Start the dozer to trigger to trigger any UI that shows after a timeout 1997 mUIDozeTrigger.startDozing(); 1998 1999 // Update the focused state here -- since we only set the focused task without 2000 // requesting view focus in onFirstLayout(), actually request view focus and 2001 // animate the focused state if we are alt-tabbing now, after the window enter 2002 // animation is completed 2003 if (mFocusedTask != null) { 2004 RecentsConfiguration config = LegacyRecentsImpl.getConfiguration(); 2005 RecentsActivityLaunchState launchState = config.getLaunchState(); 2006 setFocusedTask(mStack.indexOfTask(mFocusedTask), 2007 false /* scrollToTask */, launchState.launchedWithAltTab); 2008 TaskView focusedTaskView = getChildViewForTask(mFocusedTask); 2009 if (mTouchExplorationEnabled && focusedTaskView != null) { 2010 focusedTaskView.requestAccessibilityFocus(); 2011 } 2012 } 2013 }); 2014 } 2015 2016 // This flag is only used to choreograph the enter animation, so we can reset it here 2017 mStackReloaded = false; 2018 } 2019 onBusEvent(final MultiWindowStateChangedEvent event)2020 public final void onBusEvent(final MultiWindowStateChangedEvent event) { 2021 if (event.inMultiWindow || !event.showDeferredAnimation) { 2022 setTasks(event.stack, true /* allowNotifyStackChanges */); 2023 } else { 2024 // Reset the launch state before handling the multiwindow change 2025 RecentsActivityLaunchState launchState = LegacyRecentsImpl.getConfiguration().getLaunchState(); 2026 launchState.reset(); 2027 2028 // Defer until the next frame to ensure that we have received all the system insets, and 2029 // initial layout updates 2030 event.getAnimationTrigger().increment(); 2031 post(new Runnable() { 2032 @Override 2033 public void run() { 2034 // Scroll the stack to the front to see the undocked task 2035 mAnimationHelper.startNewStackScrollAnimation(event.stack, 2036 event.getAnimationTrigger()); 2037 event.getAnimationTrigger().decrement(); 2038 } 2039 }); 2040 } 2041 } 2042 onBusEvent(ConfigurationChangedEvent event)2043 public final void onBusEvent(ConfigurationChangedEvent event) { 2044 if (event.fromDeviceOrientationChange) { 2045 mDisplayOrientation = Utilities.getAppConfiguration(mContext).orientation; 2046 mDisplayRect = LegacyRecentsImpl.getSystemServices().getDisplayRect(); 2047 2048 // Always stop the scroller, otherwise, we may continue setting the stack scroll to the 2049 // wrong bounds in the new layout 2050 mStackScroller.stopScroller(); 2051 } 2052 reloadOnConfigurationChange(); 2053 2054 // Notify the task views of the configuration change so they can reload their resources 2055 if (!event.fromMultiWindow) { 2056 mTmpTaskViews.clear(); 2057 mTmpTaskViews.addAll(getTaskViews()); 2058 mTmpTaskViews.addAll(mViewPool.getViews()); 2059 int taskViewCount = mTmpTaskViews.size(); 2060 for (int i = 0; i < taskViewCount; i++) { 2061 mTmpTaskViews.get(i).onConfigurationChanged(); 2062 } 2063 } 2064 2065 // Update the Clear All button in case we're switching in or out of grid layout. 2066 updateStackActionButtonVisibility(); 2067 2068 // Trigger a new layout and update to the initial state if necessary. When entering split 2069 // screen, the multi-window configuration change event can happen after the stack is already 2070 // reloaded (but pending measure/layout), in this case, do not override the intiial state 2071 // and just wait for the upcoming measure/layout pass. 2072 if (event.fromMultiWindow && mInitialState == INITIAL_STATE_UPDATE_NONE) { 2073 mInitialState = INITIAL_STATE_UPDATE_LAYOUT_ONLY; 2074 requestLayout(); 2075 } else if (event.fromDeviceOrientationChange) { 2076 mInitialState = INITIAL_STATE_UPDATE_ALL; 2077 requestLayout(); 2078 } 2079 } 2080 onBusEvent(RecentsGrowingEvent event)2081 public final void onBusEvent(RecentsGrowingEvent event) { 2082 mResetToInitialStateWhenResized = true; 2083 } 2084 onBusEvent(RecentsVisibilityChangedEvent event)2085 public final void onBusEvent(RecentsVisibilityChangedEvent event) { 2086 if (!event.visible) { 2087 if (mTaskViewFocusFrame != null) { 2088 mTaskViewFocusFrame.moveGridTaskViewFocus(null); 2089 } 2090 2091 List<TaskView> taskViews = new ArrayList<>(getTaskViews()); 2092 for (int i = 0; i < taskViews.size(); i++) { 2093 mViewPool.returnViewToPool(taskViews.get(i)); 2094 } 2095 clearPrefetchingTask(); 2096 2097 // We can not reset mEnterAnimationComplete in onReload() because when docking the top 2098 // task, we can receive the enter animation callback before onReload(), so reset it 2099 // here onces Recents is not visible 2100 mEnterAnimationComplete = false; 2101 } 2102 } 2103 onBusEvent(ActivityPinnedEvent event)2104 public final void onBusEvent(ActivityPinnedEvent event) { 2105 // If an activity enters PiP while Recents is open, remove the stack task associated with 2106 // the new PiP task 2107 Task removeTask = mStack.findTaskWithId(event.taskId); 2108 if (removeTask != null) { 2109 // In this case, we remove the task, but if the last task is removed, don't dismiss 2110 // Recents to home 2111 mStack.removeTask(removeTask, AnimationProps.IMMEDIATE, false /* fromDockGesture */, 2112 false /* dismissRecentsIfAllRemoved */); 2113 } 2114 updateLayoutAlgorithm(false /* boundScroll */); 2115 updateToInitialState(); 2116 } 2117 reloadOnConfigurationChange()2118 public void reloadOnConfigurationChange() { 2119 mStableLayoutAlgorithm.reloadOnConfigurationChange(getContext()); 2120 mLayoutAlgorithm.reloadOnConfigurationChange(getContext()); 2121 } 2122 2123 /** 2124 * Returns the insert index for the task in the current set of task views. If the given task 2125 * is already in the task view list, then this method returns the insert index assuming it 2126 * is first removed at the previous index. 2127 * 2128 * @param task the task we are finding the index for 2129 * @param taskIndex the index of the task in the stack 2130 */ findTaskViewInsertIndex(Task task, int taskIndex)2131 private int findTaskViewInsertIndex(Task task, int taskIndex) { 2132 if (taskIndex != -1) { 2133 List<TaskView> taskViews = getTaskViews(); 2134 boolean foundTaskView = false; 2135 int taskViewCount = taskViews.size(); 2136 for (int i = 0; i < taskViewCount; i++) { 2137 Task tvTask = taskViews.get(i).getTask(); 2138 if (tvTask == task) { 2139 foundTaskView = true; 2140 } else if (taskIndex < mStack.indexOfTask(tvTask)) { 2141 if (foundTaskView) { 2142 return i - 1; 2143 } else { 2144 return i; 2145 } 2146 } 2147 } 2148 } 2149 return -1; 2150 } 2151 launchTask(Task task)2152 private void launchTask(Task task) { 2153 // Stop all animations 2154 cancelAllTaskViewAnimations(); 2155 2156 float curScroll = mStackScroller.getStackScroll(); 2157 float targetScroll = mLayoutAlgorithm.getStackScrollForTaskAtInitialOffset(task); 2158 float absScrollDiff = Math.abs(targetScroll - curScroll); 2159 if (getChildViewForTask(task) == null || absScrollDiff > 0.35f) { 2160 int duration = (int) (LAUNCH_NEXT_SCROLL_BASE_DURATION + 2161 absScrollDiff * LAUNCH_NEXT_SCROLL_INCR_DURATION); 2162 mStackScroller.animateScroll(targetScroll, 2163 duration, new Runnable() { 2164 @Override 2165 public void run() { 2166 EventBus.getDefault().send(new LaunchTaskEvent( 2167 getChildViewForTask(task), task, null, 2168 false /* screenPinningRequested */)); 2169 } 2170 }); 2171 } else { 2172 EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(task), task, null, 2173 false /* screenPinningRequested */)); 2174 } 2175 } 2176 2177 /** 2178 * Check whether we should use the grid layout. 2179 */ useGridLayout()2180 public boolean useGridLayout() { 2181 return mLayoutAlgorithm.useGridLayout(); 2182 } 2183 2184 /** 2185 * Reads current system flags related to accessibility and screen pinning. 2186 */ readSystemFlags()2187 private void readSystemFlags() { 2188 SystemServicesProxy ssp = LegacyRecentsImpl.getSystemServices(); 2189 mTouchExplorationEnabled = ssp.isTouchExplorationEnabled(); 2190 mScreenPinningEnabled = ActivityManagerWrapper.getInstance().isScreenPinningEnabled() 2191 && !ActivityManagerWrapper.getInstance().isLockToAppActive(); 2192 } 2193 updateStackActionButtonVisibility()2194 private void updateStackActionButtonVisibility() { 2195 if (LegacyRecentsImpl.getConfiguration().isLowRamDevice) { 2196 return; 2197 } 2198 2199 // Always show the button in grid layout. 2200 if (useGridLayout() || 2201 (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD && 2202 mStack.getTaskCount() > 0)) { 2203 EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */)); 2204 } else { 2205 EventBus.getDefault().send(new HideStackActionButtonEvent()); 2206 } 2207 } 2208 2209 /** 2210 * Returns the task to focus given the current launch state. 2211 */ getInitialFocusTaskIndex(RecentsActivityLaunchState launchState, int numTasks, boolean useGridLayout)2212 private int getInitialFocusTaskIndex(RecentsActivityLaunchState launchState, int numTasks, 2213 boolean useGridLayout) { 2214 if (launchState.launchedFromApp) { 2215 if (useGridLayout) { 2216 // If coming from another app to the grid layout, focus the front most task 2217 return numTasks - 1; 2218 } 2219 2220 // If coming from another app, focus the next task 2221 return Math.max(0, numTasks - 2); 2222 } else { 2223 // If coming from home, focus the front most task 2224 return numTasks - 1; 2225 } 2226 } 2227 2228 /** 2229 * Updates {@param transforms} to be the same size as {@param tasks}. 2230 */ matchTaskListSize(List<Task> tasks, List<TaskViewTransform> transforms)2231 private void matchTaskListSize(List<Task> tasks, List<TaskViewTransform> transforms) { 2232 // We can reuse the task transforms where possible to reduce object allocation 2233 int taskTransformCount = transforms.size(); 2234 int taskCount = tasks.size(); 2235 if (taskTransformCount < taskCount) { 2236 // If there are less transforms than tasks, then add as many transforms as necessary 2237 for (int i = taskTransformCount; i < taskCount; i++) { 2238 transforms.add(new TaskViewTransform()); 2239 } 2240 } else if (taskTransformCount > taskCount) { 2241 // If there are more transforms than tasks, then just subset the transform list 2242 transforms.subList(taskCount, taskTransformCount).clear(); 2243 } 2244 } 2245 dump(String prefix, PrintWriter writer)2246 public void dump(String prefix, PrintWriter writer) { 2247 String innerPrefix = prefix + " "; 2248 String id = Integer.toHexString(System.identityHashCode(this)); 2249 2250 writer.print(prefix); writer.print(TAG); 2251 writer.print(" hasDefRelayout="); 2252 writer.print(mDeferredTaskViewLayoutAnimation != null ? "Y" : "N"); 2253 writer.print(" clipDirty="); writer.print(mTaskViewsClipDirty ? "Y" : "N"); 2254 writer.print(" awaitingStackReload="); writer.print(mFinishedLayoutAfterStackReload ? "Y" : "N"); 2255 writer.print(" initialState="); writer.print(mInitialState); 2256 writer.print(" inMeasureLayout="); writer.print(mInMeasureLayout ? "Y" : "N"); 2257 writer.print(" enterAnimCompleted="); writer.print(mEnterAnimationComplete ? "Y" : "N"); 2258 writer.print(" touchExplorationOn="); writer.print(mTouchExplorationEnabled ? "Y" : "N"); 2259 writer.print(" screenPinningOn="); writer.print(mScreenPinningEnabled ? "Y" : "N"); 2260 writer.print(" numIgnoreTasks="); writer.print(mIgnoreTasks.size()); 2261 writer.print(" numViewPool="); writer.print(mViewPool.getViews().size()); 2262 writer.print(" stableStackBounds="); writer.print( 2263 Utilities.dumpRect(mStableStackBounds)); 2264 writer.print(" stackBounds="); writer.print( 2265 Utilities.dumpRect(mStackBounds)); 2266 writer.print(" stableWindow="); writer.print( 2267 Utilities.dumpRect(mStableWindowRect)); 2268 writer.print(" window="); writer.print(Utilities.dumpRect(mWindowRect)); 2269 writer.print(" display="); writer.print(Utilities.dumpRect(mDisplayRect)); 2270 writer.print(" orientation="); writer.print(mDisplayOrientation); 2271 writer.print(" [0x"); writer.print(id); writer.print("]"); 2272 writer.println(); 2273 2274 if (mFocusedTask != null) { 2275 writer.print(innerPrefix); 2276 writer.print("Focused task: "); 2277 mFocusedTask.dump("", writer); 2278 } 2279 2280 int numTaskViews = mTaskViews.size(); 2281 for (int i = 0; i < numTaskViews; i++) { 2282 mTaskViews.get(i).dump(innerPrefix, writer); 2283 } 2284 2285 mLayoutAlgorithm.dump(innerPrefix, writer); 2286 mStackScroller.dump(innerPrefix, writer); 2287 } 2288 } 2289