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