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.LegacyRecentsImpl;
25 import com.android.systemui.recents.RecentsActivityLaunchState;
26 import com.android.systemui.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 
TaskStackLowRamLayoutAlgorithm(Context context)54     public TaskStackLowRamLayoutAlgorithm(Context context) {
55         reloadOnConfigurationChange(context);
56     }
57 
reloadOnConfigurationChange(Context context)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 
initialize(Rect windowRect)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 
setSystemInsets(Rect systemInsets)80     public void setSystemInsets(Rect systemInsets) {
81         mSystemInsets = systemInsets;
82     }
83 
computeStackVisibilityReport(ArrayList<Task> tasks)84     public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
85         RecentsActivityLaunchState launchState = LegacyRecentsImpl.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 
getFrontOfStackTransform(TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout)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 
getBackOfStackTransform(TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout)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 
getTransform(int taskIndex, float stackScroll, TaskViewTransform transformOut, int taskCount, TaskStackLayoutAlgorithm stackLayout)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      */
getClosestTaskP(float scrollP, int numTasks, int velocity)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      */
scrollToPercentage(int scroll)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      */
percentageToScroll(float p)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      */
getMinScrollP()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      */
getMaxScrollP(int taskCount)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      */
getInitialScrollP(int taskCount, boolean fromHome)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      */
getScrollPForTask(int taskIndex)229     public float getScrollPForTask(int taskIndex) {
230         return scrollToPercentage(getTaskTopFromIndex(taskIndex) - mPaddingEndTopBottom);
231     }
232 
getTaskRect()233     public Rect getTaskRect() {
234         return mTaskRect;
235     }
236 
getMaxOverscroll()237     public float getMaxOverscroll() {
238         return MAX_OVERSCROLL;
239     }
240 
getTaskTopFromIndex(int index)241     private int getTaskTopFromIndex(int index) {
242         return getTotalHeightOfTasks(index) - mTopOffset;
243     }
244 
getTotalHeightOfTasks(int taskCount)245     private int getTotalHeightOfTasks(int taskCount) {
246         return taskCount * mTaskRect.height() + (taskCount + 1) * mPadding;
247     }
248 
fillStackTransform(TaskViewTransform transformOut, int y, int translationZ, boolean visible)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