• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
29 import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction;
30 import com.android.systemui.recents.misc.Utilities;
31 import com.android.systemui.recents.model.Task;
32 import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
33 import com.android.systemui.recents.views.TaskViewTransform;
34 
35 import java.util.ArrayList;
36 
37 public 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 
TaskGridRectInfo(int taskCount)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