RecentsImpl.java revision b61e654b9a46e79827355104760a3ac5228380bc
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
165    // Task launching
166    Rect mSearchBarBounds = new Rect();
167    Rect mTaskStackBounds = new Rect();
168    Rect mLastTaskViewBounds = new Rect();
169    TaskViewTransform mTmpTransform = new TaskViewTransform();
170    int mStatusBarHeight;
171    int mNavBarHeight;
172    int mNavBarWidth;
173    int mTaskBarHeight;
174
175    // Header (for transition)
176    TaskViewHeader mHeaderBar;
177    final Object mHeaderBarLock = new Object();
178    TaskStackView mDummyStackView;
179
180    // Variables to keep track of if we need to start recents after binding
181    boolean mTriggeredFromAltTab;
182    long mLastToggleTime;
183    DozeTrigger mFastAltTabTrigger = new DozeTrigger(FAST_ALT_TAB_DELAY_MS, new Runnable() {
184        @Override
185        public void run() {
186            // When this fires, then the user has not released alt-tab for at least
187            // FAST_ALT_TAB_DELAY_MS milliseconds
188            showRecents(mTriggeredFromAltTab, false /* draggingInRecents */, true /* animate */,
189                    false /* reloadTasks */);
190        }
191    });
192
193    Bitmap mThumbnailTransitionBitmapCache;
194    Task mThumbnailTransitionBitmapCacheKey;
195
196    public RecentsImpl(Context context) {
197        mContext = context;
198        mHandler = new Handler();
199        mAppWidgetHost = new RecentsAppWidgetHost(mContext, RecentsAppWidgetHost.HOST_ID);
200
201        // Initialize the static foreground thread
202        ForegroundThread.get();
203
204        // Register the task stack listener
205        mTaskStackListener = new TaskStackListenerImpl(mHandler);
206        SystemServicesProxy ssp = Recents.getSystemServices();
207        ssp.registerTaskStackListener(mTaskStackListener);
208
209        // Initialize the static configuration resources
210        reloadHeaderBarLayout();
211        updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
212
213        // When we start, preload the data associated with the previous recent tasks.
214        // We can use a new plan since the caches will be the same.
215        RecentsTaskLoader loader = Recents.getTaskLoader();
216        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
217        loader.preloadTasks(plan, -1, true /* isTopTaskHome */);
218        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
219        launchOpts.numVisibleTasks = loader.getIconCacheSize();
220        launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
221        launchOpts.onlyLoadForCache = true;
222        loader.loadTasks(mContext, plan, launchOpts);
223
224        //Manager used to determine if we are running on tv or not
225        UiModeManager uiModeManager = (UiModeManager) mContext.getSystemService(Context.UI_MODE_SERVICE);
226        if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
227            mRecentsIntentActivityName = RECENTS_TV_ACTIVITY;
228        } else {
229            mRecentsIntentActivityName = RECENTS_ACTIVITY;
230        }
231    }
232
233    public void onBootCompleted() {
234        mBootCompleted = true;
235        updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
236    }
237
238    public void onConfigurationChanged() {
239        reloadHeaderBarLayout();
240        updateHeaderBarLayout(true /* tryAndBindSearchWidget */, null /* stack */);
241        // Don't reuse task stack views if the configuration changes
242        mCanReuseTaskStackViews = false;
243        Recents.getConfiguration().updateOnConfigurationChange();
244    }
245
246    /**
247     * This is only called from the system user's Recents.  Secondary users will instead proxy their
248     * visibility change events through to the system user via
249     * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}.
250     */
251    public void onVisibilityChanged(Context context, boolean visible) {
252        SystemUIApplication app = (SystemUIApplication) context;
253        PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
254        if (statusBar != null) {
255            statusBar.updateRecentsVisibility(visible);
256        }
257    }
258
259    /**
260     * This is only called from the system user's Recents.  Secondary users will instead proxy their
261     * visibility change events through to the system user via
262     * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}.
263     */
264    public void onStartScreenPinning(Context context) {
265        SystemUIApplication app = (SystemUIApplication) context;
266        PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
267        if (statusBar != null) {
268            statusBar.showScreenPinningRequest(false);
269        }
270    }
271
272    public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents,
273            boolean animate, boolean reloadTasks) {
274        mTriggeredFromAltTab = triggeredFromAltTab;
275        mDraggingInRecents = draggingInRecents;
276        mReloadTasks = reloadTasks;
277        if (mFastAltTabTrigger.hasTriggered()) {
278            // We are calling this from the doze trigger, so just fall through to show Recents
279            mFastAltTabTrigger.resetTrigger();
280        } else if (mFastAltTabTrigger.isDozing()) {
281            // We are dozing but haven't yet triggered, ignore this if this is not another alt-tab,
282            // otherwise, this is an additional tab (alt-tab*), which means that we should trigger
283            // immediately (fall through and disable the pending trigger)
284            // TODO: This is tricky, we need to handle the tab key, but Recents has not yet started
285            //       so we may actually additional signal to handle multiple quick tab cases.  The
286            //       severity of this is inversely proportional to the FAST_ALT_TAB_DELAY_MS
287            //       duration though
288            if (!triggeredFromAltTab) {
289                return;
290            }
291            mFastAltTabTrigger.stopDozing();
292        } else {
293            // Otherwise, the doze trigger is not running, and if this is an alt tab, we should
294            // start the trigger and then wait for the hide (or for it to elapse)
295            if (triggeredFromAltTab) {
296                mFastAltTabTrigger.startDozing();
297                return;
298            }
299        }
300
301        try {
302            // Check if the top task is in the home stack, and start the recents activity
303            SystemServicesProxy ssp = Recents.getSystemServices();
304            ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
305            MutableBoolean isTopTaskHome = new MutableBoolean(true);
306            if (topTask == null || !ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
307                startRecentsActivity(topTask, isTopTaskHome.value, animate);
308            }
309        } catch (ActivityNotFoundException e) {
310            Log.e(TAG, "Failed to launch RecentsActivity", e);
311        }
312    }
313
314    public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
315        if (mBootCompleted) {
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                mFastAltTabTrigger.resetTrigger();
324                return;
325            }
326
327            // Defer to the activity to handle hiding recents, if it handles it, then it must still
328            // be visible
329            EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab,
330                    triggeredFromHomeKey));
331        }
332    }
333
334    public void toggleRecents() {
335        // Skip this toggle if we are already waiting to trigger recents via alt-tab
336        if (mFastAltTabTrigger.isDozing()) {
337            return;
338        }
339
340        mDraggingInRecents = false;
341        mTriggeredFromAltTab = false;
342
343        try {
344            ViewConfiguration viewConfig = ViewConfiguration.get(mContext);
345            SystemServicesProxy ssp = Recents.getSystemServices();
346            ActivityManager.RunningTaskInfo topTask = ssp.getTopMostTask();
347            MutableBoolean isTopTaskHome = new MutableBoolean(true);
348            long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
349
350            if (topTask != null && ssp.isRecentsTopMost(topTask, isTopTaskHome)) {
351                RecentsConfiguration config = Recents.getConfiguration();
352                RecentsActivityLaunchState launchState = config.getLaunchState();
353                if (!launchState.launchedWithAltTab) {
354                    // If the user taps quickly
355                    if (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        RecentsTaskLoader loader = Recents.getTaskLoader();
404        sInstanceLoadPlan = loader.createLoadPlan(mContext);
405        if (topTask != null && !ssp.isRecentsTopMost(topTask, topTaskHome)) {
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        EventBus.getDefault().send(new DockingTopTaskEvent(dragMode));
572        ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds);
573        showRecents(false /* triggeredFromAltTab */,
574                dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS, false /* animate */,
575                true /* reloadTasks*/);
576    }
577
578    /**
579     * Returns the preloaded load plan and invalidates it.
580     */
581    public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
582        RecentsTaskLoadPlan plan = sInstanceLoadPlan;
583        sInstanceLoadPlan = null;
584        return plan;
585    }
586
587    /**
588     * Reloads all the layouts for the header bar transition.
589     */
590    private void reloadHeaderBarLayout() {
591        Resources res = mContext.getResources();
592        LayoutInflater inflater = LayoutInflater.from(mContext);
593
594        mStatusBarHeight = res.getDimensionPixelSize(
595                com.android.internal.R.dimen.status_bar_height);
596        mNavBarHeight = res.getDimensionPixelSize(
597                com.android.internal.R.dimen.navigation_bar_height);
598        mNavBarWidth = res.getDimensionPixelSize(
599                com.android.internal.R.dimen.navigation_bar_width);
600        mTaskBarHeight = res.getDimensionPixelSize(
601                R.dimen.recents_task_bar_height);
602        mDummyStackView = new TaskStackView(mContext, new TaskStack());
603        mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
604                null, false);
605    }
606
607    /**
608     * Prepares the header bar layout for the next transition, if the task view bounds has changed
609     * since the last call, it will attempt to re-measure and layout the header bar to the new size.
610     *
611     * @param tryAndBindSearchWidget if set, will attempt to fetch and bind the search widget if one
612     *                               is not already bound (can be expensive)
613     * @param stack the stack to initialize the stack layout with
614     */
615    private void updateHeaderBarLayout(boolean tryAndBindSearchWidget,
616            TaskStack stack) {
617        RecentsConfiguration config = Recents.getConfiguration();
618        SystemServicesProxy ssp = Recents.getSystemServices();
619        Rect systemInsets = new Rect();
620        ssp.getStableInsets(systemInsets);
621        Rect windowRect = ssp.getWindowRect();
622        calculateWindowStableInsets(systemInsets, windowRect);
623        windowRect.offsetTo(0, 0);
624
625        // Update the configuration for the current state
626        config.update(systemInsets);
627
628        if (RecentsDebugFlags.Static.EnableSearchBar && tryAndBindSearchWidget) {
629            // Try and pre-emptively bind the search widget on startup to ensure that we
630            // have the right thumbnail bounds to animate to.
631            // Note: We have to reload the widget id before we get the task stack bounds below
632            if (ssp.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
633                config.getSearchBarBounds(windowRect, mStatusBarHeight, mSearchBarBounds);
634            }
635        }
636        config.getTaskStackBounds(windowRect, systemInsets.top, systemInsets.right,
637                mSearchBarBounds, mTaskStackBounds);
638
639        // Rebind the header bar and draw it for the transition
640        TaskStackLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm();
641        Rect taskStackBounds = new Rect(mTaskStackBounds);
642        algo.setSystemInsets(systemInsets);
643        if (stack != null) {
644            algo.initialize(taskStackBounds,
645                    TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
646        }
647        Rect taskViewBounds = algo.getUntransformedTaskViewBounds();
648        if (!taskViewBounds.equals(mLastTaskViewBounds)) {
649            mLastTaskViewBounds.set(taskViewBounds);
650
651            int taskViewWidth = taskViewBounds.width();
652            synchronized (mHeaderBarLock) {
653                mHeaderBar.measure(
654                    View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY),
655                    View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY));
656                mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
657            }
658        }
659    }
660
661    /**
662     * Given the stable insets and the rect for our window, calculates the insets that affect our
663     * window.
664     */
665    private void calculateWindowStableInsets(Rect inOutInsets, Rect windowRect) {
666        Rect displayRect = Recents.getSystemServices().getDisplayRect();
667
668        // Display rect without insets - available app space
669        Rect appRect = new Rect(displayRect);
670        appRect.inset(inOutInsets);
671
672        // Our window intersected with available app space
673        Rect windowRectWithInsets = new Rect(windowRect);
674        windowRectWithInsets.intersect(appRect);
675        inOutInsets.left = windowRectWithInsets.left - windowRect.left;
676        inOutInsets.top = windowRectWithInsets.top - windowRect.top;
677        inOutInsets.right = windowRect.right - windowRectWithInsets.right;
678        inOutInsets.bottom = windowRect.bottom - windowRectWithInsets.bottom;
679    }
680
681    /**
682     * Preloads the icon of a task.
683     */
684    private void preloadIcon(ActivityManager.RunningTaskInfo task) {
685        // Ensure that we load the running task's icon
686        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
687        launchOpts.runningTaskId = task.id;
688        launchOpts.loadThumbnails = false;
689        launchOpts.onlyLoadForCache = true;
690        Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
691    }
692
693    /**
694     * Caches the header thumbnail used for a window animation asynchronously into
695     * {@link #mThumbnailTransitionBitmapCache}.
696     */
697    private void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask,
698            TaskStack stack, TaskStackView stackView) {
699        preloadIcon(topTask);
700
701        // Update the header bar if necessary
702        updateHeaderBarLayout(false /* tryAndBindSearchWidget */, stack);
703
704        // Update the destination rect
705        mDummyStackView.updateLayoutForStack(stack);
706        final Task toTask = new Task();
707        final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
708                toTask);
709        ForegroundThread.getHandler().postAtFrontOfQueue(new Runnable() {
710            @Override
711            public void run() {
712                final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform);
713                mHandler.post(new Runnable() {
714                    @Override
715                    public void run() {
716                        mThumbnailTransitionBitmapCache = transitionBitmap;
717                        mThumbnailTransitionBitmapCacheKey = toTask;
718                    }
719                });
720            }
721        });
722    }
723
724    /**
725     * Creates the activity options for a unknown state->recents transition.
726     */
727    private ActivityOptions getUnknownTransitionActivityOptions() {
728        return ActivityOptions.makeCustomAnimation(mContext,
729                R.anim.recents_from_unknown_enter,
730                R.anim.recents_from_unknown_exit,
731                mHandler, null);
732    }
733
734    /**
735     * Creates the activity options for a home->recents transition.
736     */
737    private ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
738        if (fromSearchHome) {
739            return ActivityOptions.makeCustomAnimation(mContext,
740                    R.anim.recents_from_search_launcher_enter,
741                    R.anim.recents_from_search_launcher_exit,
742                    mHandler, null);
743        }
744        return ActivityOptions.makeCustomAnimation(mContext,
745                R.anim.recents_from_launcher_enter,
746                R.anim.recents_from_launcher_exit,
747                mHandler, null);
748    }
749
750    /**
751     * Creates the activity options for an app->recents transition.
752     */
753    private ActivityOptions getThumbnailTransitionActivityOptions(
754            ActivityManager.RunningTaskInfo topTask, TaskStack stack, TaskStackView stackView) {
755        if (topTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
756            ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
757            stackView.getScroller().setStackScrollToInitialState();
758            ArrayList<Task> tasks = stack.getStackTasks();
759            for (int i = tasks.size() - 1; i >= 0; i--) {
760                Task task = tasks.get(i);
761                if (task.isFreeformTask()) {
762                    mTmpTransform = stackView.getStackAlgorithm()
763                            .getStackTransformScreenCoordinates(task,
764                                    stackView.getScroller().getStackScroll(), mTmpTransform, null);
765                    Rect toTaskRect = new Rect();
766                    mTmpTransform.rect.round(toTaskRect);
767                    Bitmap thumbnail = getThumbnailBitmap(topTask, task, mTmpTransform);
768                    specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect));
769                }
770            }
771            AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
772            specs.toArray(specsArray);
773            return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
774                    specsArray, mHandler, null, this);
775        } else {
776            // Update the destination rect
777            Task toTask = new Task();
778            TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
779                    toTask);
780            RectF toTaskRect = toTransform.rect;
781            Bitmap thumbnail = getThumbnailBitmap(topTask, toTask, toTransform);
782            if (thumbnail != null) {
783                return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
784                        thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
785                        (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, null);
786            }
787            // If both the screenshot and thumbnail fails, then just fall back to the default transition
788            return getUnknownTransitionActivityOptions();
789        }
790    }
791
792    private Bitmap getThumbnailBitmap(ActivityManager.RunningTaskInfo topTask, Task toTask,
793            TaskViewTransform toTransform) {
794        Bitmap thumbnail;
795        if (mThumbnailTransitionBitmapCacheKey != null
796                && mThumbnailTransitionBitmapCacheKey.key != null
797                && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) {
798            thumbnail = mThumbnailTransitionBitmapCache;
799            mThumbnailTransitionBitmapCacheKey = null;
800            mThumbnailTransitionBitmapCache = null;
801        } else {
802            preloadIcon(topTask);
803            thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
804        }
805        return thumbnail;
806    }
807
808    /**
809     * Returns the transition rect for the given task id.
810     */
811    private TaskViewTransform getThumbnailTransitionTransform(TaskStack stack,
812            TaskStackView stackView, Task runningTaskOut) {
813        // Find the running task in the TaskStack
814        Task launchTask = stack.getLaunchTarget();
815        if (launchTask != null) {
816            runningTaskOut.copyFrom(launchTask);
817        } else {
818            // If no task is specified or we can not find the task just use the front most one
819            launchTask = stack.getStackFrontMostTask(true /* includeFreeform */);
820            runningTaskOut.copyFrom(launchTask);
821        }
822
823        // Get the transform for the running task
824        stackView.getScroller().setStackScrollToInitialState();
825        mTmpTransform = stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
826                stackView.getScroller().getStackScroll(), mTmpTransform, null);
827        return mTmpTransform;
828    }
829
830    /**
831     * Draws the header of a task used for the window animation into a bitmap.
832     */
833    private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) {
834        if (toTransform != null && toTask.key != null) {
835            Bitmap thumbnail;
836            synchronized (mHeaderBarLock) {
837                int toHeaderWidth = (int) toTransform.rect.width();
838                int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
839                mHeaderBar.onTaskViewSizeChanged((int) toTransform.rect.width(),
840                        (int) toTransform.rect.height());
841                thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
842                        Bitmap.Config.ARGB_8888);
843                if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
844                    thumbnail.eraseColor(0xFFff0000);
845                } else {
846                    Canvas c = new Canvas(thumbnail);
847                    c.scale(toTransform.scale, toTransform.scale);
848                    mHeaderBar.rebindToTask(toTask, false /* touchExplorationEnabled */);
849                    mHeaderBar.draw(c);
850                    c.setBitmap(null);
851                }
852            }
853            return thumbnail.createAshmemBitmap();
854        }
855        return null;
856    }
857
858    /**
859     * Shows the recents activity
860     */
861    private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
862            boolean isTopTaskHome, boolean animate) {
863        RecentsTaskLoader loader = Recents.getTaskLoader();
864
865        // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
866        // should always preload the tasks now. If we are dragging in recents, reload them as
867        // the stacks might have changed.
868        if (mReloadTasks || mTriggeredFromAltTab ||sInstanceLoadPlan == null) {
869            // Create a new load plan if preloadRecents() was never triggered
870            sInstanceLoadPlan = loader.createLoadPlan(mContext);
871        }
872        if (mReloadTasks || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
873            loader.preloadTasks(sInstanceLoadPlan, topTask.id, isTopTaskHome);
874        }
875        TaskStack stack = sInstanceLoadPlan.getTaskStack();
876
877        // Update the header bar if necessary
878        updateHeaderBarLayout(false /* tryAndBindSearchWidget */, stack);
879
880        // Prepare the dummy stack for the transition
881        mDummyStackView.updateLayoutForStack(stack);
882        TaskStackLayoutAlgorithm.VisibilityReport stackVr =
883                mDummyStackView.computeStackVisibilityReport();
884
885        if (!animate) {
886            ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, -1, -1);
887            startRecentsActivity(topTask, opts, false /* fromHome */,
888                    false /* fromSearchHome */, false /* fromThumbnail*/, stackVr);
889            return;
890        }
891
892        boolean hasRecentTasks = stack.getTaskCount() > 0;
893        boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
894
895        if (useThumbnailTransition) {
896            // Try starting with a thumbnail transition
897            ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack,
898                    mDummyStackView);
899            if (opts != null) {
900                startRecentsActivity(topTask, opts, false /* fromHome */,
901                        false /* fromSearchHome */, true /* fromThumbnail */, stackVr);
902            } else {
903                // Fall through below to the non-thumbnail transition
904                useThumbnailTransition = false;
905            }
906        }
907
908        if (!useThumbnailTransition) {
909            // If there is no thumbnail transition, but is launching from home into recents, then
910            // use a quick home transition and do the animation from home
911            if (hasRecentTasks) {
912                SystemServicesProxy ssp = Recents.getSystemServices();
913                String homeActivityPackage = ssp.getHomeActivityPackageName();
914                String searchWidgetPackage = null;
915                if (RecentsDebugFlags.Static.EnableSearchBar) {
916                    searchWidgetPackage = Prefs.getString(mContext,
917                            Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE, null);
918                } else {
919                    AppWidgetProviderInfo searchWidgetInfo = ssp.resolveSearchAppWidget();
920                    if (searchWidgetInfo != null) {
921                        searchWidgetPackage = searchWidgetInfo.provider.getPackageName();
922                    }
923                }
924
925                // Determine whether we are coming from a search owned home activity
926                boolean fromSearchHome = (homeActivityPackage != null) &&
927                        homeActivityPackage.equals(searchWidgetPackage);
928                ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
929                startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome,
930                        false /* fromThumbnail */, stackVr);
931            } else {
932                // Otherwise we do the normal fade from an unknown source
933                ActivityOptions opts = getUnknownTransitionActivityOptions();
934                startRecentsActivity(topTask, opts, true /* fromHome */,
935                        false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
936            }
937        }
938        mLastToggleTime = SystemClock.elapsedRealtime();
939    }
940
941    /**
942     * Starts the recents activity.
943     */
944    private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
945              ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail,
946              TaskStackLayoutAlgorithm.VisibilityReport vr) {
947        // Update the configuration based on the launch options
948        RecentsConfiguration config = Recents.getConfiguration();
949        RecentsActivityLaunchState launchState = config.getLaunchState();
950        launchState.launchedFromHome = fromSearchHome || fromHome;
951        launchState.launchedFromSearchHome = fromSearchHome;
952        launchState.launchedFromAppWithThumbnail = fromThumbnail;
953        launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
954        launchState.launchedWithAltTab = mTriggeredFromAltTab;
955        launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
956        launchState.launchedNumVisibleTasks = vr.numVisibleTasks;
957        launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
958        launchState.launchedHasConfigurationChanged = false;
959        launchState.launchedViaDragGesture = mDraggingInRecents;
960
961        Intent intent = new Intent();
962        intent.setClassName(RECENTS_PACKAGE, mRecentsIntentActivityName);
963        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
964                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
965                | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
966
967        if (opts != null) {
968            mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
969        } else {
970            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
971        }
972        mCanReuseTaskStackViews = true;
973        EventBus.getDefault().send(new RecentsActivityStartingEvent());
974    }
975
976    /**** OnAnimationFinishedListener Implementation ****/
977
978    @Override
979    public void onAnimationFinished() {
980        EventBus.getDefault().post(new EnterRecentsWindowLastAnimationFrameEvent());
981    }
982}
983