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.launchedFromBlacklistedApp) {
560                 mInitialScrollP = mMaxScrollP;
561             } else if (launchState.launchedWithAltTab) {
562                 mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
563             } else if (scrollToFront) {
564                 mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
565             } else {
566                 // We are overriding the initial two task positions, so set the initial scroll
567                 // position to match the second task (aka focused task) position
568                 float initialTopNormX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
569                 mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, (mNumStackTasks - 2))
570                         - Math.max(0, mUnfocusedRange.getAbsoluteX(initialTopNormX)));
571             }
572         }
573     }
574
575     /**
576      * Creates task overrides to ensure the initial stack layout if necessary.
577      */
578     public void setTaskOverridesForInitialState(TaskStack stack, boolean ignoreScrollToFront) {
579         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
580
581         mTaskIndexOverrideMap.clear();
582
583         boolean scrollToFront = launchState.launchedFromHome ||
584                 launchState.launchedFromBlacklistedApp ||
585                 launchState.launchedViaDockGesture;
586         if (getInitialFocusState() == STATE_UNFOCUSED && mNumStackTasks > 1) {
587             if (ignoreScrollToFront || (!launchState.launchedWithAltTab && !scrollToFront)) {
588                 // Set the initial scroll to the predefined state (which differs from the stack)
589                 float [] initialNormX = null;
590                 float minBottomTaskNormX = getNormalizedXFromUnfocusedY(mSystemInsets.bottom +
591                         mInitialBottomOffset, FROM_BOTTOM);
592                 float maxBottomTaskNormX = getNormalizedXFromUnfocusedY(mFocusedTopPeekHeight +
593                         mTaskRect.height() - mMinMargin, FROM_TOP);
594                 if (mNumStackTasks <= 2) {
595                     // For small stacks, position the tasks so that they are top aligned to under
596                     // the action button, but ensure that it is at least a certain offset from the
597                     // bottom of the stack
598                     initialNormX = new float[] {
599                             Math.min(maxBottomTaskNormX, minBottomTaskNormX),
600                             getNormalizedXFromUnfocusedY(mFocusedTopPeekHeight, FROM_TOP)
601                     };
602                 } else {
603                     initialNormX = new float[] {
604                             minBottomTaskNormX,
605                             getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP)
606                     };
607                 }
608
609                 mUnfocusedRange.offset(0f);
610                 List<Task> tasks = stack.getStackTasks();
611                 int taskCount = tasks.size();
612                 for (int i = taskCount - 1; i >= 0; i--) {
613                     int indexFromFront = taskCount - i - 1;
614                     if (indexFromFront >= initialNormX.length) {
615                         break;
616                     }
617                     float newTaskProgress = mInitialScrollP +
618                             mUnfocusedRange.getAbsoluteX(initialNormX[indexFromFront]);
619                     mTaskIndexOverrideMap.put(tasks.get(i).key.id, newTaskProgress);
620                 }
621             }
622         }
623     }
624
625     /**
626      * Adds and override task progress for the given task when transitioning from focused to
627      * unfocused state.
628      */
629     public void addUnfocusedTaskOverride(Task task, float stackScroll) {
630         if (mFocusState != STATE_UNFOCUSED) {
631             mFocusedRange.offset(stackScroll);
632             mUnfocusedRange.offset(stackScroll);
633             float focusedRangeX = mFocusedRange.getNormalizedX(mTaskIndexMap.get(task.key.id));
634             float focusedY = mFocusedCurveInterpolator.getInterpolation(focusedRangeX);
635             float unfocusedRangeX = mUnfocusedCurveInterpolator.getX(focusedY);
636             float unfocusedTaskProgress = stackScroll + mUnfocusedRange.getAbsoluteX(unfocusedRangeX);
637             if (Float.compare(focusedRangeX, unfocusedRangeX) != 0) {
638                 mTaskIndexOverrideMap.put(task.key.id, unfocusedTaskProgress);
639             }
640         }
641     }
642
643     /**
644      * Adds and override task progress for the given task when transitioning from focused to
645      * unfocused state.
646      */
647     public void addUnfocusedTaskOverride(TaskView taskView, float stackScroll) {
648         mFocusedRange.offset(stackScroll);
649         mUnfocusedRange.offset(stackScroll);
650
651         Task task = taskView.getTask();
652         int top = taskView.getTop() - mTaskRect.top;
653         float focusedRangeX = getNormalizedXFromFocusedY(top, FROM_TOP);
654         float unfocusedRangeX = getNormalizedXFromUnfocusedY(top, FROM_TOP);
655         float unfocusedTaskProgress = stackScroll + mUnfocusedRange.getAbsoluteX(unfocusedRangeX);
656         if (Float.compare(focusedRangeX, unfocusedRangeX) != 0) {
657             mTaskIndexOverrideMap.put(task.key.id, unfocusedTaskProgress);
658         }
659     }
660
661     public void clearUnfocusedTaskOverrides() {
662         mTaskIndexOverrideMap.clear();
663     }
664
665     /**
666      * Updates this stack when a scroll happens.
667      *
668      */
669     public float updateFocusStateOnScroll(float lastTargetStackScroll, float targetStackScroll,
670             float lastStackScroll) {
671         if (targetStackScroll == lastStackScroll) {
672             return targetStackScroll;
673         }
674
675         float deltaScroll = targetStackScroll - lastStackScroll;
676         float deltaTargetScroll = targetStackScroll - lastTargetStackScroll;
677         float newScroll = targetStackScroll;
678         mUnfocusedRange.offset(targetStackScroll);
679         for (int i = mTaskIndexOverrideMap.size() - 1; i >= 0; i--) {
680             int taskId = mTaskIndexOverrideMap.keyAt(i);
681             float x = mTaskIndexMap.get(taskId);
682             float overrideX = mTaskIndexOverrideMap.get(taskId, 0f);
683             float newOverrideX = overrideX + deltaScroll;
684             if (isInvalidOverrideX(x, overrideX, newOverrideX)) {
685                 // Remove the override once we reach the original task index
686                 mTaskIndexOverrideMap.removeAt(i);
687             } else if ((overrideX >= x && deltaScroll <= 0f) ||
688                     (overrideX <= x && deltaScroll >= 0f)) {
689                 // Scrolling from override x towards x, then lock the task in place
690                 mTaskIndexOverrideMap.put(taskId, newOverrideX);
691             } else {
692                 // Scrolling override x away from x, we should still move the scroll towards x
693                 newScroll = lastStackScroll;
694                 newOverrideX = overrideX - deltaTargetScroll;
695                 if (isInvalidOverrideX(x, overrideX, newOverrideX)) {
696                     mTaskIndexOverrideMap.removeAt(i);
697                 } else{
698                     mTaskIndexOverrideMap.put(taskId, newOverrideX);
699                 }
700             }
701         }
702         return newScroll;
703     }
704
705     private boolean isInvalidOverrideX(float x, float overrideX, float newOverrideX) {
706         boolean outOfBounds = mUnfocusedRange.getNormalizedX(newOverrideX) < 0f ||
707                 mUnfocusedRange.getNormalizedX(newOverrideX) > 1f;
708         return outOfBounds || (overrideX >= x && x >= newOverrideX) ||
709                 (overrideX <= x && x <= newOverrideX);
710     }
711
712     /**
713      * Returns the default focus state.
714      */
715     public int getInitialFocusState() {
716         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
717         RecentsDebugFlags debugFlags = Recents.getDebugFlags();
718         if (debugFlags.isPagingEnabled() || launchState.launchedWithAltTab) {
719             return STATE_FOCUSED;
720         } else {
721             return STATE_UNFOCUSED;
722         }
723     }
724
725     /**
726      * Returns the TaskViewTransform that would put the task just off the back of the stack.
727      */
728     public TaskViewTransform getBackOfStackTransform() {
729         return mBackOfStackTransform;
730     }
731
732     /**
733      * Returns the TaskViewTransform that would put the task just off the front of the stack.
734      */
735     public TaskViewTransform getFrontOfStackTransform() {
736         return mFrontOfStackTransform;
737     }
738
739     /**
740      * Returns the current stack state.
741      */
742     public StackState getStackState() {
743         return mState;
744     }
745
746     /**
747      * Returns whether this stack layout has been initialized.
748      */
749     public boolean isInitialized() {
750         return !mStackRect.isEmpty();
751     }
752
753     /**
754      * Computes the maximum number of visible tasks and thumbnails when the scroll is at the initial
755      * stack scroll.  Requires that update() is called first.
756      */
757     public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
758         // Ensure minimum visibility count
759         if (tasks.size() <= 1) {
760             return new VisibilityReport(1, 1);
761         }
762
763         // Quick return when there are no stack tasks
764         if (mNumStackTasks == 0) {
765             return new VisibilityReport(Math.max(mNumFreeformTasks, 1),
766                     Math.max(mNumFreeformTasks, 1));
767         }
768
769         // Otherwise, walk backwards in the stack and count the number of tasks and visible
770         // thumbnails and add that to the total freeform task count
771         TaskViewTransform tmpTransform = new TaskViewTransform();
772         Range currentRange = getInitialFocusState() > 0f ? mFocusedRange : mUnfocusedRange;
773         currentRange.offset(mInitialScrollP);
774         int taskBarHeight = mContext.getResources().getDimensionPixelSize(
775                 R.dimen.recents_task_view_header_height);
776         int numVisibleTasks = Math.max(mNumFreeformTasks, 1);
777         int numVisibleThumbnails = Math.max(mNumFreeformTasks, 1);
778         float prevScreenY = Integer.MAX_VALUE;
779         for (int i = tasks.size() - 1; i >= 0; i--) {
780             Task task = tasks.get(i);
781
782             // Skip freeform
783             if (task.isFreeformTask()) {
784                 continue;
785             }
786
787             // Skip invisible
788             float taskProgress = getStackScrollForTask(task);
789             if (!currentRange.isInRange(taskProgress)) {
790                 continue;
791             }
792
793             boolean isFrontMostTaskInGroup = task.group == null || task.group.isFrontMostTask(task);
794             if (isFrontMostTaskInGroup) {
795                 getStackTransform(taskProgress, taskProgress, mInitialScrollP, mFocusState,
796                         tmpTransform, null, false /* ignoreSingleTaskCase */,
797                         false /* forceUpdate */);
798                 float screenY = tmpTransform.rect.top;
799                 boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
800                 if (hasVisibleThumbnail) {
801                     numVisibleThumbnails++;
802                     numVisibleTasks++;
803                     prevScreenY = screenY;
804                 } else {
805                     // Once we hit the next front most task that does not have a visible thumbnail,
806                     // walk through remaining visible set
807                     for (int j = i; j >= 0; j--) {
808                         numVisibleTasks++;
809                         taskProgress = getStackScrollForTask(tasks.get(j));
810                         if (!currentRange.isInRange(taskProgress)) {
811                             continue;
812                         }
813                     }
814                     break;
815                 }
816             } else if (!isFrontMostTaskInGroup) {
817                 // Affiliated task, no thumbnail
818                 numVisibleTasks++;
819             }
820         }
821         return new VisibilityReport(numVisibleTasks, numVisibleThumbnails);
822     }
823
824     /**
825      * Returns the transform for the given task.  This transform is relative to the mTaskRect, which
826      * is what the view is measured and laid out with.
827      */
828     public TaskViewTransform getStackTransform(Task task, float stackScroll,
829             TaskViewTransform transformOut, TaskViewTransform frontTransform) {
830         return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
831                 false /* forceUpdate */, false /* ignoreTaskOverrides */);
832     }
833
834     public TaskViewTransform getStackTransform(Task task, float stackScroll,
835             TaskViewTransform transformOut, TaskViewTransform frontTransform,
836             boolean ignoreTaskOverrides) {
837         return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
838                 false /* forceUpdate */, ignoreTaskOverrides);
839     }
840
841     public TaskViewTransform getStackTransform(Task task, float stackScroll, int focusState,
842             TaskViewTransform transformOut, TaskViewTransform frontTransform, boolean forceUpdate,
843             boolean ignoreTaskOverrides) {
844         if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) {
845             mFreeformLayoutAlgorithm.getTransform(task, transformOut, this);
846             return transformOut;
847         } else {
848             // Return early if we have an invalid index
849             int nonOverrideTaskProgress = mTaskIndexMap.get(task.key.id, -1);
850             if (task == null || nonOverrideTaskProgress == -1) {
851                 transformOut.reset();
852                 return transformOut;
853             }
854             float taskProgress = ignoreTaskOverrides
855                     ? nonOverrideTaskProgress
856                     : getStackScrollForTask(task);
857             getStackTransform(taskProgress, nonOverrideTaskProgress, stackScroll, focusState,
858                     transformOut, frontTransform, false /* ignoreSingleTaskCase */, forceUpdate);
859             return transformOut;
860         }
861     }
862
863     /**
864      * Like {@link #getStackTransform}, but in screen coordinates
865      */
866     public TaskViewTransform getStackTransformScreenCoordinates(Task task, float stackScroll,
867             TaskViewTransform transformOut, TaskViewTransform frontTransform,
868             Rect windowOverrideRect) {
869         TaskViewTransform transform = getStackTransform(task, stackScroll, mFocusState,
870                 transformOut, frontTransform, true /* forceUpdate */,
871                 false /* ignoreTaskOverrides */);
872         return transformToScreenCoordinates(transform, windowOverrideRect);
873     }
874
875     /**
876      * Transforms the given {@param transformOut} to the screen coordinates, overriding the current
877      * window rectangle with {@param windowOverrideRect} if non-null.
878      */
879     public TaskViewTransform transformToScreenCoordinates(TaskViewTransform transformOut,
880             Rect windowOverrideRect) {
881         Rect windowRect = windowOverrideRect != null
882                 ? windowOverrideRect
883                 : Recents.getSystemServices().getWindowRect();
884         transformOut.rect.offset(windowRect.left, windowRect.top);
885         return transformOut;
886     }
887
888     /**
889      * Update/get the transform.
890      *
891      * @param ignoreSingleTaskCase When set, will ensure that the transform computed does not take
892      *                             into account the special single-task case.  This is only used
893      *                             internally to ensure that we can calculate the transform for any
894      *                             position in the stack.
895      */
896     public void getStackTransform(float taskProgress, float nonOverrideTaskProgress,
897             float stackScroll, int focusState, TaskViewTransform transformOut,
898             TaskViewTransform frontTransform, boolean ignoreSingleTaskCase, boolean forceUpdate) {
899         SystemServicesProxy ssp = Recents.getSystemServices();
900
901         // Ensure that the task is in range
902         mUnfocusedRange.offset(stackScroll);
903         mFocusedRange.offset(stackScroll);
904         boolean unfocusedVisible = mUnfocusedRange.isInRange(taskProgress);
905         boolean focusedVisible = mFocusedRange.isInRange(taskProgress);
906
907         // Skip if the task is not visible
908         if (!forceUpdate && !unfocusedVisible && !focusedVisible) {
909             transformOut.reset();
910             return;
911         }
912
913         // Map the absolute task progress to the normalized x at the stack scroll.  We use this to
914         // calculate positions along the curve.
915         mUnfocusedRange.offset(stackScroll);
916         mFocusedRange.offset(stackScroll);
917         float unfocusedRangeX = mUnfocusedRange.getNormalizedX(taskProgress);
918         float focusedRangeX = mFocusedRange.getNormalizedX(taskProgress);
919
920         // Map the absolute task progress to the normalized x at the bounded stack scroll.  We use
921         // this to calculate bounded properties, like translationZ and outline alpha.
922         float boundedStackScroll = Utilities.clamp(stackScroll, mMinScrollP, mMaxScrollP);
923         mUnfocusedRange.offset(boundedStackScroll);
924         mFocusedRange.offset(boundedStackScroll);
925         float boundedScrollUnfocusedRangeX = mUnfocusedRange.getNormalizedX(taskProgress);
926         float boundedScrollUnfocusedNonOverrideRangeX =
927                 mUnfocusedRange.getNormalizedX(nonOverrideTaskProgress);
928
929         // Map the absolute task progress to the normalized x at the upper bounded stack scroll.
930         // We use this to calculate the dim, which is bounded only on one end.
931         float lowerBoundedStackScroll = Utilities.clamp(stackScroll, -Float.MAX_VALUE, mMaxScrollP);
932         mUnfocusedRange.offset(lowerBoundedStackScroll);
933         mFocusedRange.offset(lowerBoundedStackScroll);
934         float lowerBoundedUnfocusedRangeX = mUnfocusedRange.getNormalizedX(taskProgress);
935         float lowerBoundedFocusedRangeX = mFocusedRange.getNormalizedX(taskProgress);
936
937         int x = (mStackRect.width() - mTaskRect.width()) / 2;
938         int y;
939         float z;
940         float dimAlpha;
941         float viewOutlineAlpha;
942         if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1 && !ignoreSingleTaskCase) {
943             // When there is exactly one task, then decouple the task from the stack and just move
944             // in screen space
945             float tmpP = (mMinScrollP - stackScroll) / mNumStackTasks;
946             int centerYOffset = (mStackRect.top - mTaskRect.top) +
947                     (mStackRect.height() - mSystemInsets.bottom - mTaskRect.height()) / 2;
948             y = centerYOffset + getYForDeltaP(tmpP, 0);
949             z = mMaxTranslationZ;
950             dimAlpha = 0f;
951             viewOutlineAlpha = OUTLINE_ALPHA_MIN_VALUE +
952                     (OUTLINE_ALPHA_MAX_VALUE - OUTLINE_ALPHA_MIN_VALUE) / 2f;
953
954         } else {
955             // Otherwise, update the task to the stack layout
956             int unfocusedY = (int) ((1f - mUnfocusedCurveInterpolator.getInterpolation(
957                     unfocusedRangeX)) * mStackRect.height());
958             int focusedY = (int) ((1f - mFocusedCurveInterpolator.getInterpolation(
959                     focusedRangeX)) * mStackRect.height());
960             float unfocusedDim = mUnfocusedDimCurveInterpolator.getInterpolation(
961                     lowerBoundedUnfocusedRangeX);
962             float focusedDim = mFocusedDimCurveInterpolator.getInterpolation(
963                     lowerBoundedFocusedRangeX);
964
965             // Special case, because we override the initial task positions differently for small
966             // stacks, we clamp the dim to 0 in the initial position, and then only modulate the
967             // dim when the task is scrolled back towards the top of the screen
968             if (mNumStackTasks <= 2 && nonOverrideTaskProgress == 0f) {
969                 if (boundedScrollUnfocusedRangeX >= 0.5f) {
970                     unfocusedDim = 0f;
971                 } else {
972                     float offset = mUnfocusedDimCurveInterpolator.getInterpolation(0.5f);
973                     unfocusedDim -= offset;
974                     unfocusedDim *= MAX_DIM / (MAX_DIM - offset);
975                 }
976             }
977
978             y = (mStackRect.top - mTaskRect.top) +
979                     (int) Utilities.mapRange(focusState, unfocusedY, focusedY);
980             z = Utilities.mapRange(Utilities.clamp01(boundedScrollUnfocusedNonOverrideRangeX),
981                     mMinTranslationZ, mMaxTranslationZ);
982             dimAlpha = Utilities.mapRange(focusState, unfocusedDim, focusedDim);
983             viewOutlineAlpha = Utilities.mapRange(Utilities.clamp01(boundedScrollUnfocusedRangeX),
984                     OUTLINE_ALPHA_MIN_VALUE, OUTLINE_ALPHA_MAX_VALUE);
985         }
986
987         // Fill out the transform
988         transformOut.scale = 1f;
989         transformOut.alpha = 1f;
990         transformOut.translationZ = z;
991         transformOut.dimAlpha = dimAlpha;
992         transformOut.viewOutlineAlpha = viewOutlineAlpha;
993         transformOut.rect.set(mTaskRect);
994         transformOut.rect.offset(x, y);
995         Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
996         transformOut.visible = (transformOut.rect.top < mStackRect.bottom) &&
997                 (frontTransform == null || transformOut.rect.top != frontTransform.rect.top);
998     }
999
1000     /**
1001      * Returns the untransformed task view bounds.
1002      */
1003     public Rect getUntransformedTaskViewBounds() {
1004         return new Rect(mTaskRect);
1005     }
1006
1007     /**
1008      * Returns the scroll progress to scroll to such that the top of the task is at the top of the
1009      * stack.
1010      */
1011     float getStackScrollForTask(Task t) {
1012         Float overrideP = mTaskIndexOverrideMap.get(t.key.id, null);
1013         if (overrideP == null) {
1014             return (float) mTaskIndexMap.get(t.key.id, 0);
1015         }
1016         return overrideP;
1017     }
1018
1019     /**
1020      * Returns the original scroll progress to scroll to such that the top of the task is at the top
1021      * of the stack.
1022      */
1023     float getStackScrollForTaskIgnoreOverrides(Task t) {
1024         return (float) mTaskIndexMap.get(t.key.id, 0);
1025     }
1026
1027     /**
1028      * Returns the scroll progress to scroll to such that the top of the task at the initial top
1029      * offset (which is at the task's brightest point).
1030      */
1031     float getStackScrollForTaskAtInitialOffset(Task t) {
1032         float normX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
1033         mUnfocusedRange.offset(0f);
1034         return Utilities.clamp((float) mTaskIndexMap.get(t.key.id, 0) - Math.max(0,
1035                 mUnfocusedRange.getAbsoluteX(normX)), mMinScrollP, mMaxScrollP);
1036     }
1037
1038     /**
1039      * Maps a movement in screen y, relative to {@param downY}, to a movement in along the arc
1040      * length of the curve.  We know the curve is mostly flat, so we just map the length of the
1041      * screen along the arc-length proportionally (1/arclength).
1042      */
1043     public float getDeltaPForY(int downY, int y) {
1044         float deltaP = (float) (y - downY) / mStackRect.height() *
1045                 mUnfocusedCurveInterpolator.getArcLength();
1046         return -deltaP;
1047     }
1048
1049     /**
1050      * This is the inverse of {@link #getDeltaPForY}.  Given a movement along the arc length
1051      * of the curve, map back to the screen y.
1052      */
1053     public int getYForDeltaP(float downScrollP, float p) {
1054         int y = (int) ((p - downScrollP) * mStackRect.height() *
1055                 (1f / mUnfocusedCurveInterpolator.getArcLength()));
1056         return -y;
1057     }
1058
1059     /**
1060      * Returns the task stack bounds in the current orientation.  This rect takes into account the
1061      * top and right system insets (but not the bottom inset) and left/right paddings, but _not_
1062      * the top/bottom padding or insets.
1063      */
1064     public void getTaskStackBounds(Rect displayRect, Rect windowRect, int topInset, int leftInset,
1065             int rightInset, Rect taskStackBounds) {
1066         taskStackBounds.set(windowRect.left + leftInset, windowRect.top + topInset,
1067                 windowRect.right - rightInset, windowRect.bottom);
1068
1069         // Ensure that the new width is at most the smaller display edge size
1070         int sideMargin = getScaleForExtent(windowRect, displayRect, mBaseSideMargin, mMinMargin,
1071                 WIDTH);
1072         int targetStackWidth = taskStackBounds.width() - 2 * sideMargin;
1073         if (Utilities.getAppConfiguration(mContext).orientation
1074                 == Configuration.ORIENTATION_LANDSCAPE) {
1075             // If we are in landscape, calculate the width of the stack in portrait and ensure that
1076             // we are not larger than that size
1077             Rect portraitDisplayRect = new Rect(0, 0,
1078                     Math.min(displayRect.width(), displayRect.height()),
1079                     Math.max(displayRect.width(), displayRect.height()));
1080             int portraitSideMargin = getScaleForExtent(portraitDisplayRect, portraitDisplayRect,
1081                     mBaseSideMargin, mMinMargin, WIDTH);
1082             targetStackWidth = Math.min(targetStackWidth,
1083                     portraitDisplayRect.width() - 2 * portraitSideMargin);
1084         }
1085         taskStackBounds.inset((taskStackBounds.width() - targetStackWidth) / 2, 0);
1086     }
1087
1088     /**
1089      * Retrieves resources that are constant regardless of the current configuration of the device.
1090      */
1091     public static int getDimensionForDevice(Context ctx, int phoneResId,
1092             int tabletResId, int xlargeTabletResId) {
1093         return getDimensionForDevice(ctx, phoneResId, phoneResId, tabletResId, tabletResId,
1094                 xlargeTabletResId, xlargeTabletResId);
1095     }
1096
1097     /**
1098      * Retrieves resources that are constant regardless of the current configuration of the device.
1099      */
1100     public static int getDimensionForDevice(Context ctx, int phonePortResId, int phoneLandResId,
1101             int tabletPortResId, int tabletLandResId, int xlargeTabletPortResId,
1102             int xlargeTabletLandResId) {
1103         RecentsConfiguration config = Recents.getConfiguration();
1104         Resources res = ctx.getResources();
1105         boolean isLandscape = Utilities.getAppConfiguration(ctx).orientation ==
1106                 Configuration.ORIENTATION_LANDSCAPE;
1107         if (config.isXLargeScreen) {
1108             return res.getDimensionPixelSize(isLandscape
1109                     ? xlargeTabletLandResId
1110                     : xlargeTabletPortResId);
1111         } else if (config.isLargeScreen) {
1112             return res.getDimensionPixelSize(isLandscape
1113                     ? tabletLandResId
1114                     : tabletPortResId);
1115         } else {
1116             return res.getDimensionPixelSize(isLandscape
1117                     ? phoneLandResId
1118                     : phonePortResId);
1119         }
1120     }
1121
1122     /**
1123      * Returns the normalized x on the unfocused curve given an absolute Y position (relative to the
1124      * stack height).
1125      */
1126     private float getNormalizedXFromUnfocusedY(float y, @AnchorSide int fromSide) {
1127         float offset = (fromSide == FROM_TOP)
1128                 ? mStackRect.height() - y
1129                 : y;
1130         float offsetPct = offset / mStackRect.height();
1131         return mUnfocusedCurveInterpolator.getX(offsetPct);
1132     }
1133
1134     /**
1135      * Returns the normalized x on the focused curve given an absolute Y position (relative to the
1136      * stack height).
1137      */
1138     private float getNormalizedXFromFocusedY(float y, @AnchorSide int fromSide) {
1139         float offset = (fromSide == FROM_TOP)
1140                 ? mStackRect.height() - y
1141                 : y;
1142         float offsetPct = offset / mStackRect.height();
1143         return mFocusedCurveInterpolator.getX(offsetPct);
1144     }
1145
1146     /**
1147      * Creates a new path for the focused curve.
1148      */
1149     private Path constructFocusedCurve() {
1150         // Initialize the focused curve. This curve is a piecewise curve composed of several
1151         // linear pieces that goes from (0,1) through (0.5, peek height offset),
1152         // (0.5, bottom task offsets), and (1,0).
1153         float topPeekHeightPct = (float) mFocusedTopPeekHeight / mStackRect.height();
1154         float bottomPeekHeightPct = (float) (mStackBottomOffset + mFocusedBottomPeekHeight) /
1155                 mStackRect.height();
1156         float minBottomPeekHeightPct = (float) (mFocusedTopPeekHeight + mTaskRect.height() -
1157                 mMinMargin) / mStackRect.height();
1158         Path p = new Path();
1159         p.moveTo(0f, 1f);
1160         p.lineTo(0.5f, 1f - topPeekHeightPct);
1161         p.lineTo(1f - (0.5f / mFocusedRange.relativeMax), Math.max(1f - minBottomPeekHeightPct,
1162                 bottomPeekHeightPct));
1163         p.lineTo(1f, 0f);
1164         return p;
1165     }
1166
1167     /**
1168      * Creates a new path for the unfocused curve.
1169      */
1170     private Path constructUnfocusedCurve() {
1171         // Initialize the unfocused curve. This curve is a piecewise curve composed of two quadradic
1172         // beziers that goes from (0,1) through (0.5, peek height offset) and ends at (1,0).  This
1173         // ensures that we match the range, at which 0.5 represents the stack scroll at the current
1174         // task progress.  Because the height offset can change depending on a resource, we compute
1175         // the control point of the second bezier such that between it and a first known point,
1176         // there is a tangent at (0.5, peek height offset).
1177         float cpoint1X = 0.4f;
1178         float cpoint1Y = 0.975f;
1179         float topPeekHeightPct = (float) mFocusedTopPeekHeight / mStackRect.height();
1180         float slope = ((1f - topPeekHeightPct) - cpoint1Y) / (0.5f - cpoint1X);
1181         float b = 1f - slope * cpoint1X;
1182         float cpoint2X = 0.65f;
1183         float cpoint2Y = slope * cpoint2X + b;
1184         Path p = new Path();
1185         p.moveTo(0f, 1f);
1186         p.cubicTo(0f, 1f, cpoint1X, cpoint1Y, 0.5f, 1f - topPeekHeightPct);
1187         p.cubicTo(0.5f, 1f - topPeekHeightPct, cpoint2X, cpoint2Y, 1f, 0f);
1188         return p;
1189     }
1190
1191     /**
1192      * Creates a new path for the focused dim curve.
1193      */
1194     private Path constructFocusedDimCurve() {
1195         Path p = new Path();
1196         // The focused dim interpolator starts at max dim, reduces to zero at 0.5 (the focused
1197         // task), then goes back to max dim at the next task
1198         p.moveTo(0f, MAX_DIM);
1199         p.lineTo(0.5f, 0f);
1200         p.lineTo(0.5f + (0.5f / mFocusedRange.relativeMax), MAX_DIM);
1201         p.lineTo(1f, MAX_DIM);
1202         return p;
1203     }
1204
1205     /**
1206      * Creates a new path for the unfocused dim curve.
1207      */
1208     private Path constructUnfocusedDimCurve() {
1209         float focusX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
1210         float cpoint2X = focusX + (1f - focusX) / 2;
1211         Path p = new Path();
1212         // The unfocused dim interpolator starts at max dim, reduces to zero at 0.5 (the focused
1213         // task), then goes back to max dim towards the front of the stack
1214         p.moveTo(0f, MAX_DIM);
1215         p.cubicTo(focusX * 0.5f, MAX_DIM, focusX * 0.75f, MAX_DIM * 0.75f, focusX, 0f);
1216         p.cubicTo(cpoint2X, 0f, cpoint2X, MED_DIM, 1f, MED_DIM);
1217         return p;
1218     }
1219
1220     /**
1221      * Scales the given {@param value} to the scale of the {@param instance} rect relative to the
1222      * {@param other} rect in the {@param extent} side.
1223      */
1224     private int getScaleForExtent(Rect instance, Rect other, int value, int minValue,
1225                                   @Extent int extent) {
1226         if (extent == WIDTH) {
1227             float scale = Utilities.clamp01((float) instance.width() / other.width());
1228             return Math.max(minValue, (int) (scale * value));
1229         } else if (extent == HEIGHT) {
1230             float scale = Utilities.clamp01((float) instance.height() / other.height());
1231             return Math.max(minValue, (int) (scale * value));
1232         }
1233         return value;
1234     }
1235
1236     /**
1237      * Updates the current transforms that would put a TaskView at the front and back of the stack.
1238      */
1239     private void updateFrontBackTransforms() {
1240         // Return early if we have not yet initialized
1241         if (mStackRect.isEmpty()) {
1242             return;
1243         }
1244
1245         float min = Utilities.mapRange(mFocusState, mUnfocusedRange.relativeMin,
1246                 mFocusedRange.relativeMin);
1247         float max = Utilities.mapRange(mFocusState, mUnfocusedRange.relativeMax,
1248                 mFocusedRange.relativeMax);
1249         getStackTransform(min, min, 0f, mFocusState, mBackOfStackTransform, null,
1250                 true /* ignoreSingleTaskCase */, true /* forceUpdate */);
1251         getStackTransform(max, max, 0f, mFocusState, mFrontOfStackTransform, null,
1252                 true /* ignoreSingleTaskCase */, true /* forceUpdate */);
1253         mBackOfStackTransform.visible = true;
1254         mFrontOfStackTransform.visible = true;
1255     }
1256
1257     public void dump(String prefix, PrintWriter writer) {
1258         String innerPrefix = prefix + "  ";
1259
1260         writer.print(prefix); writer.print(TAG);
1261         writer.write(" numStackTasks="); writer.write(mNumStackTasks);
1262         writer.println();
1263
1264         writer.print(innerPrefix);
1265         writer.print("insets="); writer.print(Utilities.dumpRect(mSystemInsets));
1266         writer.print(" stack="); writer.print(Utilities.dumpRect(mStackRect));
1267         writer.print(" task="); writer.print(Utilities.dumpRect(mTaskRect));
1268         writer.print(" freeform="); writer.print(Utilities.dumpRect(mFreeformRect));
1269         writer.print(" actionButton="); writer.print(Utilities.dumpRect(mStackActionButtonRect));
1270         writer.println();
1271
1272         writer.print(innerPrefix);
1273         writer.print("minScroll="); writer.print(mMinScrollP);
1274         writer.print(" maxScroll="); writer.print(mMaxScrollP);
1275         writer.print(" initialScroll="); writer.print(mInitialScrollP);
1276         writer.println();
1277
1278         writer.print(innerPrefix);
1279         writer.print("focusState="); writer.print(mFocusState);
1280         writer.println();
1281
1282         if (mTaskIndexOverrideMap.size() > 0) {
1283             for (int i = mTaskIndexOverrideMap.size() - 1; i >= 0; i--) {
1284                 int taskId = mTaskIndexOverrideMap.keyAt(i);
1285                 float x = mTaskIndexMap.get(taskId);
1286                 float overrideX = mTaskIndexOverrideMap.get(taskId, 0f);
1287
1288                 writer.print(innerPrefix);
1289                 writer.print("taskId= "); writer.print(taskId);
1290                 writer.print(" x= "); writer.print(x);
1291                 writer.print(" overrideX= "); writer.print(overrideX);
1292                 writer.println();
1293             }
1294         }
1295     }
1296 }