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