RecentsImpl.java revision 16ef39a7f6f000af761cf2c2a3e2823a0fbd0c4c
1/*
2 * Copyright (C) 2015 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;
18
19import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
20import static android.view.View.MeasureSpec;
21
22import android.app.ActivityManager;
23import android.app.ActivityOptions;
24import android.content.ActivityNotFoundException;
25import android.content.Context;
26import android.content.Intent;
27import android.content.res.Resources;
28import android.graphics.Bitmap;
29import android.graphics.Canvas;
30import android.graphics.Rect;
31import android.graphics.RectF;
32import android.graphics.drawable.Drawable;
33import android.os.Handler;
34import android.os.SystemClock;
35import android.os.UserHandle;
36import android.util.Log;
37import android.util.MutableBoolean;
38import android.view.AppTransitionAnimationSpec;
39import android.view.LayoutInflater;
40import android.view.ViewConfiguration;
41import android.view.WindowManager;
42
43import com.android.internal.logging.MetricsLogger;
44import com.android.internal.policy.DockedDividerUtils;
45import com.android.systemui.R;
46import com.android.systemui.SystemUIApplication;
47import com.android.systemui.recents.events.EventBus;
48import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
49import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
50import com.android.systemui.recents.events.activity.HideRecentsEvent;
51import com.android.systemui.recents.events.activity.IterateRecentsEvent;
52import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
53import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
54import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
55import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
56import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
57import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
58import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
59import com.android.systemui.recents.misc.DozeTrigger;
60import com.android.systemui.recents.misc.ForegroundThread;
61import com.android.systemui.recents.misc.SystemServicesProxy;
62import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
63import com.android.systemui.recents.model.RecentsTaskLoadPlan;
64import com.android.systemui.recents.model.RecentsTaskLoader;
65import com.android.systemui.recents.model.Task;
66import com.android.systemui.recents.model.TaskGrouping;
67import com.android.systemui.recents.model.TaskStack;
68import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
69import com.android.systemui.recents.views.TaskStackView;
70import com.android.systemui.recents.views.TaskStackViewScroller;
71import com.android.systemui.recents.views.TaskViewHeader;
72import com.android.systemui.recents.views.TaskViewTransform;
73import com.android.systemui.stackdivider.DividerView;
74import com.android.systemui.statusbar.BaseStatusBar;
75import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
76import com.android.systemui.statusbar.phone.PhoneStatusBar;
77
78import java.util.ArrayList;
79
80/**
81 * An implementation of the Recents component for the current user.  For secondary users, this can
82 * be called remotely from the system user.
83 */
84public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener {
85
86    private final static String TAG = "RecentsImpl";
87
88    // The minimum amount of time between each recents button press that we will handle
89    private final static int MIN_TOGGLE_DELAY_MS = 350;
90
91    // The duration within which the user releasing the alt tab (from when they pressed alt tab)
92    // that the fast alt-tab animation will run.  If the user's alt-tab takes longer than this
93    // duration, then we will toggle recents after this duration.
94    private final static int FAST_ALT_TAB_DELAY_MS = 225;
95
96    public final static String RECENTS_PACKAGE = "com.android.systemui";
97    public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
98
99    /**
100     * An implementation of TaskStackListener, that allows us to listen for changes to the system
101     * task stacks and update recents accordingly.
102     */
103    class TaskStackListenerImpl extends TaskStackListener {
104        @Override
105        public void onTaskStackChanged() {
106            // Preloads the next task
107            RecentsConfiguration config = Recents.getConfiguration();
108            if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
109                RecentsTaskLoader loader = Recents.getTaskLoader();
110                SystemServicesProxy ssp = Recents.getSystemServices();
111                ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
112
113                // Load the next task only if we aren't svelte
114                RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
115                loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
116                RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
117                // This callback is made when a new activity is launched and the old one is paused
118                // so ignore the current activity and try and preload the thumbnail for the
119                // previous one.
120                if (runningTaskInfo != null) {
121                    launchOpts.runningTaskId = runningTaskInfo.id;
122                }
123                launchOpts.numVisibleTasks = 2;
124                launchOpts.numVisibleTaskThumbnails = 2;
125                launchOpts.onlyLoadForCache = true;
126                launchOpts.onlyLoadPausedActivities = true;
127                loader.loadTasks(mContext, plan, launchOpts);
128            }
129        }
130    }
131
132    protected static RecentsTaskLoadPlan sInstanceLoadPlan;
133
134    protected Context mContext;
135    protected Handler mHandler;
136    TaskStackListenerImpl mTaskStackListener;
137    boolean mDraggingInRecents;
138    boolean mLaunchedWhileDocking;
139
140    // Task launching
141    Rect mTaskStackBounds = new Rect();
142    TaskViewTransform mTmpTransform = new TaskViewTransform();
143    int mStatusBarHeight;
144    int mNavBarHeight;
145    int mNavBarWidth;
146    int mTaskBarHeight;
147
148    // Header (for transition)
149    TaskViewHeader mHeaderBar;
150    final Object mHeaderBarLock = new Object();
151    protected TaskStackView mDummyStackView;
152
153    // Variables to keep track of if we need to start recents after binding
154    protected boolean mTriggeredFromAltTab;
155    protected long mLastToggleTime;
156    DozeTrigger mFastAltTabTrigger = new DozeTrigger(FAST_ALT_TAB_DELAY_MS, new Runnable() {
157        @Override
158        public void run() {
159            // When this fires, then the user has not released alt-tab for at least
160            // FAST_ALT_TAB_DELAY_MS milliseconds
161            showRecents(mTriggeredFromAltTab, false /* draggingInRecents */, true /* animate */,
162                    false /* reloadTasks */, false /* fromHome */,
163                    DividerView.INVALID_RECENTS_GROW_TARGET);
164        }
165    });
166
167    protected Bitmap mThumbTransitionBitmapCache;
168
169    public RecentsImpl(Context context) {
170        mContext = context;
171        mHandler = new Handler();
172
173        // Initialize the static foreground thread
174        ForegroundThread.get();
175
176        // Register the task stack listener
177        mTaskStackListener = new TaskStackListenerImpl();
178        SystemServicesProxy ssp = Recents.getSystemServices();
179        ssp.registerTaskStackListener(mTaskStackListener);
180
181        // Initialize the static configuration resources
182        LayoutInflater inflater = LayoutInflater.from(mContext);
183        mDummyStackView = new TaskStackView(mContext);
184        mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
185                null, false);
186        reloadResources();
187    }
188
189    public void onBootCompleted() {
190        // When we start, preload the data associated with the previous recent tasks.
191        // We can use a new plan since the caches will be the same.
192        RecentsTaskLoader loader = Recents.getTaskLoader();
193        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
194        loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
195        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
196        launchOpts.numVisibleTasks = loader.getIconCacheSize();
197        launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
198        launchOpts.onlyLoadForCache = true;
199        loader.loadTasks(mContext, plan, launchOpts);
200    }
201
202    public void onConfigurationChanged() {
203        reloadResources();
204        mDummyStackView.reloadOnConfigurationChange();
205        mHeaderBar.onConfigurationChanged();
206    }
207
208    /**
209     * This is only called from the system user's Recents.  Secondary users will instead proxy their
210     * visibility change events through to the system user via
211     * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}.
212     */
213    public void onVisibilityChanged(Context context, boolean visible) {
214        SystemUIApplication app = (SystemUIApplication) context;
215        PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
216        if (statusBar != null) {
217            statusBar.updateRecentsVisibility(visible);
218        }
219    }
220
221    /**
222     * This is only called from the system user's Recents.  Secondary users will instead proxy their
223     * visibility change events through to the system user via
224     * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}.
225     */
226    public void onStartScreenPinning(Context context, int taskId) {
227        SystemUIApplication app = (SystemUIApplication) context;
228        PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
229        if (statusBar != null) {
230            statusBar.showScreenPinningRequest(taskId, false);
231        }
232    }
233
234    public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents,
235            boolean animate, boolean launchedWhileDockingTask, boolean fromHome,
236            int growTarget) {
237        mTriggeredFromAltTab = triggeredFromAltTab;
238        mDraggingInRecents = draggingInRecents;
239        mLaunchedWhileDocking = launchedWhileDockingTask;
240        if (mFastAltTabTrigger.isAsleep()) {
241            // Fast alt-tab duration has elapsed, fall through to showing Recents and reset
242            mFastAltTabTrigger.stopDozing();
243        } else if (mFastAltTabTrigger.isDozing()) {
244            // Fast alt-tab duration has not elapsed.  If this is triggered by a different
245            // showRecents() call, then ignore that call for now.
246            // TODO: We can not handle quick tabs that happen between the initial showRecents() call
247            //       that started the activity and the activity starting up.  The severity of this
248            //       is inversely proportional to the FAST_ALT_TAB_DELAY_MS duration though.
249            if (!triggeredFromAltTab) {
250                return;
251            }
252            mFastAltTabTrigger.stopDozing();
253        } else if (triggeredFromAltTab) {
254            // The fast alt-tab detector is not yet running, so start the trigger and wait for the
255            // hideRecents() call, or for the fast alt-tab duration to elapse
256            mFastAltTabTrigger.startDozing();
257            return;
258        }
259
260        try {
261            // Check if the top task is in the home stack, and start the recents activity
262            SystemServicesProxy ssp = Recents.getSystemServices();
263            boolean forceVisible = launchedWhileDockingTask || draggingInRecents;
264            MutableBoolean isHomeStackVisible = new MutableBoolean(forceVisible);
265            if (forceVisible || !ssp.isRecentsActivityVisible(isHomeStackVisible)) {
266                ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
267                startRecentsActivity(runningTask, isHomeStackVisible.value || fromHome, animate,
268                        growTarget);
269            }
270        } catch (ActivityNotFoundException e) {
271            Log.e(TAG, "Failed to launch RecentsActivity", e);
272        }
273    }
274
275    public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
276        if (triggeredFromAltTab && mFastAltTabTrigger.isDozing()) {
277            // The user has released alt-tab before the trigger has run, so just show the next
278            // task immediately
279            showNextTask();
280
281            // Cancel the fast alt-tab trigger
282            mFastAltTabTrigger.stopDozing();
283            return;
284        }
285
286        // Defer to the activity to handle hiding recents, if it handles it, then it must still
287        // be visible
288        EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab,
289                triggeredFromHomeKey));
290    }
291
292    public void toggleRecents(int growTarget) {
293        // Skip this toggle if we are already waiting to trigger recents via alt-tab
294        if (mFastAltTabTrigger.isDozing()) {
295            return;
296        }
297
298        mDraggingInRecents = false;
299        mLaunchedWhileDocking = false;
300        mTriggeredFromAltTab = false;
301
302        try {
303            SystemServicesProxy ssp = Recents.getSystemServices();
304            MutableBoolean isHomeStackVisible = new MutableBoolean(true);
305            long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
306
307            if (ssp.isRecentsActivityVisible(isHomeStackVisible)) {
308                RecentsDebugFlags debugFlags = Recents.getDebugFlags();
309                RecentsConfiguration config = Recents.getConfiguration();
310                RecentsActivityLaunchState launchState = config.getLaunchState();
311                if (!launchState.launchedWithAltTab) {
312                    // If the user taps quickly
313                    if (!debugFlags.isPagingEnabled() ||
314                            (ViewConfiguration.getDoubleTapMinTime() < elapsedTime &&
315                                    elapsedTime < ViewConfiguration.getDoubleTapTimeout())) {
316                        // Launch the next focused task
317                        EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
318                    } else {
319                        // Notify recents to move onto the next task
320                        EventBus.getDefault().post(new IterateRecentsEvent());
321                    }
322                } else {
323                    // If the user has toggled it too quickly, then just eat up the event here (it's
324                    // better than showing a janky screenshot).
325                    // NOTE: Ideally, the screenshot mechanism would take the window transform into
326                    // account
327                    if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
328                        return;
329                    }
330
331                    EventBus.getDefault().post(new ToggleRecentsEvent());
332                    mLastToggleTime = SystemClock.elapsedRealtime();
333                }
334                return;
335            } else {
336                // If the user has toggled it too quickly, then just eat up the event here (it's
337                // better than showing a janky screenshot).
338                // NOTE: Ideally, the screenshot mechanism would take the window transform into
339                // account
340                if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
341                    return;
342                }
343
344                // Otherwise, start the recents activity
345                ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
346                startRecentsActivity(runningTask, isHomeStackVisible.value, true /* animate */,
347                        growTarget);
348
349                // Only close the other system windows if we are actually showing recents
350                ssp.sendCloseSystemWindows(BaseStatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
351                mLastToggleTime = SystemClock.elapsedRealtime();
352            }
353        } catch (ActivityNotFoundException e) {
354            Log.e(TAG, "Failed to launch RecentsActivity", e);
355        }
356    }
357
358    public void preloadRecents() {
359        // Preload only the raw task list into a new load plan (which will be consumed by the
360        // RecentsActivity) only if there is a task to animate to.
361        SystemServicesProxy ssp = Recents.getSystemServices();
362        MutableBoolean isHomeStackVisible = new MutableBoolean(true);
363        if (!ssp.isRecentsActivityVisible(isHomeStackVisible)) {
364            ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
365            RecentsTaskLoader loader = Recents.getTaskLoader();
366            sInstanceLoadPlan = loader.createLoadPlan(mContext);
367            sInstanceLoadPlan.preloadRawTasks(!isHomeStackVisible.value);
368            loader.preloadTasks(sInstanceLoadPlan, runningTask.id, !isHomeStackVisible.value);
369            TaskStack stack = sInstanceLoadPlan.getTaskStack();
370            if (stack.getTaskCount() > 0) {
371                // Only preload the icon (but not the thumbnail since it may not have been taken for
372                // the pausing activity)
373                preloadIcon(runningTask.id);
374
375                // At this point, we don't know anything about the stack state.  So only calculate
376                // the dimensions of the thumbnail that we need for the transition into Recents, but
377                // do not draw it until we construct the activity options when we start Recents
378                updateHeaderBarLayout(stack, null /* window rect override*/);
379            }
380        }
381    }
382
383    public void cancelPreloadingRecents() {
384        // Do nothing
385    }
386
387    public void onDraggingInRecents(float distanceFromTop) {
388        EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEvent(distanceFromTop));
389    }
390
391    public void onDraggingInRecentsEnded(float velocity) {
392        EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEndedEvent(velocity));
393    }
394
395    /**
396     * Transitions to the next recent task in the stack.
397     */
398    public void showNextTask() {
399        SystemServicesProxy ssp = Recents.getSystemServices();
400        RecentsTaskLoader loader = Recents.getTaskLoader();
401        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
402        loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
403        TaskStack focusedStack = plan.getTaskStack();
404
405        // Return early if there are no tasks in the focused stack
406        if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
407
408        // Return early if there is no running task
409        ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
410        if (runningTask == null) return;
411
412        // Find the task in the recents list
413        boolean isRunningTaskInHomeStack = SystemServicesProxy.isHomeStack(runningTask.stackId);
414        ArrayList<Task> tasks = focusedStack.getStackTasks();
415        Task toTask = null;
416        ActivityOptions launchOpts = null;
417        int taskCount = tasks.size();
418        for (int i = taskCount - 1; i >= 1; i--) {
419            Task task = tasks.get(i);
420            if (isRunningTaskInHomeStack) {
421                toTask = tasks.get(i - 1);
422                launchOpts = ActivityOptions.makeCustomAnimation(mContext,
423                        R.anim.recents_launch_next_affiliated_task_target,
424                        R.anim.recents_fast_toggle_app_home_exit);
425                break;
426            } else if (task.key.id == runningTask.id) {
427                toTask = tasks.get(i - 1);
428                launchOpts = ActivityOptions.makeCustomAnimation(mContext,
429                        R.anim.recents_launch_prev_affiliated_task_target,
430                        R.anim.recents_launch_prev_affiliated_task_source);
431                break;
432            }
433        }
434
435        // Return early if there is no next task
436        if (toTask == null) {
437            ssp.startInPlaceAnimationOnFrontMostApplication(
438                    ActivityOptions.makeCustomInPlaceAnimation(mContext,
439                            R.anim.recents_launch_prev_affiliated_task_bounce));
440            return;
441        }
442
443        // Launch the task
444        ssp.startActivityFromRecents(mContext, toTask.key, toTask.title, launchOpts);
445    }
446
447    /**
448     * Transitions to the next affiliated task.
449     */
450    public void showRelativeAffiliatedTask(boolean showNextTask) {
451        SystemServicesProxy ssp = Recents.getSystemServices();
452        RecentsTaskLoader loader = Recents.getTaskLoader();
453        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
454        loader.preloadTasks(plan, -1, false /* includeFrontMostExcludedTask */);
455        TaskStack focusedStack = plan.getTaskStack();
456
457        // Return early if there are no tasks in the focused stack
458        if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
459
460        // Return early if there is no running task (can't determine affiliated tasks in this case)
461        ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
462        if (runningTask == null) return;
463        // Return early if the running task is in the home stack (optimization)
464        if (SystemServicesProxy.isHomeStack(runningTask.stackId)) return;
465
466        // Find the task in the recents list
467        ArrayList<Task> tasks = focusedStack.getStackTasks();
468        Task toTask = null;
469        ActivityOptions launchOpts = null;
470        int taskCount = tasks.size();
471        int numAffiliatedTasks = 0;
472        for (int i = 0; i < taskCount; i++) {
473            Task task = tasks.get(i);
474            if (task.key.id == runningTask.id) {
475                TaskGrouping group = task.group;
476                Task.TaskKey toTaskKey;
477                if (showNextTask) {
478                    toTaskKey = group.getNextTaskInGroup(task);
479                    launchOpts = ActivityOptions.makeCustomAnimation(mContext,
480                            R.anim.recents_launch_next_affiliated_task_target,
481                            R.anim.recents_launch_next_affiliated_task_source);
482                } else {
483                    toTaskKey = group.getPrevTaskInGroup(task);
484                    launchOpts = ActivityOptions.makeCustomAnimation(mContext,
485                            R.anim.recents_launch_prev_affiliated_task_target,
486                            R.anim.recents_launch_prev_affiliated_task_source);
487                }
488                if (toTaskKey != null) {
489                    toTask = focusedStack.findTaskWithId(toTaskKey.id);
490                }
491                numAffiliatedTasks = group.getTaskCount();
492                break;
493            }
494        }
495
496        // Return early if there is no next task
497        if (toTask == null) {
498            if (numAffiliatedTasks > 1) {
499                if (showNextTask) {
500                    ssp.startInPlaceAnimationOnFrontMostApplication(
501                            ActivityOptions.makeCustomInPlaceAnimation(mContext,
502                                    R.anim.recents_launch_next_affiliated_task_bounce));
503                } else {
504                    ssp.startInPlaceAnimationOnFrontMostApplication(
505                            ActivityOptions.makeCustomInPlaceAnimation(mContext,
506                                    R.anim.recents_launch_prev_affiliated_task_bounce));
507                }
508            }
509            return;
510        }
511
512        // Keep track of actually launched affiliated tasks
513        MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
514
515        // Launch the task
516        ssp.startActivityFromRecents(mContext, toTask.key, toTask.title, launchOpts);
517    }
518
519    public void showNextAffiliatedTask() {
520        // Keep track of when the affiliated task is triggered
521        MetricsLogger.count(mContext, "overview_affiliated_task_next", 1);
522        showRelativeAffiliatedTask(true);
523    }
524
525    public void showPrevAffiliatedTask() {
526        // Keep track of when the affiliated task is triggered
527        MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1);
528        showRelativeAffiliatedTask(false);
529    }
530
531    public void dockTopTask(int topTaskId, int dragMode,
532            int stackCreateMode, Rect initialBounds) {
533        SystemServicesProxy ssp = Recents.getSystemServices();
534
535        // Make sure we inform DividerView before we actually start the activity so we can change
536        // the resize mode already.
537        if (ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) {
538            EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
539            showRecents(
540                    false /* triggeredFromAltTab */,
541                    dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS,
542                    false /* animate */,
543                    true /* launchedWhileDockingTask*/,
544                    false /* fromHome */,
545                    DividerView.INVALID_RECENTS_GROW_TARGET);
546        }
547    }
548
549    /**
550     * Returns the preloaded load plan and invalidates it.
551     */
552    public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
553        RecentsTaskLoadPlan plan = sInstanceLoadPlan;
554        sInstanceLoadPlan = null;
555        return plan;
556    }
557
558    /**
559     * Reloads all the resources for the current configuration.
560     */
561    private void reloadResources() {
562        Resources res = mContext.getResources();
563
564        mStatusBarHeight = res.getDimensionPixelSize(
565                com.android.internal.R.dimen.status_bar_height);
566        mNavBarHeight = res.getDimensionPixelSize(
567                com.android.internal.R.dimen.navigation_bar_height);
568        mNavBarWidth = res.getDimensionPixelSize(
569                com.android.internal.R.dimen.navigation_bar_width);
570        mTaskBarHeight = TaskStackLayoutAlgorithm.getDimensionForDevice(mContext,
571                R.dimen.recents_task_view_header_height,
572                R.dimen.recents_task_view_header_height,
573                R.dimen.recents_task_view_header_height,
574                R.dimen.recents_task_view_header_height_tablet_land,
575                R.dimen.recents_task_view_header_height,
576                R.dimen.recents_task_view_header_height_tablet_land);
577    }
578
579    /**
580     * Prepares the header bar layout for the next transition, if the task view bounds has changed
581     * since the last call, it will attempt to re-measure and layout the header bar to the new size.
582     *
583     * @param stack the stack to initialize the stack layout with
584     * @param windowRectOverride the rectangle to use when calculating the stack state which can
585     *                           be different from the current window rect if recents is resizing
586     *                           while being launched
587     */
588    private void updateHeaderBarLayout(TaskStack stack, Rect windowRectOverride) {
589        SystemServicesProxy ssp = Recents.getSystemServices();
590        Rect displayRect = ssp.getDisplayRect();
591        Rect systemInsets = new Rect();
592        ssp.getStableInsets(systemInsets);
593        Rect windowRect = windowRectOverride != null
594                ? new Rect(windowRectOverride)
595                : ssp.getWindowRect();
596        // When docked, the nav bar insets are consumed and the activity is measured without insets.
597        // However, the window bounds include the insets, so we need to subtract them here to make
598        // them identical.
599        if (ssp.hasDockedTask()) {
600            windowRect.bottom -= systemInsets.bottom;
601            systemInsets.bottom = 0;
602        }
603        calculateWindowStableInsets(systemInsets, windowRect);
604        windowRect.offsetTo(0, 0);
605
606        TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
607
608        // Rebind the header bar and draw it for the transition
609        stackLayout.setSystemInsets(systemInsets);
610        if (stack != null) {
611            stackLayout.getTaskStackBounds(displayRect, windowRect, systemInsets.top,
612                    systemInsets.right, mTaskStackBounds);
613            stackLayout.reset();
614            stackLayout.initialize(displayRect, windowRect, mTaskStackBounds,
615                    TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
616            mDummyStackView.setTasks(stack, false /* allowNotifyStackChanges */);
617
618            Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
619            if (!taskViewBounds.isEmpty()) {
620                int taskViewWidth = taskViewBounds.width();
621                synchronized (mHeaderBarLock) {
622                    if (mHeaderBar.getMeasuredWidth() != taskViewWidth ||
623                            mHeaderBar.getMeasuredHeight() != mTaskBarHeight) {
624                        mHeaderBar.measure(
625                                MeasureSpec.makeMeasureSpec(taskViewWidth, MeasureSpec.EXACTLY),
626                                MeasureSpec.makeMeasureSpec(mTaskBarHeight, MeasureSpec.EXACTLY));
627                    }
628                    mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
629                }
630
631                // Update the transition bitmap to match the new header bar height
632                if (mThumbTransitionBitmapCache == null ||
633                        (mThumbTransitionBitmapCache.getWidth() != taskViewWidth) ||
634                        (mThumbTransitionBitmapCache.getHeight() != mTaskBarHeight)) {
635                    mThumbTransitionBitmapCache = Bitmap.createBitmap(taskViewWidth,
636                            mTaskBarHeight, Bitmap.Config.ARGB_8888);
637                }
638            }
639        }
640    }
641
642    /**
643     * Given the stable insets and the rect for our window, calculates the insets that affect our
644     * window.
645     */
646    private void calculateWindowStableInsets(Rect inOutInsets, Rect windowRect) {
647        Rect displayRect = Recents.getSystemServices().getDisplayRect();
648
649        // Display rect without insets - available app space
650        Rect appRect = new Rect(displayRect);
651        appRect.inset(inOutInsets);
652
653        // Our window intersected with available app space
654        Rect windowRectWithInsets = new Rect(windowRect);
655        windowRectWithInsets.intersect(appRect);
656        inOutInsets.left = windowRectWithInsets.left - windowRect.left;
657        inOutInsets.top = windowRectWithInsets.top - windowRect.top;
658        inOutInsets.right = windowRect.right - windowRectWithInsets.right;
659        inOutInsets.bottom = windowRect.bottom - windowRectWithInsets.bottom;
660    }
661
662    /**
663     * Preloads the icon of a task.
664     */
665    private void preloadIcon(int runningTaskId) {
666        // Ensure that we load the running task's icon
667        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
668        launchOpts.runningTaskId = runningTaskId;
669        launchOpts.loadThumbnails = false;
670        launchOpts.onlyLoadForCache = true;
671        Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
672    }
673
674    /**
675     * Creates the activity options for a unknown state->recents transition.
676     */
677    protected ActivityOptions getUnknownTransitionActivityOptions() {
678        return ActivityOptions.makeCustomAnimation(mContext,
679                R.anim.recents_from_unknown_enter,
680                R.anim.recents_from_unknown_exit,
681                mHandler, null);
682    }
683
684    /**
685     * Creates the activity options for a home->recents transition.
686     */
687    protected ActivityOptions getHomeTransitionActivityOptions() {
688        return ActivityOptions.makeCustomAnimation(mContext,
689                R.anim.recents_from_launcher_enter,
690                R.anim.recents_from_launcher_exit,
691                mHandler, null);
692    }
693
694    /**
695     * Creates the activity options for an app->recents transition.
696     */
697    private ActivityOptions getThumbnailTransitionActivityOptions(
698            ActivityManager.RunningTaskInfo runningTask, TaskStackView stackView,
699                    Rect windowOverrideRect) {
700        if (runningTask != null && runningTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
701            ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
702            ArrayList<Task> tasks = stackView.getStack().getStackTasks();
703            TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
704            TaskStackViewScroller stackScroller = stackView.getScroller();
705
706            stackView.updateLayoutAlgorithm(true /* boundScroll */);
707            stackView.updateToInitialState();
708
709            for (int i = tasks.size() - 1; i >= 0; i--) {
710                Task task = tasks.get(i);
711                if (task.isFreeformTask()) {
712                    mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task,
713                            stackScroller.getStackScroll(), mTmpTransform, null,
714                            windowOverrideRect);
715                    Bitmap thumbnail = drawThumbnailTransitionBitmap(task, mTmpTransform,
716                            mThumbTransitionBitmapCache);
717                    Rect toTaskRect = new Rect();
718                    mTmpTransform.rect.round(toTaskRect);
719                    specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect));
720                }
721            }
722            AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
723            specs.toArray(specsArray);
724            return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
725                    specsArray, mHandler, null, this);
726        } else {
727            // Update the destination rect
728            Task toTask = new Task();
729            TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask,
730                    windowOverrideRect);
731            Bitmap thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform,
732                    mThumbTransitionBitmapCache);
733            if (thumbnail != null) {
734                RectF toTaskRect = toTransform.rect;
735                return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
736                        thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
737                        (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, null);
738            }
739            // If both the screenshot and thumbnail fails, then just fall back to the default transition
740            return getUnknownTransitionActivityOptions();
741        }
742    }
743
744    /**
745     * Returns the transition rect for the given task id.
746     */
747    private TaskViewTransform getThumbnailTransitionTransform(TaskStackView stackView,
748            Task runningTaskOut, Rect windowOverrideRect) {
749        // Find the running task in the TaskStack
750        TaskStack stack = stackView.getStack();
751        Task launchTask = stack.getLaunchTarget();
752        if (launchTask != null) {
753            runningTaskOut.copyFrom(launchTask);
754        } else {
755            // If no task is specified or we can not find the task just use the front most one
756            launchTask = stack.getStackFrontMostTask(true /* includeFreeform */);
757            runningTaskOut.copyFrom(launchTask);
758        }
759
760        // Get the transform for the running task
761        stackView.updateLayoutAlgorithm(true /* boundScroll */);
762        stackView.updateToInitialState();
763        stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
764                stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect);
765        return mTmpTransform;
766    }
767
768    /**
769     * Draws the header of a task used for the window animation into a bitmap.
770     */
771    private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform,
772            Bitmap thumbnail) {
773        SystemServicesProxy ssp = Recents.getSystemServices();
774        if (toTransform != null && toTask.key != null) {
775            synchronized (mHeaderBarLock) {
776                boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode();
777                mHeaderBar.onTaskViewSizeChanged((int) toTransform.rect.width(),
778                        (int) toTransform.rect.height());
779                if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
780                    thumbnail.eraseColor(0xFFff0000);
781                } else {
782                    thumbnail.eraseColor(0);
783                    Canvas c = new Canvas(thumbnail);
784                    // Workaround for b/27815919, reset the callback so that we do not trigger an
785                    // invalidate on the header bar as a result of updating the icon
786                    Drawable icon = mHeaderBar.getIconView().getDrawable();
787                    if (icon != null) {
788                        icon.setCallback(null);
789                    }
790                    mHeaderBar.bindToTask(toTask, false /* touchExplorationEnabled */,
791                            disabledInSafeMode);
792                    mHeaderBar.onTaskDataLoaded();
793                    mHeaderBar.setDimAlpha(toTransform.dimAlpha);
794                    mHeaderBar.draw(c);
795                    c.setBitmap(null);
796                }
797            }
798            return thumbnail.createAshmemBitmap();
799        }
800        return null;
801    }
802
803    /**
804     * Shows the recents activity
805     */
806    protected void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask,
807            boolean isHomeStackVisible, boolean animate, int growTarget) {
808        RecentsTaskLoader loader = Recents.getTaskLoader();
809        RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
810
811        int runningTaskId = !mLaunchedWhileDocking && (runningTask != null)
812                ? runningTask.id
813                : -1;
814
815        // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
816        // should always preload the tasks now. If we are dragging in recents, reload them as
817        // the stacks might have changed.
818        if (mLaunchedWhileDocking || mTriggeredFromAltTab ||sInstanceLoadPlan == null) {
819            // Create a new load plan if preloadRecents() was never triggered
820            sInstanceLoadPlan = loader.createLoadPlan(mContext);
821        }
822        if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
823            loader.preloadTasks(sInstanceLoadPlan, runningTaskId, !isHomeStackVisible);
824        }
825
826        TaskStack stack = sInstanceLoadPlan.getTaskStack();
827        boolean hasRecentTasks = stack.getTaskCount() > 0;
828        boolean useThumbnailTransition = (runningTask != null) && !isHomeStackVisible && hasRecentTasks;
829
830        // Update the launch state that we need in updateHeaderBarLayout()
831        launchState.launchedFromHome = !useThumbnailTransition && !mLaunchedWhileDocking;
832        launchState.launchedFromApp = useThumbnailTransition || mLaunchedWhileDocking;
833        launchState.launchedViaDockGesture = mLaunchedWhileDocking;
834        launchState.launchedViaDragGesture = mDraggingInRecents;
835        launchState.launchedToTaskId = runningTaskId;
836        launchState.launchedWithAltTab = mTriggeredFromAltTab;
837
838        // Preload the icon (this will be a null-op if we have preloaded the icon already in
839        // preloadRecents())
840        preloadIcon(runningTaskId);
841
842        // Update the header bar if necessary
843        Rect windowOverrideRect = getWindowRectOverride(growTarget);
844        updateHeaderBarLayout(stack, windowOverrideRect);
845
846        // Prepare the dummy stack for the transition
847        TaskStackLayoutAlgorithm.VisibilityReport stackVr =
848                mDummyStackView.computeStackVisibilityReport();
849
850        // Update the remaining launch state
851        launchState.launchedNumVisibleTasks = stackVr.numVisibleTasks;
852        launchState.launchedNumVisibleThumbnails = stackVr.numVisibleThumbnails;
853
854        if (!animate) {
855            startRecentsActivity(ActivityOptions.makeCustomAnimation(mContext, -1, -1));
856            return;
857        }
858
859        ActivityOptions opts;
860        if (useThumbnailTransition) {
861            // Try starting with a thumbnail transition
862            opts = getThumbnailTransitionActivityOptions(runningTask, mDummyStackView,
863                    windowOverrideRect);
864        } else {
865            // If there is no thumbnail transition, but is launching from home into recents, then
866            // use a quick home transition
867            opts = hasRecentTasks
868                ? getHomeTransitionActivityOptions()
869                : getUnknownTransitionActivityOptions();
870        }
871        startRecentsActivity(opts);
872        mLastToggleTime = SystemClock.elapsedRealtime();
873    }
874
875    private Rect getWindowRectOverride(int growTarget) {
876        if (growTarget == DividerView.INVALID_RECENTS_GROW_TARGET) {
877            return null;
878        }
879        Rect result = new Rect();
880        Rect displayRect = Recents.getSystemServices().getDisplayRect();
881        DockedDividerUtils.calculateBoundsForPosition(growTarget, WindowManager.DOCKED_BOTTOM,
882                result, displayRect.width(), displayRect.height(),
883                Recents.getSystemServices().getDockedDividerSize(mContext));
884        return result;
885    }
886
887    /**
888     * Starts the recents activity.
889     */
890    private void startRecentsActivity(ActivityOptions opts) {
891        Intent intent = new Intent();
892        intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
893        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
894                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
895                | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
896
897        if (opts != null) {
898            mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
899        } else {
900            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
901        }
902        EventBus.getDefault().send(new RecentsActivityStartingEvent());
903    }
904
905    /**** OnAnimationFinishedListener Implementation ****/
906
907    @Override
908    public void onAnimationFinished() {
909        EventBus.getDefault().post(new EnterRecentsWindowLastAnimationFrameEvent());
910    }
911}
912