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