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