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