1/*
2 * Copyright (C) 2014 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.views;
18
19import android.annotation.IntDef;
20import android.content.Context;
21import android.content.res.Configuration;
22import android.content.res.Resources;
23import android.graphics.Path;
24import android.graphics.Rect;
25import android.util.ArraySet;
26import android.util.Log;
27import android.util.SparseArray;
28import android.util.SparseIntArray;
29import android.view.ViewDebug;
30
31import com.android.systemui.R;
32import com.android.systemui.recents.Recents;
33import com.android.systemui.recents.RecentsActivityLaunchState;
34import com.android.systemui.recents.RecentsConfiguration;
35import com.android.systemui.recents.RecentsDebugFlags;
36import com.android.systemui.recents.misc.FreePathInterpolator;
37import com.android.systemui.recents.misc.SystemServicesProxy;
38import com.android.systemui.shared.recents.utilities.Utilities;
39import com.android.systemui.shared.recents.model.Task;
40import com.android.systemui.shared.recents.model.TaskStack;
41import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
42import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
43
44import java.io.PrintWriter;
45import java.lang.annotation.Retention;
46import java.lang.annotation.RetentionPolicy;
47import java.util.ArrayList;
48import java.util.List;
49
50/**
51 * Used to describe a visible range that can be normalized to [0, 1].
52 */
53class Range {
54    final float relativeMin;
55    final float relativeMax;
56    float origin;
57    float min;
58    float max;
59
60    public Range(float relMin, float relMax) {
61        min = relativeMin = relMin;
62        max = relativeMax = relMax;
63    }
64
65    /**
66     * Offsets this range to a given absolute position.
67     */
68    public void offset(float x) {
69        this.origin = x;
70        min = x + relativeMin;
71        max = x + relativeMax;
72    }
73
74    /**
75     * Returns x normalized to the range 0 to 1 such that 0 = min, 0.5 = origin and 1 = max
76     *
77     * @param x is an absolute value in the same domain as origin
78     */
79    public float getNormalizedX(float x) {
80        if (x < origin) {
81            return 0.5f + 0.5f * (x - origin) / -relativeMin;
82        } else {
83            return 0.5f + 0.5f * (x - origin) / relativeMax;
84        }
85    }
86
87    /**
88     * Given a normalized {@param x} value in this range, projected onto the full range to get an
89     * absolute value about the given {@param origin}.
90     */
91    public float getAbsoluteX(float normX) {
92        if (normX < 0.5f) {
93            return (normX - 0.5f) / 0.5f * -relativeMin;
94        } else {
95            return (normX - 0.5f) / 0.5f * relativeMax;
96        }
97    }
98
99    /**
100     * Returns whether a value at an absolute x would be within range.
101     */
102    public boolean isInRange(float absX) {
103        return (absX >= Math.floor(min)) && (absX <= Math.ceil(max));
104    }
105}
106
107/**
108 * The layout logic for a TaskStackView.  This layout needs to be able to calculate the stack layout
109 * without an activity-specific context only with the information passed in.  This layout can have
110 * two states focused and unfocused, and in the focused state, there is a task that is displayed
111 * more prominently in the stack.
112 */
113public class TaskStackLayoutAlgorithm {
114
115    private static final String TAG = "TaskStackLayoutAlgorithm";
116
117    // The distribution of view bounds alpha
118    // XXX: This is a hack because you can currently set the max alpha to be > 1f
119    public static final float OUTLINE_ALPHA_MIN_VALUE = 0f;
120    public static final float OUTLINE_ALPHA_MAX_VALUE = 2f;
121
122    // The medium/maximum dim on the tasks
123    private static final float MED_DIM = 0.15f;
124    private static final float MAX_DIM = 0.25f;
125
126    // The various focus states
127    public static final int STATE_FOCUSED = 1;
128    public static final int STATE_UNFOCUSED = 0;
129
130    // The side that an offset is anchored
131    @Retention(RetentionPolicy.SOURCE)
132    @IntDef({FROM_TOP, FROM_BOTTOM})
133    public @interface AnchorSide {}
134    private static final int FROM_TOP = 0;
135    private static final int FROM_BOTTOM = 1;
136
137    // The extent that we care about when calculating fractions
138    @Retention(RetentionPolicy.SOURCE)
139    @IntDef({WIDTH, HEIGHT})
140    public @interface Extent {}
141    private static final int WIDTH = 0;
142    private static final int HEIGHT = 1;
143
144    public interface TaskStackLayoutAlgorithmCallbacks {
145        void onFocusStateChanged(int prevFocusState, int curFocusState);
146    }
147
148    /**
149     * @return True if we should use the grid layout.
150     */
151    boolean useGridLayout() {
152        return Recents.getConfiguration().isGridEnabled;
153    }
154
155    // A report of the visibility state of the stack
156    public static class VisibilityReport {
157        public int numVisibleTasks;
158        public int numVisibleThumbnails;
159
160        public VisibilityReport(int tasks, int thumbnails) {
161            numVisibleTasks = tasks;
162            numVisibleThumbnails = thumbnails;
163        }
164    }
165
166    Context mContext;
167    private TaskStackLayoutAlgorithmCallbacks mCb;
168
169    // The task bounds (untransformed) for layout.  This rect is anchored at mTaskRoot.
170    @ViewDebug.ExportedProperty(category="recents")
171    public Rect mTaskRect = new Rect();
172    // The stack bounds, inset from the top system insets, and runs to the bottom of the screen
173    @ViewDebug.ExportedProperty(category="recents")
174    public Rect mStackRect = new Rect();
175    // This is the current system insets
176    @ViewDebug.ExportedProperty(category="recents")
177    public Rect mSystemInsets = new Rect();
178
179    // The visible ranges when the stack is focused and unfocused
180    private Range mUnfocusedRange;
181    private Range mFocusedRange;
182
183    // This is the bounds of the stack action above the stack rect
184    @ViewDebug.ExportedProperty(category="recents")
185    private Rect mStackActionButtonRect = new Rect();
186    // The base top margin for the stack from the system insets
187    @ViewDebug.ExportedProperty(category="recents")
188    private int mBaseTopMargin;
189    // The base side margin for the stack from the system insets
190    @ViewDebug.ExportedProperty(category="recents")
191    private int mBaseSideMargin;
192    // The base bottom margin for the stack from the system insets
193    @ViewDebug.ExportedProperty(category="recents")
194    private int mBaseBottomMargin;
195    private int mMinMargin;
196
197    // The initial offset that the focused task is from the top
198    @ViewDebug.ExportedProperty(category="recents")
199    private int mInitialTopOffset;
200    private int mBaseInitialTopOffset;
201    // The initial offset that the launch-from task is from the bottom
202    @ViewDebug.ExportedProperty(category="recents")
203    private int mInitialBottomOffset;
204    private int mBaseInitialBottomOffset;
205
206    // The height between the top margin and the top of the focused task
207    @ViewDebug.ExportedProperty(category="recents")
208    private int mFocusedTopPeekHeight;
209    // The height between the bottom margin and the top of task in front of the focused task
210    @ViewDebug.ExportedProperty(category="recents")
211    private int mFocusedBottomPeekHeight;
212
213    // The offset from the bottom of the stack to the bottom of the bounds when the stack is
214    // scrolled to the front
215    @ViewDebug.ExportedProperty(category="recents")
216    private int mStackBottomOffset;
217
218    /** The height, in pixels, of each task view's title bar. */
219    private int mTitleBarHeight;
220
221    // The paths defining the motion of the tasks when the stack is focused and unfocused
222    private Path mUnfocusedCurve;
223    private Path mFocusedCurve;
224    private FreePathInterpolator mUnfocusedCurveInterpolator;
225    private FreePathInterpolator mFocusedCurveInterpolator;
226
227    // The paths defining the distribution of the dim to apply to tasks in the stack when focused
228    // and unfocused
229    private Path mUnfocusedDimCurve;
230    private Path mFocusedDimCurve;
231    private FreePathInterpolator mUnfocusedDimCurveInterpolator;
232    private FreePathInterpolator mFocusedDimCurveInterpolator;
233
234    // The state of the stack focus (0..1), which controls the transition of the stack from the
235    // focused to non-focused state
236    @ViewDebug.ExportedProperty(category="recents")
237    private int mFocusState;
238
239    // The smallest scroll progress, at this value, the back most task will be visible
240    @ViewDebug.ExportedProperty(category="recents")
241    float mMinScrollP;
242    // The largest scroll progress, at this value, the front most task will be visible above the
243    // navigation bar
244    @ViewDebug.ExportedProperty(category="recents")
245    float mMaxScrollP;
246    // The initial progress that the scroller is set when you first enter recents
247    @ViewDebug.ExportedProperty(category="recents")
248    float mInitialScrollP;
249    // The task progress for the front-most task in the stack
250    @ViewDebug.ExportedProperty(category="recents")
251    float mFrontMostTaskP;
252
253    // The last computed task counts
254    @ViewDebug.ExportedProperty(category="recents")
255    int mNumStackTasks;
256
257    // The min/max z translations
258    @ViewDebug.ExportedProperty(category="recents")
259    int mMinTranslationZ;
260    @ViewDebug.ExportedProperty(category="recents")
261    public int mMaxTranslationZ;
262
263    // Optimization, allows for quick lookup of task -> index
264    private SparseIntArray mTaskIndexMap = new SparseIntArray();
265    private SparseArray<Float> mTaskIndexOverrideMap = new SparseArray<>();
266
267    TaskGridLayoutAlgorithm mTaskGridLayoutAlgorithm;
268    TaskStackLowRamLayoutAlgorithm mTaskStackLowRamLayoutAlgorithm;
269
270    // The transform to place TaskViews at the front and back of the stack respectively
271    TaskViewTransform mBackOfStackTransform = new TaskViewTransform();
272    TaskViewTransform mFrontOfStackTransform = new TaskViewTransform();
273
274    public TaskStackLayoutAlgorithm(Context context, TaskStackLayoutAlgorithmCallbacks cb) {
275        mContext = context;
276        mCb = cb;
277        mTaskGridLayoutAlgorithm = new TaskGridLayoutAlgorithm(context);
278        mTaskStackLowRamLayoutAlgorithm = new TaskStackLowRamLayoutAlgorithm(context);
279        reloadOnConfigurationChange(context);
280    }
281
282    /**
283     * Reloads the layout for the current configuration.
284     */
285    public void reloadOnConfigurationChange(Context context) {
286        Resources res = context.getResources();
287        mFocusedRange = new Range(res.getFloat(R.integer.recents_layout_focused_range_min),
288                res.getFloat(R.integer.recents_layout_focused_range_max));
289        mUnfocusedRange = new Range(res.getFloat(R.integer.recents_layout_unfocused_range_min),
290                res.getFloat(R.integer.recents_layout_unfocused_range_max));
291        mFocusState = getInitialFocusState();
292        mFocusedTopPeekHeight = res.getDimensionPixelSize(R.dimen.recents_layout_top_peek_size);
293        mFocusedBottomPeekHeight =
294                res.getDimensionPixelSize(R.dimen.recents_layout_bottom_peek_size);
295        mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_layout_z_min);
296        mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_layout_z_max);
297        mBaseInitialTopOffset = getDimensionForDevice(context,
298                R.dimen.recents_layout_initial_top_offset_phone_port,
299                R.dimen.recents_layout_initial_top_offset_phone_land,
300                R.dimen.recents_layout_initial_top_offset_tablet,
301                R.dimen.recents_layout_initial_top_offset_tablet,
302                R.dimen.recents_layout_initial_top_offset_tablet,
303                R.dimen.recents_layout_initial_top_offset_tablet,
304                R.dimen.recents_layout_initial_top_offset_tablet);
305        mBaseInitialBottomOffset = getDimensionForDevice(context,
306                R.dimen.recents_layout_initial_bottom_offset_phone_port,
307                R.dimen.recents_layout_initial_bottom_offset_phone_land,
308                R.dimen.recents_layout_initial_bottom_offset_tablet,
309                R.dimen.recents_layout_initial_bottom_offset_tablet,
310                R.dimen.recents_layout_initial_bottom_offset_tablet,
311                R.dimen.recents_layout_initial_bottom_offset_tablet,
312                R.dimen.recents_layout_initial_bottom_offset_tablet);
313        mTaskGridLayoutAlgorithm.reloadOnConfigurationChange(context);
314        mTaskStackLowRamLayoutAlgorithm.reloadOnConfigurationChange(context);
315        mMinMargin = res.getDimensionPixelSize(R.dimen.recents_layout_min_margin);
316        mBaseTopMargin = getDimensionForDevice(context,
317                R.dimen.recents_layout_top_margin_phone,
318                R.dimen.recents_layout_top_margin_tablet,
319                R.dimen.recents_layout_top_margin_tablet_xlarge,
320                R.dimen.recents_layout_top_margin_tablet);
321        mBaseSideMargin = getDimensionForDevice(context,
322                R.dimen.recents_layout_side_margin_phone,
323                R.dimen.recents_layout_side_margin_tablet,
324                R.dimen.recents_layout_side_margin_tablet_xlarge,
325                R.dimen.recents_layout_side_margin_tablet);
326        mBaseBottomMargin = res.getDimensionPixelSize(R.dimen.recents_layout_bottom_margin);
327        mTitleBarHeight = getDimensionForDevice(mContext,
328                R.dimen.recents_task_view_header_height,
329                R.dimen.recents_task_view_header_height,
330                R.dimen.recents_task_view_header_height,
331                R.dimen.recents_task_view_header_height_tablet_land,
332                R.dimen.recents_task_view_header_height,
333                R.dimen.recents_task_view_header_height_tablet_land,
334                R.dimen.recents_grid_task_view_header_height);
335    }
336
337    /**
338     * Resets this layout when the stack view is reset.
339     */
340    public void reset() {
341        mTaskIndexOverrideMap.clear();
342        setFocusState(getInitialFocusState());
343    }
344
345    /**
346     * Sets the system insets.
347     */
348    public boolean setSystemInsets(Rect systemInsets) {
349        boolean changed = !mSystemInsets.equals(systemInsets);
350        mSystemInsets.set(systemInsets);
351        mTaskGridLayoutAlgorithm.setSystemInsets(systemInsets);
352        mTaskStackLowRamLayoutAlgorithm.setSystemInsets(systemInsets);
353        return changed;
354    }
355
356    /**
357     * Sets the focused state.
358     */
359    public void setFocusState(int focusState) {
360        int prevFocusState = mFocusState;
361        mFocusState = focusState;
362        updateFrontBackTransforms();
363        if (mCb != null) {
364            mCb.onFocusStateChanged(prevFocusState, focusState);
365        }
366    }
367
368    /**
369     * Gets the focused state.
370     */
371    public int getFocusState() {
372        return mFocusState;
373    }
374
375    /**
376     * Computes the stack and task rects.  The given task stack bounds already has the top/right
377     * insets and left/right padding already applied.
378     */
379    public void initialize(Rect displayRect, Rect windowRect, Rect taskStackBounds) {
380        Rect lastStackRect = new Rect(mStackRect);
381
382        int topMargin = getScaleForExtent(windowRect, displayRect, mBaseTopMargin, mMinMargin, HEIGHT);
383        int bottomMargin = getScaleForExtent(windowRect, displayRect, mBaseBottomMargin, mMinMargin,
384                HEIGHT);
385        mInitialTopOffset = getScaleForExtent(windowRect, displayRect, mBaseInitialTopOffset,
386                mMinMargin, HEIGHT);
387        mInitialBottomOffset = mBaseInitialBottomOffset;
388
389        // Compute the stack bounds
390        mStackBottomOffset = mSystemInsets.bottom + bottomMargin;
391        mStackRect.set(taskStackBounds);
392        mStackRect.top += topMargin;
393
394        // The stack action button will take the full un-padded header space above the stack
395        mStackActionButtonRect.set(mStackRect.left, mStackRect.top - topMargin,
396                mStackRect.right, mStackRect.top + mFocusedTopPeekHeight);
397
398        // Anchor the task rect top aligned to the stack rect
399        int height = mStackRect.height() - mInitialTopOffset - mStackBottomOffset;
400        mTaskRect.set(mStackRect.left, mStackRect.top, mStackRect.right, mStackRect.top + height);
401
402        if (mTaskRect.width() <= 0 || mTaskRect.height() <= 0) {
403            // Logging for b/36654830
404            Log.e(TAG, "Invalid task rect: taskRect=" + mTaskRect + " stackRect=" + mStackRect
405                    + " displayRect=" + displayRect + " windowRect=" + windowRect
406                    + " taskStackBounds=" + taskStackBounds);
407        }
408
409        // Short circuit here if the stack rects haven't changed so we don't do all the work below
410        if (!lastStackRect.equals(mStackRect)) {
411            // Reinitialize the focused and unfocused curves
412            mUnfocusedCurve = constructUnfocusedCurve();
413            mUnfocusedCurveInterpolator = new FreePathInterpolator(mUnfocusedCurve);
414            mFocusedCurve = constructFocusedCurve();
415            mFocusedCurveInterpolator = new FreePathInterpolator(mFocusedCurve);
416            mUnfocusedDimCurve = constructUnfocusedDimCurve();
417            mUnfocusedDimCurveInterpolator = new FreePathInterpolator(mUnfocusedDimCurve);
418            mFocusedDimCurve = constructFocusedDimCurve();
419            mFocusedDimCurveInterpolator = new FreePathInterpolator(mFocusedDimCurve);
420
421            updateFrontBackTransforms();
422        }
423
424        // Initialize the grid layout
425        mTaskGridLayoutAlgorithm.initialize(windowRect);
426        mTaskStackLowRamLayoutAlgorithm.initialize(windowRect);
427    }
428
429    /**
430     * Computes the minimum and maximum scroll progress values and the progress values for each task
431     * in the stack.
432     */
433    public void update(TaskStack stack, ArraySet<Task.TaskKey> ignoreTasksSet,
434            RecentsActivityLaunchState launchState, float lastScrollPPercent) {
435        SystemServicesProxy ssp = Recents.getSystemServices();
436
437        // Clear the progress map
438        mTaskIndexMap.clear();
439
440        // Return early if we have no tasks
441        ArrayList<Task> tasks = stack.getTasks();
442        if (tasks.isEmpty()) {
443            mFrontMostTaskP = 0;
444            mMinScrollP = mMaxScrollP = mInitialScrollP = 0;
445            mNumStackTasks = 0;
446            return;
447        }
448
449        // Filter the set of stack tasks
450        ArrayList<Task> stackTasks = new ArrayList<>();
451        for (int i = 0; i < tasks.size(); i++) {
452            Task task = tasks.get(i);
453            if (ignoreTasksSet.contains(task.key)) {
454                continue;
455            }
456            stackTasks.add(task);
457        }
458        mNumStackTasks = stackTasks.size();
459
460        // Put each of the tasks in the progress map at a fixed index (does not need to actually
461        // map to a scroll position, just by index)
462        int taskCount = stackTasks.size();
463        for (int i = 0; i < taskCount; i++) {
464            Task task = stackTasks.get(i);
465            mTaskIndexMap.put(task.key.id, i);
466        }
467
468        // Calculate the min/max/initial scroll
469        Task launchTask = stack.getLaunchTarget();
470        int launchTaskIndex = launchTask != null
471                ? stack.indexOfTask(launchTask)
472                : mNumStackTasks - 1;
473        if (getInitialFocusState() == STATE_FOCUSED) {
474            int maxBottomOffset = mStackBottomOffset + mTaskRect.height();
475            float maxBottomNormX = getNormalizedXFromFocusedY(maxBottomOffset, FROM_BOTTOM);
476            mFocusedRange.offset(0f);
477            mMinScrollP = 0;
478            mMaxScrollP = Math.max(mMinScrollP, (mNumStackTasks - 1) -
479                    Math.max(0, mFocusedRange.getAbsoluteX(maxBottomNormX)));
480            if (launchState.launchedFromHome || launchState.launchedFromPipApp
481                    || launchState.launchedWithNextPipApp) {
482                mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
483            } else {
484                mInitialScrollP = Utilities.clamp(launchTaskIndex - 1, mMinScrollP, mMaxScrollP);
485            }
486        } else if (mNumStackTasks == 1) {
487            // If there is one stack task, ignore the min/max/initial scroll positions
488            mMinScrollP = 0;
489            mMaxScrollP = 0;
490            mInitialScrollP = 0;
491        } else {
492            // Set the max scroll to be the point where the front most task is visible with the
493            // stack bottom offset
494            int maxBottomOffset = mStackBottomOffset + mTaskRect.height();
495            float maxBottomNormX = getNormalizedXFromUnfocusedY(maxBottomOffset, FROM_BOTTOM);
496            mUnfocusedRange.offset(0f);
497            mMinScrollP = Recents.getConfiguration().isLowRamDevice
498                    ? mTaskStackLowRamLayoutAlgorithm.getMinScrollP()
499                    : 0;
500            mMaxScrollP = Recents.getConfiguration().isLowRamDevice
501                    ? mTaskStackLowRamLayoutAlgorithm.getMaxScrollP(taskCount)
502                    : Math.max(mMinScrollP, (mNumStackTasks - 1) -
503                    Math.max(0, mUnfocusedRange.getAbsoluteX(maxBottomNormX)));
504            boolean scrollToFront = launchState.launchedFromHome || launchState.launchedFromPipApp
505                    || launchState.launchedWithNextPipApp || launchState.launchedViaDockGesture;
506
507            if (launchState.launchedWithAltTab) {
508                mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
509            } else if (0 <= lastScrollPPercent && lastScrollPPercent <= 1) {
510                mInitialScrollP = Utilities.mapRange(lastScrollPPercent, mMinScrollP, mMaxScrollP);
511            } else if (Recents.getConfiguration().isLowRamDevice) {
512                mInitialScrollP = mTaskStackLowRamLayoutAlgorithm.getInitialScrollP(mNumStackTasks,
513                        scrollToFront);
514            } else if (scrollToFront) {
515                mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
516            } else {
517                // We are overriding the initial two task positions, so set the initial scroll
518                // position to match the second task (aka focused task) position
519                float initialTopNormX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
520                mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, (mNumStackTasks - 2))
521                        - Math.max(0, mUnfocusedRange.getAbsoluteX(initialTopNormX)));
522            }
523        }
524    }
525
526    /**
527     * Creates task overrides to ensure the initial stack layout if necessary.
528     */
529    public void setTaskOverridesForInitialState(TaskStack stack, boolean ignoreScrollToFront) {
530        RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
531
532        mTaskIndexOverrideMap.clear();
533
534        boolean scrollToFront = launchState.launchedFromHome ||
535                launchState.launchedFromPipApp ||
536                launchState.launchedWithNextPipApp ||
537                launchState.launchedViaDockGesture;
538        if (getInitialFocusState() == STATE_UNFOCUSED && mNumStackTasks > 1) {
539            if (ignoreScrollToFront || (!launchState.launchedWithAltTab && !scrollToFront)) {
540                // Set the initial scroll to the predefined state (which differs from the stack)
541                float [] initialNormX = null;
542                float minBottomTaskNormX = getNormalizedXFromUnfocusedY(mSystemInsets.bottom +
543                        mInitialBottomOffset, FROM_BOTTOM);
544                float maxBottomTaskNormX = getNormalizedXFromUnfocusedY(mFocusedTopPeekHeight +
545                        mTaskRect.height() - mMinMargin, FROM_TOP);
546                if (mNumStackTasks <= 2) {
547                    // For small stacks, position the tasks so that they are top aligned to under
548                    // the action button, but ensure that it is at least a certain offset from the
549                    // bottom of the stack
550                    initialNormX = new float[] {
551                            Math.min(maxBottomTaskNormX, minBottomTaskNormX),
552                            getNormalizedXFromUnfocusedY(mFocusedTopPeekHeight, FROM_TOP)
553                    };
554                } else {
555                    initialNormX = new float[] {
556                            minBottomTaskNormX,
557                            getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP)
558                    };
559                }
560
561                mUnfocusedRange.offset(0f);
562                List<Task> tasks = stack.getTasks();
563                int taskCount = tasks.size();
564                for (int i = taskCount - 1; i >= 0; i--) {
565                    int indexFromFront = taskCount - i - 1;
566                    if (indexFromFront >= initialNormX.length) {
567                        break;
568                    }
569                    float newTaskProgress = mInitialScrollP +
570                            mUnfocusedRange.getAbsoluteX(initialNormX[indexFromFront]);
571                    mTaskIndexOverrideMap.put(tasks.get(i).key.id, newTaskProgress);
572                }
573            }
574        }
575    }
576
577    /**
578     * Adds and override task progress for the given task when transitioning from focused to
579     * unfocused state.
580     */
581    public void addUnfocusedTaskOverride(Task task, float stackScroll) {
582        if (mFocusState != STATE_UNFOCUSED) {
583            mFocusedRange.offset(stackScroll);
584            mUnfocusedRange.offset(stackScroll);
585            float focusedRangeX = mFocusedRange.getNormalizedX(mTaskIndexMap.get(task.key.id));
586            float focusedY = mFocusedCurveInterpolator.getInterpolation(focusedRangeX);
587            float unfocusedRangeX = mUnfocusedCurveInterpolator.getX(focusedY);
588            float unfocusedTaskProgress = stackScroll + mUnfocusedRange.getAbsoluteX(unfocusedRangeX);
589            if (Float.compare(focusedRangeX, unfocusedRangeX) != 0) {
590                mTaskIndexOverrideMap.put(task.key.id, unfocusedTaskProgress);
591            }
592        }
593    }
594
595    /**
596     * Adds and override task progress for the given task when transitioning from focused to
597     * unfocused state.
598     */
599    public void addUnfocusedTaskOverride(TaskView taskView, float stackScroll) {
600        mFocusedRange.offset(stackScroll);
601        mUnfocusedRange.offset(stackScroll);
602
603        Task task = taskView.getTask();
604        int top = taskView.getTop() - mTaskRect.top;
605        float focusedRangeX = getNormalizedXFromFocusedY(top, FROM_TOP);
606        float unfocusedRangeX = getNormalizedXFromUnfocusedY(top, FROM_TOP);
607        float unfocusedTaskProgress = stackScroll + mUnfocusedRange.getAbsoluteX(unfocusedRangeX);
608        if (Float.compare(focusedRangeX, unfocusedRangeX) != 0) {
609            mTaskIndexOverrideMap.put(task.key.id, unfocusedTaskProgress);
610        }
611    }
612
613    public void clearUnfocusedTaskOverrides() {
614        mTaskIndexOverrideMap.clear();
615    }
616
617    /**
618     * Updates this stack when a scroll happens.
619     *
620     */
621    public float updateFocusStateOnScroll(float lastTargetStackScroll, float targetStackScroll,
622            float lastStackScroll) {
623        if (targetStackScroll == lastStackScroll || Recents.getConfiguration().isLowRamDevice) {
624            return targetStackScroll;
625        }
626
627        float deltaScroll = targetStackScroll - lastStackScroll;
628        float deltaTargetScroll = targetStackScroll - lastTargetStackScroll;
629        float newScroll = targetStackScroll;
630        mUnfocusedRange.offset(targetStackScroll);
631        for (int i = mTaskIndexOverrideMap.size() - 1; i >= 0; i--) {
632            int taskId = mTaskIndexOverrideMap.keyAt(i);
633            float x = mTaskIndexMap.get(taskId);
634            float overrideX = mTaskIndexOverrideMap.get(taskId, 0f);
635            float newOverrideX = overrideX + deltaScroll;
636            if (isInvalidOverrideX(x, overrideX, newOverrideX)) {
637                // Remove the override once we reach the original task index
638                mTaskIndexOverrideMap.removeAt(i);
639            } else if ((overrideX >= x && deltaScroll <= 0f) ||
640                    (overrideX <= x && deltaScroll >= 0f)) {
641                // Scrolling from override x towards x, then lock the task in place
642                mTaskIndexOverrideMap.put(taskId, newOverrideX);
643            } else {
644                // Scrolling override x away from x, we should still move the scroll towards x
645                newScroll = lastStackScroll;
646                newOverrideX = overrideX - deltaTargetScroll;
647                if (isInvalidOverrideX(x, overrideX, newOverrideX)) {
648                    mTaskIndexOverrideMap.removeAt(i);
649                } else{
650                    mTaskIndexOverrideMap.put(taskId, newOverrideX);
651                }
652            }
653        }
654        return newScroll;
655    }
656
657    private boolean isInvalidOverrideX(float x, float overrideX, float newOverrideX) {
658        boolean outOfBounds = mUnfocusedRange.getNormalizedX(newOverrideX) < 0f ||
659                mUnfocusedRange.getNormalizedX(newOverrideX) > 1f;
660        return outOfBounds || (overrideX >= x && x >= newOverrideX) ||
661                (overrideX <= x && x <= newOverrideX);
662    }
663
664    /**
665     * Returns the default focus state.
666     */
667    public int getInitialFocusState() {
668        RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
669        RecentsDebugFlags debugFlags = Recents.getDebugFlags();
670        if (launchState.launchedWithAltTab) {
671            return STATE_FOCUSED;
672        } else {
673            return STATE_UNFOCUSED;
674        }
675    }
676
677    public Rect getStackActionButtonRect() {
678        return useGridLayout()
679                ? mTaskGridLayoutAlgorithm.getStackActionButtonRect() : mStackActionButtonRect;
680    }
681
682    /**
683     * Returns the TaskViewTransform that would put the task just off the back of the stack.
684     */
685    public TaskViewTransform getBackOfStackTransform() {
686        return mBackOfStackTransform;
687    }
688
689    /**
690     * Returns the TaskViewTransform that would put the task just off the front of the stack.
691     */
692    public TaskViewTransform getFrontOfStackTransform() {
693        return mFrontOfStackTransform;
694    }
695
696    /**
697     * Returns whether this stack layout has been initialized.
698     */
699    public boolean isInitialized() {
700        return !mStackRect.isEmpty();
701    }
702
703    /**
704     * Computes the maximum number of visible tasks and thumbnails when the scroll is at the initial
705     * stack scroll.  Requires that update() is called first.
706     */
707    public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
708        if (useGridLayout()) {
709            return mTaskGridLayoutAlgorithm.computeStackVisibilityReport(tasks);
710        }
711
712        if (Recents.getConfiguration().isLowRamDevice) {
713            return mTaskStackLowRamLayoutAlgorithm.computeStackVisibilityReport(tasks);
714        }
715
716        // Ensure minimum visibility count
717        if (tasks.size() <= 1) {
718            return new VisibilityReport(1, 1);
719        }
720
721        // Otherwise, walk backwards in the stack and count the number of tasks and visible
722        // thumbnails and add that to the total task count
723        TaskViewTransform tmpTransform = new TaskViewTransform();
724        Range currentRange = getInitialFocusState() > 0f ? mFocusedRange : mUnfocusedRange;
725        currentRange.offset(mInitialScrollP);
726        int taskBarHeight = mContext.getResources().getDimensionPixelSize(
727                R.dimen.recents_task_view_header_height);
728        int numVisibleTasks = 0;
729        int numVisibleThumbnails = 0;
730        float prevScreenY = Integer.MAX_VALUE;
731        for (int i = tasks.size() - 1; i >= 0; i--) {
732            Task task = tasks.get(i);
733
734            // Skip invisible
735            float taskProgress = getStackScrollForTask(task);
736            if (!currentRange.isInRange(taskProgress)) {
737                continue;
738            }
739
740            getStackTransform(taskProgress, taskProgress, mInitialScrollP, mFocusState,
741                    tmpTransform, null, false /* ignoreSingleTaskCase */, false /* forceUpdate */);
742            float screenY = tmpTransform.rect.top;
743            boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
744            if (hasVisibleThumbnail) {
745                numVisibleThumbnails++;
746                numVisibleTasks++;
747                prevScreenY = screenY;
748            } else {
749                // Once we hit the next front most task that does not have a visible thumbnail,
750                // walk through remaining visible set
751                for (int j = i; j >= 0; j--) {
752                    taskProgress = getStackScrollForTask(tasks.get(j));
753                    if (!currentRange.isInRange(taskProgress)) {
754                        break;
755                    }
756                    numVisibleTasks++;
757                }
758                break;
759            }
760        }
761        return new VisibilityReport(numVisibleTasks, numVisibleThumbnails);
762    }
763
764    /**
765     * Returns the transform for the given task.  This transform is relative to the mTaskRect, which
766     * is what the view is measured and laid out with.
767     */
768    public TaskViewTransform getStackTransform(Task task, float stackScroll,
769            TaskViewTransform transformOut, TaskViewTransform frontTransform) {
770        return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
771                false /* forceUpdate */, false /* ignoreTaskOverrides */);
772    }
773
774    public TaskViewTransform getStackTransform(Task task, float stackScroll,
775            TaskViewTransform transformOut, TaskViewTransform frontTransform,
776            boolean ignoreTaskOverrides) {
777        return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
778                false /* forceUpdate */, ignoreTaskOverrides);
779    }
780
781    public TaskViewTransform getStackTransform(Task task, float stackScroll, int focusState,
782            TaskViewTransform transformOut, TaskViewTransform frontTransform, boolean forceUpdate,
783            boolean ignoreTaskOverrides) {
784        if (useGridLayout()) {
785            int taskIndex = mTaskIndexMap.get(task.key.id);
786            int taskCount = mTaskIndexMap.size();
787            mTaskGridLayoutAlgorithm.getTransform(taskIndex, taskCount, transformOut, this);
788            return transformOut;
789        } else if (Recents.getConfiguration().isLowRamDevice) {
790            if (task == null) {
791                transformOut.reset();
792                return transformOut;
793            }
794            int taskIndex = mTaskIndexMap.get(task.key.id);
795            mTaskStackLowRamLayoutAlgorithm.getTransform(taskIndex, stackScroll, transformOut,
796                    mNumStackTasks, this);
797            return transformOut;
798        } else {
799            // Return early if we have an invalid index
800            int nonOverrideTaskProgress = mTaskIndexMap.get(task.key.id, -1);
801            if (task == null || nonOverrideTaskProgress == -1) {
802                transformOut.reset();
803                return transformOut;
804            }
805            float taskProgress = ignoreTaskOverrides
806                    ? nonOverrideTaskProgress
807                    : getStackScrollForTask(task);
808
809            getStackTransform(taskProgress, nonOverrideTaskProgress, stackScroll, focusState,
810                    transformOut, frontTransform, false /* ignoreSingleTaskCase */, forceUpdate);
811            return transformOut;
812        }
813    }
814
815    /**
816     * Like {@link #getStackTransform}, but in screen coordinates
817     */
818    public TaskViewTransform getStackTransformScreenCoordinates(Task task, float stackScroll,
819            TaskViewTransform transformOut, TaskViewTransform frontTransform,
820            Rect windowOverrideRect) {
821        TaskViewTransform transform = getStackTransform(task, stackScroll, mFocusState,
822                transformOut, frontTransform, true /* forceUpdate */,
823                false /* ignoreTaskOverrides */);
824        return transformToScreenCoordinates(transform, windowOverrideRect);
825    }
826
827    /**
828     * Transforms the given {@param transformOut} to the screen coordinates, overriding the current
829     * window rectangle with {@param windowOverrideRect} if non-null.
830     */
831    TaskViewTransform transformToScreenCoordinates(TaskViewTransform transformOut,
832            Rect windowOverrideRect) {
833        Rect windowRect = windowOverrideRect != null
834                ? windowOverrideRect
835                : Recents.getSystemServices().getWindowRect();
836        transformOut.rect.offset(windowRect.left, windowRect.top);
837        if (useGridLayout()) {
838            // Draw the thumbnail a little lower to perfectly coincide with the view we are
839            // transitioning to, where the header bar has already been drawn.
840            transformOut.rect.offset(0, mTitleBarHeight);
841        }
842        return transformOut;
843    }
844
845    /**
846     * Update/get the transform.
847     *
848     * @param ignoreSingleTaskCase When set, will ensure that the transform computed does not take
849     *                             into account the special single-task case.  This is only used
850     *                             internally to ensure that we can calculate the transform for any
851     *                             position in the stack.
852     */
853    public void getStackTransform(float taskProgress, float nonOverrideTaskProgress,
854            float stackScroll, int focusState, TaskViewTransform transformOut,
855            TaskViewTransform frontTransform, boolean ignoreSingleTaskCase, boolean forceUpdate) {
856        SystemServicesProxy ssp = Recents.getSystemServices();
857
858        // Ensure that the task is in range
859        mUnfocusedRange.offset(stackScroll);
860        mFocusedRange.offset(stackScroll);
861        boolean unfocusedVisible = mUnfocusedRange.isInRange(taskProgress);
862        boolean focusedVisible = mFocusedRange.isInRange(taskProgress);
863
864        // Skip if the task is not visible
865        if (!forceUpdate && !unfocusedVisible && !focusedVisible) {
866            transformOut.reset();
867            return;
868        }
869
870        // Map the absolute task progress to the normalized x at the stack scroll.  We use this to
871        // calculate positions along the curve.
872        mUnfocusedRange.offset(stackScroll);
873        mFocusedRange.offset(stackScroll);
874        float unfocusedRangeX = mUnfocusedRange.getNormalizedX(taskProgress);
875        float focusedRangeX = mFocusedRange.getNormalizedX(taskProgress);
876
877        // Map the absolute task progress to the normalized x at the bounded stack scroll.  We use
878        // this to calculate bounded properties, like translationZ and outline alpha.
879        float boundedStackScroll = Utilities.clamp(stackScroll, mMinScrollP, mMaxScrollP);
880        mUnfocusedRange.offset(boundedStackScroll);
881        mFocusedRange.offset(boundedStackScroll);
882        float boundedScrollUnfocusedRangeX = mUnfocusedRange.getNormalizedX(taskProgress);
883        float boundedScrollUnfocusedNonOverrideRangeX =
884                mUnfocusedRange.getNormalizedX(nonOverrideTaskProgress);
885
886        // Map the absolute task progress to the normalized x at the upper bounded stack scroll.
887        // We use this to calculate the dim, which is bounded only on one end.
888        float lowerBoundedStackScroll = Utilities.clamp(stackScroll, -Float.MAX_VALUE, mMaxScrollP);
889        mUnfocusedRange.offset(lowerBoundedStackScroll);
890        mFocusedRange.offset(lowerBoundedStackScroll);
891        float lowerBoundedUnfocusedRangeX = mUnfocusedRange.getNormalizedX(taskProgress);
892        float lowerBoundedFocusedRangeX = mFocusedRange.getNormalizedX(taskProgress);
893
894        int x = (mStackRect.width() - mTaskRect.width()) / 2;
895        int y;
896        float z;
897        float dimAlpha;
898        float viewOutlineAlpha;
899        if (mNumStackTasks == 1 && !ignoreSingleTaskCase) {
900            // When there is exactly one task, then decouple the task from the stack and just move
901            // in screen space
902            float tmpP = (mMinScrollP - stackScroll) / mNumStackTasks;
903            int centerYOffset = (mStackRect.top - mTaskRect.top) +
904                    (mStackRect.height() - mSystemInsets.bottom - mTaskRect.height()) / 2;
905            y = centerYOffset + getYForDeltaP(tmpP, 0);
906            z = mMaxTranslationZ;
907            dimAlpha = 0f;
908            viewOutlineAlpha = OUTLINE_ALPHA_MIN_VALUE +
909                    (OUTLINE_ALPHA_MAX_VALUE - OUTLINE_ALPHA_MIN_VALUE) / 2f;
910
911        } else {
912            // Otherwise, update the task to the stack layout
913            int unfocusedY = (int) ((1f - mUnfocusedCurveInterpolator.getInterpolation(
914                    unfocusedRangeX)) * mStackRect.height());
915            int focusedY = (int) ((1f - mFocusedCurveInterpolator.getInterpolation(
916                    focusedRangeX)) * mStackRect.height());
917            float unfocusedDim = mUnfocusedDimCurveInterpolator.getInterpolation(
918                    lowerBoundedUnfocusedRangeX);
919            float focusedDim = mFocusedDimCurveInterpolator.getInterpolation(
920                    lowerBoundedFocusedRangeX);
921
922            // Special case, because we override the initial task positions differently for small
923            // stacks, we clamp the dim to 0 in the initial position, and then only modulate the
924            // dim when the task is scrolled back towards the top of the screen
925            if (mNumStackTasks <= 2 && nonOverrideTaskProgress == 0f) {
926                if (boundedScrollUnfocusedRangeX >= 0.5f) {
927                    unfocusedDim = 0f;
928                } else {
929                    float offset = mUnfocusedDimCurveInterpolator.getInterpolation(0.5f);
930                    unfocusedDim -= offset;
931                    unfocusedDim *= MAX_DIM / (MAX_DIM - offset);
932                }
933            }
934            y = (mStackRect.top - mTaskRect.top) +
935                    (int) Utilities.mapRange(focusState, unfocusedY, focusedY);
936            z = Utilities.mapRange(Utilities.clamp01(boundedScrollUnfocusedNonOverrideRangeX),
937                    mMinTranslationZ, mMaxTranslationZ);
938            dimAlpha = Utilities.mapRange(focusState, unfocusedDim, focusedDim);
939            viewOutlineAlpha = Utilities.mapRange(Utilities.clamp01(boundedScrollUnfocusedRangeX),
940                    OUTLINE_ALPHA_MIN_VALUE, OUTLINE_ALPHA_MAX_VALUE);
941        }
942
943        // Fill out the transform
944        transformOut.scale = 1f;
945        transformOut.alpha = 1f;
946        transformOut.translationZ = z;
947        transformOut.dimAlpha = dimAlpha;
948        transformOut.viewOutlineAlpha = viewOutlineAlpha;
949        transformOut.rect.set(mTaskRect);
950        transformOut.rect.offset(x, y);
951        Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
952        transformOut.visible = (transformOut.rect.top < mStackRect.bottom) &&
953                (frontTransform == null || transformOut.rect.top != frontTransform.rect.top);
954    }
955
956    /**
957     * Returns the untransformed task view bounds.
958     */
959    public Rect getUntransformedTaskViewBounds() {
960        return new Rect(mTaskRect);
961    }
962
963    /**
964     * Returns the scroll progress to scroll to such that the top of the task is at the top of the
965     * stack.
966     */
967    float getStackScrollForTask(Task t) {
968        Float overrideP = mTaskIndexOverrideMap.get(t.key.id, null);
969        if (Recents.getConfiguration().isLowRamDevice || overrideP == null) {
970            return (float) mTaskIndexMap.get(t.key.id, 0);
971        }
972        return overrideP;
973    }
974
975    /**
976     * Returns the original scroll progress to scroll to such that the top of the task is at the top
977     * of the stack.
978     */
979    float getStackScrollForTaskIgnoreOverrides(Task t) {
980        return (float) mTaskIndexMap.get(t.key.id, 0);
981    }
982
983    /**
984     * Returns the scroll progress to scroll to such that the top of the task at the initial top
985     * offset (which is at the task's brightest point).
986     */
987    float getStackScrollForTaskAtInitialOffset(Task t) {
988        if (Recents.getConfiguration().isLowRamDevice) {
989            RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
990            return mTaskStackLowRamLayoutAlgorithm.getInitialScrollP(mNumStackTasks,
991                    launchState.launchedFromHome || launchState.launchedFromPipApp
992                            || launchState.launchedWithNextPipApp);
993        }
994        float normX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
995        mUnfocusedRange.offset(0f);
996        return Utilities.clamp((float) mTaskIndexMap.get(t.key.id, 0) - Math.max(0,
997                mUnfocusedRange.getAbsoluteX(normX)), mMinScrollP, mMaxScrollP);
998    }
999
1000    /**
1001     * Maps a movement in screen y, relative to {@param downY}, to a movement in along the arc
1002     * length of the curve.  We know the curve is mostly flat, so we just map the length of the
1003     * screen along the arc-length proportionally (1/arclength).
1004     */
1005    public float getDeltaPForY(int downY, int y) {
1006        if (Recents.getConfiguration().isLowRamDevice) {
1007            return mTaskStackLowRamLayoutAlgorithm.scrollToPercentage(downY - y);
1008        }
1009        float deltaP = (float) (y - downY) / mStackRect.height() *
1010                mUnfocusedCurveInterpolator.getArcLength();
1011        return -deltaP;
1012    }
1013
1014    /**
1015     * This is the inverse of {@link #getDeltaPForY}.  Given a movement along the arc length
1016     * of the curve, map back to the screen y.
1017     */
1018    public int getYForDeltaP(float downScrollP, float p) {
1019        if (Recents.getConfiguration().isLowRamDevice) {
1020            return mTaskStackLowRamLayoutAlgorithm.percentageToScroll(downScrollP - p);
1021        }
1022        int y = (int) ((p - downScrollP) * mStackRect.height() *
1023                (1f / mUnfocusedCurveInterpolator.getArcLength()));
1024        return -y;
1025    }
1026
1027    /**
1028     * Returns the task stack bounds in the current orientation.  This rect takes into account the
1029     * top and right system insets (but not the bottom inset) and left/right paddings, but _not_
1030     * the top/bottom padding or insets.
1031     */
1032    public void getTaskStackBounds(Rect displayRect, Rect windowRect, int topInset, int leftInset,
1033            int rightInset, Rect taskStackBounds) {
1034        taskStackBounds.set(windowRect.left + leftInset, windowRect.top + topInset,
1035                windowRect.right - rightInset, windowRect.bottom);
1036
1037        // Ensure that the new width is at most the smaller display edge size
1038        int sideMargin = getScaleForExtent(windowRect, displayRect, mBaseSideMargin, mMinMargin,
1039                WIDTH);
1040        int targetStackWidth = taskStackBounds.width() - 2 * sideMargin;
1041        if (Utilities.getAppConfiguration(mContext).orientation
1042                == Configuration.ORIENTATION_LANDSCAPE) {
1043            // If we are in landscape, calculate the width of the stack in portrait and ensure that
1044            // we are not larger than that size
1045            Rect portraitDisplayRect = new Rect(0, 0,
1046                    Math.min(displayRect.width(), displayRect.height()),
1047                    Math.max(displayRect.width(), displayRect.height()));
1048            int portraitSideMargin = getScaleForExtent(portraitDisplayRect, portraitDisplayRect,
1049                    mBaseSideMargin, mMinMargin, WIDTH);
1050            targetStackWidth = Math.min(targetStackWidth,
1051                    portraitDisplayRect.width() - 2 * portraitSideMargin);
1052        }
1053        taskStackBounds.inset((taskStackBounds.width() - targetStackWidth) / 2, 0);
1054    }
1055
1056    /**
1057     * Retrieves resources that are constant regardless of the current configuration of the device.
1058     */
1059    public static int getDimensionForDevice(Context ctx, int phoneResId,
1060            int tabletResId, int xlargeTabletResId, int gridLayoutResId) {
1061        return getDimensionForDevice(ctx, phoneResId, phoneResId, tabletResId, tabletResId,
1062                xlargeTabletResId, xlargeTabletResId, gridLayoutResId);
1063    }
1064
1065    /**
1066     * Retrieves resources that are constant regardless of the current configuration of the device.
1067     */
1068    public static int getDimensionForDevice(Context ctx, int phonePortResId, int phoneLandResId,
1069            int tabletPortResId, int tabletLandResId, int xlargeTabletPortResId,
1070            int xlargeTabletLandResId, int gridLayoutResId) {
1071        RecentsConfiguration config = Recents.getConfiguration();
1072        Resources res = ctx.getResources();
1073        boolean isLandscape = Utilities.getAppConfiguration(ctx).orientation ==
1074                Configuration.ORIENTATION_LANDSCAPE;
1075        if (config.isGridEnabled) {
1076            return res.getDimensionPixelSize(gridLayoutResId);
1077        } else if (config.isXLargeScreen) {
1078            return res.getDimensionPixelSize(isLandscape
1079                    ? xlargeTabletLandResId
1080                    : xlargeTabletPortResId);
1081        } else if (config.isLargeScreen) {
1082            return res.getDimensionPixelSize(isLandscape
1083                    ? tabletLandResId
1084                    : tabletPortResId);
1085        } else {
1086            return res.getDimensionPixelSize(isLandscape
1087                    ? phoneLandResId
1088                    : phonePortResId);
1089        }
1090    }
1091
1092    /**
1093     * Returns the normalized x on the unfocused curve given an absolute Y position (relative to the
1094     * stack height).
1095     */
1096    private float getNormalizedXFromUnfocusedY(float y, @AnchorSide int fromSide) {
1097        float offset = (fromSide == FROM_TOP)
1098                ? mStackRect.height() - y
1099                : y;
1100        float offsetPct = offset / mStackRect.height();
1101        return mUnfocusedCurveInterpolator.getX(offsetPct);
1102    }
1103
1104    /**
1105     * Returns the normalized x on the focused curve given an absolute Y position (relative to the
1106     * stack height).
1107     */
1108    private float getNormalizedXFromFocusedY(float y, @AnchorSide int fromSide) {
1109        float offset = (fromSide == FROM_TOP)
1110                ? mStackRect.height() - y
1111                : y;
1112        float offsetPct = offset / mStackRect.height();
1113        return mFocusedCurveInterpolator.getX(offsetPct);
1114    }
1115
1116    /**
1117     * Creates a new path for the focused curve.
1118     */
1119    private Path constructFocusedCurve() {
1120        // Initialize the focused curve. This curve is a piecewise curve composed of several
1121        // linear pieces that goes from (0,1) through (0.5, peek height offset),
1122        // (0.5, bottom task offsets), and (1,0).
1123        float topPeekHeightPct = (float) mFocusedTopPeekHeight / mStackRect.height();
1124        float bottomPeekHeightPct = (float) (mStackBottomOffset + mFocusedBottomPeekHeight) /
1125                mStackRect.height();
1126        float minBottomPeekHeightPct = (float) (mFocusedTopPeekHeight + mTaskRect.height() -
1127                mMinMargin) / mStackRect.height();
1128        Path p = new Path();
1129        p.moveTo(0f, 1f);
1130        p.lineTo(0.5f, 1f - topPeekHeightPct);
1131        p.lineTo(1f - (0.5f / mFocusedRange.relativeMax), Math.max(1f - minBottomPeekHeightPct,
1132                bottomPeekHeightPct));
1133        p.lineTo(1f, 0f);
1134        return p;
1135    }
1136
1137    /**
1138     * Creates a new path for the unfocused curve.
1139     */
1140    private Path constructUnfocusedCurve() {
1141        // Initialize the unfocused curve. This curve is a piecewise curve composed of two quadradic
1142        // beziers that goes from (0,1) through (0.5, peek height offset) and ends at (1,0).  This
1143        // ensures that we match the range, at which 0.5 represents the stack scroll at the current
1144        // task progress.  Because the height offset can change depending on a resource, we compute
1145        // the control point of the second bezier such that between it and a first known point,
1146        // there is a tangent at (0.5, peek height offset).
1147        float cpoint1X = 0.4f;
1148        float cpoint1Y = 0.975f;
1149        float topPeekHeightPct = (float) mFocusedTopPeekHeight / mStackRect.height();
1150        float slope = ((1f - topPeekHeightPct) - cpoint1Y) / (0.5f - cpoint1X);
1151        float b = 1f - slope * cpoint1X;
1152        float cpoint2X = 0.65f;
1153        float cpoint2Y = slope * cpoint2X + b;
1154        Path p = new Path();
1155        p.moveTo(0f, 1f);
1156        p.cubicTo(0f, 1f, cpoint1X, cpoint1Y, 0.5f, 1f - topPeekHeightPct);
1157        p.cubicTo(0.5f, 1f - topPeekHeightPct, cpoint2X, cpoint2Y, 1f, 0f);
1158        return p;
1159    }
1160
1161    /**
1162     * Creates a new path for the focused dim curve.
1163     */
1164    private Path constructFocusedDimCurve() {
1165        Path p = new Path();
1166        // The focused dim interpolator starts at max dim, reduces to zero at 0.5 (the focused
1167        // task), then goes back to max dim at the next task
1168        p.moveTo(0f, MAX_DIM);
1169        p.lineTo(0.5f, 0f);
1170        p.lineTo(0.5f + (0.5f / mFocusedRange.relativeMax), MAX_DIM);
1171        p.lineTo(1f, MAX_DIM);
1172        return p;
1173    }
1174
1175    /**
1176     * Creates a new path for the unfocused dim curve.
1177     */
1178    private Path constructUnfocusedDimCurve() {
1179        float focusX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
1180        float cpoint2X = focusX + (1f - focusX) / 2;
1181        Path p = new Path();
1182        // The unfocused dim interpolator starts at max dim, reduces to zero at 0.5 (the focused
1183        // task), then goes back to max dim towards the front of the stack
1184        p.moveTo(0f, MAX_DIM);
1185        p.cubicTo(focusX * 0.5f, MAX_DIM, focusX * 0.75f, MAX_DIM * 0.75f, focusX, 0f);
1186        p.cubicTo(cpoint2X, 0f, cpoint2X, MED_DIM, 1f, MED_DIM);
1187        return p;
1188    }
1189
1190    /**
1191     * Scales the given {@param value} to the scale of the {@param instance} rect relative to the
1192     * {@param other} rect in the {@param extent} side.
1193     */
1194    private int getScaleForExtent(Rect instance, Rect other, int value, int minValue,
1195                                  @Extent int extent) {
1196        if (extent == WIDTH) {
1197            float scale = Utilities.clamp01((float) instance.width() / other.width());
1198            return Math.max(minValue, (int) (scale * value));
1199        } else if (extent == HEIGHT) {
1200            float scale = Utilities.clamp01((float) instance.height() / other.height());
1201            return Math.max(minValue, (int) (scale * value));
1202        }
1203        return value;
1204    }
1205
1206    /**
1207     * Updates the current transforms that would put a TaskView at the front and back of the stack.
1208     */
1209    private void updateFrontBackTransforms() {
1210        // Return early if we have not yet initialized
1211        if (mStackRect.isEmpty()) {
1212            return;
1213        }
1214        if (Recents.getConfiguration().isLowRamDevice) {
1215            mTaskStackLowRamLayoutAlgorithm.getBackOfStackTransform(mBackOfStackTransform, this);
1216            mTaskStackLowRamLayoutAlgorithm.getFrontOfStackTransform(mFrontOfStackTransform, this);
1217            return;
1218        }
1219
1220        float min = Utilities.mapRange(mFocusState, mUnfocusedRange.relativeMin,
1221                mFocusedRange.relativeMin);
1222        float max = Utilities.mapRange(mFocusState, mUnfocusedRange.relativeMax,
1223                mFocusedRange.relativeMax);
1224        getStackTransform(min, min, 0f, mFocusState, mBackOfStackTransform, null,
1225                true /* ignoreSingleTaskCase */, true /* forceUpdate */);
1226        getStackTransform(max, max, 0f, mFocusState, mFrontOfStackTransform, null,
1227                true /* ignoreSingleTaskCase */, true /* forceUpdate */);
1228        mBackOfStackTransform.visible = true;
1229        mFrontOfStackTransform.visible = true;
1230    }
1231
1232    /**
1233     * Returns the proper task rectangle according to the current grid state.
1234     */
1235    public Rect getTaskRect() {
1236        if (Recents.getConfiguration().isLowRamDevice) {
1237            return mTaskStackLowRamLayoutAlgorithm.getTaskRect();
1238        }
1239        return useGridLayout() ? mTaskGridLayoutAlgorithm.getTaskGridRect() : mTaskRect;
1240    }
1241
1242    public void dump(String prefix, PrintWriter writer) {
1243        String innerPrefix = prefix + "  ";
1244
1245        writer.print(prefix); writer.print(TAG);
1246        writer.write(" numStackTasks="); writer.print(mNumStackTasks);
1247        writer.println();
1248
1249        writer.print(innerPrefix);
1250        writer.print("insets="); writer.print(Utilities.dumpRect(mSystemInsets));
1251        writer.print(" stack="); writer.print(Utilities.dumpRect(mStackRect));
1252        writer.print(" task="); writer.print(Utilities.dumpRect(mTaskRect));
1253        writer.print(" actionButton="); writer.print(Utilities.dumpRect(mStackActionButtonRect));
1254        writer.println();
1255
1256        writer.print(innerPrefix);
1257        writer.print("minScroll="); writer.print(mMinScrollP);
1258        writer.print(" maxScroll="); writer.print(mMaxScrollP);
1259        writer.print(" initialScroll="); writer.print(mInitialScrollP);
1260        writer.println();
1261
1262        writer.print(innerPrefix);
1263        writer.print("focusState="); writer.print(mFocusState);
1264        writer.println();
1265
1266        if (mTaskIndexOverrideMap.size() > 0) {
1267            for (int i = mTaskIndexOverrideMap.size() - 1; i >= 0; i--) {
1268                int taskId = mTaskIndexOverrideMap.keyAt(i);
1269                float x = mTaskIndexMap.get(taskId);
1270                float overrideX = mTaskIndexOverrideMap.get(taskId, 0f);
1271
1272                writer.print(innerPrefix);
1273                writer.print("taskId= "); writer.print(taskId);
1274                writer.print(" x= "); writer.print(x);
1275                writer.print(" overrideX= "); writer.print(overrideX);
1276                writer.println();
1277            }
1278        }
1279    }
1280}