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