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.RecentsConfiguration;
     21 import com.android.systemui.recents.misc.Utilities;
     22 import com.android.systemui.recents.model.Task;
     23 
     24 import java.util.ArrayList;
     25 import 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  */
     33 public 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