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