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