RecentsView.java revision eca4ab6e99bcb2a7b31b8b4b1c3b5474297b6b25
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.recents.views;
18
19import android.app.ActivityOptions;
20import android.content.Context;
21import android.graphics.Bitmap;
22import android.graphics.Canvas;
23import android.graphics.Rect;
24import android.graphics.drawable.Drawable;
25import android.os.Bundle;
26import android.os.IRemoteCallback;
27import android.os.RemoteException;
28import android.util.ArraySet;
29import android.util.AttributeSet;
30import android.util.Log;
31import android.util.SparseArray;
32import android.view.AppTransitionAnimationSpec;
33import android.view.LayoutInflater;
34import android.view.MotionEvent;
35import android.view.View;
36import android.view.WindowInsets;
37import android.view.WindowManagerGlobal;
38import android.view.animation.AnimationUtils;
39import android.view.animation.Interpolator;
40import android.widget.FrameLayout;
41import com.android.internal.logging.MetricsLogger;
42import com.android.systemui.R;
43import com.android.systemui.recents.Constants;
44import com.android.systemui.recents.Recents;
45import com.android.systemui.recents.RecentsActivity;
46import com.android.systemui.recents.RecentsActivityLaunchState;
47import com.android.systemui.recents.RecentsAppWidgetHostView;
48import com.android.systemui.recents.RecentsConfiguration;
49import com.android.systemui.recents.events.EventBus;
50import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
51import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
52import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
53import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
54import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
55import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
56import com.android.systemui.recents.misc.SystemServicesProxy;
57import com.android.systemui.recents.model.Task;
58import com.android.systemui.recents.model.TaskStack;
59
60import java.util.ArrayList;
61import java.util.List;
62
63import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
64import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
65import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
66
67/**
68 * This view is the the top level layout that contains TaskStacks (which are laid out according
69 * to their SpaceNode bounds.
70 */
71public class RecentsView extends FrameLayout implements TaskStackView.TaskStackViewCallbacks {
72
73    private static final String TAG = "RecentsView";
74    private static final boolean DEBUG = false;
75
76    private static final boolean ADD_HEADER_BITMAP = true;
77
78    /** The RecentsView callbacks */
79    public interface RecentsViewCallbacks {
80        public void onTaskViewClicked();
81        public void onTaskLaunchFailed();
82        public void onAllTaskViewsDismissed();
83        public void runAfterPause(Runnable r);
84    }
85
86    LayoutInflater mInflater;
87
88    ArrayList<TaskStack> mStacks;
89    TaskStackView mTaskStackView;
90    RecentsAppWidgetHostView mSearchBar;
91    RecentsViewCallbacks mCb;
92
93    RecentsViewTouchHandler mTouchHandler;
94    DragView mDragView;
95    TaskStack.DockState[] mVisibleDockStates = {
96            TaskStack.DockState.LEFT,
97            TaskStack.DockState.TOP,
98            TaskStack.DockState.RIGHT,
99            TaskStack.DockState.BOTTOM,
100    };
101
102    Interpolator mFastOutSlowInInterpolator;
103
104    Rect mSystemInsets = new Rect();
105
106    public RecentsView(Context context) {
107        super(context);
108    }
109
110    public RecentsView(Context context, AttributeSet attrs) {
111        this(context, attrs, 0);
112    }
113
114    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
115        this(context, attrs, defStyleAttr, 0);
116    }
117
118    public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
119        super(context, attrs, defStyleAttr, defStyleRes);
120        setWillNotDraw(false);
121        mInflater = LayoutInflater.from(context);
122        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
123                com.android.internal.R.interpolator.fast_out_slow_in);
124        mTouchHandler = new RecentsViewTouchHandler(this);
125    }
126
127    /** Sets the callbacks */
128    public void setCallbacks(RecentsViewCallbacks cb) {
129        mCb = cb;
130    }
131
132    /** Set/get the bsp root node */
133    public void setTaskStack(TaskStack stack) {
134        RecentsConfiguration config = Recents.getConfiguration();
135        if (config.getLaunchState().launchedReuseTaskStackViews) {
136            if (mTaskStackView != null) {
137                // If onRecentsHidden is not triggered, we need to the stack view again here
138                mTaskStackView.reset();
139                mTaskStackView.setStack(stack);
140            } else {
141                mTaskStackView = new TaskStackView(getContext(), stack);
142                mTaskStackView.setCallbacks(this);
143                addView(mTaskStackView);
144            }
145        } else {
146            if (mTaskStackView != null) {
147                removeView(mTaskStackView);
148            }
149            mTaskStackView = new TaskStackView(getContext(), stack);
150            mTaskStackView.setCallbacks(this);
151            addView(mTaskStackView);
152        }
153
154        // Trigger a new layout
155        requestLayout();
156    }
157
158    /** Gets the next task in the stack - or if the last - the top task */
159    public Task getNextTaskOrTopTask(Task taskToSearch) {
160        Task returnTask = null;
161        boolean found = false;
162        if (mTaskStackView != null) {
163            TaskStack stack = mTaskStackView.getStack();
164            ArrayList<Task> taskList = stack.getTasks();
165            // Iterate the stack views and try and find the focused task
166            for (int j = taskList.size() - 1; j >= 0; --j) {
167                Task task = taskList.get(j);
168                // Return the next task in the line.
169                if (found)
170                    return task;
171                // Remember the first possible task as the top task.
172                if (returnTask == null)
173                    returnTask = task;
174                if (task == taskToSearch)
175                    found = true;
176            }
177        }
178        return returnTask;
179    }
180
181    /** Launches the focused task from the first stack if possible */
182    public boolean launchFocusedTask() {
183        if (mTaskStackView != null) {
184            TaskStack stack = mTaskStackView.getStack();
185            // Iterate the stack views and try and find the focused task
186            List<TaskView> taskViews = mTaskStackView.getTaskViews();
187            int taskViewCount = taskViews.size();
188            for (int j = 0; j < taskViewCount; j++) {
189                TaskView tv = taskViews.get(j);
190                Task task = tv.getTask();
191                if (tv.isFocusedTask()) {
192                    onTaskViewClicked(mTaskStackView, tv, stack, task, false, false, null,
193                            INVALID_STACK_ID);
194                    return true;
195                }
196            }
197        }
198        return false;
199    }
200
201    /** Launches a given task. */
202    public boolean launchTask(Task task, Rect taskBounds, int destinationStack) {
203        if (mTaskStackView != null) {
204            TaskStack stack = mTaskStackView.getStack();
205            // Iterate the stack views and try and find the given task.
206            List<TaskView> taskViews = mTaskStackView.getTaskViews();
207            int taskViewCount = taskViews.size();
208            for (int j = 0; j < taskViewCount; j++) {
209                TaskView tv = taskViews.get(j);
210                if (tv.getTask() == task) {
211                    onTaskViewClicked(mTaskStackView, tv, stack, task, false, taskBounds != null,
212                            taskBounds, destinationStack);
213                    return true;
214                }
215            }
216        }
217        return false;
218    }
219
220    /** Launches the task that Recents was launched from, if possible */
221    public boolean launchPreviousTask() {
222        if (mTaskStackView != null) {
223            TaskStack stack = mTaskStackView.getStack();
224            ArrayList<Task> tasks = stack.getTasks();
225
226            // Find the launch task in the stack
227            // TODO: replace this with an event from RecentsActivity
228            if (!tasks.isEmpty()) {
229                int taskCount = tasks.size();
230                for (int j = 0; j < taskCount; j++) {
231                    if (tasks.get(j).isLaunchTarget) {
232                        Task task = tasks.get(j);
233                        TaskView tv = mTaskStackView.getChildViewForTask(task);
234                        onTaskViewClicked(mTaskStackView, tv, stack, task, false, false, null,
235                                INVALID_STACK_ID);
236                        return true;
237                    }
238                }
239            }
240        }
241        return false;
242    }
243
244    /** Requests all task stacks to start their enter-recents animation */
245    public void startEnterRecentsAnimation(ViewAnimation.TaskViewEnterContext ctx) {
246        // We have to increment/decrement the post animation trigger in case there are no children
247        // to ensure that it runs
248        ctx.postAnimationTrigger.increment();
249        if (mTaskStackView != null) {
250            mTaskStackView.startEnterRecentsAnimation(ctx);
251        }
252        ctx.postAnimationTrigger.decrement();
253    }
254
255    /** Requests all task stacks to start their exit-recents animation */
256    public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
257        // We have to increment/decrement the post animation trigger in case there are no children
258        // to ensure that it runs
259        ctx.postAnimationTrigger.increment();
260        if (mTaskStackView != null) {
261            mTaskStackView.startExitToHomeAnimation(ctx);
262        }
263        ctx.postAnimationTrigger.decrement();
264
265        // Notify of the exit animation
266        EventBus.getDefault().send(new DismissRecentsToHomeAnimationStarted());
267    }
268
269    /** Adds the search bar */
270    public void setSearchBar(RecentsAppWidgetHostView searchBar) {
271        // Remove the previous search bar if one exists
272        if (mSearchBar != null && indexOfChild(mSearchBar) > -1) {
273            removeView(mSearchBar);
274        }
275        // Add the new search bar
276        if (searchBar != null) {
277            mSearchBar = searchBar;
278            addView(mSearchBar);
279        }
280    }
281
282    /** Returns whether there is currently a search bar */
283    public boolean hasValidSearchBar() {
284        return mSearchBar != null && !mSearchBar.isReinflateRequired();
285    }
286
287    /** Sets the visibility of the search bar */
288    public void setSearchBarVisibility(int visibility) {
289        if (mSearchBar != null) {
290            mSearchBar.setVisibility(visibility);
291            // Always bring the search bar to the top
292            mSearchBar.bringToFront();
293        }
294    }
295
296    @Override
297    protected void onAttachedToWindow() {
298        EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1);
299        EventBus.getDefault().register(mTouchHandler, RecentsActivity.EVENT_BUS_PRIORITY + 1);
300        super.onAttachedToWindow();
301    }
302
303    @Override
304    protected void onDetachedFromWindow() {
305        super.onDetachedFromWindow();
306        EventBus.getDefault().unregister(this);
307        EventBus.getDefault().unregister(mTouchHandler);
308    }
309
310    /**
311     * This is called with the full size of the window since we are handling our own insets.
312     */
313    @Override
314    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
315        RecentsConfiguration config = Recents.getConfiguration();
316        int width = MeasureSpec.getSize(widthMeasureSpec);
317        int height = MeasureSpec.getSize(heightMeasureSpec);
318
319        // Get the search bar bounds and measure the search bar layout
320        Rect searchBarSpaceBounds = new Rect();
321        if (mSearchBar != null) {
322            config.getSearchBarBounds(new Rect(0, 0, width, height), mSystemInsets.top,
323                    searchBarSpaceBounds);
324            mSearchBar.measure(
325                    MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY),
326                    MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.height(), MeasureSpec.EXACTLY));
327        }
328
329        Rect taskStackBounds = new Rect();
330        config.getTaskStackBounds(new Rect(0, 0, width, height), mSystemInsets.top,
331                mSystemInsets.right, searchBarSpaceBounds, taskStackBounds);
332        if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) {
333            mTaskStackView.setTaskStackBounds(taskStackBounds, mSystemInsets);
334            mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec);
335        }
336
337        if (mDragView != null) {
338            Rect taskRect = mTaskStackView.mLayoutAlgorithm.mTaskRect;
339            mDragView.measure(
340                    MeasureSpec.makeMeasureSpec(taskRect.width(), MeasureSpec.AT_MOST),
341                    MeasureSpec.makeMeasureSpec(taskRect.height(), MeasureSpec.AT_MOST));
342        }
343
344        setMeasuredDimension(width, height);
345    }
346
347    /**
348     * This is called with the full size of the window since we are handling our own insets.
349     */
350    @Override
351    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
352        RecentsConfiguration config = Recents.getConfiguration();
353
354        // Get the search bar bounds so that we lay it out
355        Rect measuredRect = new Rect(0, 0, getMeasuredWidth(), getMeasuredHeight());
356        Rect searchBarSpaceBounds = new Rect();
357        if (mSearchBar != null) {
358            config.getSearchBarBounds(measuredRect,
359                    mSystemInsets.top, searchBarSpaceBounds);
360            mSearchBar.layout(searchBarSpaceBounds.left, searchBarSpaceBounds.top,
361                    searchBarSpaceBounds.right, searchBarSpaceBounds.bottom);
362        }
363
364        if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) {
365            mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight());
366        }
367
368        if (mDragView != null) {
369            mDragView.layout(left, top, left + mDragView.getMeasuredWidth(),
370                    top + mDragView.getMeasuredHeight());
371        }
372    }
373
374    @Override
375    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
376        mSystemInsets.set(insets.getSystemWindowInsets());
377        requestLayout();
378        return insets.consumeSystemWindowInsets();
379    }
380
381    @Override
382    public boolean onInterceptTouchEvent(MotionEvent ev) {
383        return mTouchHandler.onInterceptTouchEvent(ev);
384    }
385
386    @Override
387    public boolean onTouchEvent(MotionEvent ev) {
388        return mTouchHandler.onTouchEvent(ev);
389    }
390
391    @Override
392    protected void dispatchDraw(Canvas canvas) {
393        super.dispatchDraw(canvas);
394        for (int i = mVisibleDockStates.length - 1; i >= 0; i--) {
395            Drawable d = mVisibleDockStates[i].viewState.dockAreaOverlay;
396            if (d.getAlpha() > 0) {
397                d.draw(canvas);
398            }
399        }
400    }
401
402    @Override
403    protected boolean verifyDrawable(Drawable who) {
404        for (int i = mVisibleDockStates.length - 1; i >= 0; i--) {
405            Drawable d = mVisibleDockStates[i].viewState.dockAreaOverlay;
406            if (d == who) {
407                return true;
408            }
409        }
410        return super.verifyDrawable(who);
411    }
412
413    /** Unfilters any filtered stacks */
414    public boolean unfilterFilteredStacks() {
415        if (mStacks != null) {
416            // Check if there are any filtered stacks and unfilter them before we back out of Recents
417            boolean stacksUnfiltered = false;
418            int numStacks = mStacks.size();
419            for (int i = 0; i < numStacks; i++) {
420                TaskStack stack = mStacks.get(i);
421                if (stack.hasFilteredTasks()) {
422                    stack.unfilterTasks();
423                    stacksUnfiltered = true;
424                }
425            }
426            return stacksUnfiltered;
427        }
428        return false;
429    }
430
431    public void disableLayersForOneFrame() {
432        if (mTaskStackView != null) {
433            mTaskStackView.disableLayersForOneFrame();
434        }
435    }
436
437    private void postDrawHeaderThumbnailTransitionRunnable(final TaskStackView view,
438            final TaskView clickedView, final int offsetX, final int offsetY,
439            final float stackScroll,
440            final ActivityOptions.OnAnimationStartedListener animStartedListener,
441            final int destinationStack) {
442        Runnable r = new Runnable() {
443            @Override
444            public void run() {
445                overrideDrawHeaderThumbnailTransition(view, clickedView, offsetX, offsetY,
446                        stackScroll, animStartedListener, destinationStack);
447
448            }
449        };
450
451        mCb.runAfterPause(r);
452    }
453
454    private void overrideDrawHeaderThumbnailTransition(TaskStackView stackView,
455            TaskView clickedTask, int offsetX, int offsetY, float stackScroll,
456            final ActivityOptions.OnAnimationStartedListener animStartedListener,
457            int destinationStack) {
458        List<AppTransitionAnimationSpec> specs = getAppTransitionAnimationSpecs(stackView,
459                clickedTask, offsetX, offsetY, stackScroll, destinationStack);
460        if (specs == null) {
461            return;
462        }
463
464        IRemoteCallback.Stub callback = new IRemoteCallback.Stub() {
465            @Override
466            public void sendResult(Bundle data) throws RemoteException {
467                post(new Runnable() {
468                    @Override
469                    public void run() {
470                        if (animStartedListener != null) {
471                            animStartedListener.onAnimationStarted();
472                        }
473                    }
474                });
475            }
476        };
477
478        AppTransitionAnimationSpec[] specsArray =
479                new AppTransitionAnimationSpec[specs.size()];
480        try {
481            WindowManagerGlobal.getWindowManagerService().overridePendingAppTransitionMultiThumb(
482                    specs.toArray(specsArray), callback, null, true /* scaleUp */);
483
484        } catch (RemoteException e) {
485            Log.w(TAG, "Error overriding app transition", e);
486        }
487    }
488
489    private List<AppTransitionAnimationSpec> getAppTransitionAnimationSpecs(TaskStackView stackView,
490            TaskView clickedTask, int offsetX, int offsetY, float stackScroll,
491            int destinationStack) {
492        final int targetStackId = destinationStack != INVALID_STACK_ID ?
493                destinationStack : clickedTask.getTask().key.stackId;
494        if (targetStackId != FREEFORM_WORKSPACE_STACK_ID
495                && targetStackId != FULLSCREEN_WORKSPACE_STACK_ID) {
496            return null;
497        }
498        // If this is a full screen stack, the transition will be towards the single, full screen
499        // task. We only need the transition spec for this task.
500        List<AppTransitionAnimationSpec> specs = new ArrayList<>();
501        if (targetStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
502            specs.add(createThumbnailHeaderAnimationSpec(
503                    stackView, offsetX, offsetY, stackScroll, clickedTask,
504                    clickedTask.getTask().key.id, ADD_HEADER_BITMAP));
505            return specs;
506        }
507        // This is a free form stack or full screen stack, so there will be multiple windows
508        // animating from thumbnails. We need transition animation specs for all of them.
509
510        // We will use top and bottom task views as a base for tasks, that aren't visible on the
511        // screen. This is necessary for cascade recents list, where some of the tasks might be
512        // hidden.
513        List<TaskView> taskViews = stackView.getTaskViews();
514        int childCount = taskViews.size();
515        TaskView topChild = taskViews.get(0);
516        TaskView bottomChild = taskViews.get(childCount - 1);
517        SparseArray<TaskView> taskViewsByTaskId = new SparseArray<>();
518        for (int i = 0; i < childCount; i++) {
519            TaskView taskView = taskViews.get(i);
520            taskViewsByTaskId.put(taskView.getTask().key.id, taskView);
521        }
522
523        TaskStack stack = stackView.getStack();
524        // We go through all tasks now and for each generate transition animation spec. If there is
525        // a view associated with a task, we use that view as a base for the animation. If there
526        // isn't, we use bottom or top view, depending on which one would be closer to the task
527        // view if it existed.
528        ArrayList<Task> tasks = stack.getTasks();
529        boolean passedClickedTask = false;
530        for (int i = 0, n = tasks.size(); i < n; i++) {
531            Task task = tasks.get(i);
532            TaskView taskView = taskViewsByTaskId.get(task.key.id);
533            if (taskView != null) {
534                specs.add(createThumbnailHeaderAnimationSpec(stackView, offsetX, offsetY,
535                        stackScroll, taskView, taskView.getTask().key.id, ADD_HEADER_BITMAP));
536                if (taskView == clickedTask) {
537                    passedClickedTask = true;
538                }
539            } else {
540                taskView = passedClickedTask ? bottomChild : topChild;
541                specs.add(createThumbnailHeaderAnimationSpec(stackView, offsetX, offsetY,
542                        stackScroll, taskView, task.key.id, !ADD_HEADER_BITMAP));
543            }
544        }
545
546        return specs;
547    }
548
549    private AppTransitionAnimationSpec createThumbnailHeaderAnimationSpec(TaskStackView stackView,
550            int offsetX, int offsetY, float stackScroll, TaskView tv, int taskId,
551            boolean addHeaderBitmap) {
552        // Disable any focused state before we draw the header
553        // Upfront the processing of the thumbnail
554        if (tv.isFocusedTask()) {
555            tv.setFocusedState(false, false /* animated */, false /* requestViewFocus */);
556        }
557        TaskViewTransform transform = new TaskViewTransform();
558        transform = stackView.getStackAlgorithm().getStackTransform(tv.mTask, stackScroll,
559                transform, null);
560
561        float scale = tv.getScaleX();
562        int fromHeaderWidth = (int) (tv.mHeaderView.getMeasuredWidth() * scale);
563        int fromHeaderHeight = (int) (tv.mHeaderView.getMeasuredHeight() * scale);
564
565        Bitmap b = null;
566        if (addHeaderBitmap) {
567            b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight,
568                    Bitmap.Config.ARGB_8888);
569
570            if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
571                b.eraseColor(0xFFff0000);
572            } else {
573                Canvas c = new Canvas(b);
574                c.scale(tv.getScaleX(), tv.getScaleY());
575                tv.mHeaderView.draw(c);
576                c.setBitmap(null);
577
578            }
579            b = b.createAshmemBitmap();
580        }
581
582        int[] pts = new int[2];
583        tv.getLocationOnScreen(pts);
584
585        final int left = pts[0] + offsetX;
586        final int top = pts[1] + offsetY;
587        final Rect rect = new Rect(left, top, left + (int) transform.rect.width(),
588                top + (int) transform.rect.height());
589
590        return new AppTransitionAnimationSpec(taskId, b, rect);
591    }
592
593    /**
594     * Cancels any running window transitions for the launched task (the task animating into
595     * Recents).
596     */
597    private void cancelLaunchedTaskWindowTransition(final Task task) {
598        SystemServicesProxy ssp = Recents.getSystemServices();
599        RecentsConfiguration config = Recents.getConfiguration();
600        RecentsActivityLaunchState launchState = config.getLaunchState();
601        if (launchState.launchedToTaskId != -1 &&
602                launchState.launchedToTaskId != task.key.id) {
603            ssp.cancelWindowTransition(launchState.launchedToTaskId);
604        }
605    }
606
607    /**** TaskStackView.TaskStackCallbacks Implementation ****/
608
609    @Override
610    public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv,
611            final TaskStack stack, final Task task, final boolean lockToTask,
612            final boolean boundsValid, final Rect bounds, int destinationStack) {
613        // Notify any callbacks of the launching of a new task
614        if (mCb != null) {
615            mCb.onTaskViewClicked();
616        }
617
618        // Upfront the processing of the thumbnail
619        TaskViewTransform transform = new TaskViewTransform();
620        View sourceView;
621        int offsetX = 0;
622        int offsetY = 0;
623        float stackScroll = stackView.getScroller().getStackScroll();
624        if (tv == null) {
625            // If there is no actual task view, then use the stack view as the source view
626            // and then offset to the expected transform rect, but bound this to just
627            // outside the display rect (to ensure we don't animate from too far away)
628            sourceView = stackView;
629            offsetX = (int) transform.rect.left;
630            offsetY = getMeasuredHeight();
631        } else {
632            sourceView = tv.mThumbnailView;
633        }
634
635        // Compute the thumbnail to scale up from
636        final SystemServicesProxy ssp = Recents.getSystemServices();
637        boolean screenPinningRequested = false;
638        ActivityOptions opts = null;
639        ActivityOptions.OnAnimationStartedListener animStartedListener = null;
640        if (task.thumbnail != null && task.thumbnail.getWidth() > 0 &&
641                task.thumbnail.getHeight() > 0) {
642            animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
643                @Override
644                public void onAnimationStarted() {
645                    // If we are launching into another task, cancel the previous task's
646                    // window transition
647                    cancelLaunchedTaskWindowTransition(task);
648
649                    if (lockToTask) {
650                        // Request screen pinning after the animation runs
651                        postDelayed(new Runnable() {
652                            @Override
653                            public void run() {
654                                EventBus.getDefault().send(new ScreenPinningRequestEvent(
655                                        getContext(), ssp));
656                            }
657                        }, 350);
658                    }
659                }
660            };
661            postDrawHeaderThumbnailTransitionRunnable(stackView, tv, offsetX, offsetY, stackScroll,
662                    animStartedListener, destinationStack);
663            opts = ActivityOptions.makeThumbnailAspectScaleUpAnimation(sourceView,
664                    Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8).createAshmemBitmap(),
665                    offsetX, offsetY, (int) transform.rect.width(), (int) transform.rect.height(),
666                    sourceView.getHandler(), animStartedListener);
667            screenPinningRequested = true;
668        } else {
669            opts = ActivityOptions.makeBasic();
670        }
671        if (boundsValid) {
672            opts.setBounds(bounds.isEmpty() ? null : bounds);
673        }
674        final ActivityOptions launchOpts = opts;
675        final boolean finalScreenPinningRequested = screenPinningRequested;
676        final Runnable launchRunnable = new Runnable() {
677            @Override
678            public void run() {
679                if (task.isActive) {
680                    // Bring an active task to the foreground
681                    ssp.moveTaskToFront(task.key.id, launchOpts);
682                } else {
683                    if (ssp.startActivityFromRecents(getContext(), task.key.id, task.activityLabel,
684                            launchOpts)) {
685                        if (!finalScreenPinningRequested) {
686                            // If we have not requested this already to be run after the window
687                            // transition, then just run it now
688                            EventBus.getDefault().send(new ScreenPinningRequestEvent(
689                                    getContext(), ssp));
690                        }
691                    } else {
692                        // Dismiss the task and return the user to home if we fail to
693                        // launch the task
694                        EventBus.getDefault().send(new DismissTaskViewEvent(task, tv));
695                        if (mCb != null) {
696                            mCb.onTaskLaunchFailed();
697                        }
698
699                        // Keep track of failed launches
700                        MetricsLogger.count(getContext(), "overview_task_launch_failed", 1);
701                    }
702                }
703            }
704        };
705
706        // Keep track of the index of the task launch
707        int taskIndexFromFront = 0;
708        int taskIndex = stack.indexOfTask(task);
709        if (taskIndex > -1) {
710            taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
711        }
712        MetricsLogger.histogram(getContext(), "overview_task_launch_index", taskIndexFromFront);
713
714        // Launch the app right away if there is no task view, otherwise, animate the icon out first
715        if (tv == null) {
716            launchRunnable.run();
717        } else {
718            if (task.group != null && !task.group.isFrontMostTask(task)) {
719                // For affiliated tasks that are behind other tasks, we must animate the front cards
720                // out of view before starting the task transition
721                stackView.startLaunchTaskAnimation(tv, launchRunnable, lockToTask);
722            } else {
723                // Otherwise, we can start the task transition immediately
724                stackView.startLaunchTaskAnimation(tv, null, lockToTask);
725                launchRunnable.run();
726            }
727        }
728    }
729
730    @Override
731    public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks) {
732        /* TODO: Not currently enabled
733        if (removedTasks != null) {
734            int taskCount = removedTasks.size();
735            for (int i = 0; i < taskCount; i++) {
736                onTaskViewDismissed(removedTasks.get(i));
737            }
738        }
739        */
740
741        mCb.onAllTaskViewsDismissed();
742
743        // Keep track of all-deletions
744        MetricsLogger.count(getContext(), "overview_task_all_dismissed", 1);
745    }
746
747    @Override
748    public void onTaskStackFilterTriggered() {
749        // Hide the search bar
750        if (mSearchBar != null) {
751            int filterDuration = getResources().getInteger(
752                    R.integer.recents_filter_animate_current_views_duration);
753            mSearchBar.animate()
754                    .alpha(0f)
755                    .setStartDelay(0)
756                    .setInterpolator(mFastOutSlowInInterpolator)
757                    .setDuration(filterDuration)
758                    .withLayer()
759                    .start();
760        }
761    }
762
763    @Override
764    public void onTaskStackUnfilterTriggered() {
765        // Show the search bar
766        if (mSearchBar != null) {
767            int filterDuration = getResources().getInteger(
768                    R.integer.recents_filter_animate_new_views_duration);
769            mSearchBar.animate()
770                    .alpha(1f)
771                    .setStartDelay(0)
772                    .setInterpolator(mFastOutSlowInInterpolator)
773                    .setDuration(filterDuration)
774                    .withLayer()
775                    .start();
776        }
777    }
778
779    /**** EventBus Events ****/
780
781    public final void onBusEvent(DragStartEvent event) {
782        // Add the drag view
783        mDragView = event.dragView;
784        addView(mDragView);
785
786        updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(),
787                TaskStack.DockState.NONE.viewState.dockAreaAlpha);
788    }
789
790    public final void onBusEvent(DragDropTargetChangedEvent event) {
791        if (event.dropTarget == null || !(event.dropTarget instanceof TaskStack.DockState)) {
792            updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(),
793                    TaskStack.DockState.NONE.viewState.dockAreaAlpha);
794        } else {
795            final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
796            updateVisibleDockRegions(new TaskStack.DockState[] {dockState}, -1);
797        }
798    }
799
800    public final void onBusEvent(final DragEndEvent event) {
801        final Runnable cleanUpRunnable = new Runnable() {
802            @Override
803            public void run() {
804                // Remove the drag view
805                removeView(mDragView);
806                mDragView = null;
807            }
808        };
809
810        // Animate the overlay alpha back to 0
811        updateVisibleDockRegions(null, -1);
812
813        if (event.dropTarget == null) {
814            // No drop targets for hit, so just animate the task back to its place
815            event.postAnimationTrigger.increment();
816            event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
817                @Override
818                public void run() {
819                    cleanUpRunnable.run();
820                }
821            });
822            // Animate the task back to where it was before then clean up afterwards
823            TaskViewTransform taskTransform = new TaskViewTransform();
824            TaskStackLayoutAlgorithm layoutAlgorithm = mTaskStackView.getStackAlgorithm();
825            layoutAlgorithm.getStackTransform(event.task,
826                    mTaskStackView.getScroller().getStackScroll(), taskTransform, null);
827            event.dragView.animate()
828                    .scaleX(taskTransform.scale)
829                    .scaleY(taskTransform.scale)
830                    .translationX((layoutAlgorithm.mTaskRect.left - event.dragView.getLeft())
831                            + taskTransform.translationX)
832                    .translationY((layoutAlgorithm.mTaskRect.top - event.dragView.getTop())
833                            + taskTransform.translationY)
834                    .setDuration(175)
835                    .setInterpolator(mFastOutSlowInInterpolator)
836                    .withEndAction(event.postAnimationTrigger.decrementAsRunnable())
837                    .start();
838
839        } else if (event.dropTarget instanceof TaskStack.DockState) {
840            final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
841
842            // For now, just remove the drag view and the original task
843            // TODO: Animate the task to the drop target rect before launching it above
844            cleanUpRunnable.run();
845
846            // Dock the task and launch it
847            SystemServicesProxy ssp = Recents.getSystemServices();
848            ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode);
849            launchTask(event.task, null, INVALID_STACK_ID);
850
851        } else {
852            // We dropped on another drop target, so just add the cleanup to the post animation
853            // trigger
854            event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
855                @Override
856                public void run() {
857                    cleanUpRunnable.run();
858                }
859            });
860        }
861    }
862
863    /**
864     * Updates the dock region to match the specified dock state.
865     */
866    private void updateVisibleDockRegions(TaskStack.DockState[] newDockStates, int overrideAlpha) {
867        ArraySet<TaskStack.DockState> newDockStatesSet = new ArraySet<>();
868        if (newDockStates != null) {
869            for (TaskStack.DockState dockState : newDockStates) {
870                newDockStatesSet.add(dockState);
871            }
872        }
873        for (TaskStack.DockState dockState : mVisibleDockStates) {
874            TaskStack.DockState.ViewState viewState = dockState.viewState;
875            if (newDockStates == null || !newDockStatesSet.contains(dockState)) {
876                // This is no longer visible, so hide it
877                viewState.startAlphaAnimation(0, 150);
878            } else {
879                // This state is now visible, update the bounds and show it
880                int alpha = (overrideAlpha != -1 ? overrideAlpha : viewState.dockAreaAlpha);
881                viewState.dockAreaOverlay.setBounds(
882                        dockState.getDockedBounds(getMeasuredWidth(), getMeasuredHeight()));
883                viewState.dockAreaOverlay.setCallback(this);
884                viewState.startAlphaAnimation(alpha, 150);
885            }
886        }
887    }
888}
889