| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.systemui.recents.views; |
| |
| import android.graphics.Rect; |
| import android.graphics.RectF; |
| import android.util.Log; |
| import com.android.systemui.recents.misc.Utilities; |
| import com.android.systemui.recents.model.Task; |
| |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| |
| /** |
| * The layout logic for the contents of the freeform workspace. |
| */ |
| public class FreeformWorkspaceLayoutAlgorithm { |
| |
| private static final String TAG = "FreeformWorkspaceLayoutAlgorithm"; |
| private static final boolean DEBUG = false; |
| |
| // Optimization, allows for quick lookup of task -> rect |
| private HashMap<Task.TaskKey, RectF> mTaskRectMap = new HashMap<>(); |
| |
| /** |
| * Updates the layout for each of the freeform workspace tasks. This is called after the stack |
| * layout is updated. |
| */ |
| public void update(List<Task> freeformTasks, TaskStackLayoutAlgorithm stackLayout) { |
| Collections.reverse(freeformTasks); |
| mTaskRectMap.clear(); |
| |
| int numFreeformTasks = stackLayout.mNumFreeformTasks; |
| if (!freeformTasks.isEmpty()) { |
| |
| // Normalize the widths so that we can calculate the best layout below |
| int workspaceWidth = stackLayout.mFreeformRect.width(); |
| int workspaceHeight = stackLayout.mFreeformRect.height(); |
| float normalizedWorkspaceWidth = (float) workspaceWidth / workspaceHeight; |
| float normalizedWorkspaceHeight = 1f; |
| float[] normalizedTaskWidths = new float[numFreeformTasks]; |
| for (int i = 0; i < numFreeformTasks; i++) { |
| Task task = freeformTasks.get(i); |
| float rowTaskWidth; |
| if (task.bounds != null) { |
| rowTaskWidth = (float) task.bounds.width() / task.bounds.height(); |
| } else { |
| // If this is a stack task that was dragged into the freeform workspace, then |
| // the task will not yet have an associated bounds, so assume the full workspace |
| // width for the time being |
| rowTaskWidth = normalizedWorkspaceWidth; |
| } |
| // Bound the task width to the workspace width so that at the worst case, it will |
| // fit its own row |
| normalizedTaskWidths[i] = Math.min(rowTaskWidth, |
| normalizedWorkspaceWidth); |
| } |
| |
| // Determine the scale to best fit each of the tasks in the workspace |
| float rowScale = 0.85f; |
| float rowWidth = 0f; |
| float maxRowWidth = 0f; |
| int rowCount = 1; |
| for (int i = 0; i < numFreeformTasks;) { |
| float width = normalizedTaskWidths[i] * rowScale; |
| if (rowWidth + width > normalizedWorkspaceWidth) { |
| // That is too long for this row, create new row |
| rowWidth = 0f; |
| if ((rowCount + 1) * rowScale > normalizedWorkspaceHeight) { |
| // The new row is too high, so we need to try fitting again. Update the |
| // scale to be the smaller of the scale needed to fit the task in the |
| // previous row, or the scale needed to fit the new row |
| rowScale = Math.min(normalizedWorkspaceWidth / (rowWidth + width), |
| normalizedWorkspaceHeight / (rowCount + 1)); |
| rowCount = 1; |
| i = 0; |
| } else { |
| // The new row fits, so continue |
| rowCount++; |
| i++; |
| } |
| } else { |
| // Task is OK in this row |
| rowWidth += width; |
| i++; |
| } |
| maxRowWidth = Math.max(rowWidth, maxRowWidth); |
| } |
| |
| // Normalize each of the actual rects to that scale |
| int height = (int) (rowScale * workspaceHeight); |
| float rowTop = ((1f - (rowScale * rowCount)) * workspaceHeight) / 2f; |
| float defaultRowLeft = ((1f - (maxRowWidth / normalizedWorkspaceWidth)) * |
| workspaceWidth) / 2f; |
| float rowLeft = defaultRowLeft; |
| for (int i = 0; i < numFreeformTasks; i++) { |
| Task task = freeformTasks.get(i); |
| int width = (int) (height * normalizedTaskWidths[i]); |
| if (rowLeft + width > workspaceWidth) { |
| // This goes on the next line |
| rowTop += height; |
| rowLeft = defaultRowLeft; |
| } |
| RectF rect = new RectF(rowLeft, rowTop, rowLeft + width, rowTop + height); |
| rowLeft += width; |
| mTaskRectMap.put(task.key, rect); |
| } |
| } |
| } |
| |
| /** |
| * Returns whether the transform is available for the given task. |
| */ |
| public boolean isTransformAvailable(Task task, TaskStackLayoutAlgorithm stackLayout) { |
| if (stackLayout.mNumFreeformTasks == 0 || task == null) { |
| return false; |
| } |
| return mTaskRectMap.containsKey(task.key); |
| } |
| |
| /** |
| * Returns the transform for the given task. Any rect returned will be offset by the actual |
| * transform for the freeform workspace. |
| */ |
| public TaskViewTransform getTransform(Task task, TaskViewTransform transformOut, |
| TaskStackLayoutAlgorithm stackLayout) { |
| if (mTaskRectMap.containsKey(task.key)) { |
| Rect taskRect = stackLayout.mTaskRect; |
| RectF ffRect = mTaskRectMap.get(task.key); |
| float scale = Math.max(ffRect.width() / taskRect.width(), |
| ffRect.height() / taskRect.height()); |
| int topOffset = (stackLayout.mFreeformRect.top - taskRect.top); |
| int scaleXOffset = (int) (((1f - scale) * taskRect.width()) / 2); |
| int scaleYOffset = (int) (((1f - scale) * taskRect.height()) / 2); |
| |
| transformOut.scale = scale * 0.95f; |
| transformOut.translationX = (int) (ffRect.left - scaleXOffset); |
| transformOut.translationY = (int) (topOffset + ffRect.top - scaleYOffset); |
| transformOut.translationZ = stackLayout.mMaxTranslationZ; |
| transformOut.clipBottom = (int) (taskRect.height() - (ffRect.height() / scale)); |
| transformOut.clipRight = (int) (taskRect.width() - (ffRect.width() / scale)); |
| if (task.thumbnail != null) { |
| transformOut.thumbnailScale = Math.min( |
| ((float) taskRect.width() - transformOut.clipRight) / |
| task.thumbnail.getWidth(), |
| ((float) taskRect.height() - transformOut.clipBottom) / |
| task.thumbnail.getHeight()); |
| } else { |
| transformOut.thumbnailScale = 1f; |
| } |
| transformOut.rect.set(taskRect); |
| transformOut.rect.offset(transformOut.translationX, transformOut.translationY); |
| Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale); |
| transformOut.rect.right -= transformOut.clipRight * scale; |
| transformOut.rect.bottom -= transformOut.clipBottom * scale; |
| transformOut.visible = true; |
| transformOut.p = 1f; |
| |
| if (DEBUG) { |
| Log.d(TAG, "getTransform: " + task.key + ", " + transformOut); |
| } |
| return transformOut; |
| } |
| return null; |
| } |
| } |