Home | History | Annotate | Download | only in views
      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 }