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