1/*
2 * Copyright (C) 2016 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.grid;
18
19import static com.android.systemui.recents.views.TaskStackLayoutAlgorithm.*;
20
21import android.content.Context;
22import android.content.res.Resources;
23import android.graphics.Point;
24import android.graphics.Rect;
25import android.view.WindowManager;
26
27import com.android.systemui.R;
28import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent;
29import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction;
30import com.android.systemui.recents.misc.Utilities;
31import com.android.systemui.recents.model.Task;
32import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
33import com.android.systemui.recents.views.TaskViewTransform;
34
35import java.util.ArrayList;
36
37public class TaskGridLayoutAlgorithm  {
38
39    private final String TAG = "TaskGridLayoutAlgorithm";
40    public static final int MAX_LAYOUT_TASK_COUNT = 8;
41
42    /** The horizontal padding around the whole recents view. */
43    private int mPaddingLeftRight;
44    /** The vertical padding around the whole recents view. */
45    private int mPaddingTopBottom;
46    /** The padding between task views. */
47    private int mPaddingTaskView;
48
49    private Rect mWindowRect;
50    private Point mScreenSize = new Point();
51
52    private Rect mTaskGridRect;
53
54    /** The height, in pixels, of each task view's title bar. */
55    private int mTitleBarHeight;
56
57    /** The aspect ratio of each task thumbnail, without the title bar. */
58    private float mAppAspectRatio;
59    private Rect mSystemInsets = new Rect();
60
61    /** The thickness of the focused task view frame. */
62    private int mFocusedFrameThickness;
63
64    /**
65     * When the amount of tasks is determined, the size and position of every task view can be
66     * decided. Each instance of TaskGridRectInfo store the task view information for a certain
67     * amount of tasks.
68     */
69    class TaskGridRectInfo {
70        Rect size;
71        int[] xOffsets;
72        int[] yOffsets;
73        int tasksPerLine;
74        int lines;
75
76        TaskGridRectInfo(int taskCount) {
77            size = new Rect();
78            xOffsets = new int[taskCount];
79            yOffsets = new int[taskCount];
80
81            int layoutTaskCount = Math.min(MAX_LAYOUT_TASK_COUNT, taskCount);
82            tasksPerLine = getTasksPerLine(layoutTaskCount);
83            lines = layoutTaskCount < 4 ? 1 : 2;
84
85            // A couple of special cases.
86            boolean landscapeWindow = mWindowRect.width() > mWindowRect.height();
87            boolean landscapeTaskView = mAppAspectRatio > 1;
88            // If we're in portrait but task views are landscape, show more lines of fewer tasks.
89            if (!landscapeWindow && landscapeTaskView) {
90                tasksPerLine = layoutTaskCount < 2 ? 1 : 2;
91                lines = layoutTaskCount < 3 ? 1 : (
92                        layoutTaskCount < 5 ? 2 : (
93                                layoutTaskCount < 7 ? 3 : 4));
94            }
95            // If we're in landscape but task views are portrait, show fewer lines of more tasks.
96            if (landscapeWindow && !landscapeTaskView) {
97                tasksPerLine = layoutTaskCount < 7 ? layoutTaskCount : 6;
98                lines = layoutTaskCount < 7 ? 1 : 2;
99            }
100
101            int taskWidth, taskHeight;
102            int maxTaskWidth = (mWindowRect.width() - 2 * mPaddingLeftRight
103                - (tasksPerLine - 1) * mPaddingTaskView) / tasksPerLine;
104            int maxTaskHeight = (mWindowRect.height() - 2 * mPaddingTopBottom
105                - (lines - 1) * mPaddingTaskView) / lines;
106
107            if (maxTaskHeight >= maxTaskWidth / mAppAspectRatio + mTitleBarHeight) {
108                // Width bound.
109                taskWidth = maxTaskWidth;
110                // Here we should round the height to the nearest integer.
111                taskHeight = (int) (maxTaskWidth / mAppAspectRatio + mTitleBarHeight + 0.5);
112            } else {
113                // Height bound.
114                taskHeight = maxTaskHeight;
115                // Here we should round the width to the nearest integer.
116                taskWidth = (int) ((taskHeight - mTitleBarHeight) * mAppAspectRatio + 0.5);
117            }
118            size.set(0, 0, taskWidth, taskHeight);
119
120            int emptySpaceX = mWindowRect.width() - 2 * mPaddingLeftRight
121                - (tasksPerLine * taskWidth) - (tasksPerLine - 1) * mPaddingTaskView;
122            int emptySpaceY = mWindowRect.height() - 2 * mPaddingTopBottom
123                - (lines * taskHeight) - (lines - 1) * mPaddingTaskView;
124            for (int taskIndex = 0; taskIndex < taskCount; taskIndex++) {
125                // We also need to invert the index in order to display the most recent tasks first.
126                int taskLayoutIndex = taskCount - taskIndex - 1;
127
128                int xIndex = taskLayoutIndex % tasksPerLine;
129                int yIndex = taskLayoutIndex / tasksPerLine;
130                xOffsets[taskIndex] = mWindowRect.left +
131                    emptySpaceX / 2 + mPaddingLeftRight + (taskWidth + mPaddingTaskView) * xIndex;
132                yOffsets[taskIndex] = mWindowRect.top +
133                    emptySpaceY / 2 + mPaddingTopBottom + (taskHeight + mPaddingTaskView) * yIndex;
134            }
135        }
136
137        private int getTasksPerLine(int taskCount) {
138            switch(taskCount) {
139                case 0:
140                    return 0;
141                case 1:
142                    return 1;
143                case 2:
144                case 4:
145                    return 2;
146                case 3:
147                case 5:
148                case 6:
149                    return 3;
150                case 7:
151                case 8:
152                    return 4;
153                default:
154                    throw new IllegalArgumentException("Unsupported task count " + taskCount);
155            }
156        }
157    }
158
159    /**
160     * We can find task view sizes and positions from mTaskGridRectInfoList[k - 1] when there
161     * are k tasks.
162     */
163    private TaskGridRectInfo[] mTaskGridRectInfoList;
164
165    public TaskGridLayoutAlgorithm(Context context) {
166        reloadOnConfigurationChange(context);
167    }
168
169    public void reloadOnConfigurationChange(Context context) {
170        Resources res = context.getResources();
171        mPaddingTaskView = res.getDimensionPixelSize(R.dimen.recents_grid_padding_task_view);
172        mFocusedFrameThickness = res.getDimensionPixelSize(
173            R.dimen.recents_grid_task_view_focused_frame_thickness);
174
175        mTaskGridRect = new Rect();
176        mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_grid_task_view_header_height);
177
178        WindowManager windowManager = (WindowManager) context
179                .getSystemService(Context.WINDOW_SERVICE);
180        windowManager.getDefaultDisplay().getRealSize(mScreenSize);
181
182        updateAppAspectRatio();
183    }
184
185    /**
186     * Returns the proper task view transform of a certain task view, according to its index and the
187     * amount of task views.
188     * @param taskIndex     The index of the task view whose transform we want. It's never greater
189     *                      than {@link MAX_LAYOUT_TASK_COUNT}.
190     * @param taskCount     The current amount of task views.
191     * @param transformOut  The result transform that this method returns.
192     * @param stackLayout   The base stack layout algorithm.
193     * @return  The expected transform of the (taskIndex)th task view.
194     */
195    public TaskViewTransform getTransform(int taskIndex, int taskCount,
196        TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout) {
197        if (taskCount == 0) {
198            transformOut.reset();
199            return transformOut;
200        }
201
202        TaskGridRectInfo gridInfo = mTaskGridRectInfoList[taskCount - 1];
203        mTaskGridRect.set(gridInfo.size);
204
205        int x = gridInfo.xOffsets[taskIndex];
206        int y = gridInfo.yOffsets[taskIndex];
207        float z = stackLayout.mMaxTranslationZ;
208
209        // We always set the dim alpha to 0, since we don't want grid task views to dim.
210        float dimAlpha = 0f;
211        // We always set the alpha of the view outline to 1, to make sure the shadow is visible.
212        float viewOutlineAlpha = 1f;
213
214        // We also need to invert the index in order to display the most recent tasks first.
215        int taskLayoutIndex = taskCount - taskIndex - 1;
216        boolean isTaskViewVisible = taskLayoutIndex < MAX_LAYOUT_TASK_COUNT;
217
218        // Fill out the transform
219        transformOut.scale = 1f;
220        transformOut.alpha = isTaskViewVisible ? 1f : 0f;
221        transformOut.translationZ = z;
222        transformOut.dimAlpha = dimAlpha;
223        transformOut.viewOutlineAlpha = viewOutlineAlpha;
224        transformOut.rect.set(mTaskGridRect);
225        transformOut.rect.offset(x, y);
226        Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
227        // We only show the 8 most recent tasks.
228        transformOut.visible = isTaskViewVisible;
229        return transformOut;
230    }
231
232    /**
233     * Return the proper task index to focus for arrow key navigation.
234     * @param taskCount             The amount of tasks.
235     * @param currentFocusedIndex   The index of the currently focused task.
236     * @param direction             The direction we're navigating.
237     * @return  The index of the task that should get the focus.
238     */
239    public int navigateFocus(int taskCount, int currentFocusedIndex, Direction direction) {
240        if (taskCount < 1 || taskCount > MAX_LAYOUT_TASK_COUNT) {
241            return -1;
242        }
243        if (currentFocusedIndex == -1) {
244            return 0;
245        }
246        int newIndex = currentFocusedIndex;
247        final TaskGridRectInfo gridInfo = mTaskGridRectInfoList[taskCount - 1];
248        final int currentLine = (taskCount - 1 - currentFocusedIndex) / gridInfo.tasksPerLine;
249        switch (direction) {
250            case UP:
251                newIndex += gridInfo.tasksPerLine;
252                newIndex = newIndex >= taskCount ? currentFocusedIndex : newIndex;
253                break;
254            case DOWN:
255                newIndex -= gridInfo.tasksPerLine;
256                newIndex = newIndex < 0 ? currentFocusedIndex : newIndex;
257                break;
258            case LEFT:
259                newIndex++;
260                final int leftMostIndex = (taskCount - 1) - currentLine * gridInfo.tasksPerLine;
261                newIndex = newIndex > leftMostIndex ? currentFocusedIndex : newIndex;
262                break;
263            case RIGHT:
264                newIndex--;
265                int rightMostIndex =
266                    (taskCount - 1) - (currentLine + 1) * gridInfo.tasksPerLine + 1;
267                rightMostIndex = rightMostIndex < 0 ? 0 : rightMostIndex;
268                newIndex = newIndex < rightMostIndex ? currentFocusedIndex : newIndex;
269                break;
270        }
271        return newIndex;
272    }
273
274    public void initialize(Rect windowRect) {
275        mWindowRect = windowRect;
276        // Define paddings in terms of percentage of the total area.
277        mPaddingLeftRight = (int) (0.025f * Math.min(mWindowRect.width(), mWindowRect.height()));
278        mPaddingTopBottom = (int) (0.1 * mWindowRect.height());
279
280        // Pre-calculate the positions and offsets of task views so that we can reuse them directly
281        // in the future.
282        mTaskGridRectInfoList = new TaskGridRectInfo[MAX_LAYOUT_TASK_COUNT];
283        for (int i = 0; i < MAX_LAYOUT_TASK_COUNT; i++) {
284            mTaskGridRectInfoList[i] = new TaskGridRectInfo(i + 1);
285        }
286    }
287
288    public void setSystemInsets(Rect systemInsets) {
289        mSystemInsets = systemInsets;
290        updateAppAspectRatio();
291    }
292
293    private void updateAppAspectRatio() {
294        int usableWidth = mScreenSize.x - mSystemInsets.left - mSystemInsets.right;
295        int usableHeight = mScreenSize.y - mSystemInsets.top - mSystemInsets.bottom;
296        mAppAspectRatio = (float) usableWidth / (float) usableHeight;
297    }
298
299    public Rect getStackActionButtonRect() {
300        Rect buttonRect = new Rect(mWindowRect);
301        buttonRect.right -= mPaddingLeftRight;
302        buttonRect.left += mPaddingLeftRight;
303        buttonRect.bottom = buttonRect.top + mPaddingTopBottom;
304        return buttonRect;
305    }
306
307    public void updateTaskGridRect(int taskCount) {
308        if (taskCount > 0) {
309            TaskGridRectInfo gridInfo = mTaskGridRectInfoList[taskCount - 1];
310            mTaskGridRect.set(gridInfo.size);
311        }
312    }
313
314    public Rect getTaskGridRect() {
315        return mTaskGridRect;
316    }
317
318    public int getFocusFrameThickness() {
319        return mFocusedFrameThickness;
320    }
321
322    public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
323        int visibleCount = Math.min(TaskGridLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT, tasks.size());
324        return new VisibilityReport(visibleCount, visibleCount);
325    }
326}
327