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