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