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