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