Home | History | Annotate | Download | only in lowram
      1 /*
      2  * Copyright (C) 2017 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.lowram;
     18 
     19 import android.content.Context;
     20 import android.graphics.Rect;
     21 import android.view.ViewConfiguration;
     22 
     23 import com.android.systemui.R;
     24 import com.android.systemui.recents.Recents;
     25 import com.android.systemui.recents.RecentsActivityLaunchState;
     26 import com.android.systemui.shared.recents.utilities.Utilities;
     27 import com.android.systemui.shared.recents.model.Task;
     28 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
     29 import com.android.systemui.recents.views.TaskViewTransform;
     30 
     31 import java.util.ArrayList;
     32 
     33 import static com.android.systemui.recents.views.TaskStackLayoutAlgorithm.VisibilityReport;
     34 
     35 public class TaskStackLowRamLayoutAlgorithm {
     36 
     37     private static final String TAG = "TaskStackLowRamLayoutAlgorithm";
     38     private static final float MAX_OVERSCROLL = 0.2f / 0.3f;
     39 
     40     public static final int MAX_LAYOUT_TASK_COUNT = 9;
     41     public static final int NUM_TASK_VISIBLE_LAUNCHED_FROM_HOME = 2;
     42     public static final int NUM_TASK_VISIBLE_LAUNCHED_FROM_APP =
     43             NUM_TASK_VISIBLE_LAUNCHED_FROM_HOME + 1;
     44     private Rect mWindowRect;
     45 
     46     private int mFlingThreshold;
     47     private int mPadding;
     48     private int mPaddingLeftRight;
     49     private int mTopOffset;
     50     private int mPaddingEndTopBottom;
     51     private Rect mTaskRect = new Rect();
     52     private Rect mSystemInsets = new Rect();
     53 
     54     public TaskStackLowRamLayoutAlgorithm(Context context) {
     55         reloadOnConfigurationChange(context);
     56     }
     57 
     58     public void reloadOnConfigurationChange(Context context) {
     59         mPadding = context.getResources()
     60                 .getDimensionPixelSize(R.dimen.recents_layout_side_margin_phone);
     61         mFlingThreshold = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
     62     }
     63 
     64     public void initialize(Rect windowRect) {
     65         mWindowRect = windowRect;
     66         if (mWindowRect.height() > 0) {
     67             int windowHeight = mWindowRect.height() - mSystemInsets.bottom;
     68             int windowWidth = mWindowRect.width() - mSystemInsets.right - mSystemInsets.left;
     69             int width = Math.min(windowWidth, windowHeight) - mPadding * 2;
     70             boolean isLandscape = windowWidth > windowHeight;
     71             mTaskRect.set(0, 0, width, isLandscape ? width * 2 / 3 : width);
     72             mPaddingLeftRight = (windowWidth - mTaskRect.width()) / 2;
     73             mPaddingEndTopBottom = (windowHeight - mTaskRect.height()) / 2;
     74 
     75             // Compute the top offset to center tasks in the middle of the screen
     76             mTopOffset = (getTotalHeightOfTasks(MAX_LAYOUT_TASK_COUNT) - windowHeight) / 2;
     77         }
     78     }
     79 
     80     public void setSystemInsets(Rect systemInsets) {
     81         mSystemInsets = systemInsets;
     82     }
     83 
     84     public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
     85         RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
     86         int maxVisible = launchState.launchedFromHome || launchState.launchedFromPipApp
     87                     || launchState.launchedWithNextPipApp
     88                 ? NUM_TASK_VISIBLE_LAUNCHED_FROM_HOME
     89                 : NUM_TASK_VISIBLE_LAUNCHED_FROM_APP;
     90         int visibleCount = Math.min(maxVisible, tasks.size());
     91         return new VisibilityReport(visibleCount, visibleCount);
     92     }
     93 
     94     public void getFrontOfStackTransform(TaskViewTransform transformOut,
     95             TaskStackLayoutAlgorithm stackLayout) {
     96         if (mWindowRect == null) {
     97             transformOut.reset();
     98             return;
     99         }
    100 
    101         // Calculate the static task y position 2 tasks after/below the middle/current task
    102         int windowHeight = mWindowRect.height() - mSystemInsets.bottom;
    103         int bottomOfCurrentTask = (windowHeight + mTaskRect.height()) / 2;
    104         int y = bottomOfCurrentTask + mTaskRect.height() + mPadding * 2;
    105         fillStackTransform(transformOut, y, stackLayout.mMaxTranslationZ, true);
    106     }
    107 
    108     public void getBackOfStackTransform(TaskViewTransform transformOut,
    109             TaskStackLayoutAlgorithm stackLayout) {
    110         if (mWindowRect == null) {
    111             transformOut.reset();
    112             return;
    113         }
    114 
    115         // Calculate the static task y position 2 tasks before/above the middle/current task
    116         int windowHeight = mWindowRect.height() - mSystemInsets.bottom;
    117         int topOfCurrentTask = (windowHeight - mTaskRect.height()) / 2;
    118         int y = topOfCurrentTask - (mTaskRect.height() + mPadding) * 2;
    119         fillStackTransform(transformOut, y, stackLayout.mMaxTranslationZ, true);
    120     }
    121 
    122     public TaskViewTransform getTransform(int taskIndex, float stackScroll,
    123             TaskViewTransform transformOut, int taskCount, TaskStackLayoutAlgorithm stackLayout) {
    124         if (taskCount == 0) {
    125             transformOut.reset();
    126             return transformOut;
    127         }
    128         boolean visible = true;
    129         int y;
    130         if (taskCount > 1) {
    131             y = getTaskTopFromIndex(taskIndex) - percentageToScroll(stackScroll);
    132 
    133             // Check visibility from the bottom of the task
    134             visible = y + mPadding + getTaskRect().height() > 0;
    135         } else {
    136             int windowHeight = mWindowRect.height() - mSystemInsets.bottom;
    137             y = (windowHeight - mTaskRect.height()) / 2 - percentageToScroll(stackScroll);
    138         }
    139         fillStackTransform(transformOut, y, stackLayout.mMaxTranslationZ, visible);
    140         return transformOut;
    141     }
    142 
    143     /**
    144      * Finds the closest task to the scroll percentage in the y axis and returns the percentage of
    145      * the task to scroll to.
    146      * @param scrollP percentage to find nearest to
    147      * @param numTasks number of tasks in recents stack
    148      * @param velocity speed of fling
    149      */
    150     public float getClosestTaskP(float scrollP, int numTasks, int velocity) {
    151         int y = percentageToScroll(scrollP);
    152 
    153         int lastY = getTaskTopFromIndex(0) - mPaddingEndTopBottom;
    154         for (int i = 1; i < numTasks; i++) {
    155             int taskY = getTaskTopFromIndex(i) - mPaddingEndTopBottom;
    156             int diff = taskY - y;
    157             if (diff > 0) {
    158                 int diffPrev = Math.abs(y - lastY);
    159                 boolean useNext = diff > diffPrev;
    160                 if (Math.abs(velocity) > mFlingThreshold) {
    161                     useNext = velocity > 0;
    162                 }
    163                 return useNext
    164                         ? scrollToPercentage(lastY) : scrollToPercentage(taskY);
    165             }
    166             lastY = taskY;
    167         }
    168         return scrollToPercentage(lastY);
    169     }
    170 
    171     /**
    172      * Convert a scroll value to a percentage
    173      * @param scroll a scroll value
    174      * @return a percentage that represents the scroll from the total height of tasks
    175      */
    176     public float scrollToPercentage(int scroll) {
    177         return (float) scroll / (mTaskRect.height() + mPadding);
    178     }
    179 
    180     /**
    181      * Converts a percentage to the scroll value from the total height of tasks
    182      * @param p a percentage that represents the scroll value
    183      * @return a scroll value in pixels
    184      */
    185     public int percentageToScroll(float p) {
    186         return (int) (p * (mTaskRect.height() + mPadding));
    187     }
    188 
    189     /**
    190      * Get the min scroll progress for low ram layout. This computes the top position of the
    191      * first task and reduce by the end padding to center the first task
    192      * @return position of max scroll
    193      */
    194     public float getMinScrollP() {
    195         return getScrollPForTask(0);
    196     }
    197 
    198     /**
    199      * Get the max scroll progress for low ram layout. This computes the top position of the last
    200      * task and reduce by the end padding to center the last task
    201      * @param taskCount the amount of tasks in the recents stack
    202      * @return position of max scroll
    203      */
    204     public float getMaxScrollP(int taskCount) {
    205         return getScrollPForTask(taskCount - 1);
    206     }
    207 
    208     /**
    209      * Get the initial scroll value whether launched from home or from an app.
    210      * @param taskCount the amount of tasks currently in recents
    211      * @param fromHome if launching recents from home or not
    212      * @return from home it will return max value and from app it will return 2nd last task
    213      */
    214     public float getInitialScrollP(int taskCount, boolean fromHome) {
    215         if (fromHome) {
    216             return getMaxScrollP(taskCount);
    217         }
    218         if (taskCount < 2) {
    219             return 0;
    220         }
    221         return getScrollPForTask(taskCount - 2);
    222     }
    223 
    224     /**
    225      * Get the scroll progress for any task
    226      * @param taskIndex task index to get the scroll progress of
    227      * @return scroll progress of task
    228      */
    229     public float getScrollPForTask(int taskIndex) {
    230         return scrollToPercentage(getTaskTopFromIndex(taskIndex) - mPaddingEndTopBottom);
    231     }
    232 
    233     public Rect getTaskRect() {
    234         return mTaskRect;
    235     }
    236 
    237     public float getMaxOverscroll() {
    238         return MAX_OVERSCROLL;
    239     }
    240 
    241     private int getTaskTopFromIndex(int index) {
    242         return getTotalHeightOfTasks(index) - mTopOffset;
    243     }
    244 
    245     private int getTotalHeightOfTasks(int taskCount) {
    246         return taskCount * mTaskRect.height() + (taskCount + 1) * mPadding;
    247     }
    248 
    249     private void fillStackTransform(TaskViewTransform transformOut, int y, int translationZ,
    250             boolean visible) {
    251         transformOut.scale = 1f;
    252         transformOut.alpha = 1f;
    253         transformOut.translationZ = translationZ;
    254         transformOut.dimAlpha = 0f;
    255         transformOut.viewOutlineAlpha = 1f;
    256         transformOut.rect.set(getTaskRect());
    257         transformOut.rect.offset(mPaddingLeftRight + mSystemInsets.left, y);
    258         Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
    259         transformOut.visible = visible;
    260     }
    261 }
    262