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.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.print(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 }