1 /*
2  * Copyright (C) 2014 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;
18 
19 import android.content.Context;
20 import android.graphics.RectF;
21 import android.util.ArrayMap;
22 
23 import com.android.systemui.R;
24 import com.android.systemui.recents.model.Task;
25 
26 import java.util.Collections;
27 import java.util.List;
28 
29 /**
30  * The layout logic for the contents of the freeform workspace.
31  */
32 public class FreeformWorkspaceLayoutAlgorithm {
33 
34     // Optimization, allows for quick lookup of task -> rect
35     private ArrayMap<Task.TaskKey, RectF> mTaskRectMap = new ArrayMap<>();
36 
37     private int mTaskPadding;
38 
FreeformWorkspaceLayoutAlgorithm(Context context)39     public FreeformWorkspaceLayoutAlgorithm(Context context) {
40         reloadOnConfigurationChange(context);
41     }
42 
43     /**
44      * Reloads the layout for the current configuration.
45      */
reloadOnConfigurationChange(Context context)46     public void reloadOnConfigurationChange(Context context) {
47         // This is applied to the edges of each task
48         mTaskPadding = context.getResources().getDimensionPixelSize(
49                 R.dimen.recents_freeform_layout_task_padding) / 2;
50     }
51 
52     /**
53      * Updates the layout for each of the freeform workspace tasks.  This is called after the stack
54      * layout is updated.
55      */
update(List<Task> freeformTasks, TaskStackLayoutAlgorithm stackLayout)56     public void update(List<Task> freeformTasks, TaskStackLayoutAlgorithm stackLayout) {
57         Collections.reverse(freeformTasks);
58         mTaskRectMap.clear();
59 
60         int numFreeformTasks = stackLayout.mNumFreeformTasks;
61         if (!freeformTasks.isEmpty()) {
62 
63             // Normalize the widths so that we can calculate the best layout below
64             int workspaceWidth = stackLayout.mFreeformRect.width();
65             int workspaceHeight = stackLayout.mFreeformRect.height();
66             float normalizedWorkspaceWidth = (float) workspaceWidth / workspaceHeight;
67             float normalizedWorkspaceHeight = 1f;
68             float[] normalizedTaskWidths = new float[numFreeformTasks];
69             for (int i = 0; i < numFreeformTasks; i++) {
70                 Task task = freeformTasks.get(i);
71                 float rowTaskWidth;
72                 if (task.bounds != null) {
73                     rowTaskWidth = (float) task.bounds.width() / task.bounds.height();
74                 } else {
75                     // If this is a stack task that was dragged into the freeform workspace, then
76                     // the task will not yet have an associated bounds, so assume the full workspace
77                     // width for the time being
78                     rowTaskWidth = normalizedWorkspaceWidth;
79                 }
80                 // Bound the task width to the workspace width so that at the worst case, it will
81                 // fit its own row
82                 normalizedTaskWidths[i] = Math.min(rowTaskWidth, normalizedWorkspaceWidth);
83             }
84 
85             // Determine the scale to best fit each of the tasks in the workspace
86             float rowScale = 0.85f;
87             float rowWidth = 0f;
88             float maxRowWidth = 0f;
89             int rowCount = 1;
90             for (int i = 0; i < numFreeformTasks;) {
91                 float width = normalizedTaskWidths[i] * rowScale;
92                 if (rowWidth + width > normalizedWorkspaceWidth) {
93                     // That is too long for this row, create new row
94                     if ((rowCount + 1) * rowScale > normalizedWorkspaceHeight) {
95                         // The new row is too high, so we need to try fitting again.  Update the
96                         // scale to be the smaller of the scale needed to fit the task in the
97                         // previous row, or the scale needed to fit the new row
98                         rowScale = Math.min(normalizedWorkspaceWidth / (rowWidth + width),
99                                 normalizedWorkspaceHeight / (rowCount + 1));
100                         rowCount = 1;
101                         rowWidth = 0;
102                         i = 0;
103                     } else {
104                         // The new row fits, so continue
105                         rowWidth = width;
106                         rowCount++;
107                         i++;
108                     }
109                 } else {
110                     // Task is OK in this row
111                     rowWidth += width;
112                     i++;
113                 }
114                 maxRowWidth = Math.max(rowWidth, maxRowWidth);
115             }
116 
117             // Normalize each of the actual rects to that scale
118             float defaultRowLeft = ((1f - (maxRowWidth / normalizedWorkspaceWidth)) *
119                     workspaceWidth) / 2f;
120             float rowLeft = defaultRowLeft;
121             float rowTop = ((1f - (rowScale * rowCount)) * workspaceHeight) / 2f;
122             float rowHeight = rowScale * workspaceHeight;
123             for (int i = 0; i < numFreeformTasks; i++) {
124                 Task task = freeformTasks.get(i);
125                 float width = rowHeight * normalizedTaskWidths[i];
126                 if (rowLeft + width > workspaceWidth) {
127                     // This goes on the next line
128                     rowTop += rowHeight;
129                     rowLeft = defaultRowLeft;
130                 }
131                 RectF rect = new RectF(rowLeft, rowTop, rowLeft + width, rowTop + rowHeight);
132                 rect.inset(mTaskPadding, mTaskPadding);
133                 rowLeft += width;
134                 mTaskRectMap.put(task.key, rect);
135             }
136         }
137     }
138 
139     /**
140      * Returns whether the transform is available for the given task.
141      */
isTransformAvailable(Task task, TaskStackLayoutAlgorithm stackLayout)142     public boolean isTransformAvailable(Task task, TaskStackLayoutAlgorithm stackLayout) {
143         if (stackLayout.mNumFreeformTasks == 0 || task == null) {
144             return false;
145         }
146         return mTaskRectMap.containsKey(task.key);
147     }
148 
149     /**
150      * Returns the transform for the given task.  Any rect returned will be offset by the actual
151      * transform for the freeform workspace.
152      */
getTransform(Task task, TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout)153     public TaskViewTransform getTransform(Task task, TaskViewTransform transformOut,
154             TaskStackLayoutAlgorithm stackLayout) {
155         if (mTaskRectMap.containsKey(task.key)) {
156             final RectF ffRect = mTaskRectMap.get(task.key);
157 
158             transformOut.scale = 1f;
159             transformOut.alpha = 1f;
160             transformOut.translationZ = stackLayout.mMaxTranslationZ;
161             transformOut.dimAlpha = 0f;
162             transformOut.viewOutlineAlpha = TaskStackLayoutAlgorithm.OUTLINE_ALPHA_MAX_VALUE;
163             transformOut.rect.set(ffRect);
164             transformOut.rect.offset(stackLayout.mFreeformRect.left, stackLayout.mFreeformRect.top);
165             transformOut.visible = true;
166             return transformOut;
167         }
168         return null;
169     }
170 }
171