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