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 android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ObjectAnimator;
22 import android.animation.ValueAnimator;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.graphics.Canvas;
26 import android.graphics.Rect;
27 import android.view.LayoutInflater;
28 import android.view.MotionEvent;
29 import android.view.View;
30 import android.widget.FrameLayout;
31 import android.widget.OverScroller;
32 import com.android.systemui.R;
33 import com.android.systemui.recents.Constants;
34 import com.android.systemui.recents.RecentsConfiguration;
35 import com.android.systemui.recents.misc.Console;
36 import com.android.systemui.recents.misc.DozeTrigger;
37 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
38 import com.android.systemui.recents.misc.Utilities;
39 import com.android.systemui.recents.model.RecentsPackageMonitor;
40 import com.android.systemui.recents.model.RecentsTaskLoader;
41 import com.android.systemui.recents.model.Task;
42 import com.android.systemui.recents.model.TaskStack;
44 import java.util.ArrayList;
45 import java.util.HashMap;
46 import java.util.HashSet;
49 /* The visual representation of a task stack view */
50 public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
51 TaskView.TaskViewCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task>,
52 RecentsPackageMonitor.PackageCallbacks {
54 /** The TaskView callbacks */
55 interface TaskStackViewCallbacks {
56 public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t,
58 public void onTaskViewAppInfoClicked(Task t);
59 public void onTaskViewDismissed(Task t);
60 public void onAllTaskViewsDismissed();
61 public void onTaskStackFilterTriggered();
62 public void onTaskStackUnfilterTriggered();
65 RecentsConfiguration mConfig;
68 TaskStackViewLayoutAlgorithm mStackAlgorithm;
69 TaskStackViewFilterAlgorithm mFilterAlgorithm;
70 TaskStackViewTouchHandler mTouchHandler;
71 TaskStackViewCallbacks mCb;
72 ViewPool<TaskView, Task> mViewPool;
73 ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<TaskViewTransform>();
74 DozeTrigger mUIDozeTrigger;
75 DebugOverlayView mDebugOverlay;
76 Rect mTaskStackBounds = new Rect();
78 // The virtual stack scroll that we use for the card layout
83 int mFocusedTaskIndex = -1;
84 OverScroller mScroller;
85 ObjectAnimator mScrollAnimator;
86 boolean mEnableStackClipping = true;
89 ReferenceCountedTrigger mHwLayersTrigger;
90 int mStackViewsAnimationDuration;
91 boolean mStackViewsDirty = true;
92 boolean mAwaitingFirstLayout = true;
93 boolean mStartEnterAnimationRequestedAfterLayout;
94 ViewAnimation.TaskViewEnterContext mStartEnterAnimationContext;
95 int[] mTmpVisibleRange = new int[2];
96 Rect mTmpRect = new Rect();
97 Rect mTmpRect2 = new Rect();
98 TaskViewTransform mTmpTransform = new TaskViewTransform();
99 HashMap<Task, TaskView> mTmpTaskViewMap = new HashMap<Task, TaskView>();
100 LayoutInflater mInflater;
102 // A convenience runnable to return all views to the pool
103 // XXX: After this is set, we should mark this task stack view as disabled and check that in synchronize model
104 Runnable mReturnAllViewsToPoolRunnable = new Runnable() {
107 int childCount = getChildCount();
108 for (int i = childCount - 1; i >= 0; i--) {
109 mViewPool.returnViewToPool((TaskView) getChildAt(i));
114 public TaskStackView(Context context, TaskStack stack) {
116 mConfig = RecentsConfiguration.getInstance();
118 mStack.setCallbacks(this);
119 mScroller = new OverScroller(context);
120 mTouchHandler = new TaskStackViewTouchHandler(context, this);
121 mViewPool = new ViewPool<TaskView, Task>(context, this);
122 mInflater = LayoutInflater.from(context);
123 mStackAlgorithm = new TaskStackViewLayoutAlgorithm(mConfig);
124 mFilterAlgorithm = new TaskStackViewFilterAlgorithm(mConfig, this, mViewPool);
125 mUIDozeTrigger = new DozeTrigger(mConfig.taskBarDismissDozeDelaySeconds, new Runnable() {
128 // Show the task bar dismiss buttons
129 int childCount = getChildCount();
130 for (int i = 0; i < childCount; i++) {
131 TaskView tv = (TaskView) getChildAt(i);
132 tv.startNoUserInteractionAnimation();
136 mHwLayersTrigger = new ReferenceCountedTrigger(getContext(), new Runnable() {
139 // Enable hw layers on each of the children
140 int childCount = getChildCount();
141 for (int i = 0; i < childCount; i++) {
142 TaskView tv = (TaskView) getChildAt(i);
149 // Disable hw layers on each of the children
150 int childCount = getChildCount();
151 for (int i = 0; i < childCount; i++) {
152 TaskView tv = (TaskView) getChildAt(i);
153 tv.disableHwLayers();
159 new Throwable("Invalid hw layers ref count").printStackTrace();
160 Console.logError(getContext(), "Invalid HW layers ref count");
165 /** Sets the callbacks */
166 void setCallbacks(TaskStackViewCallbacks cb) {
170 /** Sets the debug overlay */
171 public void setDebugOverlay(DebugOverlayView overlay) {
172 mDebugOverlay = overlay;
175 /** Requests that the views be synchronized with the model */
176 void requestSynchronizeStackViewsWithModel() {
177 requestSynchronizeStackViewsWithModel(0);
179 void requestSynchronizeStackViewsWithModel(int duration) {
180 if (!mStackViewsDirty) {
181 invalidate(mStackAlgorithm.mStackRect);
182 mStackViewsDirty = true;
184 if (mAwaitingFirstLayout) {
185 // Skip the animation if we are awaiting first layout
186 mStackViewsAnimationDuration = 0;
188 mStackViewsAnimationDuration = Math.max(mStackViewsAnimationDuration, duration);
192 /** Finds the child view given a specific task. */
193 public TaskView getChildViewForTask(Task t) {
194 int childCount = getChildCount();
195 for (int i = 0; i < childCount; i++) {
196 TaskView tv = (TaskView) getChildAt(i);
197 if (tv.getTask() == t) {
204 /** Returns the stack algorithm for this task stack. */
205 public TaskStackViewLayoutAlgorithm getStackAlgorithm() {
206 return mStackAlgorithm;
210 * Gets the stack transforms of a list of tasks, and returns the visible range of tasks.
212 private boolean updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms,
213 ArrayList<Task> tasks,
215 int[] visibleRangeOut,
216 boolean boundTranslationsToRect) {
217 // XXX: We should be intelligent about wheee to look for the visible stack range using the
218 // current stack scroll.
219 // XXX: We should log extra cases like the ones below where we don't expect to hit very often
220 // XXX: Print out approximately how many indices we have to go through to find the first visible transform
222 int taskTransformCount = taskTransforms.size();
223 int taskCount = tasks.size();
224 int frontMostVisibleIndex = -1;
225 int backMostVisibleIndex = -1;
229 // We can reuse the task transforms where possible to reduce object allocation
230 if (taskTransformCount < taskCount) {
231 // If there are less transforms than tasks, then add as many transforms as necessary
232 for (int i = taskTransformCount; i < taskCount; i++) {
233 taskTransforms.add(new TaskViewTransform());
235 } else if (taskTransformCount > taskCount) {
236 // If there are more transforms than tasks, then just subset the transform list
237 taskTransforms.subList(0, taskCount);
240 // Update the stack transforms
241 for (int i = taskCount - 1; i >= 0; i--) {
242 TaskViewTransform transform = mStackAlgorithm.getStackTransform(tasks.get(i), stackScroll, taskTransforms.get(i));
243 if (transform.visible) {
244 if (frontMostVisibleIndex < 0) {
245 frontMostVisibleIndex = i;
247 backMostVisibleIndex = i;
249 if (backMostVisibleIndex != -1) {
250 // We've reached the end of the visible range, so going down the rest of the
251 // stack, we can just reset the transforms accordingly
253 taskTransforms.get(i).reset();
260 if (boundTranslationsToRect) {
261 transform.translationY = Math.min(transform.translationY,
262 mStackAlgorithm.mViewRect.bottom);
265 if (visibleRangeOut != null) {
266 visibleRangeOut[0] = frontMostVisibleIndex;
267 visibleRangeOut[1] = backMostVisibleIndex;
269 return frontMostVisibleIndex != -1 && backMostVisibleIndex != -1;
273 * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. This
274 * call is less optimal than calling updateStackTransforms directly.
276 private ArrayList<TaskViewTransform> getStackTransforms(ArrayList<Task> tasks,
278 int[] visibleRangeOut,
279 boolean boundTranslationsToRect) {
280 ArrayList<TaskViewTransform> taskTransforms = new ArrayList<TaskViewTransform>();
281 updateStackTransforms(taskTransforms, tasks, stackScroll, visibleRangeOut,
282 boundTranslationsToRect);
283 return taskTransforms;
286 /** Synchronizes the views with the model */
287 boolean synchronizeStackViewsWithModel() {
288 if (mStackViewsDirty) {
289 // Get all the task transforms
290 ArrayList<Task> tasks = mStack.getTasks();
291 int stackScroll = getStackScroll();
292 int[] visibleRange = mTmpVisibleRange;
293 boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms, tasks, stackScroll, visibleRange, false);
295 // Return all the invisible children to the pool
296 mTmpTaskViewMap.clear();
297 int childCount = getChildCount();
298 for (int i = childCount - 1; i >= 0; i--) {
299 TaskView tv = (TaskView) getChildAt(i);
300 Task task = tv.getTask();
301 int taskIndex = mStack.indexOfTask(task);
302 if (visibleRange[1] <= taskIndex && taskIndex <= visibleRange[0]) {
303 mTmpTaskViewMap.put(task, tv);
305 mViewPool.returnViewToPool(tv);
309 // Pick up all the newly visible children and update all the existing children
310 for (int i = visibleRange[0]; isValidVisibleRange && i >= visibleRange[1]; i--) {
311 Task task = tasks.get(i);
312 TaskViewTransform transform = mCurrentTaskTransforms.get(i);
313 TaskView tv = mTmpTaskViewMap.get(task);
314 int taskIndex = mStack.indexOfTask(task);
317 tv = mViewPool.pickUpViewFromPool(task, task);
318 if (mStackViewsAnimationDuration > 0) {
319 // For items in the list, put them in start animating them from the
320 // approriate ends of the list where they are expected to appear
321 if (transform.t < 0) {
322 mTmpTransform = mStackAlgorithm.getStackTransform(tasks.get(0), stackScroll, mTmpTransform);
324 mTmpTransform = mStackAlgorithm.getStackTransform(tasks.get(Math.min(tasks.size() - 1, visibleRange[0] + 1)),
325 stackScroll, mTmpTransform);
327 tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0);
331 // Animate the task into place
332 tv.updateViewPropertiesToTaskTransform(mCurrentTaskTransforms.get(taskIndex),
333 mStackViewsAnimationDuration);
336 // Reset the request-synchronize params
337 mStackViewsAnimationDuration = 0;
338 mStackViewsDirty = false;
344 /** Updates the clip for each of the task views. */
345 void clipTaskViews() {
346 // Update the clip on each task child
347 if (Constants.DebugFlags.App.EnableTaskStackClipping && mEnableStackClipping) {
348 int childCount = getChildCount();
349 for (int i = 0; i < childCount - 1; i++) {
350 TaskView tv = (TaskView) getChildAt(i);
351 TaskView nextTv = null;
352 TaskView tmpTv = null;
354 if (tv.shouldClipViewInStack()) {
355 // Find the next view to clip against
357 while (nextIndex < getChildCount()) {
358 tmpTv = (TaskView) getChildAt(++nextIndex);
359 if (tmpTv != null && tmpTv.shouldClipViewInStack()) {
365 // Clip against the next view, this is just an approximation since we are
366 // stacked and we can make assumptions about the visibility of the this
367 // task relative to the ones in front of it.
368 if (nextTv != null) {
369 // We calculate the bottom clip independent of the footer (since we animate
371 int scaledMaxFooterHeight = (int) (tv.getMaxFooterHeight() * tv.getScaleX());
372 tv.getHitRect(mTmpRect);
373 nextTv.getHitRect(mTmpRect2);
374 clipBottom = (mTmpRect.bottom - scaledMaxFooterHeight - mTmpRect2.top);
377 tv.getViewBounds().setClipBottom(clipBottom);
379 if (getChildCount() > 0) {
380 // The front most task should never be clipped
381 TaskView tv = (TaskView) getChildAt(getChildCount() - 1);
382 tv.getViewBounds().setClipBottom(0);
387 /** Enables/Disables clipping of the tasks in the stack. */
388 void setStackClippingEnabled(boolean stackClippingEnabled) {
389 if (!stackClippingEnabled) {
390 int childCount = getChildCount();
391 for (int i = 0; i < childCount; i++) {
392 TaskView tv = (TaskView) getChildAt(i);
393 tv.getViewBounds().setClipBottom(0);
396 mEnableStackClipping = stackClippingEnabled;
399 /** The stack insets to apply to the stack contents */
400 public void setStackInsetRect(Rect r) {
401 mTaskStackBounds.set(r);
404 /** Sets the current stack scroll */
405 public void setStackScroll(int value) {
406 mStackScroll = value;
407 mUIDozeTrigger.poke();
408 requestSynchronizeStackViewsWithModel();
411 /** Sets the current stack scroll without synchronizing the stack view with the model */
412 public void setStackScrollRaw(int value) {
413 mStackScroll = value;
414 mUIDozeTrigger.poke();
416 /** Sets the current stack scroll to the initial state when you first enter recents */
417 public void setStackScrollToInitialState() {
418 setStackScroll(getInitialStackScroll());
420 /** Computes the initial stack scroll for the stack. */
421 int getInitialStackScroll() {
422 if (mStack.getTaskCount() > 2) {
423 return Math.max(mMinScroll, mMaxScroll - (int) (mStackAlgorithm.mTaskRect.height() * (3f/4f)));
428 /** Gets the current stack scroll */
429 public int getStackScroll() {
433 /** Animates the stack scroll into bounds */
434 ObjectAnimator animateBoundScroll() {
435 int curScroll = getStackScroll();
436 int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
437 if (newScroll != curScroll) {
438 // Enable hw layers on the stack
439 addHwLayersRefCount("animateBoundScroll");
441 // Start a new scroll animation
442 animateScroll(curScroll, newScroll, new Runnable() {
445 // Disable hw layers on the stack
446 decHwLayersRefCount("animateBoundScroll");
450 return mScrollAnimator;
453 /** Animates the stack scroll */
454 void animateScroll(int curScroll, int newScroll, final Runnable postRunnable) {
455 // Abort any current animations
457 abortBoundScrollAnimation();
459 mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll);
460 mScrollAnimator.setDuration(Utilities.calculateTranslationAnimationDuration(newScroll -
462 mScrollAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
463 mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
465 public void onAnimationUpdate(ValueAnimator animation) {
466 setStackScroll((Integer) animation.getAnimatedValue());
469 mScrollAnimator.addListener(new AnimatorListenerAdapter() {
471 public void onAnimationEnd(Animator animation) {
472 if (postRunnable != null) {
475 mScrollAnimator.removeAllListeners();
478 mScrollAnimator.start();
481 /** Aborts any current stack scrolls */
482 void abortBoundScrollAnimation() {
483 if (mScrollAnimator != null) {
484 mScrollAnimator.cancel();
488 /** Aborts the scroller and any current fling */
489 void abortScroller() {
490 if (!mScroller.isFinished()) {
491 // Abort the scroller
492 mScroller.abortAnimation();
493 // And disable hw layers on the stack
494 decHwLayersRefCount("flingScroll");
498 /** Bounds the current scroll if necessary */
499 public boolean boundScroll() {
500 int curScroll = getStackScroll();
501 int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
502 if (newScroll != curScroll) {
503 setStackScroll(newScroll);
510 * Bounds the current scroll if necessary, but does not synchronize the stack view with the
513 public boolean boundScrollRaw() {
514 int curScroll = getStackScroll();
515 int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll, curScroll));
516 if (newScroll != curScroll) {
517 setStackScrollRaw(newScroll);
524 /** Returns the amount that the scroll is out of bounds */
525 int getScrollAmountOutOfBounds(int scroll) {
526 if (scroll < mMinScroll) {
527 return mMinScroll - scroll;
528 } else if (scroll > mMaxScroll) {
529 return scroll - mMaxScroll;
534 /** Returns whether the specified scroll is out of bounds */
535 boolean isScrollOutOfBounds() {
536 return getScrollAmountOutOfBounds(mStackScroll) != 0;
539 /** Updates the min and max virtual scroll bounds */
540 void updateMinMaxScroll(boolean boundScrollToNewMinMax) {
541 // Compute the min and max scroll values
542 mStackAlgorithm.computeMinMaxScroll(mStack.getTasks());
543 mMinScroll = mStackAlgorithm.mMinScroll;
544 mMaxScroll = mStackAlgorithm.mMaxScroll;
547 if (boundScrollToNewMinMax) {
552 /** Animates a task view in this stack as it launches. */
553 public void animateOnLaunchingTask(TaskView tv, final Runnable r) {
554 // Hide each of the task bar dismiss buttons
555 int childCount = getChildCount();
556 for (int i = 0; i < childCount; i++) {
557 TaskView t = (TaskView) getChildAt(i);
559 t.startLaunchTaskAnimation(r, true);
561 t.startLaunchTaskAnimation(null, false);
566 /** Focuses the task at the specified index in the stack */
567 void focusTask(int taskIndex, boolean scrollToNewPosition) {
568 if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
569 mFocusedTaskIndex = taskIndex;
571 // Focus the view if possible, otherwise, focus the view after we scroll into position
572 Task t = mStack.getTasks().get(taskIndex);
573 TaskView tv = getChildViewForTask(t);
574 Runnable postScrollRunnable = null;
578 postScrollRunnable = new Runnable() {
581 Task t = mStack.getTasks().get(mFocusedTaskIndex);
582 TaskView tv = getChildViewForTask(t);
590 if (scrollToNewPosition) {
591 // Scroll the view into position
592 int newScroll = Math.max(mMinScroll, Math.min(mMaxScroll,
593 mStackAlgorithm.getStackScrollForTaskIndex(t)));
595 animateScroll(getStackScroll(), newScroll, postScrollRunnable);
597 if (postScrollRunnable != null) {
598 postScrollRunnable.run();
604 /** Focuses the next task in the stack */
605 void focusNextTask(boolean forward) {
606 // Find the next index to focus
607 int numTasks = mStack.getTaskCount();
608 if (mFocusedTaskIndex < 0) {
609 mFocusedTaskIndex = numTasks - 1;
611 if (0 <= mFocusedTaskIndex && mFocusedTaskIndex < numTasks) {
612 mFocusedTaskIndex = Math.max(0, Math.min(numTasks - 1,
613 mFocusedTaskIndex + (forward ? -1 : 1)));
615 focusTask(mFocusedTaskIndex, true);
618 /** Enables the hw layers and increments the hw layer requirement ref count */
619 void addHwLayersRefCount(String reason) {
620 mHwLayersTrigger.increment();
623 /** Decrements the hw layer requirement ref count and disables the hw layers when we don't
624 need them anymore. */
625 void decHwLayersRefCount(String reason) {
626 mHwLayersTrigger.decrement();
630 public boolean onInterceptTouchEvent(MotionEvent ev) {
631 return mTouchHandler.onInterceptTouchEvent(ev);
635 public boolean onTouchEvent(MotionEvent ev) {
636 return mTouchHandler.onTouchEvent(ev);
640 public void computeScroll() {
641 if (mScroller.computeScrollOffset()) {
642 setStackScroll(mScroller.getCurrY());
645 // If we just finished scrolling, then disable the hw layers
646 if (mScroller.isFinished()) {
647 decHwLayersRefCount("finishedFlingScroll");
653 public void dispatchDraw(Canvas canvas) {
654 if (synchronizeStackViewsWithModel()) {
657 super.dispatchDraw(canvas);
660 /** Computes the stack and task rects */
661 public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds) {
662 // Compute the rects in the stack algorithm
663 mStackAlgorithm.computeRects(mStack.getTasks(), windowWidth, windowHeight, taskStackBounds);
665 // Update the scroll bounds
666 updateMinMaxScroll(false);
670 * This is called with the full window width and height to allow stack view children to
671 * perform the full screen transition down.
674 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
675 int width = MeasureSpec.getSize(widthMeasureSpec);
676 int height = MeasureSpec.getSize(heightMeasureSpec);
678 // Compute our stack/task rects
679 Rect taskStackBounds = new Rect(mTaskStackBounds);
680 taskStackBounds.bottom -= mConfig.systemInsets.bottom;
681 computeRects(width, height, taskStackBounds);
683 // If this is the first layout, then scroll to the front of the stack and synchronize the
684 // stack views immediately to load all the views
685 if (mAwaitingFirstLayout) {
686 setStackScrollToInitialState();
687 requestSynchronizeStackViewsWithModel();
688 synchronizeStackViewsWithModel();
690 // Find the first task and mark it as full screen
691 if (mConfig.launchedFromAppWithScreenshot) {
692 int childCount = getChildCount();
693 for (int i = 0; i < childCount; i++) {
694 TaskView tv = (TaskView) getChildAt(i);
695 if (tv.getTask().isLaunchTarget) {
696 tv.setIsFullScreen(true);
703 // Measure each of the TaskViews
704 int childCount = getChildCount();
705 for (int i = 0; i < childCount; i++) {
706 TaskView tv = (TaskView) getChildAt(i);
707 if (tv.isFullScreenView()) {
708 tv.measure(widthMeasureSpec, heightMeasureSpec);
711 MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.width(),
712 MeasureSpec.EXACTLY),
713 MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.height() +
714 tv.getMaxFooterHeight(), MeasureSpec.EXACTLY));
718 setMeasuredDimension(width, height);
722 * This is called with the size of the space not including the top or right insets, or the
723 * search bar height in portrait (but including the search bar width in landscape, since we want
727 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
728 // Layout each of the children
729 int childCount = getChildCount();
730 for (int i = 0; i < childCount; i++) {
731 TaskView tv = (TaskView) getChildAt(i);
732 if (tv.isFullScreenView()) {
733 tv.layout(left, top, left + tv.getMeasuredWidth(), top + tv.getMeasuredHeight());
735 tv.layout(mStackAlgorithm.mTaskRect.left, mStackAlgorithm.mTaskRect.top,
736 mStackAlgorithm.mTaskRect.right, mStackAlgorithm.mTaskRect.bottom +
737 tv.getMaxFooterHeight());
741 if (mAwaitingFirstLayout) {
742 mAwaitingFirstLayout = false;
747 /** Handler for the first layout. */
748 void onFirstLayout() {
749 // Prepare the first view for its enter animation
750 int offscreenY = mStackAlgorithm.mViewRect.bottom -
751 (mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mViewRect.top);
752 int childCount = getChildCount();
753 for (int i = childCount - 1; i >= 0; i--) {
754 TaskView tv = (TaskView) getChildAt(i);
755 tv.prepareEnterRecentsAnimation(tv.getTask().isLaunchTarget, offscreenY);
758 // If the enter animation started already and we haven't completed a layout yet, do the
759 // enter animation now
760 if (mStartEnterAnimationRequestedAfterLayout) {
761 startEnterRecentsAnimation(mStartEnterAnimationContext);
762 mStartEnterAnimationRequestedAfterLayout = false;
763 mStartEnterAnimationContext = null;
766 // Update the focused task index to be the next item to the top task
767 if (mConfig.launchedWithAltTab) {
768 // When alt-tabbing, we focus the next previous task
769 focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
773 /** Requests this task stacks to start it's enter-recents animation */
774 public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) {
775 // If we are still waiting to layout, then just defer until then
776 if (mAwaitingFirstLayout) {
777 mStartEnterAnimationRequestedAfterLayout = true;
778 mStartEnterAnimationContext = ctx;
782 if (mStack.getTaskCount() > 0) {
783 // Animate all the task views into view
784 int childCount = getChildCount();
785 for (int i = childCount - 1; i >= 0; i--) {
786 TaskView tv = (TaskView) getChildAt(i);
787 Task task = tv.getTask();
788 ctx.currentTaskTransform = new TaskViewTransform();
789 ctx.currentStackViewIndex = i;
790 ctx.currentStackViewCount = childCount;
791 ctx.currentTaskRect = mStackAlgorithm.mTaskRect;
792 mStackAlgorithm.getStackTransform(task, getStackScroll(), ctx.currentTaskTransform);
793 tv.startEnterRecentsAnimation(ctx);
796 // Add a runnable to the post animation ref counter to clear all the views
797 ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
801 mUIDozeTrigger.startDozing();
802 // Request an update of the task views after the animation in to
803 // relayout the fullscreen view back to its normal size
804 if (mConfig.launchedFromAppWithScreenshot) {
805 requestSynchronizeStackViewsWithModel();
812 /** Requests this task stacks to start it's exit-recents animation. */
813 public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
814 // Animate all the task views into view
815 ctx.offscreenTranslationY = mStackAlgorithm.mViewRect.bottom -
816 (mStackAlgorithm.mTaskRect.top - mStackAlgorithm.mViewRect.top);
817 int childCount = getChildCount();
818 for (int i = 0; i < childCount; i++) {
819 TaskView tv = (TaskView) getChildAt(i);
820 tv.startExitToHomeAnimation(ctx);
823 // Add a runnable to the post animation ref counter to clear all the views
824 ctx.postAnimationTrigger.addLastDecrementRunnable(mReturnAllViewsToPoolRunnable);
828 protected void onScrollChanged(int l, int t, int oldl, int oldt) {
829 super.onScrollChanged(l, t, oldl, oldt);
830 requestSynchronizeStackViewsWithModel();
833 public boolean isTransformedTouchPointInView(float x, float y, View child) {
834 return isTransformedTouchPointInView(x, y, child, null);
837 /** Pokes the dozer on user interaction. */
838 void onUserInteraction() {
839 // Poke the doze trigger if it is dozing
840 mUIDozeTrigger.poke();
843 /**** TaskStackCallbacks Implementation ****/
846 public void onStackTaskAdded(TaskStack stack, Task t) {
847 // Update the task offsets
848 mStackAlgorithm.updateTaskOffsets(mStack.getTasks());
850 requestSynchronizeStackViewsWithModel();
854 public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask) {
855 // Update the task offsets
856 mStackAlgorithm.updateTaskOffsets(mStack.getTasks());
858 // Remove the view associated with this task, we can't rely on updateTransforms
859 // to work here because the task is no longer in the list
860 TaskView tv = getChildViewForTask(removedTask);
862 mViewPool.returnViewToPool(tv);
865 // Notify the callback that we've removed the task and it can clean up after it
866 mCb.onTaskViewDismissed(removedTask);
868 // Update the min/max scroll and animate other task views into their new positions
869 updateMinMaxScroll(true);
870 int movement = (int) mStackAlgorithm.getTaskOverlapHeight();
871 requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement));
873 // Update the new front most task
874 if (newFrontMostTask != null) {
875 TaskView frontTv = getChildViewForTask(newFrontMostTask);
876 if (frontTv != null) {
877 frontTv.onTaskBound(newFrontMostTask);
881 // If there are no remaining tasks, then either unfilter the current stack, or just close
882 // the activity if there are no filtered stacks
883 if (mStack.getTaskCount() == 0) {
884 boolean shouldFinishActivity = true;
885 if (mStack.hasFilteredTasks()) {
886 mStack.unfilterTasks();
887 shouldFinishActivity = (mStack.getTaskCount() == 0);
889 if (shouldFinishActivity) {
890 mCb.onAllTaskViewsDismissed();
896 public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks,
898 // Stash the scroll and filtered task for us to restore to when we unfilter
899 mStashedScroll = getStackScroll();
901 // Calculate the current task transforms
902 ArrayList<TaskViewTransform> curTaskTransforms =
903 getStackTransforms(curTasks, getStackScroll(), null, true);
905 // Update the task offsets
906 mStackAlgorithm.updateTaskOffsets(mStack.getTasks());
908 // Scroll the item to the top of the stack (sans-peek) rect so that we can see it better
909 updateMinMaxScroll(false);
910 float overlapHeight = mStackAlgorithm.getTaskOverlapHeight();
911 setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight));
914 // Compute the transforms of the items in the new stack after setting the new scroll
915 final ArrayList<Task> tasks = mStack.getTasks();
916 final ArrayList<TaskViewTransform> taskTransforms =
917 getStackTransforms(mStack.getTasks(), getStackScroll(), null, true);
920 mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
922 // Notify any callbacks
923 mCb.onTaskStackFilterTriggered();
927 public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) {
928 // Calculate the current task transforms
929 final ArrayList<TaskViewTransform> curTaskTransforms =
930 getStackTransforms(curTasks, getStackScroll(), null, true);
932 // Update the task offsets
933 mStackAlgorithm.updateTaskOffsets(mStack.getTasks());
935 // Restore the stashed scroll
936 updateMinMaxScroll(false);
937 setStackScrollRaw(mStashedScroll);
940 // Compute the transforms of the items in the new stack after restoring the stashed scroll
941 final ArrayList<Task> tasks = mStack.getTasks();
942 final ArrayList<TaskViewTransform> taskTransforms =
943 getStackTransforms(tasks, getStackScroll(), null, true);
946 mFilterAlgorithm.startFilteringAnimation(curTasks, curTaskTransforms, tasks, taskTransforms);
948 // Clear the saved vars
951 // Notify any callbacks
952 mCb.onTaskStackUnfilterTriggered();
955 /**** ViewPoolConsumer Implementation ****/
958 public TaskView createView(Context context) {
959 return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
963 public void prepareViewToEnterPool(TaskView tv) {
964 Task task = tv.getTask();
966 // Report that this tasks's data is no longer being used
967 RecentsTaskLoader.getInstance().unloadTaskData(task);
969 // Detach the view from the hierarchy
970 detachViewFromParent(tv);
973 tv.disableHwLayers();
975 // Reset the view properties
976 tv.resetViewProperties();
980 public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) {
981 // Rebind the task and request that this task's data be filled into the TaskView
982 tv.onTaskBound(task);
983 RecentsTaskLoader.getInstance().loadTaskData(task);
985 // Sanity check, the task view should always be clipping against the stack at this point,
986 // but just in case, re-enable it here
987 tv.setClipViewInStack(true);
989 // If the doze trigger has already fired, then update the state for this task view
990 if (mUIDozeTrigger.hasTriggered()) {
991 tv.setNoUserInteractionState();
994 // Find the index where this task should be placed in the stack
995 int insertIndex = -1;
996 int taskIndex = mStack.indexOfTask(task);
997 if (taskIndex != -1) {
998 int childCount = getChildCount();
999 for (int i = 0; i < childCount; i++) {
1000 Task tvTask = ((TaskView) getChildAt(i)).getTask();
1001 if (taskIndex < mStack.indexOfTask(tvTask)) {
1008 // Add/attach the view to the hierarchy
1010 addView(tv, insertIndex);
1012 // Set the callbacks and listeners for this new view
1013 tv.setTouchEnabled(true);
1014 tv.setCallbacks(this);
1016 attachViewToParent(tv, insertIndex, tv.getLayoutParams());
1019 // Enable hw layers on this view if hw layers are enabled on the stack
1020 if (mHwLayersTrigger.getCount() > 0) {
1021 tv.enableHwLayers();
1026 public boolean hasPreferredData(TaskView tv, Task preferredData) {
1027 return (tv.getTask() == preferredData);
1030 /**** TaskViewCallbacks Implementation ****/
1033 public void onTaskViewAppIconClicked(TaskView tv) {
1034 if (Constants.DebugFlags.App.EnableTaskFiltering) {
1035 if (mStack.hasFilteredTasks()) {
1036 mStack.unfilterTasks();
1038 mStack.filterTasks(tv.getTask());
1044 public void onTaskViewAppInfoClicked(TaskView tv) {
1046 mCb.onTaskViewAppInfoClicked(tv.getTask());
1051 public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask) {
1052 // Cancel any doze triggers
1053 mUIDozeTrigger.stopDozing();
1056 mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask);
1061 public void onTaskViewDismissed(TaskView tv) {
1062 Task task = tv.getTask();
1063 // Remove the task from the view
1064 mStack.removeTask(task);
1068 public void onTaskViewClipStateChanged(TaskView tv) {
1069 invalidate(mStackAlgorithm.mStackRect);
1072 /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
1075 public void onComponentRemoved(HashSet<ComponentName> cns) {
1076 // For other tasks, just remove them directly if they no longer exist
1077 ArrayList<Task> tasks = mStack.getTasks();
1078 for (int i = tasks.size() - 1; i >= 0; i--) {
1079 final Task t = tasks.get(i);
1080 if (cns.contains(t.key.baseIntent.getComponent())) {
1081 TaskView tv = getChildViewForTask(t);
1083 // For visible children, defer removing the task until after the animation
1084 tv.startDeleteTaskAnimation(new Runnable() {
1087 mStack.removeTask(t);
1091 // Otherwise, remove the task from the stack immediately
1092 mStack.removeTask(t);