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        reloadOnConfigurationChange(context);
41    }
42
43    /**
44     * Reloads the layout for the current configuration.
45     */
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     */
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     */
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     */
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