RecentsImpl.java revision fc48b072e8caa62849ee305f89f38d248d4a1ded
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;
20import static android.view.View.MeasureSpec;
21
22import android.app.ActivityManager;
23import android.app.ActivityOptions;
24import android.content.ActivityNotFoundException;
25import android.content.Context;
26import android.content.Intent;
27import android.content.res.Resources;
28import android.graphics.Bitmap;
29import android.graphics.Canvas;
30import android.graphics.Rect;
31import android.graphics.RectF;
32import android.graphics.drawable.Drawable;
33import android.os.Handler;
34import android.os.SystemClock;
35import android.os.UserHandle;
36import android.util.Log;
37import android.util.MutableBoolean;
38import android.view.AppTransitionAnimationSpec;
39import android.view.LayoutInflater;
40import android.view.ViewConfiguration;
41import android.view.WindowManager;
42
43import com.android.internal.logging.MetricsLogger;
44import com.android.internal.policy.DockedDividerUtils;
45import com.android.systemui.R;
46import com.android.systemui.SystemUIApplication;
47import com.android.systemui.recents.events.EventBus;
48import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
49import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
50import com.android.systemui.recents.events.activity.HideRecentsEvent;
51import com.android.systemui.recents.events.activity.IterateRecentsEvent;
52import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
53import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
54import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
55import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
56import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
57import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
58import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
59import com.android.systemui.recents.misc.DozeTrigger;
60import com.android.systemui.recents.misc.ForegroundThread;
61import com.android.systemui.recents.misc.SystemServicesProxy;
62import com.android.systemui.recents.misc.SystemServicesProxy.TaskStackListener;
63import com.android.systemui.recents.model.RecentsTaskLoadPlan;
64import com.android.systemui.recents.model.RecentsTaskLoader;
65import com.android.systemui.recents.model.Task;
66import com.android.systemui.recents.model.TaskGrouping;
67import com.android.systemui.recents.model.TaskStack;
68import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
69import com.android.systemui.recents.views.TaskStackView;
70import com.android.systemui.recents.views.TaskStackViewScroller;
71import com.android.systemui.recents.views.TaskViewHeader;
72import com.android.systemui.recents.views.TaskViewTransform;
73import com.android.systemui.stackdivider.DividerView;
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
99    /**
100     * An implementation of TaskStackListener, that allows us to listen for changes to the system
101     * task stacks and update recents accordingly.
102     */
103    class TaskStackListenerImpl extends TaskStackListener {
104        @Override
105        public void onTaskStackChanged() {
106            // Preloads the next task
107            RecentsConfiguration config = Recents.getConfiguration();
108            if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
109                RecentsTaskLoader loader = Recents.getTaskLoader();
110                SystemServicesProxy ssp = Recents.getSystemServices();
111                ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
112
113                // Load the next task only if we aren't svelte
114                RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
115                loader.preloadTasks(plan, -1, true /* isHomeStackVisible */);
116                RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
117                // This callback is made when a new activity is launched and the old one is paused
118                // so ignore the current activity and try and preload the thumbnail for the
119                // previous one.
120                if (runningTaskInfo != null) {
121                    launchOpts.runningTaskId = runningTaskInfo.id;
122                }
123                launchOpts.numVisibleTasks = 2;
124                launchOpts.numVisibleTaskThumbnails = 2;
125                launchOpts.onlyLoadForCache = true;
126                launchOpts.onlyLoadPausedActivities = true;
127                loader.loadTasks(mContext, plan, launchOpts);
128            }
129        }
130    }
131
132    protected static RecentsTaskLoadPlan sInstanceLoadPlan;
133
134    protected Context mContext;
135    protected Handler mHandler;
136    TaskStackListenerImpl mTaskStackListener;
137    boolean mDraggingInRecents;
138    boolean mLaunchedWhileDocking;
139
140    // Task launching
141    Rect mTaskStackBounds = new Rect();
142    TaskViewTransform mTmpTransform = new TaskViewTransform();
143    int mStatusBarHeight;
144    int mNavBarHeight;
145    int mNavBarWidth;
146    int mTaskBarHeight;
147
148    // Header (for transition)
149    TaskViewHeader mHeaderBar;
150    final Object mHeaderBarLock = new Object();
151    protected TaskStackView mDummyStackView;
152
153    // Variables to keep track of if we need to start recents after binding
154    protected boolean mTriggeredFromAltTab;
155    protected long mLastToggleTime;
156    DozeTrigger mFastAltTabTrigger = new DozeTrigger(FAST_ALT_TAB_DELAY_MS, new Runnable() {
157        @Override
158        public void run() {
159            // When this fires, then the user has not released alt-tab for at least
160            // FAST_ALT_TAB_DELAY_MS milliseconds
161            showRecents(mTriggeredFromAltTab, false /* draggingInRecents */, true /* animate */,
162                    false /* reloadTasks */, false /* fromHome */,
163                    DividerView.INVALID_RECENTS_GROW_TARGET);
164        }
165    });
166
167    protected Bitmap mThumbTransitionBitmapCache;
168
169    public RecentsImpl(Context context) {
170        mContext = context;
171        mHandler = new Handler();
172
173        // Initialize the static foreground thread
174        ForegroundThread.get();
175
176        // Register the task stack listener
177        mTaskStackListener = new TaskStackListenerImpl();
178        SystemServicesProxy ssp = Recents.getSystemServices();
179        ssp.registerTaskStackListener(mTaskStackListener);
180
181        // Initialize the static configuration resources
182        LayoutInflater inflater = LayoutInflater.from(mContext);
183        mDummyStackView = new TaskStackView(mContext);
184        mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
185                null, false);
186        reloadResources();
187
188        // When we start, preload the data associated with the previous recent tasks.
189        // We can use a new plan since the caches will be the same.
190        RecentsTaskLoader loader = Recents.getTaskLoader();
191        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
192        loader.preloadTasks(plan, -1, true /* isHomeStackVisible */);
193        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
194        launchOpts.numVisibleTasks = loader.getIconCacheSize();
195        launchOpts.numVisibleTaskThumbnails = loader.getThumbnailCacheSize();
196        launchOpts.onlyLoadForCache = true;
197        loader.loadTasks(mContext, plan, launchOpts);
198    }
199
200    public void onBootCompleted() {
201        // Do nothing
202    }
203
204    public void onConfigurationChanged() {
205        reloadResources();
206        mDummyStackView.reloadOnConfigurationChange();
207        mHeaderBar.onConfigurationChanged();
208    }
209
210    /**
211     * This is only called from the system user's Recents.  Secondary users will instead proxy their
212     * visibility change events through to the system user via
213     * {@link Recents#onBusEvent(RecentsVisibilityChangedEvent)}.
214     */
215    public void onVisibilityChanged(Context context, boolean visible) {
216        SystemUIApplication app = (SystemUIApplication) context;
217        PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
218        if (statusBar != null) {
219            statusBar.updateRecentsVisibility(visible);
220        }
221    }
222
223    /**
224     * This is only called from the system user's Recents.  Secondary users will instead proxy their
225     * visibility change events through to the system user via
226     * {@link Recents#onBusEvent(ScreenPinningRequestEvent)}.
227     */
228    public void onStartScreenPinning(Context context, int taskId) {
229        SystemUIApplication app = (SystemUIApplication) context;
230        PhoneStatusBar statusBar = app.getComponent(PhoneStatusBar.class);
231        if (statusBar != null) {
232            statusBar.showScreenPinningRequest(taskId, false);
233        }
234    }
235
236    public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents,
237            boolean animate, boolean launchedWhileDockingTask, boolean fromHome,
238            int growTarget) {
239        mTriggeredFromAltTab = triggeredFromAltTab;
240        mDraggingInRecents = draggingInRecents;
241        mLaunchedWhileDocking = launchedWhileDockingTask;
242        if (mFastAltTabTrigger.isAsleep()) {
243            // Fast alt-tab duration has elapsed, fall through to showing Recents and reset
244            mFastAltTabTrigger.stopDozing();
245        } else if (mFastAltTabTrigger.isDozing()) {
246            // Fast alt-tab duration has not elapsed.  If this is triggered by a different
247            // showRecents() call, then ignore that call for now.
248            // TODO: We can not handle quick tabs that happen between the initial showRecents() call
249            //       that started the activity and the activity starting up.  The severity of this
250            //       is inversely proportional to the FAST_ALT_TAB_DELAY_MS duration though.
251            if (!triggeredFromAltTab) {
252                return;
253            }
254            mFastAltTabTrigger.stopDozing();
255        } else if (triggeredFromAltTab) {
256            // The fast alt-tab detector is not yet running, so start the trigger and wait for the
257            // hideRecents() call, or for the fast alt-tab duration to elapse
258            mFastAltTabTrigger.startDozing();
259            return;
260        }
261
262        try {
263            // Check if the top task is in the home stack, and start the recents activity
264            SystemServicesProxy ssp = Recents.getSystemServices();
265            MutableBoolean isHomeStackVisible = new MutableBoolean(false);
266            if (!ssp.isRecentsActivityVisible(isHomeStackVisible)) {
267                ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
268                startRecentsActivity(runningTask, isHomeStackVisible.value || fromHome, animate,
269                        growTarget);
270            }
271        } catch (ActivityNotFoundException e) {
272            Log.e(TAG, "Failed to launch RecentsActivity", e);
273        }
274    }
275
276    public void hideRecents(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) {
277        if (triggeredFromAltTab && mFastAltTabTrigger.isDozing()) {
278            // The user has released alt-tab before the trigger has run, so just show the next
279            // task immediately
280            showNextTask();
281
282            // Cancel the fast alt-tab trigger
283            mFastAltTabTrigger.stopDozing();
284            return;
285        }
286
287        // Defer to the activity to handle hiding recents, if it handles it, then it must still
288        // be visible
289        EventBus.getDefault().post(new HideRecentsEvent(triggeredFromAltTab,
290                triggeredFromHomeKey));
291    }
292
293    public void toggleRecents(int growTarget) {
294        // Skip this toggle if we are already waiting to trigger recents via alt-tab
295        if (mFastAltTabTrigger.isDozing()) {
296            return;
297        }
298
299        mDraggingInRecents = false;
300        mLaunchedWhileDocking = false;
301        mTriggeredFromAltTab = false;
302
303        try {
304            SystemServicesProxy ssp = Recents.getSystemServices();
305            MutableBoolean isHomeStackVisible = new MutableBoolean(true);
306            long elapsedTime = SystemClock.elapsedRealtime() - mLastToggleTime;
307
308            if (ssp.isRecentsActivityVisible(isHomeStackVisible)) {
309                RecentsDebugFlags debugFlags = Recents.getDebugFlags();
310                RecentsConfiguration config = Recents.getConfiguration();
311                RecentsActivityLaunchState launchState = config.getLaunchState();
312                if (!launchState.launchedWithAltTab) {
313                    // If the user taps quickly
314                    if (!debugFlags.isPagingEnabled() ||
315                            (ViewConfiguration.getDoubleTapMinTime() < elapsedTime &&
316                                    elapsedTime < ViewConfiguration.getDoubleTapTimeout())) {
317                        // Launch the next focused task
318                        EventBus.getDefault().post(new LaunchNextTaskRequestEvent());
319                    } else {
320                        // Notify recents to move onto the next task
321                        EventBus.getDefault().post(new IterateRecentsEvent());
322                    }
323                } else {
324                    // If the user has toggled it too quickly, then just eat up the event here (it's
325                    // better than showing a janky screenshot).
326                    // NOTE: Ideally, the screenshot mechanism would take the window transform into
327                    // account
328                    if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
329                        return;
330                    }
331
332                    EventBus.getDefault().post(new ToggleRecentsEvent());
333                    mLastToggleTime = SystemClock.elapsedRealtime();
334                }
335                return;
336            } else {
337                // If the user has toggled it too quickly, then just eat up the event here (it's
338                // better than showing a janky screenshot).
339                // NOTE: Ideally, the screenshot mechanism would take the window transform into
340                // account
341                if (elapsedTime < MIN_TOGGLE_DELAY_MS) {
342                    return;
343                }
344
345                // Otherwise, start the recents activity
346                ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
347                startRecentsActivity(runningTask, isHomeStackVisible.value, true /* animate */,
348                        growTarget);
349
350                // Only close the other system windows if we are actually showing recents
351                ssp.sendCloseSystemWindows(BaseStatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS);
352                mLastToggleTime = SystemClock.elapsedRealtime();
353            }
354        } catch (ActivityNotFoundException e) {
355            Log.e(TAG, "Failed to launch RecentsActivity", e);
356        }
357    }
358
359    public void preloadRecents() {
360        // Preload only the raw task list into a new load plan (which will be consumed by the
361        // RecentsActivity) only if there is a task to animate to.
362        SystemServicesProxy ssp = Recents.getSystemServices();
363        MutableBoolean isHomeStackVisible = new MutableBoolean(true);
364        if (!ssp.isRecentsActivityVisible(isHomeStackVisible)) {
365            ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
366            RecentsTaskLoader loader = Recents.getTaskLoader();
367            sInstanceLoadPlan = loader.createLoadPlan(mContext);
368            sInstanceLoadPlan.preloadRawTasks(isHomeStackVisible.value);
369            loader.preloadTasks(sInstanceLoadPlan, runningTask.id, isHomeStackVisible.value);
370            TaskStack stack = sInstanceLoadPlan.getTaskStack();
371            if (stack.getTaskCount() > 0) {
372                // Only preload the icon (but not the thumbnail since it may not have been taken for
373                // the pausing activity)
374                preloadIcon(runningTask);
375
376                // At this point, we don't know anything about the stack state.  So only calculate
377                // the dimensions of the thumbnail that we need for the transition into Recents, but
378                // do not draw it until we construct the activity options when we start Recents
379                updateHeaderBarLayout(stack, null /* window rect override*/);
380            }
381        }
382    }
383
384    public void cancelPreloadingRecents() {
385        // Do nothing
386    }
387
388    public void onDraggingInRecents(float distanceFromTop) {
389        EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEvent(distanceFromTop));
390    }
391
392    public void onDraggingInRecentsEnded(float velocity) {
393        EventBus.getDefault().sendOntoMainThread(new DraggingInRecentsEndedEvent(velocity));
394    }
395
396    /**
397     * Transitions to the next recent task in the stack.
398     */
399    public void showNextTask() {
400        SystemServicesProxy ssp = Recents.getSystemServices();
401        RecentsTaskLoader loader = Recents.getTaskLoader();
402        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
403        loader.preloadTasks(plan, -1, true /* isHomeStackVisible */);
404        TaskStack focusedStack = plan.getTaskStack();
405
406        // Return early if there are no tasks in the focused stack
407        if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
408
409        // Return early if there is no running task
410        ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
411        if (runningTask == null) return;
412
413        // Find the task in the recents list
414        boolean isRunningTaskInHomeStack = SystemServicesProxy.isHomeStack(runningTask.stackId);
415        ArrayList<Task> tasks = focusedStack.getStackTasks();
416        Task toTask = null;
417        ActivityOptions launchOpts = null;
418        int taskCount = tasks.size();
419        for (int i = taskCount - 1; i >= 1; i--) {
420            Task task = tasks.get(i);
421            if (isRunningTaskInHomeStack) {
422                toTask = tasks.get(i - 1);
423                launchOpts = ActivityOptions.makeCustomAnimation(mContext,
424                        R.anim.recents_launch_next_affiliated_task_target,
425                        R.anim.recents_fast_toggle_app_home_exit);
426                break;
427            } else if (task.key.id == runningTask.id) {
428                toTask = tasks.get(i - 1);
429                launchOpts = ActivityOptions.makeCustomAnimation(mContext,
430                        R.anim.recents_launch_prev_affiliated_task_target,
431                        R.anim.recents_launch_prev_affiliated_task_source);
432                break;
433            }
434        }
435
436        // Return early if there is no next task
437        if (toTask == null) {
438            ssp.startInPlaceAnimationOnFrontMostApplication(
439                    ActivityOptions.makeCustomInPlaceAnimation(mContext,
440                            R.anim.recents_launch_prev_affiliated_task_bounce));
441            return;
442        }
443
444        // Launch the task
445        ssp.startActivityFromRecents(mContext, toTask.key, toTask.title, launchOpts);
446    }
447
448    /**
449     * Transitions to the next affiliated task.
450     */
451    public void showRelativeAffiliatedTask(boolean showNextTask) {
452        SystemServicesProxy ssp = Recents.getSystemServices();
453        RecentsTaskLoader loader = Recents.getTaskLoader();
454        RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
455        loader.preloadTasks(plan, -1, true /* isHomeStackVisible */);
456        TaskStack focusedStack = plan.getTaskStack();
457
458        // Return early if there are no tasks in the focused stack
459        if (focusedStack == null || focusedStack.getTaskCount() == 0) return;
460
461        // Return early if there is no running task (can't determine affiliated tasks in this case)
462        ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
463        if (runningTask == null) return;
464        // Return early if the running task is in the home stack (optimization)
465        if (SystemServicesProxy.isHomeStack(runningTask.stackId)) return;
466
467        // Find the task in the recents list
468        ArrayList<Task> tasks = focusedStack.getStackTasks();
469        Task toTask = null;
470        ActivityOptions launchOpts = null;
471        int taskCount = tasks.size();
472        int numAffiliatedTasks = 0;
473        for (int i = 0; i < taskCount; i++) {
474            Task task = tasks.get(i);
475            if (task.key.id == runningTask.id) {
476                TaskGrouping group = task.group;
477                Task.TaskKey toTaskKey;
478                if (showNextTask) {
479                    toTaskKey = group.getNextTaskInGroup(task);
480                    launchOpts = ActivityOptions.makeCustomAnimation(mContext,
481                            R.anim.recents_launch_next_affiliated_task_target,
482                            R.anim.recents_launch_next_affiliated_task_source);
483                } else {
484                    toTaskKey = group.getPrevTaskInGroup(task);
485                    launchOpts = ActivityOptions.makeCustomAnimation(mContext,
486                            R.anim.recents_launch_prev_affiliated_task_target,
487                            R.anim.recents_launch_prev_affiliated_task_source);
488                }
489                if (toTaskKey != null) {
490                    toTask = focusedStack.findTaskWithId(toTaskKey.id);
491                }
492                numAffiliatedTasks = group.getTaskCount();
493                break;
494            }
495        }
496
497        // Return early if there is no next task
498        if (toTask == null) {
499            if (numAffiliatedTasks > 1) {
500                if (showNextTask) {
501                    ssp.startInPlaceAnimationOnFrontMostApplication(
502                            ActivityOptions.makeCustomInPlaceAnimation(mContext,
503                                    R.anim.recents_launch_next_affiliated_task_bounce));
504                } else {
505                    ssp.startInPlaceAnimationOnFrontMostApplication(
506                            ActivityOptions.makeCustomInPlaceAnimation(mContext,
507                                    R.anim.recents_launch_prev_affiliated_task_bounce));
508                }
509            }
510            return;
511        }
512
513        // Keep track of actually launched affiliated tasks
514        MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
515
516        // Launch the task
517        ssp.startActivityFromRecents(mContext, toTask.key, toTask.title, launchOpts);
518    }
519
520    public void showNextAffiliatedTask() {
521        // Keep track of when the affiliated task is triggered
522        MetricsLogger.count(mContext, "overview_affiliated_task_next", 1);
523        showRelativeAffiliatedTask(true);
524    }
525
526    public void showPrevAffiliatedTask() {
527        // Keep track of when the affiliated task is triggered
528        MetricsLogger.count(mContext, "overview_affiliated_task_prev", 1);
529        showRelativeAffiliatedTask(false);
530    }
531
532    public void dockTopTask(int topTaskId, int dragMode,
533            int stackCreateMode, Rect initialBounds) {
534        SystemServicesProxy ssp = Recents.getSystemServices();
535
536        // Make sure we inform DividerView before we actually start the activity so we can change
537        // the resize mode already.
538        if (ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) {
539            EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
540            showRecents(
541                    false /* triggeredFromAltTab */,
542                    dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS,
543                    false /* animate */,
544                    true /* launchedWhileDockingTask*/,
545                    false /* fromHome */,
546                    DividerView.INVALID_RECENTS_GROW_TARGET);
547        }
548    }
549
550    /**
551     * Returns the preloaded load plan and invalidates it.
552     */
553    public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
554        RecentsTaskLoadPlan plan = sInstanceLoadPlan;
555        sInstanceLoadPlan = null;
556        return plan;
557    }
558
559    /**
560     * Reloads all the resources for the current configuration.
561     */
562    private void reloadResources() {
563        Resources res = mContext.getResources();
564
565        mStatusBarHeight = res.getDimensionPixelSize(
566                com.android.internal.R.dimen.status_bar_height);
567        mNavBarHeight = res.getDimensionPixelSize(
568                com.android.internal.R.dimen.navigation_bar_height);
569        mNavBarWidth = res.getDimensionPixelSize(
570                com.android.internal.R.dimen.navigation_bar_width);
571        mTaskBarHeight = TaskStackLayoutAlgorithm.getDimensionForDevice(mContext,
572                R.dimen.recents_task_view_header_height,
573                R.dimen.recents_task_view_header_height,
574                R.dimen.recents_task_view_header_height,
575                R.dimen.recents_task_view_header_height_tablet_land,
576                R.dimen.recents_task_view_header_height,
577                R.dimen.recents_task_view_header_height_tablet_land);
578    }
579
580    /**
581     * Prepares the header bar layout for the next transition, if the task view bounds has changed
582     * since the last call, it will attempt to re-measure and layout the header bar to the new size.
583     *
584     * @param stack the stack to initialize the stack layout with
585     * @param windowRectOverride the rectangle to use when calculating the stack state which can
586     *                           be different from the current window rect if recents is resizing
587     *                           while being launched
588     */
589    private void updateHeaderBarLayout(TaskStack stack, Rect windowRectOverride) {
590        SystemServicesProxy ssp = Recents.getSystemServices();
591        Rect displayRect = ssp.getDisplayRect();
592        Rect systemInsets = new Rect();
593        ssp.getStableInsets(systemInsets);
594        Rect windowRect = windowRectOverride != null
595                ? new Rect(windowRectOverride)
596                : ssp.getWindowRect();
597        // When docked, the nav bar insets are consumed and the activity is measured without insets.
598        // However, the window bounds include the insets, so we need to subtract them here to make
599        // them identical.
600        if (ssp.hasDockedTask()) {
601            windowRect.bottom -= systemInsets.bottom;
602            systemInsets.bottom = 0;
603        }
604        calculateWindowStableInsets(systemInsets, windowRect);
605        windowRect.offsetTo(0, 0);
606
607        TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
608
609        // Rebind the header bar and draw it for the transition
610        stackLayout.setSystemInsets(systemInsets);
611        if (stack != null) {
612            stackLayout.getTaskStackBounds(displayRect, windowRect, systemInsets.top,
613                    systemInsets.right, mTaskStackBounds);
614            stackLayout.reset();
615            stackLayout.initialize(displayRect, windowRect, mTaskStackBounds,
616                    TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack));
617            mDummyStackView.setTasks(stack, false /* allowNotifyStackChanges */);
618
619            Rect taskViewBounds = stackLayout.getUntransformedTaskViewBounds();
620            if (!taskViewBounds.isEmpty()) {
621                int taskViewWidth = taskViewBounds.width();
622                synchronized (mHeaderBarLock) {
623                    if (mHeaderBar.getMeasuredWidth() != taskViewWidth ||
624                            mHeaderBar.getMeasuredHeight() != mTaskBarHeight) {
625                        mHeaderBar.measure(
626                                MeasureSpec.makeMeasureSpec(taskViewWidth, MeasureSpec.EXACTLY),
627                                MeasureSpec.makeMeasureSpec(mTaskBarHeight, MeasureSpec.EXACTLY));
628                    }
629                    mHeaderBar.layout(0, 0, taskViewWidth, mTaskBarHeight);
630                }
631
632                // Update the transition bitmap to match the new header bar height
633                if (mThumbTransitionBitmapCache == null ||
634                        (mThumbTransitionBitmapCache.getWidth() != taskViewWidth) ||
635                        (mThumbTransitionBitmapCache.getHeight() != mTaskBarHeight)) {
636                    mThumbTransitionBitmapCache = Bitmap.createBitmap(taskViewWidth,
637                            mTaskBarHeight, Bitmap.Config.ARGB_8888);
638                }
639            }
640        }
641    }
642
643    /**
644     * Given the stable insets and the rect for our window, calculates the insets that affect our
645     * window.
646     */
647    private void calculateWindowStableInsets(Rect inOutInsets, Rect windowRect) {
648        Rect displayRect = Recents.getSystemServices().getDisplayRect();
649
650        // Display rect without insets - available app space
651        Rect appRect = new Rect(displayRect);
652        appRect.inset(inOutInsets);
653
654        // Our window intersected with available app space
655        Rect windowRectWithInsets = new Rect(windowRect);
656        windowRectWithInsets.intersect(appRect);
657        inOutInsets.left = windowRectWithInsets.left - windowRect.left;
658        inOutInsets.top = windowRectWithInsets.top - windowRect.top;
659        inOutInsets.right = windowRect.right - windowRectWithInsets.right;
660        inOutInsets.bottom = windowRect.bottom - windowRectWithInsets.bottom;
661    }
662
663    /**
664     * Preloads the icon of a task.
665     */
666    private void preloadIcon(ActivityManager.RunningTaskInfo task) {
667        // Ensure that we load the running task's icon
668        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
669        launchOpts.runningTaskId = task.id;
670        launchOpts.loadThumbnails = false;
671        launchOpts.onlyLoadForCache = true;
672        Recents.getTaskLoader().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
673    }
674
675    /**
676     * Creates the activity options for a unknown state->recents transition.
677     */
678    protected ActivityOptions getUnknownTransitionActivityOptions() {
679        return ActivityOptions.makeCustomAnimation(mContext,
680                R.anim.recents_from_unknown_enter,
681                R.anim.recents_from_unknown_exit,
682                mHandler, null);
683    }
684
685    /**
686     * Creates the activity options for a home->recents transition.
687     */
688    protected ActivityOptions getHomeTransitionActivityOptions() {
689        return ActivityOptions.makeCustomAnimation(mContext,
690                R.anim.recents_from_launcher_enter,
691                R.anim.recents_from_launcher_exit,
692                mHandler, null);
693    }
694
695    /**
696     * Creates the activity options for an app->recents transition.
697     */
698    private ActivityOptions getThumbnailTransitionActivityOptions(
699            ActivityManager.RunningTaskInfo runningTask, TaskStackView stackView,
700                    Rect windowOverrideRect) {
701        if (runningTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
702            ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
703            ArrayList<Task> tasks = stackView.getStack().getStackTasks();
704            TaskStackLayoutAlgorithm stackLayout = stackView.getStackAlgorithm();
705            TaskStackViewScroller stackScroller = stackView.getScroller();
706
707            stackView.updateLayoutAlgorithm(true /* boundScroll */);
708            stackView.updateToInitialState();
709
710            for (int i = tasks.size() - 1; i >= 0; i--) {
711                Task task = tasks.get(i);
712                if (task.isFreeformTask()) {
713                    mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task,
714                            stackScroller.getStackScroll(), mTmpTransform, null,
715                            windowOverrideRect);
716                    Bitmap thumbnail = drawThumbnailTransitionBitmap(task, mTmpTransform,
717                            mThumbTransitionBitmapCache);
718                    Rect toTaskRect = new Rect();
719                    mTmpTransform.rect.round(toTaskRect);
720                    specs.add(new AppTransitionAnimationSpec(task.key.id, thumbnail, toTaskRect));
721                }
722            }
723            AppTransitionAnimationSpec[] specsArray = new AppTransitionAnimationSpec[specs.size()];
724            specs.toArray(specsArray);
725            return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
726                    specsArray, mHandler, null, this);
727        } else {
728            // Update the destination rect
729            Task toTask = new Task();
730            TaskViewTransform toTransform = getThumbnailTransitionTransform(stackView, toTask,
731                    windowOverrideRect);
732            Bitmap thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform,
733                    mThumbTransitionBitmapCache);
734            if (thumbnail != null) {
735                RectF toTaskRect = toTransform.rect;
736                return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
737                        thumbnail, (int) toTaskRect.left, (int) toTaskRect.top,
738                        (int) toTaskRect.width(), (int) toTaskRect.height(), mHandler, null);
739            }
740            // If both the screenshot and thumbnail fails, then just fall back to the default transition
741            return getUnknownTransitionActivityOptions();
742        }
743    }
744
745    /**
746     * Returns the transition rect for the given task id.
747     */
748    private TaskViewTransform getThumbnailTransitionTransform(TaskStackView stackView,
749            Task runningTaskOut, Rect windowOverrideRect) {
750        // Find the running task in the TaskStack
751        TaskStack stack = stackView.getStack();
752        Task launchTask = stack.getLaunchTarget();
753        if (launchTask != null) {
754            runningTaskOut.copyFrom(launchTask);
755        } else {
756            // If no task is specified or we can not find the task just use the front most one
757            launchTask = stack.getStackFrontMostTask(true /* includeFreeform */);
758            runningTaskOut.copyFrom(launchTask);
759        }
760
761        // Get the transform for the running task
762        stackView.updateLayoutAlgorithm(true /* boundScroll */);
763        stackView.updateToInitialState();
764        stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
765                stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect);
766        return mTmpTransform;
767    }
768
769    /**
770     * Draws the header of a task used for the window animation into a bitmap.
771     */
772    private Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform,
773            Bitmap thumbnail) {
774        SystemServicesProxy ssp = Recents.getSystemServices();
775        if (toTransform != null && toTask.key != null) {
776            synchronized (mHeaderBarLock) {
777                boolean disabledInSafeMode = !toTask.isSystemApp && ssp.isInSafeMode();
778                mHeaderBar.onTaskViewSizeChanged((int) toTransform.rect.width(),
779                        (int) toTransform.rect.height());
780                if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
781                    thumbnail.eraseColor(0xFFff0000);
782                } else {
783                    thumbnail.eraseColor(0);
784                    Canvas c = new Canvas(thumbnail);
785                    // Workaround for b/27815919, reset the callback so that we do not trigger an
786                    // invalidate on the header bar as a result of updating the icon
787                    Drawable icon = mHeaderBar.getIconView().getDrawable();
788                    if (icon != null) {
789                        icon.setCallback(null);
790                    }
791                    mHeaderBar.bindToTask(toTask, false /* touchExplorationEnabled */,
792                            disabledInSafeMode);
793                    mHeaderBar.onTaskDataLoaded();
794                    mHeaderBar.setDimAlpha(toTransform.dimAlpha);
795                    mHeaderBar.draw(c);
796                    c.setBitmap(null);
797                }
798            }
799            return thumbnail.createAshmemBitmap();
800        }
801        return null;
802    }
803
804    /**
805     * Shows the recents activity
806     */
807    protected void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask,
808            boolean isHomeStackVisible, boolean animate, int growTarget) {
809        RecentsTaskLoader loader = Recents.getTaskLoader();
810        RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
811
812        // In the case where alt-tab is triggered, we never get a preloadRecents() call, so we
813        // should always preload the tasks now. If we are dragging in recents, reload them as
814        // the stacks might have changed.
815        if (mLaunchedWhileDocking || mTriggeredFromAltTab ||sInstanceLoadPlan == null) {
816            // Create a new load plan if preloadRecents() was never triggered
817            sInstanceLoadPlan = loader.createLoadPlan(mContext);
818        }
819        if (mLaunchedWhileDocking || mTriggeredFromAltTab || !sInstanceLoadPlan.hasTasks()) {
820            loader.preloadTasks(sInstanceLoadPlan, runningTask.id, isHomeStackVisible);
821        }
822
823        TaskStack stack = sInstanceLoadPlan.getTaskStack();
824        boolean hasRecentTasks = stack.getTaskCount() > 0;
825        boolean useThumbnailTransition = (runningTask != null) && !isHomeStackVisible && hasRecentTasks;
826
827        // Update the launch state that we need in updateHeaderBarLayout()
828        launchState.launchedFromHome = !useThumbnailTransition;
829        launchState.launchedFromApp = useThumbnailTransition || mLaunchedWhileDocking;
830        launchState.launchedViaDockGesture = mLaunchedWhileDocking;
831        launchState.launchedViaDragGesture = mDraggingInRecents;
832        launchState.launchedToTaskId = (runningTask != null) ? runningTask.id : -1;
833        launchState.launchedWithAltTab = mTriggeredFromAltTab;
834
835        // Preload the icon (this will be a null-op if we have preloaded the icon already in
836        // preloadRecents())
837        preloadIcon(runningTask);
838
839        // Update the header bar if necessary
840        Rect windowOverrideRect = getWindowRectOverride(growTarget);
841        updateHeaderBarLayout(stack, windowOverrideRect);
842
843        // Prepare the dummy stack for the transition
844        TaskStackLayoutAlgorithm.VisibilityReport stackVr =
845                mDummyStackView.computeStackVisibilityReport();
846
847        // Update the remaining launch state
848        launchState.launchedNumVisibleTasks = stackVr.numVisibleTasks;
849        launchState.launchedNumVisibleThumbnails = stackVr.numVisibleThumbnails;
850
851        if (!animate) {
852            startRecentsActivity(ActivityOptions.makeCustomAnimation(mContext, -1, -1));
853            return;
854        }
855
856        ActivityOptions opts;
857        if (useThumbnailTransition) {
858            // Try starting with a thumbnail transition
859            opts = getThumbnailTransitionActivityOptions(runningTask, mDummyStackView,
860                    windowOverrideRect);
861        } else {
862            // If there is no thumbnail transition, but is launching from home into recents, then
863            // use a quick home transition
864            opts = hasRecentTasks
865                ? getHomeTransitionActivityOptions()
866                : getUnknownTransitionActivityOptions();
867        }
868        startRecentsActivity(opts);
869        mLastToggleTime = SystemClock.elapsedRealtime();
870    }
871
872    private Rect getWindowRectOverride(int growTarget) {
873        if (growTarget == DividerView.INVALID_RECENTS_GROW_TARGET) {
874            return null;
875        }
876        Rect result = new Rect();
877        Rect displayRect = Recents.getSystemServices().getDisplayRect();
878        DockedDividerUtils.calculateBoundsForPosition(growTarget, WindowManager.DOCKED_BOTTOM,
879                result, displayRect.width(), displayRect.height(),
880                Recents.getSystemServices().getDockedDividerSize(mContext));
881        return result;
882    }
883
884    /**
885     * Starts the recents activity.
886     */
887    private void startRecentsActivity(ActivityOptions opts) {
888        Intent intent = new Intent();
889        intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
890        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
891                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
892                | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
893
894        if (opts != null) {
895            mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
896        } else {
897            mContext.startActivityAsUser(intent, UserHandle.CURRENT);
898        }
899        EventBus.getDefault().send(new RecentsActivityStartingEvent());
900    }
901
902    /**** OnAnimationFinishedListener Implementation ****/
903
904    @Override
905    public void onAnimationFinished() {
906        EventBus.getDefault().post(new EnterRecentsWindowLastAnimationFrameEvent());
907    }
908}
909