Home | History | Annotate | Download | only in grid
      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.shared.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 
     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