136a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson/*
236a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson * Copyright (C) 2014 The Android Open Source Project
336a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson *
436a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson * Licensed under the Apache License, Version 2.0 (the "License");
536a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson * you may not use this file except in compliance with the License.
636a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson * You may obtain a copy of the License at
736a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson *
836a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson *      http://www.apache.org/licenses/LICENSE-2.0
936a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson *
1036a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson * Unless required by applicable law or agreed to in writing, software
1136a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson * distributed under the License is distributed on an "AS IS" BASIS,
1236a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1336a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson * See the License for the specific language governing permissions and
1436a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson * limitations under the License.
1536a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson */
1636a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson
1736a5a2c7003ef8157f276b411c3fda47ad2f75e3Winsonpackage com.android.systemui.recents.views;
1836a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson
19509d0d0c9e2ee165d04e898fea59f8941ac7138dWinson Chungimport android.content.Context;
205b7dd536aa6cb8ce323b47cee109f879feb0d33aWinsonimport android.graphics.RectF;
215500390a006f2bbea565068234774a36cea076c0Winsonimport android.util.ArrayMap;
22c0d7058b14c24cd07912f5629c26b39b7b4673d5Winson
23509d0d0c9e2ee165d04e898fea59f8941ac7138dWinson Chungimport com.android.systemui.R;
2436a5a2c7003ef8157f276b411c3fda47ad2f75e3Winsonimport com.android.systemui.recents.model.Task;
2536a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson
265b7dd536aa6cb8ce323b47cee109f879feb0d33aWinsonimport java.util.Collections;
27a5e6b36034fa66549dd71601a86397381c6bf02bWinsonimport java.util.List;
2836a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson
2936a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson/**
3036a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson * The layout logic for the contents of the freeform workspace.
3136a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson */
3236a5a2c7003ef8157f276b411c3fda47ad2f75e3Winsonpublic class FreeformWorkspaceLayoutAlgorithm {
3336a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson
345b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson    // Optimization, allows for quick lookup of task -> rect
355500390a006f2bbea565068234774a36cea076c0Winson    private ArrayMap<Task.TaskKey, RectF> mTaskRectMap = new ArrayMap<>();
3636a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson
37509d0d0c9e2ee165d04e898fea59f8941ac7138dWinson Chung    private int mTaskPadding;
38509d0d0c9e2ee165d04e898fea59f8941ac7138dWinson Chung
39509d0d0c9e2ee165d04e898fea59f8941ac7138dWinson Chung    public FreeformWorkspaceLayoutAlgorithm(Context context) {
40e693aafe0511c2a7ffc571b22abeefba44046225Winson        reloadOnConfigurationChange(context);
41e693aafe0511c2a7ffc571b22abeefba44046225Winson    }
42e693aafe0511c2a7ffc571b22abeefba44046225Winson
43e693aafe0511c2a7ffc571b22abeefba44046225Winson    /**
44e693aafe0511c2a7ffc571b22abeefba44046225Winson     * Reloads the layout for the current configuration.
45e693aafe0511c2a7ffc571b22abeefba44046225Winson     */
46e693aafe0511c2a7ffc571b22abeefba44046225Winson    public void reloadOnConfigurationChange(Context context) {
47509d0d0c9e2ee165d04e898fea59f8941ac7138dWinson Chung        // This is applied to the edges of each task
48509d0d0c9e2ee165d04e898fea59f8941ac7138dWinson Chung        mTaskPadding = context.getResources().getDimensionPixelSize(
4959924fe0d9136cf349759bea1e06b661603f95feWinson                R.dimen.recents_freeform_layout_task_padding) / 2;
50509d0d0c9e2ee165d04e898fea59f8941ac7138dWinson Chung    }
51509d0d0c9e2ee165d04e898fea59f8941ac7138dWinson Chung
5236a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson    /**
5336a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson     * Updates the layout for each of the freeform workspace tasks.  This is called after the stack
5436a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson     * layout is updated.
5536a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson     */
56a5e6b36034fa66549dd71601a86397381c6bf02bWinson    public void update(List<Task> freeformTasks, TaskStackLayoutAlgorithm stackLayout) {
575b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson        Collections.reverse(freeformTasks);
585b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson        mTaskRectMap.clear();
59a5e6b36034fa66549dd71601a86397381c6bf02bWinson
6036a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson        int numFreeformTasks = stackLayout.mNumFreeformTasks;
6136a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson        if (!freeformTasks.isEmpty()) {
6236a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson
635b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson            // Normalize the widths so that we can calculate the best layout below
645b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson            int workspaceWidth = stackLayout.mFreeformRect.width();
655b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson            int workspaceHeight = stackLayout.mFreeformRect.height();
665b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson            float normalizedWorkspaceWidth = (float) workspaceWidth / workspaceHeight;
675b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson            float normalizedWorkspaceHeight = 1f;
685b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson            float[] normalizedTaskWidths = new float[numFreeformTasks];
695b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson            for (int i = 0; i < numFreeformTasks; i++) {
7036a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson                Task task = freeformTasks.get(i);
715b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                float rowTaskWidth;
725b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                if (task.bounds != null) {
735b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                    rowTaskWidth = (float) task.bounds.width() / task.bounds.height();
745b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                } else {
755b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                    // If this is a stack task that was dragged into the freeform workspace, then
765b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                    // the task will not yet have an associated bounds, so assume the full workspace
775b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                    // width for the time being
785b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                    rowTaskWidth = normalizedWorkspaceWidth;
795b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                }
805b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                // Bound the task width to the workspace width so that at the worst case, it will
815b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                // fit its own row
82e693aafe0511c2a7ffc571b22abeefba44046225Winson                normalizedTaskWidths[i] = Math.min(rowTaskWidth, normalizedWorkspaceWidth);
8336a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson            }
8436a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson
855b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson            // Determine the scale to best fit each of the tasks in the workspace
865b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson            float rowScale = 0.85f;
875b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson            float rowWidth = 0f;
885b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson            float maxRowWidth = 0f;
895b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson            int rowCount = 1;
905b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson            for (int i = 0; i < numFreeformTasks;) {
915b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                float width = normalizedTaskWidths[i] * rowScale;
925b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                if (rowWidth + width > normalizedWorkspaceWidth) {
935b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                    // That is too long for this row, create new row
945b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                    if ((rowCount + 1) * rowScale > normalizedWorkspaceHeight) {
955b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                        // The new row is too high, so we need to try fitting again.  Update the
965b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                        // scale to be the smaller of the scale needed to fit the task in the
975b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                        // previous row, or the scale needed to fit the new row
985b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                        rowScale = Math.min(normalizedWorkspaceWidth / (rowWidth + width),
995b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                                normalizedWorkspaceHeight / (rowCount + 1));
1005b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                        rowCount = 1;
101bb410951b9ae373999c058fa141b3522ba169960Winson                        rowWidth = 0;
1025b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                        i = 0;
1035b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                    } else {
1045b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                        // The new row fits, so continue
105bb410951b9ae373999c058fa141b3522ba169960Winson                        rowWidth = width;
1065b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                        rowCount++;
1075b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                        i++;
1085b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                    }
1095b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                } else {
1105b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                    // Task is OK in this row
1115b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                    rowWidth += width;
1125b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                    i++;
1135b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                }
1145b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                maxRowWidth = Math.max(rowWidth, maxRowWidth);
1155b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson            }
1165b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson
1175b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson            // Normalize each of the actual rects to that scale
1185b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson            float defaultRowLeft = ((1f - (maxRowWidth / normalizedWorkspaceWidth)) *
1195b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                    workspaceWidth) / 2f;
1205b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson            float rowLeft = defaultRowLeft;
121bb410951b9ae373999c058fa141b3522ba169960Winson            float rowTop = ((1f - (rowScale * rowCount)) * workspaceHeight) / 2f;
122bb410951b9ae373999c058fa141b3522ba169960Winson            float rowHeight = rowScale * workspaceHeight;
1235b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson            for (int i = 0; i < numFreeformTasks; i++) {
1245b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                Task task = freeformTasks.get(i);
125bb410951b9ae373999c058fa141b3522ba169960Winson                float width = rowHeight * normalizedTaskWidths[i];
1265b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                if (rowLeft + width > workspaceWidth) {
1275b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                    // This goes on the next line
128bb410951b9ae373999c058fa141b3522ba169960Winson                    rowTop += rowHeight;
1295b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                    rowLeft = defaultRowLeft;
1305b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                }
131bb410951b9ae373999c058fa141b3522ba169960Winson                RectF rect = new RectF(rowLeft, rowTop, rowLeft + width, rowTop + rowHeight);
132509d0d0c9e2ee165d04e898fea59f8941ac7138dWinson Chung                rect.inset(mTaskPadding, mTaskPadding);
1335b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                rowLeft += width;
1345b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson                mTaskRectMap.put(task.key, rect);
13536a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson            }
13636a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson        }
13736a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson    }
13836a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson
13936a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson    /**
14036a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson     * Returns whether the transform is available for the given task.
14136a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson     */
142a5e6b36034fa66549dd71601a86397381c6bf02bWinson    public boolean isTransformAvailable(Task task, TaskStackLayoutAlgorithm stackLayout) {
143a5e6b36034fa66549dd71601a86397381c6bf02bWinson        if (stackLayout.mNumFreeformTasks == 0 || task == null) {
14436a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson            return false;
14536a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson        }
1465b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson        return mTaskRectMap.containsKey(task.key);
14736a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson    }
14836a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson
14936a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson    /**
15036a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson     * Returns the transform for the given task.  Any rect returned will be offset by the actual
15136a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson     * transform for the freeform workspace.
15236a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson     */
153a5e6b36034fa66549dd71601a86397381c6bf02bWinson    public TaskViewTransform getTransform(Task task, TaskViewTransform transformOut,
154a5e6b36034fa66549dd71601a86397381c6bf02bWinson            TaskStackLayoutAlgorithm stackLayout) {
1555b7dd536aa6cb8ce323b47cee109f879feb0d33aWinson        if (mTaskRectMap.containsKey(task.key)) {
156bb410951b9ae373999c058fa141b3522ba169960Winson            final RectF ffRect = mTaskRectMap.get(task.key);
157479f7446cf489fb99c91b3fc2bf96b1dc184f5baWinson
158de0591a0788c96757cce1eed93a7e8bc8bd0ef01Winson            transformOut.scale = 1f;
159bb410951b9ae373999c058fa141b3522ba169960Winson            transformOut.alpha = 1f;
160a5e6b36034fa66549dd71601a86397381c6bf02bWinson            transformOut.translationZ = stackLayout.mMaxTranslationZ;
1611bcf3c4742da5a1d9c04c73efac5c2418142c262Winson            transformOut.dimAlpha = 0f;
1621499150478b2836a7d2549129ccaed005b24bc06Winson            transformOut.viewOutlineAlpha = TaskStackLayoutAlgorithm.OUTLINE_ALPHA_MAX_VALUE;
163bb410951b9ae373999c058fa141b3522ba169960Winson            transformOut.rect.set(ffRect);
164bb410951b9ae373999c058fa141b3522ba169960Winson            transformOut.rect.offset(stackLayout.mFreeformRect.left, stackLayout.mFreeformRect.top);
16536a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson            transformOut.visible = true;
16636a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson            return transformOut;
16736a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson        }
16836a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson        return null;
16936a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson    }
17036a5a2c7003ef8157f276b411c3fda47ad2f75e3Winson}
171