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