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