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