RecentsImpl.java revision 003eda6c230a1b1c4e3709466c46660e7602d740
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                    false /* relayoutTaskStack */);
649        }
650        Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
651        if (!taskViewBounds.equals(mLastTaskViewBounds)) {
652            mLastTaskViewBounds.set(taskViewBounds);
653
654            int taskViewWidth = taskViewBounds.width();
655            synchronized (mHeaderBarLock) {
656                mHeaderBar.measure(
657                    View.MeasureSpec.makeMeasureSpec(taskViewWidth, View.MeasureSpec.EXACTLY),
658                    View.MeasureSpec.makeMeasureSpec(mTaskBarHeight, View.MeasureSpec.EXACTLY));
659                mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
660            }
661        }
662    }
663
664    /**
665     * Given the stable insets and the rect for our window, calculates the insets that affect our
666     * window.
667     */
668    private void calculateWindowStableInsets(Rect inOutInsets, Rect windowRect) {
669        Rect displayRect = Recents.getSystemServices().getDisplayRect();
670
671        // Display rect without insets - available app space
672        Rect appRect = new Rect(displayRect);
673        appRect.inset(inOutInsets);
674
675        // Our window intersected with available app space
676        Rect windowRectWithInsets = new Rect(windowRect);
677        windowRectWithInsets.intersect(appRect);
678        inOutInsets.left = windowRectWithInsets.left - windowRect.left;
679        inOutInsets.top = windowRectWithInsets.top - windowRect.top;
680        inOutInsets.right = windowRect.right - windowRectWithInsets.right;
681        inOutInsets.bottom = windowRect.bottom - windowRectWithInsets.bottom;
682    }
683
684    /**
685     * Preloads the icon of a task.
686     */
687    private void preloadIcon(ActivityManager.RunningTaskInfo task) {
688        // Ensure that we load the running task's icon
689        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
690        launchOpts.runningTaskId = task.id;
691        launchOpts.loadThumbnails = false;
692        launchOpts.onlyLoadForCache = true;
693        Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
694    }
695
696    /**
697     * Caches the header thumbnail used for a window animation asynchronously into
698     * {@link #mThumbnailTransitionBitmapCache}.
699     */
700    private void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask,
701            TaskStack stack, TaskStackView stackView) {
702        preloadIcon(topTask);
703
704        // Update the header bar if necessary
705        updateHeaderBarLayout(false /* tryAndBindSearchWidget */, stack);
706
707        // Update the destination rect
708        final Task toTask = new Task();
709        final TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask);
710        ForegroundThread.getHandler().postAtFrontOfQueue(new Runnable() {
711            @Override
712            public void run() {
713                final Bitmap transitionBitmap = drawThumbnailTransitionBitmap(toTask, toTransform);
714                mHandler.post(new Runnable() {
715                    @Override
716                    public void run() {
717                        mThumbnailTransitionBitmapCache = transitionBitmap;
718                        mThumbnailTransitionBitmapCacheKey = toTask;
719                    }
720                });
721            }
722        });
723    }
724
725    /**
726     * Creates the activity options for a unknown state->recents transition.
727     */
728    private ActivityOptions getUnknownTransitionActivityOptions() {
729        return ActivityOptions.makeCustomAnimation(mContext,
730                R.anim.recents_from_unknown_enter,
731                R.anim.recents_from_unknown_exit,
732                mHandler, null);
733    }
734
735    /**
736     * Creates the activity options for a home->recents transition.
737     */
738    private ActivityOptions getHomeTransitionActivityOptions(boolean fromSearchHome) {
739        if (fromSearchHome) {
740            return ActivityOptions.makeCustomAnimation(mContext,
741                    R.anim.recents_from_search_launcher_enter,
742                    R.anim.recents_from_search_launcher_exit,
743                    mHandler, null);
744        }
745        return ActivityOptions.makeCustomAnimation(mContext,
746                R.anim.recents_from_launcher_enter,
747                R.anim.recents_from_launcher_exit,
748                mHandler, null);
749    }
750
751    /**
752     * Creates the activity options for an app->recents transition.
753     */
754    private ActivityOptions getThumbnailTransitionActivityOptions(
755            ActivityManager.RunningTaskInfo topTask, TaskStackView stackView) {
756        if (topTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
757            ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
758            ArrayList<Task> tasks = stackView.getStack().getStackTasks();
759            TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
760            TaskStackViewScroller stackScroller = stackView.getScroller();
761
762            stackView.updateLayoutAlgorithm(true /* boundScroll */);
763            stackView.updateToInitialState();
764
765            for (int i = tasks.size() - 1; i >= 0; i--) {
766                Task task = tasks.get(i);
767                if (task.isFreeformTask()) {
768                    mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task,
769                                    stackScroller.getStackScroll(), mTmpTransform, null);
770                    Rect toTaskRect = new Rect();
771                    mTmpTransform.rect.round(toTaskRect);
772                    Bitmap thumbnail = getThumbnailBitmap(topTask, task, mTmpTransform);
773                    specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect));
774                }
775            }
776            AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
777            specs.toArray(specsArray);
778            return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
779                    specsArray, mHandler, null, this);
780        } else {
781            // Update the destination rect
782            Task toTask = new Task();
783            TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask);
784            RectF toTaskRect = toTransform.rect;
785            Bitmap thumbnail = getThumbnailBitmap(topTask, toTask, toTransform);
786            if (thumbnail != null) {
787                return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
788                        thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
789                        (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, null);
790            }
791            // If both the screenshot and thumbnail fails, then just fall back to the default transition
792            return getUnknownTransitionActivityOptions();
793        }
794    }
795
796    private Bitmap getThumbnailBitmap(ActivityManager.RunningTaskInfo topTask, Task toTask,
797            TaskViewTransform toTransform) {
798        Bitmap thumbnail;
799        if (mThumbnailTransitionBitmapCacheKey != null
800                && mThumbnailTransitionBitmapCacheKey.key != null
801                && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) {
802            thumbnail = mThumbnailTransitionBitmapCache;
803            mThumbnailTransitionBitmapCacheKey = null;
804            mThumbnailTransitionBitmapCache = null;
805        } else {
806            preloadIcon(topTask);
807            thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
808        }
809        return thumbnail;
810    }
811
812    /**
813     * Returns the transition rect for the given task id.
814     */
815    private TaskViewTransform getThumbnailTransitionTransform(TaskStackView stackView,
816            Task runningTaskOut) {
817        // Find the running task in the TaskStack
818        TaskStack stack = stackView.getStack();
819        Task launchTask = stack.getLaunchTarget();
820        if (launchTask != null) {
821            runningTaskOut.copyFrom(launchTask);
822        } else {
823            // If no task is specified or we can not find the task just use the front most one
824            launchTask = stack.getStackFrontMostTask(true /* includeFreeform */);
825            runningTaskOut.copyFrom(launchTask);
826        }
827
828        // Get the transform for the running task
829        stackView.updateLayoutAlgorithm(true /* boundScroll */);
830        stackView.updateToInitialState();
831        mTmpTransform = stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
832                stackView.getScroller().getStackScroll(), mTmpTransform, null);
833        return mTmpTransform;
834    }
835
836    /**
837     * Draws the header of a task used for the window animation into a bitmap.
838     */
839    private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) {
840        SystemServicesProxy ssp = Recents.getSystemServices();
841        if (toTransform != null && toTask.key != null) {
842            Bitmap thumbnail;
843            synchronized (mHeaderBarLock) {
844                int toHeaderWidth = (int) toTransform.rect.width();
845                int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
846                boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode();
847                mHeaderBar.onTaskViewSizeChanged((int) toTransform.rect.width(),
848                        (int) toTransform.rect.height());
849                thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
850                        Bitmap.Config.ARGB_8888);
851                if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
852                    thumbnail.eraseColor(0xFFff0000);
853                } else {
854                    Canvas c = new Canvas(thumbnail);
855                    c.scale(toTransform.scale, toTransform.scale);
856                    mHeaderBar.rebindToTask(toTask, false /* touchExplorationEnabled */,
857                            disabledInSafeMode);
858                    mHeaderBar.setDimAlpha(toTransform.dimAlpha);
859                    mHeaderBar.draw(c);
860                    c.setBitmap(null);
861                }
862            }
863            return thumbnail.createAshmemBitmap();
864        }
865        return null;
866    }
867
868    /**
869     * Shows the recents activity
870     */
871    private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
872            boolean isTopTaskHome, boolean animate) {
873        RecentsTaskLoader loader = Recents.getTaskLoader();
874
875        // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
876        // should always preload the tasks now. If we are dragging in recents, reload them as
877        // the stacks might have changed.
878        if (mLaunchedWhileDocking || mTriggeredFromAltTab ||sInstanceLoadPlan == null) {
879            // Create a new load plan if preloadRecents() was never triggered
880            sInstanceLoadPlan = loader.createLoadPlan(mContext);
881        }
882        if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
883            loader.preloadTasks(sInstanceLoadPlan, topTask.id, isTopTaskHome);
884        }
885        TaskStack stack = sInstanceLoadPlan.getTaskStack();
886
887        // Update the header bar if necessary
888        updateHeaderBarLayout(false /* tryAndBindSearchWidget */, stack);
889
890        // Prepare the dummy stack for the transition
891        TaskStackLayoutAlgorithm.VisibilityReport stackVr =
892                mDummyStackView.computeStackVisibilityReport();
893
894        if (!animate) {
895            ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, -1, -1);
896            startRecentsActivity(topTask, opts, false /* fromHome */,
897                    false /* fromSearchHome */, false /* fromThumbnail*/, stackVr);
898            return;
899        }
900
901        boolean hasRecentTasks = stack.getTaskCount() > 0;
902        boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
903
904        if (useThumbnailTransition) {
905            // Try starting with a thumbnail transition
906            ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, mDummyStackView);
907            if (opts != null) {
908                startRecentsActivity(topTask, opts, false /* fromHome */,
909                        false /* fromSearchHome */, true /* fromThumbnail */, stackVr);
910            } else {
911                // Fall through below to the non-thumbnail transition
912                useThumbnailTransition = false;
913            }
914        }
915
916        if (!useThumbnailTransition) {
917            // If there is no thumbnail transition, but is launching from home into recents, then
918            // use a quick home transition and do the animation from home
919            if (hasRecentTasks) {
920                SystemServicesProxy ssp = Recents.getSystemServices();
921                String homeActivityPackage = ssp.getHomeActivityPackageName();
922                String searchWidgetPackage = null;
923                if (RecentsDebugFlags.Static.EnableSearchBar) {
924                    searchWidgetPackage = Prefs.getString(mContext,
925                            Prefs.Key.OVERVIEW_SEARCH_APP_WIDGET_PACKAGE, null);
926                } else {
927                    AppWidgetProviderInfo searchWidgetInfo = ssp.resolveSearchAppWidget();
928                    if (searchWidgetInfo != null) {
929                        searchWidgetPackage = searchWidgetInfo.provider.getPackageName();
930                    }
931                }
932
933                // Determine whether we are coming from a search owned home activity
934                boolean fromSearchHome = (homeActivityPackage != null) &&
935                        homeActivityPackage.equals(searchWidgetPackage);
936                ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
937                startRecentsActivity(topTask, opts, true /* fromHome */, fromSearchHome,
938                        false /* fromThumbnail */, stackVr);
939            } else {
940                // Otherwise we do the normal fade from an unknown source
941                ActivityOptions opts = getUnknownTransitionActivityOptions();
942                startRecentsActivity(topTask, opts, true /* fromHome */,
943                        false /* fromSearchHome */, false /* fromThumbnail */, stackVr);
944            }
945        }
946        mLastToggleTime = SystemClock.elapsedRealtime();
947    }
948
949    /**
950     * Starts the recents activity.
951     */
952    private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask,
953                ActivityOptions opts, boolean fromHome, boolean fromSearchHome,
954                boolean fromThumbnail, TaskStackLayoutAlgorithm.VisibilityReport vr) {
955        // Update the configuration based on the launch options
956        RecentsConfiguration config = Recents.getConfiguration();
957        RecentsActivityLaunchState launchState = config.getLaunchState();
958        launchState.launchedFromHome = fromSearchHome || fromHome;
959        launchState.launchedFromSearchHome = fromSearchHome;
960        launchState.launchedFromApp = fromThumbnail || mLaunchedWhileDocking;
961        launchState.launchedFromAppDocked = mLaunchedWhileDocking;
962        launchState.launchedToTaskId = (topTask != null) ? topTask.id : -1;
963        launchState.launchedWithAltTab = mTriggeredFromAltTab;
964        launchState.launchedReuseTaskStackViews = mCanReuseTaskStackViews;
965        launchState.launchedNumVisibleTasks = vr.numVisibleTasks;
966        launchState.launchedNumVisibleThumbnails = vr.numVisibleThumbnails;
967        launchState.launchedHasConfigurationChanged = false;
968        launchState.launchedViaDragGesture = mDraggingInRecents;
969        launchState.launchedWhileDocking = mLaunchedWhileDocking;
970
971        Intent intent = new Intent();
972        intent.setClassName(RECENTS_PACKAGE, mRecentsIntentActivityName);
973        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
974                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
975                | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
976
977        if (opts != null) {
978            mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
979        } else {
980            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
981        }
982        mCanReuseTaskStackViews = true;
983        EventBus.getDefault().send(new RecentsActivityStartingEvent());
984    }
985
986    /**** OnAnimationFinishedListener Implementation ****/
987
988    @Override
989    public void onAnimationFinished() {
990        EventBus.getDefault().post(new EnterRecentsWindowLastAnimationFrameEvent());
991    }
992}
993