| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.systemui.recents.views.grid; |
| |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.graphics.Point; |
| import android.graphics.Rect; |
| import android.view.WindowManager; |
| |
| import com.android.systemui.R; |
| import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent; |
| import com.android.systemui.recents.events.ui.focus.NavigateTaskViewEvent.Direction; |
| import com.android.systemui.recents.misc.Utilities; |
| import com.android.systemui.recents.views.TaskStackLayoutAlgorithm; |
| import com.android.systemui.recents.views.TaskViewTransform; |
| |
| public class TaskGridLayoutAlgorithm { |
| |
| private final String TAG = "TaskGridLayoutAlgorithm"; |
| public static final int MAX_LAYOUT_TASK_COUNT = 8; |
| |
| /** The horizontal padding around the whole recents view. */ |
| private int mPaddingLeftRight; |
| /** The vertical padding around the whole recents view. */ |
| private int mPaddingTopBottom; |
| /** The padding between task views. */ |
| private int mPaddingTaskView; |
| |
| private Rect mWindowRect; |
| private Point mScreenSize = new Point(); |
| |
| private Rect mTaskGridRect; |
| |
| /** The height, in pixels, of each task view's title bar. */ |
| private int mTitleBarHeight; |
| |
| /** The aspect ratio of each task thumbnail, without the title bar. */ |
| private float mAppAspectRatio; |
| private Rect mSystemInsets = new Rect(); |
| |
| /** The thickness of the focused task view frame. */ |
| private int mFocusedFrameThickness; |
| |
| /** |
| * When the amount of tasks is determined, the size and position of every task view can be |
| * decided. Each instance of TaskGridRectInfo store the task view information for a certain |
| * amount of tasks. |
| */ |
| class TaskGridRectInfo { |
| Rect size; |
| int[] xOffsets; |
| int[] yOffsets; |
| int tasksPerLine; |
| int lines; |
| |
| TaskGridRectInfo(int taskCount) { |
| size = new Rect(); |
| xOffsets = new int[taskCount]; |
| yOffsets = new int[taskCount]; |
| |
| int layoutTaskCount = Math.min(MAX_LAYOUT_TASK_COUNT, taskCount); |
| tasksPerLine = getTasksPerLine(layoutTaskCount); |
| lines = layoutTaskCount < 4 ? 1 : 2; |
| |
| // A couple of special cases. |
| boolean landscapeWindow = mWindowRect.width() > mWindowRect.height(); |
| boolean landscapeTaskView = mAppAspectRatio > 1; |
| // If we're in portrait but task views are landscape, show more lines of fewer tasks. |
| if (!landscapeWindow && landscapeTaskView) { |
| tasksPerLine = layoutTaskCount < 2 ? 1 : 2; |
| lines = layoutTaskCount < 3 ? 1 : ( |
| layoutTaskCount < 5 ? 2 : ( |
| layoutTaskCount < 7 ? 3 : 4)); |
| } |
| // If we're in landscape but task views are portrait, show fewer lines of more tasks. |
| if (landscapeWindow && !landscapeTaskView) { |
| tasksPerLine = layoutTaskCount < 7 ? layoutTaskCount : 6; |
| lines = layoutTaskCount < 7 ? 1 : 2; |
| } |
| |
| int taskWidth, taskHeight; |
| int maxTaskWidth = (mWindowRect.width() - 2 * mPaddingLeftRight |
| - (tasksPerLine - 1) * mPaddingTaskView) / tasksPerLine; |
| int maxTaskHeight = (mWindowRect.height() - 2 * mPaddingTopBottom |
| - (lines - 1) * mPaddingTaskView) / lines; |
| |
| if (maxTaskHeight >= maxTaskWidth / mAppAspectRatio + mTitleBarHeight) { |
| // Width bound. |
| taskWidth = maxTaskWidth; |
| // Here we should round the height to the nearest integer. |
| taskHeight = (int) (maxTaskWidth / mAppAspectRatio + mTitleBarHeight + 0.5); |
| } else { |
| // Height bound. |
| taskHeight = maxTaskHeight; |
| // Here we should round the width to the nearest integer. |
| taskWidth = (int) ((taskHeight - mTitleBarHeight) * mAppAspectRatio + 0.5); |
| } |
| size.set(0, 0, taskWidth, taskHeight); |
| |
| int emptySpaceX = mWindowRect.width() - 2 * mPaddingLeftRight |
| - (tasksPerLine * taskWidth) - (tasksPerLine - 1) * mPaddingTaskView; |
| int emptySpaceY = mWindowRect.height() - 2 * mPaddingTopBottom |
| - (lines * taskHeight) - (lines - 1) * mPaddingTaskView; |
| for (int taskIndex = 0; taskIndex < taskCount; taskIndex++) { |
| // We also need to invert the index in order to display the most recent tasks first. |
| int taskLayoutIndex = taskCount - taskIndex - 1; |
| |
| int xIndex = taskLayoutIndex % tasksPerLine; |
| int yIndex = taskLayoutIndex / tasksPerLine; |
| xOffsets[taskIndex] = mWindowRect.left + |
| emptySpaceX / 2 + mPaddingLeftRight + (taskWidth + mPaddingTaskView) * xIndex; |
| yOffsets[taskIndex] = mWindowRect.top + |
| emptySpaceY / 2 + mPaddingTopBottom + (taskHeight + mPaddingTaskView) * yIndex; |
| } |
| } |
| |
| private int getTasksPerLine(int taskCount) { |
| switch(taskCount) { |
| case 0: |
| return 0; |
| case 1: |
| return 1; |
| case 2: |
| case 4: |
| return 2; |
| case 3: |
| case 5: |
| case 6: |
| return 3; |
| case 7: |
| case 8: |
| return 4; |
| default: |
| throw new IllegalArgumentException("Unsupported task count " + taskCount); |
| } |
| } |
| } |
| |
| /** |
| * We can find task view sizes and positions from mTaskGridRectInfoList[k - 1] when there |
| * are k tasks. |
| */ |
| private TaskGridRectInfo[] mTaskGridRectInfoList; |
| |
| public TaskGridLayoutAlgorithm(Context context) { |
| reloadOnConfigurationChange(context); |
| } |
| |
| public void reloadOnConfigurationChange(Context context) { |
| Resources res = context.getResources(); |
| mPaddingTaskView = res.getDimensionPixelSize(R.dimen.recents_grid_padding_task_view); |
| mFocusedFrameThickness = res.getDimensionPixelSize( |
| R.dimen.recents_grid_task_view_focused_frame_thickness); |
| |
| mTaskGridRect = new Rect(); |
| mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_grid_task_view_header_height); |
| |
| WindowManager windowManager = (WindowManager) context |
| .getSystemService(Context.WINDOW_SERVICE); |
| windowManager.getDefaultDisplay().getRealSize(mScreenSize); |
| |
| updateAppAspectRatio(); |
| } |
| |
| /** |
| * Returns the proper task view transform of a certain task view, according to its index and the |
| * amount of task views. |
| * @param taskIndex The index of the task view whose transform we want. It's never greater |
| * than {@link MAX_LAYOUT_TASK_COUNT}. |
| * @param taskCount The current amount of task views. |
| * @param transformOut The result transform that this method returns. |
| * @param stackLayout The base stack layout algorithm. |
| * @return The expected transform of the (taskIndex)th task view. |
| */ |
| public TaskViewTransform getTransform(int taskIndex, int taskCount, |
| TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout) { |
| if (taskCount == 0) { |
| transformOut.reset(); |
| return transformOut; |
| } |
| |
| TaskGridRectInfo gridInfo = mTaskGridRectInfoList[taskCount - 1]; |
| mTaskGridRect.set(gridInfo.size); |
| |
| int x = gridInfo.xOffsets[taskIndex]; |
| int y = gridInfo.yOffsets[taskIndex]; |
| float z = stackLayout.mMaxTranslationZ; |
| |
| // We always set the dim alpha to 0, since we don't want grid task views to dim. |
| float dimAlpha = 0f; |
| // We always set the alpha of the view outline to 1, to make sure the shadow is visible. |
| float viewOutlineAlpha = 1f; |
| |
| // We also need to invert the index in order to display the most recent tasks first. |
| int taskLayoutIndex = taskCount - taskIndex - 1; |
| boolean isTaskViewVisible = taskLayoutIndex < MAX_LAYOUT_TASK_COUNT; |
| |
| // Fill out the transform |
| transformOut.scale = 1f; |
| transformOut.alpha = isTaskViewVisible ? 1f : 0f; |
| transformOut.translationZ = z; |
| transformOut.dimAlpha = dimAlpha; |
| transformOut.viewOutlineAlpha = viewOutlineAlpha; |
| transformOut.rect.set(mTaskGridRect); |
| transformOut.rect.offset(x, y); |
| Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale); |
| // We only show the 8 most recent tasks. |
| transformOut.visible = isTaskViewVisible; |
| return transformOut; |
| } |
| |
| /** |
| * Return the proper task index to focus for arrow key navigation. |
| * @param taskCount The amount of tasks. |
| * @param currentFocusedIndex The index of the currently focused task. |
| * @param direction The direction we're navigating. |
| * @return The index of the task that should get the focus. |
| */ |
| public int navigateFocus(int taskCount, int currentFocusedIndex, Direction direction) { |
| if (taskCount < 1 || taskCount > MAX_LAYOUT_TASK_COUNT) { |
| return -1; |
| } |
| if (currentFocusedIndex == -1) { |
| return 0; |
| } |
| int newIndex = currentFocusedIndex; |
| final TaskGridRectInfo gridInfo = mTaskGridRectInfoList[taskCount - 1]; |
| final int currentLine = (taskCount - 1 - currentFocusedIndex) / gridInfo.tasksPerLine; |
| switch (direction) { |
| case UP: |
| newIndex += gridInfo.tasksPerLine; |
| newIndex = newIndex >= taskCount ? currentFocusedIndex : newIndex; |
| break; |
| case DOWN: |
| newIndex -= gridInfo.tasksPerLine; |
| newIndex = newIndex < 0 ? currentFocusedIndex : newIndex; |
| break; |
| case LEFT: |
| newIndex++; |
| final int leftMostIndex = (taskCount - 1) - currentLine * gridInfo.tasksPerLine; |
| newIndex = newIndex > leftMostIndex ? currentFocusedIndex : newIndex; |
| break; |
| case RIGHT: |
| newIndex--; |
| int rightMostIndex = |
| (taskCount - 1) - (currentLine + 1) * gridInfo.tasksPerLine + 1; |
| rightMostIndex = rightMostIndex < 0 ? 0 : rightMostIndex; |
| newIndex = newIndex < rightMostIndex ? currentFocusedIndex : newIndex; |
| break; |
| } |
| return newIndex; |
| } |
| |
| public void initialize(Rect windowRect) { |
| mWindowRect = windowRect; |
| // Define paddings in terms of percentage of the total area. |
| mPaddingLeftRight = (int) (0.025f * Math.min(mWindowRect.width(), mWindowRect.height())); |
| mPaddingTopBottom = (int) (0.1 * mWindowRect.height()); |
| |
| // Pre-calculate the positions and offsets of task views so that we can reuse them directly |
| // in the future. |
| mTaskGridRectInfoList = new TaskGridRectInfo[MAX_LAYOUT_TASK_COUNT]; |
| for (int i = 0; i < MAX_LAYOUT_TASK_COUNT; i++) { |
| mTaskGridRectInfoList[i] = new TaskGridRectInfo(i + 1); |
| } |
| } |
| |
| public void setSystemInsets(Rect systemInsets) { |
| mSystemInsets = systemInsets; |
| updateAppAspectRatio(); |
| } |
| |
| private void updateAppAspectRatio() { |
| int usableWidth = mScreenSize.x - mSystemInsets.left - mSystemInsets.right; |
| int usableHeight = mScreenSize.y - mSystemInsets.top - mSystemInsets.bottom; |
| mAppAspectRatio = (float) usableWidth / (float) usableHeight; |
| } |
| |
| public Rect getStackActionButtonRect() { |
| Rect buttonRect = new Rect(mWindowRect); |
| buttonRect.right -= mPaddingLeftRight; |
| buttonRect.left += mPaddingLeftRight; |
| buttonRect.bottom = buttonRect.top + mPaddingTopBottom; |
| return buttonRect; |
| } |
| |
| public void updateTaskGridRect(int taskCount) { |
| if (taskCount > 0) { |
| TaskGridRectInfo gridInfo = mTaskGridRectInfoList[taskCount - 1]; |
| mTaskGridRect.set(gridInfo.size); |
| } |
| } |
| |
| public Rect getTaskGridRect() { |
| return mTaskGridRect; |
| } |
| |
| public int getFocusFrameThickness() { |
| return mFocusedFrameThickness; |
| } |
| } |