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