TaskStackViewLayoutAlgorithm.java revision a91c293be26b2deb5434eb827a800fa0c80dc92c
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    // A report of the visibility state of the stack
39    public class VisibilityReport {
40        public int numVisibleTasks;
41        public int numVisibleThumbnails;
42
43        /** Package level ctor */
44        VisibilityReport(int tasks, int thumbnails) {
45            numVisibleTasks = tasks;
46            numVisibleThumbnails = thumbnails;
47        }
48    }
49
50    RecentsConfiguration mConfig;
51
52    // The various rects that define the stack view
53    Rect mViewRect = new Rect();
54    Rect mStackVisibleRect = new Rect();
55    Rect mStackRect = new Rect();
56    Rect mTaskRect = new Rect();
57
58    // The min/max scroll progress
59    float mMinScrollP;
60    float mMaxScrollP;
61    float mInitialScrollP;
62    int mWithinAffiliationOffset;
63    int mBetweenAffiliationOffset;
64    HashMap<Task.TaskKey, Float> mTaskProgressMap = new HashMap<Task.TaskKey, Float>();
65
66    // Log function
67    static final float XScale = 1.75f;  // The large the XScale, the longer the flat area of the curve
68    static final float LogBase = 3000;
69    static final int PrecisionSteps = 250;
70    static float[] xp;
71    static float[] px;
72
73    public TaskStackViewLayoutAlgorithm(RecentsConfiguration config) {
74        mConfig = config;
75
76        // Precompute the path
77        initializeCurve();
78    }
79
80    /** Computes the stack and task rects */
81    public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds) {
82        // Compute the stack rects
83        mViewRect.set(0, 0, windowWidth, windowHeight);
84        mStackRect.set(taskStackBounds);
85        mStackVisibleRect.set(taskStackBounds);
86        mStackVisibleRect.bottom = mViewRect.bottom;
87
88        int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * mStackRect.width());
89        int heightPadding = mConfig.taskStackTopPaddingPx;
90        mStackRect.inset(widthPadding, heightPadding);
91
92        // Compute the task rect
93        int size = mStackRect.width();
94        int left = mStackRect.left + (mStackRect.width() - size) / 2;
95        mTaskRect.set(left, mStackRect.top,
96                left + size, mStackRect.top + size);
97
98        // Update the affiliation offsets
99        float visibleTaskPct = 0.5f;
100        mWithinAffiliationOffset = mConfig.taskBarHeight;
101        mBetweenAffiliationOffset = (int) (visibleTaskPct * mTaskRect.height());
102    }
103
104    /** Computes the minimum and maximum scroll progress values.  This method may be called before
105     * the RecentsConfiguration is set, so we need to pass in the alt-tab state. */
106    void computeMinMaxScroll(ArrayList<Task> tasks, boolean launchedWithAltTab,
107            boolean launchedFromHome) {
108        // Clear the progress map
109        mTaskProgressMap.clear();
110
111        // Return early if we have no tasks
112        if (tasks.isEmpty()) {
113            mMinScrollP = mMaxScrollP = 0;
114            return;
115        }
116
117        // Note that we should account for the scale difference of the offsets at the screen bottom
118        int taskHeight = mTaskRect.height();
119        float pAtBottomOfStackRect = screenYToCurveProgress(mStackVisibleRect.bottom);
120        float pWithinAffiliateTop = screenYToCurveProgress(mStackVisibleRect.bottom -
121                mWithinAffiliationOffset);
122        float scale = curveProgressToScale(pWithinAffiliateTop);
123        int scaleYOffset = (int) (((1f - scale) * taskHeight) / 2);
124        pWithinAffiliateTop = screenYToCurveProgress(mStackVisibleRect.bottom -
125                mWithinAffiliationOffset + scaleYOffset);
126        float pWithinAffiliateOffset = pAtBottomOfStackRect - pWithinAffiliateTop;
127        float pBetweenAffiliateOffset = pAtBottomOfStackRect -
128                screenYToCurveProgress(mStackVisibleRect.bottom - mBetweenAffiliationOffset);
129        float pTaskHeightOffset = pAtBottomOfStackRect -
130                screenYToCurveProgress(mStackVisibleRect.bottom - taskHeight);
131        float pNavBarOffset = pAtBottomOfStackRect -
132                screenYToCurveProgress(mStackVisibleRect.bottom - (mStackVisibleRect.bottom -
133                        mStackRect.bottom));
134
135        // Update the task offsets
136        float pAtBackMostCardTop = 0.5f;
137        float pAtFrontMostCardTop = pAtBackMostCardTop;
138        float pAtSecondFrontMostCardTop = pAtBackMostCardTop;
139        int taskCount = tasks.size();
140        for (int i = 0; i < taskCount; i++) {
141            Task task = tasks.get(i);
142            mTaskProgressMap.put(task.key, pAtFrontMostCardTop);
143
144            if (i < (taskCount - 1)) {
145                // Increment the peek height
146                float pPeek = task.group.isFrontMostTask(task) ?
147                        pBetweenAffiliateOffset : pWithinAffiliateOffset;
148                pAtSecondFrontMostCardTop = pAtFrontMostCardTop;
149                pAtFrontMostCardTop += pPeek;
150            }
151        }
152
153        mMaxScrollP = pAtFrontMostCardTop - ((1f - pTaskHeightOffset - pNavBarOffset));
154        mMinScrollP = tasks.size() == 1 ? Math.max(mMaxScrollP, 0f) : 0f;
155        if (launchedWithAltTab) {
156            if (launchedFromHome) {
157                // Center the top most task, since that will be focused first
158                mInitialScrollP = pAtSecondFrontMostCardTop - 0.5f;
159            } else {
160                // Center the second top most task, since that will be focused first
161                mInitialScrollP = pAtSecondFrontMostCardTop - 0.5f;
162            }
163        } else {
164            mInitialScrollP = pAtFrontMostCardTop - 0.825f;
165        }
166        mInitialScrollP = Math.max(0, mInitialScrollP);
167    }
168
169    /**
170     * Computes the maximum number of visible tasks and thumbnails.  Requires that
171     * computeMinMaxScroll() is called first.
172     */
173    public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
174        if (tasks.size() <= 1) {
175            return new VisibilityReport(1, 1);
176        }
177
178        // Walk backwards in the task stack and count the number of tasks and visible thumbnails
179        int taskHeight = mTaskRect.height();
180        int numVisibleTasks = 1;
181        int numVisibleThumbnails = 1;
182        float progress = mTaskProgressMap.get(tasks.get(tasks.size() - 1).key) - mInitialScrollP;
183        int prevScreenY = curveProgressToScreenY(progress);
184        for (int i = tasks.size() - 2; i >= 0; i--) {
185            Task task = tasks.get(i);
186            progress = mTaskProgressMap.get(task.key) - mInitialScrollP;
187            if (progress < 0) {
188                break;
189            }
190            boolean isFrontMostTaskInGroup = task.group.isFrontMostTask(task);
191            if (isFrontMostTaskInGroup) {
192                float scaleAtP = curveProgressToScale(progress);
193                int scaleYOffsetAtP = (int) (((1f - scaleAtP) * taskHeight) / 2);
194                int screenY = curveProgressToScreenY(progress) + scaleYOffsetAtP;
195                boolean hasVisibleThumbnail = (prevScreenY - screenY) > mConfig.taskBarHeight;
196                if (hasVisibleThumbnail) {
197                    numVisibleThumbnails++;
198                    numVisibleTasks++;
199                    prevScreenY = screenY;
200                } else {
201                    // Once we hit the next front most task that does not have a visible thumbnail,
202                    // walk through remaining visible set
203                    for (int j = i; j >= 0; j--) {
204                        numVisibleTasks++;
205                        progress = mTaskProgressMap.get(tasks.get(j).key) - mInitialScrollP;
206                        if (progress < 0) {
207                            break;
208                        }
209                    }
210                    break;
211                }
212            } else if (!isFrontMostTaskInGroup) {
213                // Affiliated task, no thumbnail
214                numVisibleTasks++;
215            }
216        }
217        return new VisibilityReport(numVisibleTasks, numVisibleThumbnails);
218    }
219
220    /** Update/get the transform */
221    public TaskViewTransform getStackTransform(Task task, float stackScroll,
222            TaskViewTransform transformOut, TaskViewTransform prevTransform) {
223        // Return early if we have an invalid index
224        if (task == null || !mTaskProgressMap.containsKey(task.key)) {
225            transformOut.reset();
226            return transformOut;
227        }
228        return getStackTransform(mTaskProgressMap.get(task.key), stackScroll, transformOut,
229                prevTransform);
230    }
231
232    /** Update/get the transform */
233    public TaskViewTransform getStackTransform(float taskProgress, float stackScroll,
234            TaskViewTransform transformOut, TaskViewTransform prevTransform) {
235        float pTaskRelative = taskProgress - stackScroll;
236        float pBounded = Math.max(0, Math.min(pTaskRelative, 1f));
237        // If the task top is outside of the bounds below the screen, then immediately reset it
238        if (pTaskRelative > 1f) {
239            transformOut.reset();
240            transformOut.rect.set(mTaskRect);
241            return transformOut;
242        }
243        // The check for the top is trickier, since we want to show the next task if it is at all
244        // visible, even if p < 0.
245        if (pTaskRelative < 0f) {
246            if (prevTransform != null && Float.compare(prevTransform.p, 0f) <= 0) {
247                transformOut.reset();
248                transformOut.rect.set(mTaskRect);
249                return transformOut;
250            }
251        }
252        float scale = curveProgressToScale(pBounded);
253        int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2);
254        int minZ = mConfig.taskViewTranslationZMinPx;
255        int maxZ = mConfig.taskViewTranslationZMaxPx;
256        transformOut.scale = scale;
257        transformOut.translationY = curveProgressToScreenY(pBounded) - mStackVisibleRect.top -
258                scaleYOffset;
259        transformOut.translationZ = Math.max(minZ, minZ + (pBounded * (maxZ - minZ)));
260        transformOut.rect.set(mTaskRect);
261        transformOut.rect.offset(0, transformOut.translationY);
262        Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
263        transformOut.visible = true;
264        transformOut.p = pTaskRelative;
265        return transformOut;
266    }
267
268    /** Returns the untransformed task view size. */
269    public Rect getUntransformedTaskViewSize() {
270        Rect tvSize = new Rect(mTaskRect);
271        tvSize.offsetTo(0, 0);
272        return tvSize;
273    }
274
275    /** Returns the scroll to such task top = 1f; */
276    float getStackScrollForTask(Task t) {
277        return mTaskProgressMap.get(t.key);
278    }
279
280    /** Initializes the curve. */
281    public static void initializeCurve() {
282        if (xp != null && px != null) return;
283        xp = new float[PrecisionSteps + 1];
284        px = new float[PrecisionSteps + 1];
285
286        // Approximate f(x)
287        float[] fx = new float[PrecisionSteps + 1];
288        float step = 1f / PrecisionSteps;
289        float x = 0;
290        for (int xStep = 0; xStep <= PrecisionSteps; xStep++) {
291            fx[xStep] = logFunc(x);
292            x += step;
293        }
294        // Calculate the arc length for x:1->0
295        float pLength = 0;
296        float[] dx = new float[PrecisionSteps + 1];
297        dx[0] = 0;
298        for (int xStep = 1; xStep < PrecisionSteps; xStep++) {
299            dx[xStep] = (float) Math.sqrt(Math.pow(fx[xStep] - fx[xStep - 1], 2) + Math.pow(step, 2));
300            pLength += dx[xStep];
301        }
302        // Approximate p(x), a function of cumulative progress with x, normalized to 0..1
303        float p = 0;
304        px[0] = 0f;
305        px[PrecisionSteps] = 1f;
306        for (int xStep = 1; xStep <= PrecisionSteps; xStep++) {
307            p += Math.abs(dx[xStep] / pLength);
308            px[xStep] = p;
309        }
310        // Given p(x), calculate the inverse function x(p). This assumes that x(p) is also a valid
311        // function.
312        int xStep = 0;
313        p = 0;
314        xp[0] = 0f;
315        xp[PrecisionSteps] = 1f;
316        for (int pStep = 0; pStep < PrecisionSteps; pStep++) {
317            // Walk forward in px and find the x where px <= p && p < px+1
318            while (xStep < PrecisionSteps) {
319                if (px[xStep] > p) break;
320                xStep++;
321            }
322            // Now, px[xStep-1] <= p < px[xStep]
323            if (xStep == 0) {
324                xp[pStep] = 0;
325            } else {
326                // Find x such that proportionally, x is correct
327                float fraction = (p - px[xStep - 1]) / (px[xStep] - px[xStep - 1]);
328                x = (xStep - 1 + fraction) * step;
329                xp[pStep] = x;
330            }
331            p += step;
332        }
333    }
334
335    /** Reverses and scales out x. */
336    static float reverse(float x) {
337        return (-x * XScale) + 1;
338    }
339    /** The log function describing the curve. */
340    static float logFunc(float x) {
341        return 1f - (float) (Math.pow(LogBase, reverse(x))) / (LogBase);
342    }
343    /** The inverse of the log function describing the curve. */
344    float invLogFunc(float y) {
345        return (float) (Math.log((1f - reverse(y)) * (LogBase - 1) + 1) / Math.log(LogBase));
346    }
347
348    /** Converts from the progress along the curve to a screen coordinate. */
349    int curveProgressToScreenY(float p) {
350        if (p < 0 || p > 1) return mStackVisibleRect.top + (int) (p * mStackVisibleRect.height());
351        float pIndex = p * PrecisionSteps;
352        int pFloorIndex = (int) Math.floor(pIndex);
353        int pCeilIndex = (int) Math.ceil(pIndex);
354        float xFraction = 0;
355        if (pFloorIndex < PrecisionSteps && (pCeilIndex != pFloorIndex)) {
356            float pFraction = (pIndex - pFloorIndex) / (pCeilIndex - pFloorIndex);
357            xFraction = (xp[pCeilIndex] - xp[pFloorIndex]) * pFraction;
358        }
359        float x = xp[pFloorIndex] + xFraction;
360        return mStackVisibleRect.top + (int) (x * mStackVisibleRect.height());
361    }
362
363    /** Converts from the progress along the curve to a scale. */
364    float curveProgressToScale(float p) {
365        if (p < 0) return StackPeekMinScale;
366        if (p > 1) return 1f;
367        float scaleRange = (1f - StackPeekMinScale);
368        float scale = StackPeekMinScale + (p * scaleRange);
369        return scale;
370    }
371
372    /** Converts from a screen coordinate to the progress along the curve. */
373    float screenYToCurveProgress(int screenY) {
374        float x = (float) (screenY - mStackVisibleRect.top) / mStackVisibleRect.height();
375        if (x < 0 || x > 1) return x;
376        float xIndex = x * PrecisionSteps;
377        int xFloorIndex = (int) Math.floor(xIndex);
378        int xCeilIndex = (int) Math.ceil(xIndex);
379        float pFraction = 0;
380        if (xFloorIndex < PrecisionSteps && (xCeilIndex != xFloorIndex)) {
381            float xFraction = (xIndex - xFloorIndex) / (xCeilIndex - xFloorIndex);
382            pFraction = (px[xCeilIndex] - px[xFloorIndex]) * xFraction;
383        }
384        return px[xFloorIndex] + pFraction;
385    }
386}
387