OSDN Git Service

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