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