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
17package com.android.systemui.recents.views.lowram;
18
19import android.content.Context;
20import android.graphics.Rect;
21import android.view.ViewConfiguration;
22
23import com.android.systemui.R;
24import com.android.systemui.recents.Recents;
25import com.android.systemui.recents.RecentsActivityLaunchState;
26import com.android.systemui.recents.misc.Utilities;
27import com.android.systemui.recents.model.Task;
28import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
29import com.android.systemui.recents.views.TaskViewTransform;
30
31import java.util.ArrayList;
32
33import static com.android.systemui.recents.views.TaskStackLayoutAlgorithm.VisibilityReport;
34
35public 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