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.graphics.Rect;
     20 import com.android.systemui.recents.Constants;
     21 import com.android.systemui.recents.RecentsConfiguration;
     22 import com.android.systemui.recents.misc.Utilities;
     23 import com.android.systemui.recents.model.Task;
     24 
     25 import java.util.ArrayList;
     26 import java.util.HashMap;
     27 
     28 /* The layout logic for a TaskStackView.
     29  *
     30  * We are using a curve that defines the curve of the tasks as that go back in the recents list.
     31  * The curve is defined such that at curve progress p = 0 is the end of the curve (the top of the
     32  * stack rect), and p = 1 at the start of the curve and the bottom of the stack rect.
     33  */
     34 public class TaskStackViewLayoutAlgorithm {
     35 
     36     // These are all going to change
     37     static final float StackPeekMinScale = 0.8f; // The min scale of the last card in the peek area
     38 
     39     // A report of the visibility state of the stack
     40     public class VisibilityReport {
     41         public int numVisibleTasks;
     42         public int numVisibleThumbnails;
     43 
     44         /** Package level ctor */
     45         VisibilityReport(int tasks, int thumbnails) {
     46             numVisibleTasks = tasks;
     47             numVisibleThumbnails = thumbnails;
     48         }
     49     }
     50 
     51     RecentsConfiguration mConfig;
     52 
     53     // The various rects that define the stack view
     54     Rect mViewRect = new Rect();
     55     Rect mStackVisibleRect = new Rect();
     56     Rect mStackRect = new Rect();
     57     Rect mTaskRect = new Rect();
     58 
     59     // The min/max scroll progress
     60     float mMinScrollP;
     61     float mMaxScrollP;
     62     float mInitialScrollP;
     63     int mWithinAffiliationOffset;
     64     int mBetweenAffiliationOffset;
     65     HashMap<Task.TaskKey, Float> mTaskProgressMap = new HashMap<Task.TaskKey, Float>();
     66 
     67     // Log function
     68     static final float XScale = 1.75f;  // The large the XScale, the longer the flat area of the curve
     69     static final float LogBase = 3000;
     70     static final int PrecisionSteps = 250;
     71     static float[] xp;
     72     static float[] px;
     73 
     74     public TaskStackViewLayoutAlgorithm(RecentsConfiguration config) {
     75         mConfig = config;
     76 
     77         // Precompute the path
     78         initializeCurve();
     79     }
     80 
     81     /** Computes the stack and task rects */
     82     public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds) {
     83         // Compute the stack rects
     84         mViewRect.set(0, 0, windowWidth, windowHeight);
     85         mStackRect.set(taskStackBounds);
     86         mStackVisibleRect.set(taskStackBounds);
     87         mStackVisibleRect.bottom = mViewRect.bottom;
     88 
     89         int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * mStackRect.width());
     90         int heightPadding = mConfig.taskStackTopPaddingPx;
     91         mStackRect.inset(widthPadding, heightPadding);
     92 
     93         // Compute the task rect
     94         int size = mStackRect.width();
     95         int left = mStackRect.left + (mStackRect.width() - size) / 2;
     96         mTaskRect.set(left, mStackRect.top,
     97                 left + size, mStackRect.top + size);
     98 
     99         // Update the affiliation offsets
    100         float visibleTaskPct = 0.5f;
    101         mWithinAffiliationOffset = mConfig.taskBarHeight;
    102         mBetweenAffiliationOffset = (int) (visibleTaskPct * mTaskRect.height());
    103     }
    104 
    105     /** Computes the minimum and maximum scroll progress values.  This method may be called before
    106      * the RecentsConfiguration is set, so we need to pass in the alt-tab state. */
    107     void computeMinMaxScroll(ArrayList<Task> tasks, boolean launchedWithAltTab,
    108             boolean launchedFromHome) {
    109         // Clear the progress map
    110         mTaskProgressMap.clear();
    111 
    112         // Return early if we have no tasks
    113         if (tasks.isEmpty()) {
    114             mMinScrollP = mMaxScrollP = 0;
    115             return;
    116         }
    117 
    118         // Note that we should account for the scale difference of the offsets at the screen bottom
    119         int taskHeight = mTaskRect.height();
    120         float pAtBottomOfStackRect = screenYToCurveProgress(mStackVisibleRect.bottom);
    121         float pWithinAffiliateTop = screenYToCurveProgress(mStackVisibleRect.bottom -
    122                 mWithinAffiliationOffset);
    123         float scale = curveProgressToScale(pWithinAffiliateTop);
    124         int scaleYOffset = (int) (((1f - scale) * taskHeight) / 2);
    125         pWithinAffiliateTop = screenYToCurveProgress(mStackVisibleRect.bottom -
    126                 mWithinAffiliationOffset + scaleYOffset);
    127         float pWithinAffiliateOffset = pAtBottomOfStackRect - pWithinAffiliateTop;
    128         float pBetweenAffiliateOffset = pAtBottomOfStackRect -
    129                 screenYToCurveProgress(mStackVisibleRect.bottom - mBetweenAffiliationOffset);
    130         float pTaskHeightOffset = pAtBottomOfStackRect -
    131                 screenYToCurveProgress(mStackVisibleRect.bottom - taskHeight);
    132         float pNavBarOffset = pAtBottomOfStackRect -
    133                 screenYToCurveProgress(mStackVisibleRect.bottom - (mStackVisibleRect.bottom -
    134                         mStackRect.bottom));
    135         float pDismissAllButtonOffset = 0f;
    136         if (Constants.DebugFlags.App.EnableDismissAll) {
    137             pDismissAllButtonOffset = pAtBottomOfStackRect -
    138                 screenYToCurveProgress(mStackVisibleRect.bottom - mConfig.dismissAllButtonSizePx);
    139         }
    140 
    141         // Update the task offsets
    142         float pAtBackMostCardTop = 0.5f;
    143         float pAtFrontMostCardTop = pAtBackMostCardTop;
    144         int taskCount = tasks.size();
    145         for (int i = 0; i < taskCount; i++) {
    146             Task task = tasks.get(i);
    147             mTaskProgressMap.put(task.key, pAtFrontMostCardTop);
    148 
    149             if (i < (taskCount - 1)) {
    150                 // Increment the peek height
    151                 float pPeek = task.group.isFrontMostTask(task) ?
    152                         pBetweenAffiliateOffset : pWithinAffiliateOffset;
    153                 pAtFrontMostCardTop += pPeek;
    154             }
    155         }
    156 
    157         mMaxScrollP = pAtFrontMostCardTop + pDismissAllButtonOffset -
    158                 ((1f - pTaskHeightOffset - pNavBarOffset));
    159         mMinScrollP = tasks.size() == 1 ? Math.max(mMaxScrollP, 0f) : 0f;
    160         if (launchedWithAltTab && launchedFromHome) {
    161             // Center the top most task, since that will be focused first
    162             mInitialScrollP = mMaxScrollP;
    163         } else {
    164             mInitialScrollP = pAtFrontMostCardTop - 0.825f;
    165         }
    166         mInitialScrollP = Math.min(mMaxScrollP, 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         if (!mTaskProgressMap.containsKey(t.key)) return 0f;
    278         return mTaskProgressMap.get(t.key);
    279     }
    280 
    281     /** Initializes the curve. */
    282     public static void initializeCurve() {
    283         if (xp != null && px != null) return;
    284         xp = new float[PrecisionSteps + 1];
    285         px = new float[PrecisionSteps + 1];
    286 
    287         // Approximate f(x)
    288         float[] fx = new float[PrecisionSteps + 1];
    289         float step = 1f / PrecisionSteps;
    290         float x = 0;
    291         for (int xStep = 0; xStep <= PrecisionSteps; xStep++) {
    292             fx[xStep] = logFunc(x);
    293             x += step;
    294         }
    295         // Calculate the arc length for x:1->0
    296         float pLength = 0;
    297         float[] dx = new float[PrecisionSteps + 1];
    298         dx[0] = 0;
    299         for (int xStep = 1; xStep < PrecisionSteps; xStep++) {
    300             dx[xStep] = (float) Math.sqrt(Math.pow(fx[xStep] - fx[xStep - 1], 2) + Math.pow(step, 2));
    301             pLength += dx[xStep];
    302         }
    303         // Approximate p(x), a function of cumulative progress with x, normalized to 0..1
    304         float p = 0;
    305         px[0] = 0f;
    306         px[PrecisionSteps] = 1f;
    307         for (int xStep = 1; xStep <= PrecisionSteps; xStep++) {
    308             p += Math.abs(dx[xStep] / pLength);
    309             px[xStep] = p;
    310         }
    311         // Given p(x), calculate the inverse function x(p). This assumes that x(p) is also a valid
    312         // function.
    313         int xStep = 0;
    314         p = 0;
    315         xp[0] = 0f;
    316         xp[PrecisionSteps] = 1f;
    317         for (int pStep = 0; pStep < PrecisionSteps; pStep++) {
    318             // Walk forward in px and find the x where px <= p && p < px+1
    319             while (xStep < PrecisionSteps) {
    320                 if (px[xStep] > p) break;
    321                 xStep++;
    322             }
    323             // Now, px[xStep-1] <= p < px[xStep]
    324             if (xStep == 0) {
    325                 xp[pStep] = 0;
    326             } else {
    327                 // Find x such that proportionally, x is correct
    328                 float fraction = (p - px[xStep - 1]) / (px[xStep] - px[xStep - 1]);
    329                 x = (xStep - 1 + fraction) * step;
    330                 xp[pStep] = x;
    331             }
    332             p += step;
    333         }
    334     }
    335 
    336     /** Reverses and scales out x. */
    337     static float reverse(float x) {
    338         return (-x * XScale) + 1;
    339     }
    340     /** The log function describing the curve. */
    341     static float logFunc(float x) {
    342         return 1f - (float) (Math.pow(LogBase, reverse(x))) / (LogBase);
    343     }
    344     /** The inverse of the log function describing the curve. */
    345     float invLogFunc(float y) {
    346         return (float) (Math.log((1f - reverse(y)) * (LogBase - 1) + 1) / Math.log(LogBase));
    347     }
    348 
    349     /** Converts from the progress along the curve to a screen coordinate. */
    350     int curveProgressToScreenY(float p) {
    351         if (p < 0 || p > 1) return mStackVisibleRect.top + (int) (p * mStackVisibleRect.height());
    352         float pIndex = p * PrecisionSteps;
    353         int pFloorIndex = (int) Math.floor(pIndex);
    354         int pCeilIndex = (int) Math.ceil(pIndex);
    355         float xFraction = 0;
    356         if (pFloorIndex < PrecisionSteps && (pCeilIndex != pFloorIndex)) {
    357             float pFraction = (pIndex - pFloorIndex) / (pCeilIndex - pFloorIndex);
    358             xFraction = (xp[pCeilIndex] - xp[pFloorIndex]) * pFraction;
    359         }
    360         float x = xp[pFloorIndex] + xFraction;
    361         return mStackVisibleRect.top + (int) (x * mStackVisibleRect.height());
    362     }
    363 
    364     /** Converts from the progress along the curve to a scale. */
    365     float curveProgressToScale(float p) {
    366         if (p < 0) return StackPeekMinScale;
    367         if (p > 1) return 1f;
    368         float scaleRange = (1f - StackPeekMinScale);
    369         float scale = StackPeekMinScale + (p * scaleRange);
    370         return scale;
    371     }
    372 
    373     /** Converts from a screen coordinate to the progress along the curve. */
    374     float screenYToCurveProgress(int screenY) {
    375         float x = (float) (screenY - mStackVisibleRect.top) / mStackVisibleRect.height();
    376         if (x < 0 || x > 1) return x;
    377         float xIndex = x * PrecisionSteps;
    378         int xFloorIndex = (int) Math.floor(xIndex);
    379         int xCeilIndex = (int) Math.ceil(xIndex);
    380         float pFraction = 0;
    381         if (xFloorIndex < PrecisionSteps && (xCeilIndex != xFloorIndex)) {
    382             float xFraction = (xIndex - xFloorIndex) / (xCeilIndex - xFloorIndex);
    383             pFraction = (px[xCeilIndex] - px[xFloorIndex]) * xFraction;
    384         }
    385         return px[xFloorIndex] + pFraction;
    386     }
    387 }
    388