TaskStackViewLayoutAlgorithm.java revision a4ccb86ddc8f9f486aee25fb836f4aff97bf7679
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
17package com.android.systemui.recents.views;
18
19import android.graphics.Rect;
20import com.android.systemui.recents.RecentsConfiguration;
21import com.android.systemui.recents.misc.Utilities;
22import com.android.systemui.recents.model.Task;
23
24import java.util.ArrayList;
25import java.util.HashMap;
26
27/* The layout logic for a TaskStackView.
28 *
29 * We are using a curve that defines the curve of the tasks as that go back in the recents list.
30 * The curve is defined such that at curve progress p = 0 is the end of the curve (the top of the
31 * stack rect), and p = 1 at the start of the curve and the bottom of the stack rect.
32 */
33public class TaskStackViewLayoutAlgorithm {
34
35    // These are all going to change
36    static final float StackPeekMinScale = 0.8f; // The min scale of the last card in the peek area
37
38    RecentsConfiguration mConfig;
39
40    // The various rects that define the stack view
41    Rect mViewRect = new Rect();
42    Rect mStackVisibleRect = new Rect();
43    Rect mStackRect = new Rect();
44    Rect mTaskRect = new Rect();
45
46    // The min/max scroll progress
47    float mMinScrollP;
48    float mMaxScrollP;
49    float mInitialScrollP;
50    int mWithinAffiliationOffset;
51    int mBetweenAffiliationOffset;
52    HashMap<Task.TaskKey, Float> mTaskProgressMap = new HashMap<Task.TaskKey, Float>();
53
54    // Log function
55    static final float XScale = 1.75f;  // The large the XScale, the longer the flat area of the curve
56    static final float LogBase = 3000;
57    static final int PrecisionSteps = 250;
58    static float[] xp;
59    static float[] px;
60
61    public TaskStackViewLayoutAlgorithm(RecentsConfiguration config) {
62        mConfig = config;
63
64        // Precompute the path
65        initializeCurve();
66    }
67
68    /** Computes the stack and task rects */
69    public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds) {
70        // Compute the stack rects
71        mViewRect.set(0, 0, windowWidth, windowHeight);
72        mStackRect.set(taskStackBounds);
73        mStackVisibleRect.set(taskStackBounds);
74        mStackVisibleRect.bottom = mViewRect.bottom;
75
76        int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * mStackRect.width());
77        int heightPadding = mConfig.taskStackTopPaddingPx;
78        mStackRect.inset(widthPadding, heightPadding);
79
80        // Compute the task rect
81        int size = mStackRect.width();
82        int left = mStackRect.left + (mStackRect.width() - size) / 2;
83        mTaskRect.set(left, mStackRect.top,
84                left + size, mStackRect.top + size);
85
86        // Update the affiliation offsets
87        float visibleTaskPct = 0.5f;
88        mWithinAffiliationOffset = mConfig.taskBarHeight;
89        mBetweenAffiliationOffset = (int) (visibleTaskPct * mTaskRect.height());
90    }
91
92    /** Computes the minimum and maximum scroll progress values.  This method may be called before
93     * the RecentsConfiguration is set, so we need to pass in the alt-tab state. */
94    void computeMinMaxScroll(ArrayList<Task> tasks, boolean launchedWithAltTab,
95            boolean launchedFromHome) {
96        // Clear the progress map
97        mTaskProgressMap.clear();
98
99        // Return early if we have no tasks
100        if (tasks.isEmpty()) {
101            mMinScrollP = mMaxScrollP = 0;
102            return;
103        }
104
105        // Note that we should account for the scale difference of the offsets at the screen bottom
106        int taskHeight = mTaskRect.height();
107        float pAtBottomOfStackRect = screenYToCurveProgress(mStackVisibleRect.bottom);
108        float pWithinAffiliateTop = screenYToCurveProgress(mStackVisibleRect.bottom -
109                mWithinAffiliationOffset);
110        float scale = curveProgressToScale(pWithinAffiliateTop);
111        int scaleYOffset = (int) (((1f - scale) * taskHeight) / 2);
112        pWithinAffiliateTop = screenYToCurveProgress(mStackVisibleRect.bottom -
113                mWithinAffiliationOffset + scaleYOffset);
114        float pWithinAffiliateOffset = pAtBottomOfStackRect - pWithinAffiliateTop;
115        float pBetweenAffiliateOffset = pAtBottomOfStackRect -
116                screenYToCurveProgress(mStackVisibleRect.bottom - mBetweenAffiliationOffset);
117        float pTaskHeightOffset = pAtBottomOfStackRect -
118                screenYToCurveProgress(mStackVisibleRect.bottom - taskHeight);
119        float pNavBarOffset = pAtBottomOfStackRect -
120                screenYToCurveProgress(mStackVisibleRect.bottom - (mStackVisibleRect.bottom - mStackRect.bottom));
121
122        // Update the task offsets
123        float pAtBackMostCardTop = 0.5f;
124        float pAtFrontMostCardTop = pAtBackMostCardTop;
125        float pAtSecondFrontMostCardTop = pAtBackMostCardTop;
126        int taskCount = tasks.size();
127        for (int i = 0; i < taskCount; i++) {
128            Task task = tasks.get(i);
129            mTaskProgressMap.put(task.key, pAtFrontMostCardTop);
130
131            if (i < (taskCount - 1)) {
132                // Increment the peek height
133                float pPeek = task.group.isFrontMostTask(task) ? pBetweenAffiliateOffset :
134                    pWithinAffiliateOffset;
135                pAtSecondFrontMostCardTop = pAtFrontMostCardTop;
136                pAtFrontMostCardTop += pPeek;
137            }
138        }
139
140        mMaxScrollP = pAtFrontMostCardTop - ((1f - pTaskHeightOffset - pNavBarOffset));
141        mMinScrollP = tasks.size() == 1 ? Math.max(mMaxScrollP, 0f) : 0f;
142        if (launchedWithAltTab) {
143            if (launchedFromHome) {
144                // Center the top most task, since that will be focused first
145                mInitialScrollP = pAtSecondFrontMostCardTop - 0.5f;
146            } else {
147                // Center the second top most task, since that will be focused first
148                mInitialScrollP = pAtSecondFrontMostCardTop - 0.5f;
149            }
150        } else {
151            mInitialScrollP = pAtFrontMostCardTop - 0.825f;
152        }
153        mInitialScrollP = Math.max(0, mInitialScrollP);
154    }
155
156    /** Update/get the transform */
157    public TaskViewTransform getStackTransform(Task task, float stackScroll, TaskViewTransform transformOut,
158            TaskViewTransform prevTransform) {
159        // Return early if we have an invalid index
160        if (task == null || !mTaskProgressMap.containsKey(task.key)) {
161            transformOut.reset();
162            return transformOut;
163        }
164        return getStackTransform(mTaskProgressMap.get(task.key), stackScroll, transformOut, prevTransform);
165    }
166
167    /** Update/get the transform */
168    public TaskViewTransform getStackTransform(float taskProgress, float stackScroll, TaskViewTransform transformOut, TaskViewTransform prevTransform) {
169        float pTaskRelative = taskProgress - stackScroll;
170        float pBounded = Math.max(0, Math.min(pTaskRelative, 1f));
171        // If the task top is outside of the bounds below the screen, then immediately reset it
172        if (pTaskRelative > 1f) {
173            transformOut.reset();
174            transformOut.rect.set(mTaskRect);
175            return transformOut;
176        }
177        // The check for the top is trickier, since we want to show the next task if it is at all
178        // visible, even if p < 0.
179        if (pTaskRelative < 0f) {
180            if (prevTransform != null && Float.compare(prevTransform.p, 0f) <= 0) {
181                transformOut.reset();
182                transformOut.rect.set(mTaskRect);
183                return transformOut;
184            }
185        }
186        float scale = curveProgressToScale(pBounded);
187        int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
188        int minZ = mConfig.taskViewTranslationZMinPx;
189        int maxZ = mConfig.taskViewTranslationZMaxPx;
190        transformOut.scale = scale;
191        transformOut.translationY = curveProgressToScreenY(pBounded) - mStackVisibleRect.top -
192                scaleYOffset;
193        transformOut.translationZ = Math.max(minZ, minZ + (pBounded * (maxZ - minZ)));
194        transformOut.rect.set(mTaskRect);
195        transformOut.rect.offset(0, transformOut.translationY);
196        Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
197        transformOut.visible = true;
198        transformOut.p = pTaskRelative;
199        return transformOut;
200    }
201
202    /**
203     * Returns the untransformed task view size.
204     */
205    public Rect getUntransformedTaskViewSize() {
206        Rect tvSize = new Rect(mTaskRect);
207        tvSize.offsetTo(0, 0);
208        return tvSize;
209    }
210
211    /**
212     * Returns the scroll to such task top = 1f;
213     */
214    float getStackScrollForTask(Task t) {
215        return mTaskProgressMap.get(t.key);
216    }
217
218    /** Initializes the curve. */
219    public static void initializeCurve() {
220        if (xp != null && px != null) return;
221        xp = new float[PrecisionSteps + 1];
222        px = new float[PrecisionSteps + 1];
223
224        // Approximate f(x)
225        float[] fx = new float[PrecisionSteps + 1];
226        float step = 1f / PrecisionSteps;
227        float x = 0;
228        for (int xStep = 0; xStep <= PrecisionSteps; xStep++) {
229            fx[xStep] = logFunc(x);
230            x += step;
231        }
232        // Calculate the arc length for x:1->0
233        float pLength = 0;
234        float[] dx = new float[PrecisionSteps + 1];
235        dx[0] = 0;
236        for (int xStep = 1; xStep < PrecisionSteps; xStep++) {
237            dx[xStep] = (float) Math.sqrt(Math.pow(fx[xStep] - fx[xStep - 1], 2) + Math.pow(step, 2));
238            pLength += dx[xStep];
239        }
240        // Approximate p(x), a function of cumulative progress with x, normalized to 0..1
241        float p = 0;
242        px[0] = 0f;
243        px[PrecisionSteps] = 1f;
244        for (int xStep = 1; xStep <= PrecisionSteps; xStep++) {
245            p += Math.abs(dx[xStep] / pLength);
246            px[xStep] = p;
247        }
248        // Given p(x), calculate the inverse function x(p). This assumes that x(p) is also a valid
249        // function.
250        int xStep = 0;
251        p = 0;
252        xp[0] = 0f;
253        xp[PrecisionSteps] = 1f;
254        for (int pStep = 0; pStep < PrecisionSteps; pStep++) {
255            // Walk forward in px and find the x where px <= p && p < px+1
256            while (xStep < PrecisionSteps) {
257                if (px[xStep] > p) break;
258                xStep++;
259            }
260            // Now, px[xStep-1] <= p < px[xStep]
261            if (xStep == 0) {
262                xp[pStep] = 0;
263            } else {
264                // Find x such that proportionally, x is correct
265                float fraction = (p - px[xStep - 1]) / (px[xStep] - px[xStep - 1]);
266                x = (xStep - 1 + fraction) * step;
267                xp[pStep] = x;
268            }
269            p += step;
270        }
271    }
272
273    /** Reverses and scales out x. */
274    static float reverse(float x) {
275        return (-x * XScale) + 1;
276    }
277    /** The log function describing the curve. */
278    static float logFunc(float x) {
279        return 1f - (float) (Math.pow(LogBase, reverse(x))) / (LogBase);
280    }
281    /** The inverse of the log function describing the curve. */
282    float invLogFunc(float y) {
283        return (float) (Math.log((1f - reverse(y)) * (LogBase - 1) + 1) / Math.log(LogBase));
284    }
285
286    /** Converts from the progress along the curve to a screen coordinate. */
287    int curveProgressToScreenY(float p) {
288        if (p < 0 || p > 1) return mStackVisibleRect.top + (int) (p * mStackVisibleRect.height());
289        float pIndex = p * PrecisionSteps;
290        int pFloorIndex = (int) Math.floor(pIndex);
291        int pCeilIndex = (int) Math.ceil(pIndex);
292        float xFraction = 0;
293        if (pFloorIndex < PrecisionSteps && (pCeilIndex != pFloorIndex)) {
294            float pFraction = (pIndex - pFloorIndex) / (pCeilIndex - pFloorIndex);
295            xFraction = (xp[pCeilIndex] - xp[pFloorIndex]) * pFraction;
296        }
297        float x = xp[pFloorIndex] + xFraction;
298        return mStackVisibleRect.top + (int) (x * mStackVisibleRect.height());
299    }
300
301    /** Converts from the progress along the curve to a scale. */
302    float curveProgressToScale(float p) {
303        if (p < 0) return StackPeekMinScale;
304        if (p > 1) return 1f;
305        float scaleRange = (1f - StackPeekMinScale);
306        float scale = StackPeekMinScale + (p * scaleRange);
307        return scale;
308    }
309
310    /** Converts from a screen coordinate to the progress along the curve. */
311    float screenYToCurveProgress(int screenY) {
312        float x = (float) (screenY - mStackVisibleRect.top) / mStackVisibleRect.height();
313        if (x < 0 || x > 1) return x;
314        float xIndex = x * PrecisionSteps;
315        int xFloorIndex = (int) Math.floor(xIndex);
316        int xCeilIndex = (int) Math.ceil(xIndex);
317        float pFraction = 0;
318        if (xFloorIndex < PrecisionSteps && (xCeilIndex != xFloorIndex)) {
319            float xFraction = (xIndex - xFloorIndex) / (xCeilIndex - xFloorIndex);
320            pFraction = (px[xCeilIndex] - px[xFloorIndex]) * xFraction;
321        }
322        return px[xFloorIndex] + pFraction;
323    }
324}
325