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