2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.systemui.recents.views;
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;
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;
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;
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;
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> {
108 private static final String TAG = "TaskStackView";
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;
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;
119 private static final int LAUNCH_NEXT_SCROLL_BASE_DURATION = 216;
120 private static final int LAUNCH_NEXT_SCROLL_INCR_DURATION = 32;
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;
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;
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;
153 @ViewDebug.ExportedProperty(deepExport=true, prefix="doze_")
154 private DozeTrigger mUIDozeTrigger;
155 @ViewDebug.ExportedProperty(deepExport=true, prefix="focused_task_")
156 private Task mFocusedTask;
158 private int mTaskCornerRadiusPx;
159 private int mDividerSize;
160 private int mStartTimerIndicatorDuration;
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")
168 private int mInitialState = INITIAL_STATE_UPDATE_ALL;
169 @ViewDebug.ExportedProperty(category="recents")
170 private boolean mInMeasureLayout = false;
171 @ViewDebug.ExportedProperty(category="recents")
172 private boolean mEnterAnimationComplete = false;
173 @ViewDebug.ExportedProperty(category="recents")
174 boolean mTouchExplorationEnabled;
175 @ViewDebug.ExportedProperty(category="recents")
176 boolean mScreenPinningEnabled;
178 // The stable stack bounds are the full bounds that we were measured with from RecentsView
179 @ViewDebug.ExportedProperty(category="recents")
180 private Rect mStableStackBounds = new Rect();
181 // The current stack bounds are dynamic and may change as the user drags and drops
182 @ViewDebug.ExportedProperty(category="recents")
183 private Rect mStackBounds = new Rect();
184 // The current window bounds at the point we were measured
185 @ViewDebug.ExportedProperty(category="recents")
186 private Rect mStableWindowRect = new Rect();
187 // The current window bounds are dynamic and may change as the user drags and drops
188 @ViewDebug.ExportedProperty(category="recents")
189 private Rect mWindowRect = new Rect();
190 // The current display bounds
191 @ViewDebug.ExportedProperty(category="recents")
192 private Rect mDisplayRect = new Rect();
193 // The current display orientation
194 @ViewDebug.ExportedProperty(category="recents")
195 private int mDisplayOrientation = Configuration.ORIENTATION_UNDEFINED;
197 private Rect mTmpRect = new Rect();
198 private ArrayMap<Task.TaskKey, TaskView> mTmpTaskViewMap = new ArrayMap<>();
199 private List<TaskView> mTmpTaskViews = new ArrayList<>();
200 private TaskViewTransform mTmpTransform = new TaskViewTransform();
201 private int[] mTmpIntPair = new int[2];
202 private boolean mResetToInitialStateWhenResized;
203 private int mLastWidth;
204 private int mLastHeight;
206 // A convenience update listener to request updating clipping of tasks
207 private ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =
208 new ValueAnimator.AnimatorUpdateListener() {
210 public void onAnimationUpdate(ValueAnimator animation) {
211 if (!mTaskViewsClipDirty) {
212 mTaskViewsClipDirty = true;
218 // The drop targets for a task drag
219 private DropTarget mFreeformWorkspaceDropTarget = new DropTarget() {
221 public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
222 boolean isCurrentTarget) {
223 // This drop target has a fixed bounds and should be checked last, so just fall through
224 // if it is the current target
225 if (!isCurrentTarget) {
226 return mLayoutAlgorithm.mFreeformRect.contains(x, y);
232 private DropTarget mStackDropTarget = new DropTarget() {
234 public boolean acceptsDrop(int x, int y, int width, int height, Rect insets,
235 boolean isCurrentTarget) {
236 // This drop target has a fixed bounds and should be checked last, so just fall through
237 // if it is the current target
238 if (!isCurrentTarget) {
239 return mLayoutAlgorithm.mStackRect.contains(x, y);
245 public TaskStackView(Context context) {
247 SystemServicesProxy ssp = Recents.getSystemServices();
248 Resources res = context.getResources();
250 // Set the stack first
251 mStack.setCallbacks(this);
252 mViewPool = new ViewPool<>(context, this);
253 mInflater = LayoutInflater.from(context);
254 mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, this);
255 mStableLayoutAlgorithm = new TaskStackLayoutAlgorithm(context, null);
256 mStackScroller = new TaskStackViewScroller(context, this, mLayoutAlgorithm);
257 mTouchHandler = new TaskStackViewTouchHandler(context, this, mStackScroller);
258 mAnimationHelper = new TaskStackAnimationHelper(context, this);
259 mTaskCornerRadiusPx = res.getDimensionPixelSize(
260 R.dimen.recents_task_view_rounded_corners_radius);
261 mDividerSize = ssp.getDockedDividerSize(context);
262 mDisplayOrientation = Utilities.getAppConfiguration(mContext).orientation;
263 mDisplayRect = ssp.getDisplayRect();
265 int taskBarDismissDozeDelaySeconds = getResources().getInteger(
266 R.integer.recents_task_bar_dismiss_delay_seconds);
267 mUIDozeTrigger = new DozeTrigger(taskBarDismissDozeDelaySeconds, new Runnable() {
270 // Show the task bar dismiss buttons
271 List<TaskView> taskViews = getTaskViews();
272 int taskViewCount = taskViews.size();
273 for (int i = 0; i < taskViewCount; i++) {
274 TaskView tv = taskViews.get(i);
275 tv.startNoUserInteractionAnimation();
279 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
281 mFreeformWorkspaceBackground = (GradientDrawable) getContext().getDrawable(
282 R.drawable.recents_freeform_workspace_bg);
283 mFreeformWorkspaceBackground.setCallback(this);
284 if (ssp.hasFreeformWorkspaceSupport()) {
285 mFreeformWorkspaceBackground.setColor(
286 getContext().getColor(R.color.recents_freeform_workspace_bg_color));
291 protected void onAttachedToWindow() {
292 EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
293 super.onAttachedToWindow();
298 protected void onDetachedFromWindow() {
299 super.onDetachedFromWindow();
300 EventBus.getDefault().unregister(this);
304 * Called from RecentsActivity when it is relaunched.
306 void onReload(boolean isResumingFromVisible) {
307 if (!isResumingFromVisible) {
308 // Reset the focused task
309 resetFocusedTask(getFocusedTask());
312 // Reset the state of each of the task views
313 List<TaskView> taskViews = new ArrayList<>();
314 taskViews.addAll(getTaskViews());
315 taskViews.addAll(mViewPool.getViews());
316 for (int i = taskViews.size() - 1; i >= 0; i--) {
317 taskViews.get(i).onReload(isResumingFromVisible);
320 // Reset the stack state
322 mTaskViewsClipDirty = true;
323 mEnterAnimationComplete = false;
324 mUIDozeTrigger.stopDozing();
325 if (isResumingFromVisible) {
326 // Animate in the freeform workspace
327 int ffBgAlpha = mLayoutAlgorithm.getStackState().freeformBackgroundAlpha;
328 animateFreeformWorkspaceBackgroundAlpha(ffBgAlpha, new AnimationProps(150,
329 Interpolators.FAST_OUT_SLOW_IN));
331 mStackScroller.reset();
332 mStableLayoutAlgorithm.reset();
333 mLayoutAlgorithm.reset();
336 // Since we always animate to the same place in (the initial state), always reset the stack
337 // to the initial state when resuming
338 mAwaitingFirstLayout = true;
339 mInitialState = INITIAL_STATE_UPDATE_ALL;
344 * Sets the stack tasks of this TaskStackView from the given TaskStack.
346 public void setTasks(TaskStack stack, boolean allowNotifyStackChanges) {
347 boolean isInitialized = mLayoutAlgorithm.isInitialized();
349 // Only notify if we are already initialized, otherwise, everything will pick up all the
350 // new and old tasks when we next layout
351 mStack.setTasks(getContext(), stack.computeAllTasksList(),
352 allowNotifyStackChanges && isInitialized);
355 /** Returns the task stack. */
356 public TaskStack getStack() {
361 * Updates this TaskStackView to the initial state.
363 public void updateToInitialState() {
364 mStackScroller.setStackScrollToInitialState();
365 mLayoutAlgorithm.setTaskOverridesForInitialState(mStack, false /* ignoreScrollToFront */);
368 /** Updates the list of task views */
369 void updateTaskViewsList() {
371 int childCount = getChildCount();
372 for (int i = 0; i < childCount; i++) {
373 View v = getChildAt(i);
374 if (v instanceof TaskView) {
375 mTaskViews.add((TaskView) v);
380 /** Gets the list of task views */
381 List<TaskView> getTaskViews() {
386 * Returns the front most task view.
388 * @param stackTasksOnly if set, will return the front most task view in the stack (by default
389 * the front most task view will be freeform since they are placed above
392 private TaskView getFrontMostTaskView(boolean stackTasksOnly) {
393 List<TaskView> taskViews = getTaskViews();
394 int taskViewCount = taskViews.size();
395 for (int i = taskViewCount - 1; i >= 0; i--) {
396 TaskView tv = taskViews.get(i);
397 Task task = tv.getTask();
398 if (stackTasksOnly && task.isFreeformTask()) {
407 * Finds the child view given a specific {@param task}.
409 public TaskView getChildViewForTask(Task t) {
410 List<TaskView> taskViews = getTaskViews();
411 int taskViewCount = taskViews.size();
412 for (int i = 0; i < taskViewCount; i++) {
413 TaskView tv = taskViews.get(i);
414 if (tv.getTask() == t) {
421 /** Returns the stack algorithm for this task stack. */
422 public TaskStackLayoutAlgorithm getStackAlgorithm() {
423 return mLayoutAlgorithm;
427 * Returns the touch handler for this task stack.
429 public TaskStackViewTouchHandler getTouchHandler() {
430 return mTouchHandler;
434 * Adds a task to the ignored set.
436 void addIgnoreTask(Task task) {
437 mIgnoreTasks.add(task.key);
441 * Removes a task from the ignored set.
443 void removeIgnoreTask(Task task) {
444 mIgnoreTasks.remove(task.key);
448 * Returns whether the specified {@param task} is ignored.
450 boolean isIgnoredTask(Task task) {
451 return mIgnoreTasks.contains(task.key);
455 * Computes the task transforms at the current stack scroll for all visible tasks. If a valid
456 * target stack scroll is provided (ie. is different than {@param curStackScroll}), then the
457 * visible range includes all tasks at the target stack scroll. This is useful for ensure that
458 * all views necessary for a transition or animation will be visible at the start.
460 * This call ignores freeform tasks.
462 * @param taskTransforms The set of task view transforms to reuse, this list will be sized to
463 * match the size of {@param tasks}
464 * @param tasks The set of tasks for which to generate transforms
465 * @param curStackScroll The current stack scroll
466 * @param targetStackScroll The stack scroll that we anticipate we are going to be scrolling to.
467 * The range of the union of the visible views at the current and
468 * target stack scrolls will be returned.
469 * @param ignoreTasksSet The set of tasks to skip for purposes of calculaing the visible range.
470 * Transforms will still be calculated for the ignore tasks.
471 * @return the front and back most visible task indices (there may be non visible tasks in
472 * between this range)
474 int[] computeVisibleTaskTransforms(ArrayList<TaskViewTransform> taskTransforms,
475 ArrayList<Task> tasks, float curStackScroll, float targetStackScroll,
476 ArraySet<Task.TaskKey> ignoreTasksSet, boolean ignoreTaskOverrides) {
477 int taskCount = tasks.size();
478 int[] visibleTaskRange = mTmpIntPair;
479 visibleTaskRange[0] = -1;
480 visibleTaskRange[1] = -1;
481 boolean useTargetStackScroll = Float.compare(curStackScroll, targetStackScroll) != 0;
483 // We can reuse the task transforms where possible to reduce object allocation
484 Utilities.matchTaskListSize(tasks, taskTransforms);
486 // Update the stack transforms
487 TaskViewTransform frontTransform = null;
488 TaskViewTransform frontTransformAtTarget = null;
489 TaskViewTransform transform = null;
490 TaskViewTransform transformAtTarget = null;
491 for (int i = taskCount - 1; i >= 0; i--) {
492 Task task = tasks.get(i);
494 // Calculate the current and (if necessary) the target transform for the task
495 transform = mLayoutAlgorithm.getStackTransform(task, curStackScroll,
496 taskTransforms.get(i), frontTransform, ignoreTaskOverrides);
497 if (useTargetStackScroll && !transform.visible) {
498 // If we have a target stack scroll and the task is not currently visible, then we
499 // just update the transform at the new scroll
500 // TODO: Optimize this
501 transformAtTarget = mLayoutAlgorithm.getStackTransform(task,
502 targetStackScroll, new TaskViewTransform(), frontTransformAtTarget);
503 if (transformAtTarget.visible) {
504 transform.copyFrom(transformAtTarget);
508 // For ignore tasks, only calculate the stack transform and skip the calculation of the
509 // visible stack indices
510 if (ignoreTasksSet.contains(task.key)) {
514 // For freeform tasks, only calculate the stack transform and skip the calculation of
515 // the visible stack indices
516 if (task.isFreeformTask()) {
520 frontTransform = transform;
521 frontTransformAtTarget = transformAtTarget;
522 if (transform.visible) {
523 if (visibleTaskRange[0] < 0) {
524 visibleTaskRange[0] = i;
526 visibleTaskRange[1] = i;
529 return visibleTaskRange;
533 * Binds the visible {@link TaskView}s at the given target scroll.
535 void bindVisibleTaskViews(float targetStackScroll) {
536 bindVisibleTaskViews(targetStackScroll, false /* ignoreTaskOverrides */);
540 * Synchronizes the set of children {@link TaskView}s to match the visible set of tasks in the
541 * current {@link TaskStack}. This call does not continue on to update their position to the
542 * computed {@link TaskViewTransform}s of the visible range, but only ensures that they will
543 * be added/removed from the view hierarchy and placed in the correct Z order and initial
544 * position (if not currently on screen).
546 * @param targetStackScroll If provided, will ensure that the set of visible {@link TaskView}s
547 * includes those visible at the current stack scroll, and all at the
548 * target stack scroll.
549 * @param ignoreTaskOverrides If set, the visible task computation will get the transforms for
550 * tasks at their non-overridden task progress
552 void bindVisibleTaskViews(float targetStackScroll, boolean ignoreTaskOverrides) {
553 // Get all the task transforms
554 ArrayList<Task> tasks = mStack.getStackTasks();
555 int[] visibleTaskRange = computeVisibleTaskTransforms(mCurrentTaskTransforms, tasks,
556 mStackScroller.getStackScroll(), targetStackScroll, mIgnoreTasks,
557 ignoreTaskOverrides);
559 // Return all the invisible children to the pool
560 mTmpTaskViewMap.clear();
561 List<TaskView> taskViews = getTaskViews();
562 int lastFocusedTaskIndex = -1;
563 int taskViewCount = taskViews.size();
564 for (int i = taskViewCount - 1; i >= 0; i--) {
565 TaskView tv = taskViews.get(i);
566 Task task = tv.getTask();
568 // Skip ignored tasks
569 if (mIgnoreTasks.contains(task.key)) {
573 // It is possible for the set of lingering TaskViews to differ from the stack if the
574 // stack was updated before the relayout. If the task view is no longer in the stack,
575 // then just return it back to the view pool.
576 int taskIndex = mStack.indexOfStackTask(task);
577 TaskViewTransform transform = null;
578 if (taskIndex != -1) {
579 transform = mCurrentTaskTransforms.get(taskIndex);
582 if (task.isFreeformTask() || (transform != null && transform.visible)) {
583 mTmpTaskViewMap.put(task.key, tv);
585 if (mTouchExplorationEnabled && Utilities.isDescendentAccessibilityFocused(tv)) {
586 lastFocusedTaskIndex = taskIndex;
587 resetFocusedTask(task);
589 mViewPool.returnViewToPool(tv);
593 // Pick up all the newly visible children
594 for (int i = tasks.size() - 1; i >= 0; i--) {
595 Task task = tasks.get(i);
596 TaskViewTransform transform = mCurrentTaskTransforms.get(i);
598 // Skip ignored tasks
599 if (mIgnoreTasks.contains(task.key)) {
603 // Skip the invisible non-freeform stack tasks
604 if (!task.isFreeformTask() && !transform.visible) {
608 TaskView tv = mTmpTaskViewMap.get(task.key);
610 tv = mViewPool.pickUpViewFromPool(task, task);
611 if (task.isFreeformTask()) {
612 updateTaskViewToTransform(tv, transform, AnimationProps.IMMEDIATE);
614 if (transform.rect.top <= mLayoutAlgorithm.mStackRect.top) {
615 updateTaskViewToTransform(tv, mLayoutAlgorithm.getBackOfStackTransform(),
616 AnimationProps.IMMEDIATE);
618 updateTaskViewToTransform(tv, mLayoutAlgorithm.getFrontOfStackTransform(),
619 AnimationProps.IMMEDIATE);
623 // Reattach it in the right z order
624 final int taskIndex = mStack.indexOfStackTask(task);
625 final int insertIndex = findTaskViewInsertIndex(task, taskIndex);
626 if (insertIndex != getTaskViews().indexOf(tv)){
627 detachViewFromParent(tv);
628 attachViewToParent(tv, insertIndex, tv.getLayoutParams());
629 updateTaskViewsList();
634 // Update the focus if the previous focused task was returned to the view pool
635 if (lastFocusedTaskIndex != -1) {
636 int newFocusedTaskIndex = (lastFocusedTaskIndex < visibleTaskRange[1])
637 ? visibleTaskRange[1]
638 : visibleTaskRange[0];
639 setFocusedTask(newFocusedTaskIndex, false /* scrollToTask */,
640 true /* requestViewFocus */);
641 TaskView focusedTaskView = getChildViewForTask(mFocusedTask);
642 if (focusedTaskView != null) {
643 focusedTaskView.requestAccessibilityFocus();
649 * @see #relayoutTaskViews(AnimationProps, ArrayMap<Task, AnimationProps>, boolean)
651 public void relayoutTaskViews(AnimationProps animation) {
652 relayoutTaskViews(animation, null /* animationOverrides */,
653 false /* ignoreTaskOverrides */);
657 * Relayout the the visible {@link TaskView}s to their current transforms as specified by the
658 * {@link TaskStackLayoutAlgorithm} with the given {@param animation}. This call cancels any
659 * animations that are current running on those task views, and will ensure that the children
660 * {@link TaskView}s will match the set of visible tasks in the stack. If a {@link Task} has
661 * an animation provided in {@param animationOverrides}, that will be used instead.
663 private void relayoutTaskViews(AnimationProps animation,
664 ArrayMap<Task, AnimationProps> animationOverrides,
665 boolean ignoreTaskOverrides) {
666 // If we had a deferred animation, cancel that
667 cancelDeferredTaskViewLayoutAnimation();
669 // Synchronize the current set of TaskViews
670 bindVisibleTaskViews(mStackScroller.getStackScroll(),
671 ignoreTaskOverrides /* ignoreTaskOverrides */);
673 // Animate them to their final transforms with the given animation
674 List<TaskView> taskViews = getTaskViews();
675 int taskViewCount = taskViews.size();
676 for (int i = 0; i < taskViewCount; i++) {
677 TaskView tv = taskViews.get(i);
678 Task task = tv.getTask();
679 int taskIndex = mStack.indexOfStackTask(task);
680 TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex);
682 if (mIgnoreTasks.contains(task.key)) {
686 if (animationOverrides != null && animationOverrides.containsKey(task)) {
687 animation = animationOverrides.get(task);
690 updateTaskViewToTransform(tv, transform, animation);
695 * Posts an update to synchronize the {@link TaskView}s with the stack on the next frame.
697 void relayoutTaskViewsOnNextFrame(AnimationProps animation) {
698 mDeferredTaskViewLayoutAnimation = animation;
703 * Called to update a specific {@link TaskView} to a given {@link TaskViewTransform} with a
704 * given set of {@link AnimationProps} properties.
706 public void updateTaskViewToTransform(TaskView taskView, TaskViewTransform transform,
707 AnimationProps animation) {
708 if (taskView.isAnimatingTo(transform)) {
711 taskView.cancelTransformAnimation();
712 taskView.updateViewPropertiesToTaskTransform(transform, animation,
713 mRequestUpdateClippingListener);
717 * Returns the current task transforms of all tasks, falling back to the stack layout if there
718 * is no {@link TaskView} for the task.
720 public void getCurrentTaskTransforms(ArrayList<Task> tasks,
721 ArrayList<TaskViewTransform> transformsOut) {
722 Utilities.matchTaskListSize(tasks, transformsOut);
723 int focusState = mLayoutAlgorithm.getFocusState();
724 for (int i = tasks.size() - 1; i >= 0; i--) {
725 Task task = tasks.get(i);
726 TaskViewTransform transform = transformsOut.get(i);
727 TaskView tv = getChildViewForTask(task);
729 transform.fillIn(tv);
731 mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(),
732 focusState, transform, null, true /* forceUpdate */,
733 false /* ignoreTaskOverrides */);
735 transform.visible = true;
740 * Returns the task transforms for all the tasks in the stack if the stack was at the given
741 * {@param stackScroll} and {@param focusState}.
743 public void getLayoutTaskTransforms(float stackScroll, int focusState, ArrayList<Task> tasks,
744 boolean ignoreTaskOverrides, ArrayList<TaskViewTransform> transformsOut) {
745 Utilities.matchTaskListSize(tasks, transformsOut);
746 for (int i = tasks.size() - 1; i >= 0; i--) {
747 Task task = tasks.get(i);
748 TaskViewTransform transform = transformsOut.get(i);
749 mLayoutAlgorithm.getStackTransform(task, stackScroll, focusState, transform, null,
750 true /* forceUpdate */, ignoreTaskOverrides);
751 transform.visible = true;
756 * Cancels the next deferred task view layout.
758 void cancelDeferredTaskViewLayoutAnimation() {
759 mDeferredTaskViewLayoutAnimation = null;
763 * Cancels all {@link TaskView} animations.
765 void cancelAllTaskViewAnimations() {
766 List<TaskView> taskViews = getTaskViews();
767 for (int i = taskViews.size() - 1; i >= 0; i--) {
768 final TaskView tv = taskViews.get(i);
769 if (!mIgnoreTasks.contains(tv.getTask().key)) {
770 tv.cancelTransformAnimation();
776 * Updates the clip for each of the task views from back to front.
778 private void clipTaskViews() {
779 // Update the clip on each task child
780 List<TaskView> taskViews = getTaskViews();
781 TaskView tmpTv = null;
782 TaskView prevVisibleTv = null;
783 int taskViewCount = taskViews.size();
784 for (int i = 0; i < taskViewCount; i++) {
785 TaskView tv = taskViews.get(i);
786 TaskView frontTv = null;
789 if (isIgnoredTask(tv.getTask())) {
790 // For each of the ignore tasks, update the translationZ of its TaskView to be
791 // between the translationZ of the tasks immediately underneath it
792 if (prevVisibleTv != null) {
793 tv.setTranslationZ(Math.max(tv.getTranslationZ(),
794 prevVisibleTv.getTranslationZ() + 0.1f));
798 if (i < (taskViewCount - 1) && tv.shouldClipViewInStack()) {
799 // Find the next view to clip against
800 for (int j = i + 1; j < taskViewCount; j++) {
801 tmpTv = taskViews.get(j);
803 if (tmpTv.shouldClipViewInStack()) {
809 // Clip against the next view, this is just an approximation since we are
810 // stacked and we can make assumptions about the visibility of the this
811 // task relative to the ones in front of it.
812 if (frontTv != null) {
813 float taskBottom = tv.getBottom();
814 float frontTaskTop = frontTv.getTop();
815 if (frontTaskTop < taskBottom) {
816 // Map the stack view space coordinate (the rects) to view space
817 clipBottom = (int) (taskBottom - frontTaskTop) - mTaskCornerRadiusPx;
821 tv.getViewBounds().setClipBottom(clipBottom);
822 tv.mThumbnailView.updateThumbnailVisibility(clipBottom - tv.getPaddingBottom());
825 mTaskViewsClipDirty = false;
829 * Updates the layout algorithm min and max virtual scroll bounds.
831 public void updateLayoutAlgorithm(boolean boundScrollToNewMinMax) {
832 // Compute the min and max scroll values
833 mLayoutAlgorithm.update(mStack, mIgnoreTasks);
835 // Update the freeform workspace background
836 SystemServicesProxy ssp = Recents.getSystemServices();
837 if (ssp.hasFreeformWorkspaceSupport()) {
838 mTmpRect.set(mLayoutAlgorithm.mFreeformRect);
839 mFreeformWorkspaceBackground.setBounds(mTmpRect);
842 if (boundScrollToNewMinMax) {
843 mStackScroller.boundScroll();
848 * Updates the stack layout to its stable places.
850 private void updateLayoutToStableBounds() {
851 mWindowRect.set(mStableWindowRect);
852 mStackBounds.set(mStableStackBounds);
853 mLayoutAlgorithm.setSystemInsets(mStableLayoutAlgorithm.mSystemInsets);
854 mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds,
855 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
856 updateLayoutAlgorithm(true /* boundScroll */);
859 /** Returns the scroller. */
860 public TaskStackViewScroller getScroller() {
861 return mStackScroller;
865 * Sets the focused task to the provided (bounded taskIndex).
867 * @return whether or not the stack will scroll as a part of this focus change
869 private boolean setFocusedTask(int taskIndex, boolean scrollToTask,
870 final boolean requestViewFocus) {
871 return setFocusedTask(taskIndex, scrollToTask, requestViewFocus, 0);
875 * Sets the focused task to the provided (bounded focusTaskIndex).
877 * @return whether or not the stack will scroll as a part of this focus change
879 private boolean setFocusedTask(int focusTaskIndex, boolean scrollToTask,
880 boolean requestViewFocus, int timerIndicatorDuration) {
881 // Find the next task to focus
882 int newFocusedTaskIndex = mStack.getTaskCount() > 0 ?
883 Utilities.clamp(focusTaskIndex, 0, mStack.getTaskCount() - 1) : -1;
884 final Task newFocusedTask = (newFocusedTaskIndex != -1) ?
885 mStack.getStackTasks().get(newFocusedTaskIndex) : null;
887 // Reset the last focused task state if changed
888 if (mFocusedTask != null) {
889 // Cancel the timer indicator, if applicable
890 if (timerIndicatorDuration > 0) {
891 final TaskView tv = getChildViewForTask(mFocusedTask);
893 tv.getHeaderView().cancelFocusTimerIndicator();
897 resetFocusedTask(mFocusedTask);
900 boolean willScroll = false;
901 mFocusedTask = newFocusedTask;
903 if (newFocusedTask != null) {
904 // Start the timer indicator, if applicable
905 if (timerIndicatorDuration > 0) {
906 final TaskView tv = getChildViewForTask(mFocusedTask);
908 tv.getHeaderView().startFocusTimerIndicator(timerIndicatorDuration);
910 // The view is null; set a flag for later
911 mStartTimerIndicatorDuration = timerIndicatorDuration;
916 // Cancel any running enter animations at this point when we scroll or change focus
917 if (!mEnterAnimationComplete) {
918 cancelAllTaskViewAnimations();
921 mLayoutAlgorithm.clearUnfocusedTaskOverrides();
922 willScroll = mAnimationHelper.startScrollToFocusedTaskAnimation(newFocusedTask,
925 // Focus the task view
926 TaskView newFocusedTaskView = getChildViewForTask(newFocusedTask);
927 if (newFocusedTaskView != null) {
928 newFocusedTaskView.setFocusedState(true, requestViewFocus);
936 * Sets the focused task relative to the currently focused task.
938 * @param forward whether to go to the next task in the stack (along the curve) or the previous
939 * @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and
940 * if the currently focused task is not a stack task, will set the focus
941 * to the first visible stack task
942 * @param animated determines whether to actually draw the highlight along with the change in
945 public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated) {
946 setRelativeFocusedTask(forward, stackTasksOnly, animated, false, 0);
950 * Sets the focused task relative to the currently focused task.
952 * @param forward whether to go to the next task in the stack (along the curve) or the previous
953 * @param stackTasksOnly if set, will ensure that the traversal only goes along stack tasks, and
954 * if the currently focused task is not a stack task, will set the focus
955 * to the first visible stack task
956 * @param animated determines whether to actually draw the highlight along with the change in
958 * @param cancelWindowAnimations if set, will attempt to cancel window animations if a scroll
960 * @param timerIndicatorDuration the duration to initialize the auto-advance timer indicator
962 public void setRelativeFocusedTask(boolean forward, boolean stackTasksOnly, boolean animated,
963 boolean cancelWindowAnimations, int timerIndicatorDuration) {
964 Task focusedTask = getFocusedTask();
965 int newIndex = mStack.indexOfStackTask(focusedTask);
966 if (focusedTask != null) {
967 if (stackTasksOnly) {
968 List<Task> tasks = mStack.getStackTasks();
969 if (focusedTask.isFreeformTask()) {
970 // Try and focus the front most stack task
971 TaskView tv = getFrontMostTaskView(stackTasksOnly);
973 newIndex = mStack.indexOfStackTask(tv.getTask());
976 // Try the next task if it is a stack task
977 int tmpNewIndex = newIndex + (forward ? -1 : 1);
978 if (0 <= tmpNewIndex && tmpNewIndex < tasks.size()) {
979 Task t = tasks.get(tmpNewIndex);
980 if (!t.isFreeformTask()) {
981 newIndex = tmpNewIndex;
986 // No restrictions, lets just move to the new task (looping forward/backwards if
988 int taskCount = mStack.getTaskCount();
989 newIndex = (newIndex + (forward ? -1 : 1) + taskCount) % taskCount;
992 // We don't have a focused task
993 float stackScroll = mStackScroller.getStackScroll();
994 ArrayList<Task> tasks = mStack.getStackTasks();
995 int taskCount = tasks.size();
997 // Walk backwards and focus the next task smaller than the current stack scroll
998 for (newIndex = taskCount - 1; newIndex >= 0; newIndex--) {
999 float taskP = mLayoutAlgorithm.getStackScrollForTask(tasks.get(newIndex));
1000 if (Float.compare(taskP, stackScroll) <= 0) {
1005 // Walk forwards and focus the next task larger than the current stack scroll
1006 for (newIndex = 0; newIndex < taskCount; newIndex++) {
1007 float taskP = mLayoutAlgorithm.getStackScrollForTask(tasks.get(newIndex));
1008 if (Float.compare(taskP, stackScroll) >= 0) {
1014 if (newIndex != -1) {
1015 boolean willScroll = setFocusedTask(newIndex, true /* scrollToTask */,
1016 true /* requestViewFocus */, timerIndicatorDuration);
1017 if (willScroll && cancelWindowAnimations) {
1018 // As we iterate to the next/previous task, cancel any current/lagging window
1019 // transition animations
1020 EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null));
1026 * Resets the focused task.
1028 void resetFocusedTask(Task task) {
1030 TaskView tv = getChildViewForTask(task);
1032 tv.setFocusedState(false, false /* requestViewFocus */);
1035 mFocusedTask = null;
1039 * Returns the focused task.
1041 Task getFocusedTask() {
1042 return mFocusedTask;
1046 * Returns the accessibility focused task.
1048 Task getAccessibilityFocusedTask() {
1049 List<TaskView> taskViews = getTaskViews();
1050 int taskViewCount = taskViews.size();
1051 for (int i = 0; i < taskViewCount; i++) {
1052 TaskView tv = taskViews.get(i);
1053 if (Utilities.isDescendentAccessibilityFocused(tv)) {
1054 return tv.getTask();
1057 TaskView frontTv = getFrontMostTaskView(true /* stackTasksOnly */);
1058 if (frontTv != null) {
1059 return frontTv.getTask();
1065 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1066 super.onInitializeAccessibilityEvent(event);
1067 List<TaskView> taskViews = getTaskViews();
1068 int taskViewCount = taskViews.size();
1069 if (taskViewCount > 0) {
1070 TaskView backMostTask = taskViews.get(0);
1071 TaskView frontMostTask = taskViews.get(taskViewCount - 1);
1072 event.setFromIndex(mStack.indexOfStackTask(backMostTask.getTask()));
1073 event.setToIndex(mStack.indexOfStackTask(frontMostTask.getTask()));
1074 event.setContentDescription(frontMostTask.getTask().title);
1076 event.setItemCount(mStack.getTaskCount());
1078 int stackHeight = mLayoutAlgorithm.mStackRect.height();
1079 event.setScrollY((int) (mStackScroller.getStackScroll() * stackHeight));
1080 event.setMaxScrollY((int) (mLayoutAlgorithm.mMaxScrollP * stackHeight));
1084 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1085 super.onInitializeAccessibilityNodeInfo(info);
1086 List<TaskView> taskViews = getTaskViews();
1087 int taskViewCount = taskViews.size();
1088 if (taskViewCount > 1) {
1089 // Find the accessibility focused task
1090 Task focusedTask = getAccessibilityFocusedTask();
1091 info.setScrollable(true);
1092 int focusedTaskIndex = mStack.indexOfStackTask(focusedTask);
1093 if (focusedTaskIndex > 0) {
1094 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
1096 if (0 <= focusedTaskIndex && focusedTaskIndex < mStack.getTaskCount() - 1) {
1097 info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
1103 public CharSequence getAccessibilityClassName() {
1104 return ScrollView.class.getName();
1108 public boolean performAccessibilityAction(int action, Bundle arguments) {
1109 if (super.performAccessibilityAction(action, arguments)) {
1112 Task focusedTask = getAccessibilityFocusedTask();
1113 int taskIndex = mStack.indexOfStackTask(focusedTask);
1114 if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
1116 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
1117 setFocusedTask(taskIndex + 1, true /* scrollToTask */, true /* requestViewFocus */,
1121 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
1122 setFocusedTask(taskIndex - 1, true /* scrollToTask */, true /* requestViewFocus */,
1132 public boolean onInterceptTouchEvent(MotionEvent ev) {
1133 return mTouchHandler.onInterceptTouchEvent(ev);
1137 public boolean onTouchEvent(MotionEvent ev) {
1138 return mTouchHandler.onTouchEvent(ev);
1142 public boolean onGenericMotionEvent(MotionEvent ev) {
1143 return mTouchHandler.onGenericMotionEvent(ev);
1147 public void computeScroll() {
1148 if (mStackScroller.computeScroll()) {
1149 // Notify accessibility
1150 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED);
1152 if (mDeferredTaskViewLayoutAnimation != null) {
1153 relayoutTaskViews(mDeferredTaskViewLayoutAnimation);
1154 mTaskViewsClipDirty = true;
1155 mDeferredTaskViewLayoutAnimation = null;
1157 if (mTaskViewsClipDirty) {
1163 * Computes the maximum number of visible tasks and thumbnails. Requires that
1164 * updateLayoutForStack() is called first.
1166 public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
1167 return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getStackTasks());
1171 * Updates the system insets.
1173 public void setSystemInsets(Rect systemInsets) {
1174 boolean changed = false;
1175 changed |= mStableLayoutAlgorithm.setSystemInsets(systemInsets);
1176 changed |= mLayoutAlgorithm.setSystemInsets(systemInsets);
1183 * This is called with the full window width and height to allow stack view children to
1184 * perform the full screen transition down.
1187 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1188 mInMeasureLayout = true;
1189 int width = MeasureSpec.getSize(widthMeasureSpec);
1190 int height = MeasureSpec.getSize(heightMeasureSpec);
1192 // Update the stable stack bounds, but only update the current stack bounds if the stable
1193 // bounds have changed. This is because we may get spurious measures while dragging where
1194 // our current stack bounds reflect the target drop region.
1195 mLayoutAlgorithm.getTaskStackBounds(mDisplayRect, new Rect(0, 0, width, height),
1196 mLayoutAlgorithm.mSystemInsets.top, mLayoutAlgorithm.mSystemInsets.left,
1197 mLayoutAlgorithm.mSystemInsets.right, mTmpRect);
1198 if (!mTmpRect.equals(mStableStackBounds)) {
1199 mStableStackBounds.set(mTmpRect);
1200 mStackBounds.set(mTmpRect);
1201 mStableWindowRect.set(0, 0, width, height);
1202 mWindowRect.set(0, 0, width, height);
1205 // Compute the rects in the stack algorithm
1206 mStableLayoutAlgorithm.initialize(mDisplayRect, mStableWindowRect, mStableStackBounds,
1207 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
1208 mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds,
1209 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
1210 updateLayoutAlgorithm(false /* boundScroll */);
1212 // If this is the first layout, then scroll to the front of the stack, then update the
1213 // TaskViews with the stack so that we can lay them out
1214 boolean resetToInitialState = (width != mLastWidth || height != mLastHeight)
1215 && mResetToInitialStateWhenResized;
1216 if (mAwaitingFirstLayout || mInitialState != INITIAL_STATE_UPDATE_NONE
1217 || resetToInitialState) {
1218 if (mInitialState != INITIAL_STATE_UPDATE_LAYOUT_ONLY || resetToInitialState) {
1219 updateToInitialState();
1220 mResetToInitialStateWhenResized = false;
1222 if (!mAwaitingFirstLayout) {
1223 mInitialState = INITIAL_STATE_UPDATE_NONE;
1227 // Rebind all the views, including the ignore ones
1228 bindVisibleTaskViews(mStackScroller.getStackScroll(), false /* ignoreTaskOverrides */);
1230 // Measure each of the TaskViews
1231 mTmpTaskViews.clear();
1232 mTmpTaskViews.addAll(getTaskViews());
1233 mTmpTaskViews.addAll(mViewPool.getViews());
1234 int taskViewCount = mTmpTaskViews.size();
1235 for (int i = 0; i < taskViewCount; i++) {
1236 measureTaskView(mTmpTaskViews.get(i));
1239 setMeasuredDimension(width, height);
1241 mLastHeight = height;
1242 mInMeasureLayout = false;
1246 * Measures a TaskView.
1248 private void measureTaskView(TaskView tv) {
1249 Rect padding = new Rect();
1250 if (tv.getBackground() != null) {
1251 tv.getBackground().getPadding(padding);
1253 mTmpRect.set(mStableLayoutAlgorithm.mTaskRect);
1254 mTmpRect.union(mLayoutAlgorithm.mTaskRect);
1256 MeasureSpec.makeMeasureSpec(mTmpRect.width() + padding.left + padding.right,
1257 MeasureSpec.EXACTLY),
1258 MeasureSpec.makeMeasureSpec(mTmpRect.height() + padding.top + padding.bottom,
1259 MeasureSpec.EXACTLY));
1263 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1264 // Layout each of the TaskViews
1265 mTmpTaskViews.clear();
1266 mTmpTaskViews.addAll(getTaskViews());
1267 mTmpTaskViews.addAll(mViewPool.getViews());
1268 int taskViewCount = mTmpTaskViews.size();
1269 for (int i = 0; i < taskViewCount; i++) {
1270 layoutTaskView(changed, mTmpTaskViews.get(i));
1274 if (mStackScroller.isScrollOutOfBounds()) {
1275 mStackScroller.boundScroll();
1279 // Relayout all of the task views including the ignored ones
1280 relayoutTaskViews(AnimationProps.IMMEDIATE);
1283 if (mAwaitingFirstLayout || !mEnterAnimationComplete) {
1284 mAwaitingFirstLayout = false;
1285 mInitialState = INITIAL_STATE_UPDATE_NONE;
1291 * Lays out a TaskView.
1293 private void layoutTaskView(boolean changed, TaskView tv) {
1295 Rect padding = new Rect();
1296 if (tv.getBackground() != null) {
1297 tv.getBackground().getPadding(padding);
1299 mTmpRect.set(mStableLayoutAlgorithm.mTaskRect);
1300 mTmpRect.union(mLayoutAlgorithm.mTaskRect);
1301 tv.cancelTransformAnimation();
1302 tv.layout(mTmpRect.left - padding.left, mTmpRect.top - padding.top,
1303 mTmpRect.right + padding.right, mTmpRect.bottom + padding.bottom);
1305 // If the layout has not changed, then just lay it out again in-place
1306 tv.layout(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom());
1310 /** Handler for the first layout. */
1311 void onFirstLayout() {
1312 // Setup the view for the enter animation
1313 mAnimationHelper.prepareForEnterAnimation();
1315 // Animate in the freeform workspace
1316 int ffBgAlpha = mLayoutAlgorithm.getStackState().freeformBackgroundAlpha;
1317 animateFreeformWorkspaceBackgroundAlpha(ffBgAlpha, new AnimationProps(150,
1318 Interpolators.FAST_OUT_SLOW_IN));
1320 // Set the task focused state without requesting view focus, and leave the focus animations
1321 // until after the enter-animation
1322 RecentsConfiguration config = Recents.getConfiguration();
1323 RecentsActivityLaunchState launchState = config.getLaunchState();
1324 int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount());
1325 if (focusedTaskIndex != -1) {
1326 setFocusedTask(focusedTaskIndex, false /* scrollToTask */,
1327 false /* requestViewFocus */);
1330 // Update the stack action button visibility
1331 if (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
1332 mStack.getTaskCount() > 0) {
1333 EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
1335 EventBus.getDefault().send(new HideStackActionButtonEvent());
1339 public boolean isTouchPointInView(float x, float y, TaskView tv) {
1340 mTmpRect.set(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom());
1341 mTmpRect.offset((int) tv.getTranslationX(), (int) tv.getTranslationY());
1342 return mTmpRect.contains((int) x, (int) y);
1346 * Returns a non-ignored task in the {@param tasks} list that can be used as an achor when
1347 * calculating the scroll position before and after a layout change.
1349 public Task findAnchorTask(List<Task> tasks, MutableBoolean isFrontMostTask) {
1350 for (int i = tasks.size() - 1; i >= 0; i--) {
1351 Task task = tasks.get(i);
1353 // Ignore deleting tasks
1354 if (isIgnoredTask(task)) {
1355 if (i == tasks.size() - 1) {
1356 isFrontMostTask.value = true;
1366 protected void onDraw(Canvas canvas) {
1367 super.onDraw(canvas);
1369 // Draw the freeform workspace background
1370 SystemServicesProxy ssp = Recents.getSystemServices();
1371 if (ssp.hasFreeformWorkspaceSupport()) {
1372 if (mFreeformWorkspaceBackground.getAlpha() > 0) {
1373 mFreeformWorkspaceBackground.draw(canvas);
1379 protected boolean verifyDrawable(Drawable who) {
1380 if (who == mFreeformWorkspaceBackground) {
1383 return super.verifyDrawable(who);
1387 * Launches the freeform tasks.
1389 public boolean launchFreeformTasks() {
1390 ArrayList<Task> tasks = mStack.getFreeformTasks();
1391 if (!tasks.isEmpty()) {
1392 Task frontTask = tasks.get(tasks.size() - 1);
1393 if (frontTask != null && frontTask.isFreeformTask()) {
1394 EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(frontTask),
1395 frontTask, null, INVALID_STACK_ID, false));
1402 /**** TaskStackCallbacks Implementation ****/
1405 public void onStackTaskAdded(TaskStack stack, Task newTask) {
1406 // Update the min/max scroll and animate other task views into their new positions
1407 updateLayoutAlgorithm(true /* boundScroll */);
1409 // Animate all the tasks into place
1410 relayoutTaskViews(mAwaitingFirstLayout
1411 ? AnimationProps.IMMEDIATE
1412 : new AnimationProps(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN));
1416 * We expect that the {@link TaskView} associated with the removed task is already hidden.
1419 public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask,
1420 AnimationProps animation, boolean fromDockGesture) {
1421 if (mFocusedTask == removedTask) {
1422 resetFocusedTask(removedTask);
1425 // Remove the view associated with this task, we can't rely on updateTransforms
1426 // to work here because the task is no longer in the list
1427 TaskView tv = getChildViewForTask(removedTask);
1429 mViewPool.returnViewToPool(tv);
1432 // Remove the task from the ignored set
1433 removeIgnoreTask(removedTask);
1435 // If requested, relayout with the given animation
1436 if (animation != null) {
1437 updateLayoutAlgorithm(true /* boundScroll */);
1438 relayoutTaskViews(animation);
1441 // Update the new front most task's action button
1442 if (mScreenPinningEnabled && newFrontMostTask != null) {
1443 TaskView frontTv = getChildViewForTask(newFrontMostTask);
1444 if (frontTv != null) {
1445 frontTv.showActionButton(true /* fadeIn */, DEFAULT_SYNC_STACK_DURATION);
1449 // If there are no remaining tasks, then just close recents
1450 if (mStack.getTaskCount() == 0) {
1451 EventBus.getDefault().send(new AllTaskViewsDismissedEvent(fromDockGesture
1452 ? R.string.recents_empty_message
1453 : R.string.recents_empty_message_dismissed_all));
1458 public void onStackTasksRemoved(TaskStack stack) {
1459 // Reset the focused task
1460 resetFocusedTask(getFocusedTask());
1462 // Return all the views to the pool
1463 List<TaskView> taskViews = new ArrayList<>();
1464 taskViews.addAll(getTaskViews());
1465 for (int i = taskViews.size() - 1; i >= 0; i--) {
1466 mViewPool.returnViewToPool(taskViews.get(i));
1469 // Remove all the ignore tasks
1470 mIgnoreTasks.clear();
1472 // If there are no remaining tasks, then just close recents
1473 EventBus.getDefault().send(new AllTaskViewsDismissedEvent(
1474 R.string.recents_empty_message_dismissed_all));
1478 public void onStackTasksUpdated(TaskStack stack) {
1479 // Update the layout and immediately layout
1480 updateLayoutAlgorithm(false /* boundScroll */);
1481 relayoutTaskViews(AnimationProps.IMMEDIATE);
1483 // Rebind all the task views. This will not trigger new resources to be loaded
1484 // unless they have actually changed
1485 List<TaskView> taskViews = getTaskViews();
1486 int taskViewCount = taskViews.size();
1487 for (int i = 0; i < taskViewCount; i++) {
1488 TaskView tv = taskViews.get(i);
1489 bindTaskView(tv, tv.getTask());
1493 /**** ViewPoolConsumer Implementation ****/
1496 public TaskView createView(Context context) {
1497 return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
1501 public void onReturnViewToPool(TaskView tv) {
1502 final Task task = tv.getTask();
1504 // Unbind the task from the task view
1505 unbindTaskView(tv, task);
1507 // Reset the view properties and view state
1508 tv.clearAccessibilityFocus();
1509 tv.resetViewProperties();
1510 tv.setFocusedState(false, false /* requestViewFocus */);
1511 tv.setClipViewInStack(false);
1512 if (mScreenPinningEnabled) {
1513 tv.hideActionButton(false /* fadeOut */, 0 /* duration */, false /* scaleDown */, null);
1516 // Detach the view from the hierarchy
1517 detachViewFromParent(tv);
1518 // Update the task views list after removing the task view
1519 updateTaskViewsList();
1523 public void onPickUpViewFromPool(TaskView tv, Task task, boolean isNewView) {
1524 // Find the index where this task should be placed in the stack
1525 int taskIndex = mStack.indexOfStackTask(task);
1526 int insertIndex = findTaskViewInsertIndex(task, taskIndex);
1528 // Add/attach the view to the hierarchy
1530 if (mInMeasureLayout) {
1531 // If we are measuring the layout, then just add the view normally as it will be
1532 // laid out during the layout pass
1533 addView(tv, insertIndex);
1535 // Otherwise, this is from a bindVisibleTaskViews() call outside the measure/layout
1536 // pass, and we should layout the new child ourselves
1537 ViewGroup.LayoutParams params = tv.getLayoutParams();
1538 if (params == null) {
1539 params = generateDefaultLayoutParams();
1541 addViewInLayout(tv, insertIndex, params, true /* preventRequestLayout */);
1542 measureTaskView(tv);
1543 layoutTaskView(true /* changed */, tv);
1546 attachViewToParent(tv, insertIndex, tv.getLayoutParams());
1548 // Update the task views list after adding the new task view
1549 updateTaskViewsList();
1551 // Bind the task view to the new task
1552 bindTaskView(tv, task);
1554 // If the doze trigger has already fired, then update the state for this task view
1555 if (mUIDozeTrigger.isAsleep()) {
1556 tv.setNoUserInteractionState();
1559 // Set the new state for this view, including the callbacks and view clipping
1560 tv.setCallbacks(this);
1561 tv.setTouchEnabled(true);
1562 tv.setClipViewInStack(true);
1563 if (mFocusedTask == task) {
1564 tv.setFocusedState(true, false /* requestViewFocus */);
1565 if (mStartTimerIndicatorDuration > 0) {
1566 // The timer indicator couldn't be started before, so start it now
1567 tv.getHeaderView().startFocusTimerIndicator(mStartTimerIndicatorDuration);
1568 mStartTimerIndicatorDuration = 0;
1572 // Restore the action button visibility if it is the front most task view
1573 if (mScreenPinningEnabled && tv.getTask() ==
1574 mStack.getStackFrontMostTask(false /* includeFreeform */)) {
1575 tv.showActionButton(false /* fadeIn */, 0 /* fadeInDuration */);
1580 public boolean hasPreferredData(TaskView tv, Task preferredData) {
1581 return (tv.getTask() == preferredData);
1584 private void bindTaskView(TaskView tv, Task task) {
1585 // Rebind the task and request that this task's data be filled into the TaskView
1586 tv.onTaskBound(task, mTouchExplorationEnabled, mDisplayOrientation, mDisplayRect);
1588 // Load the task data
1589 Recents.getTaskLoader().loadTaskData(task);
1592 private void unbindTaskView(TaskView tv, Task task) {
1593 // Report that this task's data is no longer being used
1594 Recents.getTaskLoader().unloadTaskData(task);
1597 /**** TaskViewCallbacks Implementation ****/
1600 public void onTaskViewClipStateChanged(TaskView tv) {
1601 if (!mTaskViewsClipDirty) {
1602 mTaskViewsClipDirty = true;
1607 /**** TaskStackLayoutAlgorithm.TaskStackLayoutAlgorithmCallbacks ****/
1610 public void onFocusStateChanged(int prevFocusState, int curFocusState) {
1611 if (mDeferredTaskViewLayoutAnimation == null) {
1612 mUIDozeTrigger.poke();
1613 relayoutTaskViewsOnNextFrame(AnimationProps.IMMEDIATE);
1617 /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
1620 public void onStackScrollChanged(float prevScroll, float curScroll, AnimationProps animation) {
1621 mUIDozeTrigger.poke();
1622 if (animation != null) {
1623 relayoutTaskViewsOnNextFrame(animation);
1626 if (mEnterAnimationComplete) {
1627 if (prevScroll > SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
1628 curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
1629 mStack.getTaskCount() > 0) {
1630 EventBus.getDefault().send(new ShowStackActionButtonEvent(true /* translate */));
1631 } else if (prevScroll < HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
1632 curScroll >= HIDE_STACK_ACTION_BUTTON_SCROLL_THRESHOLD) {
1633 EventBus.getDefault().send(new HideStackActionButtonEvent());
1638 /**** EventBus Events ****/
1640 public final void onBusEvent(PackagesChangedEvent event) {
1641 // Compute which components need to be removed
1642 ArraySet<ComponentName> removedComponents = mStack.computeComponentsRemoved(
1643 event.packageName, event.userId);
1645 // For other tasks, just remove them directly if they no longer exist
1646 ArrayList<Task> tasks = mStack.getStackTasks();
1647 for (int i = tasks.size() - 1; i >= 0; i--) {
1648 final Task t = tasks.get(i);
1649 if (removedComponents.contains(t.key.getComponent())) {
1650 final TaskView tv = getChildViewForTask(t);
1652 // For visible children, defer removing the task until after the animation
1655 // Otherwise, remove the task from the stack immediately
1656 mStack.removeTask(t, AnimationProps.IMMEDIATE, false /* fromDockGesture */);
1662 public final void onBusEvent(LaunchTaskEvent event) {
1663 // Cancel any doze triggers once a task is launched
1664 mUIDozeTrigger.stopDozing();
1667 public final void onBusEvent(LaunchNextTaskRequestEvent event) {
1668 int launchTaskIndex = mStack.indexOfStackTask(mStack.getLaunchTarget());
1669 if (launchTaskIndex != -1) {
1670 launchTaskIndex = Math.max(0, launchTaskIndex - 1);
1672 launchTaskIndex = mStack.getTaskCount() - 1;
1674 if (launchTaskIndex != -1) {
1675 // Stop all animations
1676 cancelAllTaskViewAnimations();
1678 final Task launchTask = mStack.getStackTasks().get(launchTaskIndex);
1679 float curScroll = mStackScroller.getStackScroll();
1680 float targetScroll = mLayoutAlgorithm.getStackScrollForTaskAtInitialOffset(launchTask);
1681 float absScrollDiff = Math.abs(targetScroll - curScroll);
1682 if (getChildViewForTask(launchTask) == null || absScrollDiff > 0.35f) {
1683 int duration = (int) (LAUNCH_NEXT_SCROLL_BASE_DURATION +
1684 absScrollDiff * LAUNCH_NEXT_SCROLL_INCR_DURATION);
1685 mStackScroller.animateScroll(targetScroll,
1686 duration, new Runnable() {
1689 EventBus.getDefault().send(new LaunchTaskEvent(
1690 getChildViewForTask(launchTask), launchTask, null,
1691 INVALID_STACK_ID, false /* screenPinningRequested */));
1695 EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(launchTask),
1696 launchTask, null, INVALID_STACK_ID, false /* screenPinningRequested */));
1699 MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_LAUNCH_PREVIOUS_TASK,
1700 launchTask.key.getComponent().toString());
1701 } else if (mStack.getTaskCount() == 0) {
1702 // If there are no tasks, then just hide recents back to home.
1703 EventBus.getDefault().send(new HideRecentsEvent(false, true));
1707 public final void onBusEvent(LaunchTaskStartedEvent event) {
1708 mAnimationHelper.startLaunchTaskAnimation(event.taskView, event.screenPinningRequested,
1709 event.getAnimationTrigger());
1712 public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
1713 // Stop any scrolling
1714 mTouchHandler.cancelNonDismissTaskAnimations();
1715 mStackScroller.stopScroller();
1716 mStackScroller.stopBoundScrollAnimation();
1717 cancelDeferredTaskViewLayoutAnimation();
1719 // Start the task animations
1720 mAnimationHelper.startExitToHomeAnimation(event.animated, event.getAnimationTrigger());
1722 // Dismiss the freeform workspace background
1723 int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
1724 animateFreeformWorkspaceBackgroundAlpha(0, new AnimationProps(taskViewExitToHomeDuration,
1725 Interpolators.FAST_OUT_SLOW_IN));
1728 public final void onBusEvent(DismissFocusedTaskViewEvent event) {
1729 if (mFocusedTask != null) {
1730 TaskView tv = getChildViewForTask(mFocusedTask);
1734 resetFocusedTask(mFocusedTask);
1738 public final void onBusEvent(DismissTaskViewEvent event) {
1739 // For visible children, defer removing the task until after the animation
1740 mAnimationHelper.startDeleteTaskAnimation(event.taskView, event.getAnimationTrigger());
1743 public final void onBusEvent(final DismissAllTaskViewsEvent event) {
1744 // Keep track of the tasks which will have their data removed
1745 ArrayList<Task> tasks = new ArrayList<>(mStack.getStackTasks());
1746 mAnimationHelper.startDeleteAllTasksAnimation(getTaskViews(), event.getAnimationTrigger());
1747 event.addPostAnimationCallback(new Runnable() {
1750 // Announce for accessibility
1751 announceForAccessibility(getContext().getString(
1752 R.string.accessibility_recents_all_items_dismissed));
1754 // Remove all tasks and delete the task data for all tasks
1755 mStack.removeAllTasks();
1756 for (int i = tasks.size() - 1; i >= 0; i--) {
1757 EventBus.getDefault().send(new DeleteTaskDataEvent(tasks.get(i)));
1760 MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_DISMISS_ALL);
1766 public final void onBusEvent(TaskViewDismissedEvent event) {
1767 // Announce for accessibility
1768 announceForAccessibility(getContext().getString(
1769 R.string.accessibility_recents_item_dismissed, event.task.title));
1771 // Remove the task from the stack
1772 mStack.removeTask(event.task, event.animation, false /* fromDockGesture */);
1773 EventBus.getDefault().send(new DeleteTaskDataEvent(event.task));
1775 MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_DISMISS,
1776 event.task.key.getComponent().toString());
1779 public final void onBusEvent(FocusNextTaskViewEvent event) {
1780 // Stop any scrolling
1781 mStackScroller.stopScroller();
1782 mStackScroller.stopBoundScrollAnimation();
1784 setRelativeFocusedTask(true, false /* stackTasksOnly */, true /* animated */, false,
1785 event.timerIndicatorDuration);
1788 public final void onBusEvent(FocusPreviousTaskViewEvent event) {
1789 // Stop any scrolling
1790 mStackScroller.stopScroller();
1791 mStackScroller.stopBoundScrollAnimation();
1793 setRelativeFocusedTask(false, false /* stackTasksOnly */, true /* animated */);
1796 public final void onBusEvent(UserInteractionEvent event) {
1797 // Poke the doze trigger on user interaction
1798 mUIDozeTrigger.poke();
1800 RecentsDebugFlags debugFlags = Recents.getDebugFlags();
1801 if (debugFlags.isFastToggleRecentsEnabled() && mFocusedTask != null) {
1802 TaskView tv = getChildViewForTask(mFocusedTask);
1804 tv.getHeaderView().cancelFocusTimerIndicator();
1809 public final void onBusEvent(DragStartEvent event) {
1810 // Ensure that the drag task is not animated
1811 addIgnoreTask(event.task);
1813 if (event.task.isFreeformTask()) {
1814 // Animate to the front of the stack
1815 mStackScroller.animateScroll(mLayoutAlgorithm.mInitialScrollP, null);
1818 // Enlarge the dragged view slightly
1819 float finalScale = event.taskView.getScaleX() * DRAG_SCALE_FACTOR;
1820 mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(),
1821 mTmpTransform, null);
1822 mTmpTransform.scale = finalScale;
1823 mTmpTransform.translationZ = mLayoutAlgorithm.mMaxTranslationZ + 1;
1824 mTmpTransform.dimAlpha = 0f;
1825 updateTaskViewToTransform(event.taskView, mTmpTransform,
1826 new AnimationProps(DRAG_SCALE_DURATION, Interpolators.FAST_OUT_SLOW_IN));
1829 public final void onBusEvent(DragStartInitializeDropTargetsEvent event) {
1830 SystemServicesProxy ssp = Recents.getSystemServices();
1831 if (ssp.hasFreeformWorkspaceSupport()) {
1832 event.handler.registerDropTargetForCurrentDrag(mStackDropTarget);
1833 event.handler.registerDropTargetForCurrentDrag(mFreeformWorkspaceDropTarget);
1837 public final void onBusEvent(DragDropTargetChangedEvent event) {
1838 AnimationProps animation = new AnimationProps(SLOW_SYNC_STACK_DURATION,
1839 Interpolators.FAST_OUT_SLOW_IN);
1840 boolean ignoreTaskOverrides = false;
1841 if (event.dropTarget instanceof TaskStack.DockState) {
1842 // Calculate the new task stack bounds that matches the window size that Recents will
1843 // have after the drop
1844 final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
1845 Rect systemInsets = new Rect(mStableLayoutAlgorithm.mSystemInsets);
1846 // When docked, the nav bar insets are consumed and the activity is measured without
1847 // insets. However, the window bounds include the insets, so we need to subtract them
1848 // here to make them identical.
1849 int height = getMeasuredHeight();
1850 height -= systemInsets.bottom;
1851 systemInsets.bottom = 0;
1852 mStackBounds.set(dockState.getDockedTaskStackBounds(mDisplayRect, getMeasuredWidth(),
1853 height, mDividerSize, systemInsets,
1854 mLayoutAlgorithm, getResources(), mWindowRect));
1855 mLayoutAlgorithm.setSystemInsets(systemInsets);
1856 mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds,
1857 TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
1858 updateLayoutAlgorithm(true /* boundScroll */);
1859 ignoreTaskOverrides = true;
1861 // Restore the pre-drag task stack bounds, but ensure that we don't layout the dragging
1862 // task view, so add it back to the ignore set after updating the layout
1863 removeIgnoreTask(event.task);
1864 updateLayoutToStableBounds();
1865 addIgnoreTask(event.task);
1867 relayoutTaskViews(animation, null /* animationOverrides */, ignoreTaskOverrides);
1870 public final void onBusEvent(final DragEndEvent event) {
1871 // We don't handle drops on the dock regions
1872 if (event.dropTarget instanceof TaskStack.DockState) {
1873 // However, we do need to reset the overrides, since the last state of this task stack
1874 // view layout was ignoring task overrides (see DragDropTargetChangedEvent handler)
1875 mLayoutAlgorithm.clearUnfocusedTaskOverrides();
1879 boolean isFreeformTask = event.task.isFreeformTask();
1880 boolean hasChangedStacks =
1881 (!isFreeformTask && event.dropTarget == mFreeformWorkspaceDropTarget) ||
1882 (isFreeformTask && event.dropTarget == mStackDropTarget);
1884 if (hasChangedStacks) {
1885 // Move the task to the right position in the stack (ie. the front of the stack if
1886 // freeform or the front of the stack if fullscreen). Note, we MUST move the tasks
1887 // before we update their stack ids, otherwise, the keys will have changed.
1888 if (event.dropTarget == mFreeformWorkspaceDropTarget) {
1889 mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID);
1890 } else if (event.dropTarget == mStackDropTarget) {
1891 mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID);
1893 updateLayoutAlgorithm(true /* boundScroll */);
1895 // Move the task to the new stack in the system after the animation completes
1896 event.addPostAnimationCallback(new Runnable() {
1899 SystemServicesProxy ssp = Recents.getSystemServices();
1900 ssp.moveTaskToStack(event.task.key.id, event.task.key.stackId);
1905 // Restore the task, so that relayout will apply to it below
1906 removeIgnoreTask(event.task);
1908 // Convert the dragging task view back to its final layout-space rect
1909 Utilities.setViewFrameFromTranslation(event.taskView);
1911 // Animate all the tasks into place
1912 ArrayMap<Task, AnimationProps> animationOverrides = new ArrayMap<>();
1913 animationOverrides.put(event.task, new AnimationProps(SLOW_SYNC_STACK_DURATION,
1914 Interpolators.FAST_OUT_SLOW_IN,
1915 event.getAnimationTrigger().decrementOnAnimationEnd()));
1916 relayoutTaskViews(new AnimationProps(SLOW_SYNC_STACK_DURATION,
1917 Interpolators.FAST_OUT_SLOW_IN));
1918 event.getAnimationTrigger().increment();
1921 public final void onBusEvent(final DragEndCancelledEvent event) {
1922 // Restore the pre-drag task stack bounds, including the dragging task view
1923 removeIgnoreTask(event.task);
1924 updateLayoutToStableBounds();
1926 // Convert the dragging task view back to its final layout-space rect
1927 Utilities.setViewFrameFromTranslation(event.taskView);
1929 // Animate all the tasks into place
1930 ArrayMap<Task, AnimationProps> animationOverrides = new ArrayMap<>();
1931 animationOverrides.put(event.task, new AnimationProps(SLOW_SYNC_STACK_DURATION,
1932 Interpolators.FAST_OUT_SLOW_IN,
1933 event.getAnimationTrigger().decrementOnAnimationEnd()));
1934 relayoutTaskViews(new AnimationProps(SLOW_SYNC_STACK_DURATION,
1935 Interpolators.FAST_OUT_SLOW_IN));
1936 event.getAnimationTrigger().increment();
1939 public final void onBusEvent(IterateRecentsEvent event) {
1940 if (!mEnterAnimationComplete) {
1941 // Cancel the previous task's window transition before animating the focused state
1942 EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(null));
1946 public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
1947 mEnterAnimationComplete = true;
1949 if (mStack.getTaskCount() > 0) {
1950 // Start the task enter animations
1951 mAnimationHelper.startEnterAnimation(event.getAnimationTrigger());
1953 // Add a runnable to the post animation ref counter to clear all the views
1954 event.addPostAnimationCallback(new Runnable() {
1957 // Start the dozer to trigger to trigger any UI that shows after a timeout
1958 mUIDozeTrigger.startDozing();
1960 // Update the focused state here -- since we only set the focused task without
1961 // requesting view focus in onFirstLayout(), actually request view focus and
1962 // animate the focused state if we are alt-tabbing now, after the window enter
1963 // animation is completed
1964 if (mFocusedTask != null) {
1965 RecentsConfiguration config = Recents.getConfiguration();
1966 RecentsActivityLaunchState launchState = config.getLaunchState();
1967 setFocusedTask(mStack.indexOfStackTask(mFocusedTask),
1968 false /* scrollToTask */, launchState.launchedWithAltTab);
1969 TaskView focusedTaskView = getChildViewForTask(mFocusedTask);
1970 if (mTouchExplorationEnabled && focusedTaskView != null) {
1971 focusedTaskView.requestAccessibilityFocus();
1975 EventBus.getDefault().send(new EnterRecentsTaskStackAnimationCompletedEvent());
1981 public final void onBusEvent(UpdateFreeformTaskViewVisibilityEvent event) {
1982 List<TaskView> taskViews = getTaskViews();
1983 int taskViewCount = taskViews.size();
1984 for (int i = 0; i < taskViewCount; i++) {
1985 TaskView tv = taskViews.get(i);
1986 Task task = tv.getTask();
1987 if (task.isFreeformTask()) {
1988 tv.setVisibility(event.visible ? View.VISIBLE : View.INVISIBLE);
1993 public final void onBusEvent(final MultiWindowStateChangedEvent event) {
1994 if (event.inMultiWindow || !event.showDeferredAnimation) {
1995 setTasks(event.stack, true /* allowNotifyStackChanges */);
1997 // Reset the launch state before handling the multiwindow change
1998 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
1999 launchState.reset();
2001 // Defer until the next frame to ensure that we have received all the system insets, and
2002 // initial layout updates
2003 event.getAnimationTrigger().increment();
2004 post(new Runnable() {
2007 // Scroll the stack to the front to see the undocked task
2008 mAnimationHelper.startNewStackScrollAnimation(event.stack,
2009 event.getAnimationTrigger());
2010 event.getAnimationTrigger().decrement();
2016 public final void onBusEvent(ConfigurationChangedEvent event) {
2017 if (event.fromDeviceOrientationChange) {
2018 mDisplayOrientation = Utilities.getAppConfiguration(mContext).orientation;
2019 mDisplayRect = Recents.getSystemServices().getDisplayRect();
2021 // Always stop the scroller, otherwise, we may continue setting the stack scroll to the
2022 // wrong bounds in the new layout
2023 mStackScroller.stopScroller();
2025 reloadOnConfigurationChange();
2027 // Notify the task views of the configuration change so they can reload their resources
2028 if (!event.fromMultiWindow) {
2029 mTmpTaskViews.clear();
2030 mTmpTaskViews.addAll(getTaskViews());
2031 mTmpTaskViews.addAll(mViewPool.getViews());
2032 int taskViewCount = mTmpTaskViews.size();
2033 for (int i = 0; i < taskViewCount; i++) {
2034 mTmpTaskViews.get(i).onConfigurationChanged();
2038 // Trigger a new layout and update to the initial state if necessary
2039 if (event.fromMultiWindow) {
2040 mInitialState = INITIAL_STATE_UPDATE_LAYOUT_ONLY;
2042 } else if (event.fromDeviceOrientationChange) {
2043 mInitialState = INITIAL_STATE_UPDATE_ALL;
2048 public final void onBusEvent(RecentsGrowingEvent event) {
2049 mResetToInitialStateWhenResized = true;
2052 public void reloadOnConfigurationChange() {
2053 mStableLayoutAlgorithm.reloadOnConfigurationChange(getContext());
2054 mLayoutAlgorithm.reloadOnConfigurationChange(getContext());
2058 * Starts an alpha animation on the freeform workspace background.
2060 private void animateFreeformWorkspaceBackgroundAlpha(int targetAlpha,
2061 AnimationProps animation) {
2062 if (mFreeformWorkspaceBackground.getAlpha() == targetAlpha) {
2066 Utilities.cancelAnimationWithoutCallbacks(mFreeformWorkspaceBackgroundAnimator);
2067 mFreeformWorkspaceBackgroundAnimator = ObjectAnimator.ofInt(mFreeformWorkspaceBackground,
2068 Utilities.DRAWABLE_ALPHA, mFreeformWorkspaceBackground.getAlpha(), targetAlpha);
2069 mFreeformWorkspaceBackgroundAnimator.setStartDelay(
2070 animation.getDuration(AnimationProps.ALPHA));
2071 mFreeformWorkspaceBackgroundAnimator.setDuration(
2072 animation.getDuration(AnimationProps.ALPHA));
2073 mFreeformWorkspaceBackgroundAnimator.setInterpolator(
2074 animation.getInterpolator(AnimationProps.ALPHA));
2075 mFreeformWorkspaceBackgroundAnimator.start();
2079 * Returns the insert index for the task in the current set of task views. If the given task
2080 * is already in the task view list, then this method returns the insert index assuming it
2081 * is first removed at the previous index.
2083 * @param task the task we are finding the index for
2084 * @param taskIndex the index of the task in the stack
2086 private int findTaskViewInsertIndex(Task task, int taskIndex) {
2087 if (taskIndex != -1) {
2088 List<TaskView> taskViews = getTaskViews();
2089 boolean foundTaskView = false;
2090 int taskViewCount = taskViews.size();
2091 for (int i = 0; i < taskViewCount; i++) {
2092 Task tvTask = taskViews.get(i).getTask();
2093 if (tvTask == task) {
2094 foundTaskView = true;
2095 } else if (taskIndex < mStack.indexOfStackTask(tvTask)) {
2096 if (foundTaskView) {
2108 * Reads current system flags related to accessibility and screen pinning.
2110 private void readSystemFlags() {
2111 SystemServicesProxy ssp = Recents.getSystemServices();
2112 mTouchExplorationEnabled = ssp.isTouchExplorationEnabled();
2113 mScreenPinningEnabled = ssp.getSystemSetting(getContext(),
2114 Settings.System.LOCK_TO_APP_ENABLED) != 0;
2117 public void dump(String prefix, PrintWriter writer) {
2118 String innerPrefix = prefix + " ";
2119 String id = Integer.toHexString(System.identityHashCode(this));
2121 writer.print(prefix); writer.print(TAG);
2122 writer.print(" hasDefRelayout=");
2123 writer.print(mDeferredTaskViewLayoutAnimation != null ? "Y" : "N");
2124 writer.print(" clipDirty="); writer.print(mTaskViewsClipDirty ? "Y" : "N");
2125 writer.print(" awaitingFirstLayout="); writer.print(mAwaitingFirstLayout ? "Y" : "N");
2126 writer.print(" initialState="); writer.print(mInitialState);
2127 writer.print(" inMeasureLayout="); writer.print(mInMeasureLayout ? "Y" : "N");
2128 writer.print(" enterAnimCompleted="); writer.print(mEnterAnimationComplete ? "Y" : "N");
2129 writer.print(" touchExplorationOn="); writer.print(mTouchExplorationEnabled ? "Y" : "N");
2130 writer.print(" screenPinningOn="); writer.print(mScreenPinningEnabled ? "Y" : "N");
2131 writer.print(" numIgnoreTasks="); writer.print(mIgnoreTasks.size());
2132 writer.print(" numViewPool="); writer.print(mViewPool.getViews().size());
2133 writer.print(" stableStackBounds="); writer.print(Utilities.dumpRect(mStableStackBounds));
2134 writer.print(" stackBounds="); writer.print(Utilities.dumpRect(mStackBounds));
2135 writer.print(" stableWindow="); writer.print(Utilities.dumpRect(mStableWindowRect));
2136 writer.print(" window="); writer.print(Utilities.dumpRect(mWindowRect));
2137 writer.print(" display="); writer.print(Utilities.dumpRect(mDisplayRect));
2138 writer.print(" orientation="); writer.print(mDisplayOrientation);
2139 writer.print(" [0x"); writer.print(id); writer.print("]");
2142 if (mFocusedTask != null) {
2143 writer.print(innerPrefix);
2144 writer.print("Focused task: ");
2145 mFocusedTask.dump("", writer);
2148 mLayoutAlgorithm.dump(innerPrefix, writer);
2149 mStackScroller.dump(innerPrefix, writer);