DO NOT MERGE - Port 2D recents implementation from master to nyc-mr2
Bug: 32101881
Test: Checked Recents layout/behavior on local sw600dp device
Change-Id: I40be7dbaf8bc017b4c7c449f9bca657817107ceb
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index eb9beb6..ad229ad 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -207,8 +207,6 @@
getSystemService(Context.UI_MODE_SERVICE);
if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
mImpl = new RecentsTvImpl(mContext);
- } else if (SystemProperties.getBoolean("ro.recents.grid", false) == true) {
- mImpl = new RecentsGridImpl(mContext);
} else {
mImpl = new RecentsImpl(mContext);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 73c6e6e..711f0c6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -20,6 +20,7 @@
import android.content.res.Resources;
import android.graphics.Rect;
+import android.os.SystemProperties;
import com.android.systemui.R;
import com.android.systemui.recents.misc.SystemServicesProxy;
@@ -58,6 +59,10 @@
public boolean fakeShadows;
public int svelteLevel;
+ // Whether this product supports Grid-based Recents. If this is field is set to true, then
+ // Recents will layout task views in a grid mode when there's enough space in the screen.
+ public boolean isGridEnabled;
+
public RecentsConfiguration(Context context) {
// Load only resources that can not change after the first load either through developer
// settings or via multi window
@@ -66,6 +71,7 @@
Resources res = appContext.getResources();
fakeShadows = res.getBoolean(R.bool.config_recents_fake_shadows);
svelteLevel = res.getInteger(R.integer.recents_svelte_level);
+ isGridEnabled = SystemProperties.getBoolean("ro.recents.grid", false);
float screenDensity = context.getResources().getDisplayMetrics().density;
smallestWidth = ssp.getDeviceSmallestWidth();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 42d1b61..2263a4e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -180,10 +180,7 @@
ssp.registerTaskStackListener(mTaskStackListener);
// Initialize the static configuration resources
- LayoutInflater inflater = LayoutInflater.from(mContext);
mDummyStackView = new TaskStackView(mContext);
- mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
- null, false);
reloadResources();
}
@@ -204,14 +201,6 @@
Resources res = mContext.getResources();
reloadResources();
mDummyStackView.reloadOnConfigurationChange();
- // Update the header bar direction directly as it is not attached to anything and does not
- // layout except in updateHeaderBarLayout()
- mHeaderBar.setLayoutDirection(res.getConfiguration().getLayoutDirection());
- mHeaderBar.onConfigurationChanged();
- mHeaderBar.forceLayout();
- mHeaderBar.measure(
- MeasureSpec.makeMeasureSpec(mHeaderBar.getMeasuredWidth(), MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(mHeaderBar.getMeasuredHeight(), MeasureSpec.EXACTLY));
}
/**
@@ -582,7 +571,13 @@
R.dimen.recents_task_view_header_height,
R.dimen.recents_task_view_header_height_tablet_land,
R.dimen.recents_task_view_header_height,
- R.dimen.recents_task_view_header_height_tablet_land);
+ R.dimen.recents_task_view_header_height_tablet_land,
+ R.dimen.recents_grid_task_view_header_height);
+
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ mHeaderBar = (TaskViewHeader) inflater.inflate(R.layout.recents_task_view_header,
+ null, false);
+ mHeaderBar.setLayoutDirection(res.getConfiguration().getLayoutDirection());
}
/**
@@ -721,7 +716,7 @@
if (task.isFreeformTask()) {
mTmpTransform = stackLayout.getStackTransformScreenCoordinates(task,
stackScroller.getStackScroll(), mTmpTransform, null,
- windowOverrideRect);
+ windowOverrideRect, false /* useGridLayout */);
Bitmap thumbnail = drawThumbnailTransitionBitmap(task, mTmpTransform,
mThumbTransitionBitmapCache);
Rect toTaskRect = new Rect();
@@ -771,7 +766,8 @@
stackView.updateLayoutAlgorithm(true /* boundScroll */);
stackView.updateToInitialState();
stackView.getStackAlgorithm().getStackTransformScreenCoordinates(launchTask,
- stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect);
+ stackView.getScroller().getStackScroll(), mTmpTransform, null, windowOverrideRect,
+ Recents.getConfiguration().isGridEnabled);
return mTmpTransform;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
index 2c5c437..d64fc36 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -79,14 +79,15 @@
public static final Rect EMPTY_RECT = new Rect();
/**
- * @return the first parent walking up the view hierarchy that has the given class type.
+ * @return the first parent walking up the view hierarchy that has the given class type (or
+ * a subclass).
*
* @param parentClass must be a class derived from {@link View}
*/
public static <T extends View> T findParent(View v, Class<T> parentClass) {
ViewParent parent = v.getParent();
while (parent != null) {
- if (parent.getClass().equals(parentClass)) {
+ if (parentClass.isAssignableFrom(parent.getClass())) {
return (T) parent;
}
parent = parent.getParent();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 745f5a5..178cb9f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -849,6 +849,24 @@
return null;
}
+ /**
+ * Returns the task in stack tasks which should be launched next if Recents are toggled
+ * again, or null if there is no task to be launched.
+ */
+ public Task getNextLaunchTarget() {
+ int taskCount = getTaskCount();
+ if (taskCount == 0) {
+ return null;
+ }
+ int launchTaskIndex = indexOfStackTask(getLaunchTarget());
+ if (launchTaskIndex != -1) {
+ launchTaskIndex = Math.max(0, launchTaskIndex - 1);
+ } else {
+ launchTaskIndex = getTaskCount() - 1;
+ }
+ return getStackTasks().get(launchTaskIndex);
+ }
+
/** Returns the index of this task in this current task stack */
public int indexOfStackTask(Task t) {
return mStackTaskList.indexOf(t);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
index 253d06a..dba085e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
@@ -30,17 +30,17 @@
private static final float MIN_ALPHA = 0.1f;
private static final float MAX_ALPHA = 0.8f;
- View mSourceView;
+ protected View mSourceView;
@ViewDebug.ExportedProperty(category="recents")
- Rect mClipRect = new Rect();
+ protected Rect mClipRect = new Rect();
@ViewDebug.ExportedProperty(category="recents")
- Rect mClipBounds = new Rect();
+ protected Rect mClipBounds = new Rect();
@ViewDebug.ExportedProperty(category="recents")
- Rect mLastClipBounds = new Rect();
+ protected Rect mLastClipBounds = new Rect();
@ViewDebug.ExportedProperty(category="recents")
- int mCornerRadius;
+ protected int mCornerRadius;
@ViewDebug.ExportedProperty(category="recents")
- float mAlpha = 1f;
+ protected float mAlpha = 1f;
public AnimateableViewBounds(View source, int cornerRadius) {
mSourceView = source;
@@ -110,7 +110,7 @@
return mClipRect.bottom;
}
- private void updateClipBounds() {
+ protected void updateClipBounds() {
mClipBounds.set(Math.max(0, mClipRect.left), Math.max(0, mClipRect.top),
mSourceView.getWidth() - Math.max(0, mClipRect.right),
mSourceView.getHeight() - Math.max(0, mClipRect.bottom));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 24ef433..8d32c9c 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -95,7 +95,6 @@
private static final int SHOW_STACK_ACTION_BUTTON_DURATION = 134;
private static final int HIDE_STACK_ACTION_BUTTON_DURATION = 100;
- private TaskStack mStack;
private TaskStackView mTaskStackView;
private TextView mStackActionButton;
private TextView mEmptyView;
@@ -195,7 +194,6 @@
* Called from RecentsActivity when the task stack is updated.
*/
public void updateStack(TaskStack stack, boolean setStackViewTasks) {
- mStack = stack;
if (setStackViewTasks) {
mTaskStackView.setTasks(stack, true /* allowNotifyStackChanges */);
}
@@ -212,7 +210,7 @@
* Returns the current TaskStack.
*/
public TaskStack getStack() {
- return mStack;
+ return mTaskStackView.getStack();
}
/*
@@ -251,8 +249,7 @@
/** Launches the task that recents was launched from if possible */
public boolean launchPreviousTask() {
if (mTaskStackView != null) {
- TaskStack stack = mTaskStackView.getStack();
- Task task = stack.getLaunchTarget();
+ Task task = getStack().getLaunchTarget();
if (task != null) {
TaskView taskView = mTaskStackView.getChildViewForTask(task);
EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
@@ -340,7 +337,8 @@
if (RecentsDebugFlags.Static.EnableStackActionButton) {
// Measure the stack action button within the constraints of the space above the stack
- Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.mStackActionButtonRect;
+ Rect buttonBounds = mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect(
+ mTaskStackView.useGridLayout());
measureChild(mStackActionButton,
MeasureSpec.makeMeasureSpec(buttonBounds.width(), MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(buttonBounds.height(), MeasureSpec.AT_MOST));
@@ -437,8 +435,9 @@
public final void onBusEvent(LaunchTaskEvent event) {
mLastTaskLaunchedWasFreeform = event.task.isFreeformTask();
- mTransitionHelper.launchTaskFromRecents(mStack, event.task, mTaskStackView, event.taskView,
- event.screenPinningRequested, event.targetTaskBounds, event.targetTaskStack);
+ mTransitionHelper.launchTaskFromRecents(getStack(), event.task, mTaskStackView,
+ event.taskView, event.screenPinningRequested, event.targetTaskBounds,
+ event.targetTaskStack);
}
public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
@@ -514,8 +513,7 @@
EventBus.getDefault().send(new DockedFirstAnimationFrameEvent());
// Remove the task and don't bother relaying out, as all the tasks will be
// relaid out when the stack changes on the multiwindow change event
- mTaskStackView.getStack().removeTask(event.task, null,
- true /* fromDockGesture */);
+ getStack().removeTask(event.task, null, true /* fromDockGesture */);
}
};
@@ -536,7 +534,7 @@
MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP,
event.task.getTopComponent().flattenToShortString());
} else {
- EventBus.getDefault().send(new DragEndCancelledEvent(mStack, event.task,
+ EventBus.getDefault().send(new DragEndCancelledEvent(getStack(), event.task,
event.taskView));
}
} else {
@@ -598,7 +596,7 @@
public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
if (!launchState.launchedViaDockGesture && !launchState.launchedFromApp
- && mStack.getTaskCount() > 0) {
+ && getStack().getTaskCount() > 0) {
animateBackgroundScrim(1f,
TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION);
}
@@ -775,7 +773,8 @@
* @return the bounds of the stack action button.
*/
private Rect getStackActionButtonBoundsFromStackLayout() {
- Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.mStackActionButtonRect);
+ Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect(
+ mTaskStackView.useGridLayout()));
int left = isLayoutRtl()
? actionButtonRect.left - mStackActionButton.getPaddingLeft()
: actionButtonRect.right + mStackActionButton.getPaddingRight()
@@ -797,8 +796,8 @@
writer.print(" [0x"); writer.print(id); writer.print("]");
writer.println();
- if (mStack != null) {
- mStack.dump(innerPrefix, writer);
+ if (getStack() != null) {
+ getStack().dump(innerPrefix, writer);
}
if (mTaskStackView != null) {
mTaskStackView.dump(innerPrefix, writer);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
index 493e618..c1f4c8a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -157,7 +157,7 @@
// Get the current transform for the task, which will be used to position it offscreen
stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
- null);
+ null, mStackView.useGridLayout());
if (hideTask) {
tv.setVisibility(View.INVISIBLE);
@@ -230,7 +230,7 @@
// Get the current transform for the task, which will be updated to the final transform
// to animate to depending on how recents was invoked
stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
- null);
+ null, mStackView.useGridLayout());
if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
if (task.isLaunchTarget) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index fce7f9d..f0644a5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -38,6 +38,7 @@
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -240,14 +241,14 @@
// This is the current system insets
@ViewDebug.ExportedProperty(category="recents")
public Rect mSystemInsets = new Rect();
- // This is the bounds of the stack action above the stack rect
- @ViewDebug.ExportedProperty(category="recents")
- public Rect mStackActionButtonRect = new Rect();
// The visible ranges when the stack is focused and unfocused
private Range mUnfocusedRange;
private Range mFocusedRange;
+ // This is the bounds of the stack action above the stack rect
+ @ViewDebug.ExportedProperty(category="recents")
+ private Rect mStackActionButtonRect = new Rect();
// The base top margin for the stack from the system insets
@ViewDebug.ExportedProperty(category="recents")
private int mBaseTopMargin;
@@ -326,7 +327,7 @@
@ViewDebug.ExportedProperty(category="recents")
int mMinTranslationZ;
@ViewDebug.ExportedProperty(category="recents")
- int mMaxTranslationZ;
+ public int mMaxTranslationZ;
// Optimization, allows for quick lookup of task -> index
private SparseIntArray mTaskIndexMap = new SparseIntArray();
@@ -334,6 +335,7 @@
// The freeform workspace layout
FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm;
+ TaskGridLayoutAlgorithm mTaskGridLayoutAlgorithm;
// The transform to place TaskViews at the front and back of the stack respectively
TaskViewTransform mBackOfStackTransform = new TaskViewTransform();
@@ -344,19 +346,7 @@
mContext = context;
mCb = cb;
mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm(context);
- mMinMargin = res.getDimensionPixelSize(R.dimen.recents_layout_min_margin);
- mBaseTopMargin = getDimensionForDevice(context,
- R.dimen.recents_layout_top_margin_phone,
- R.dimen.recents_layout_top_margin_tablet,
- R.dimen.recents_layout_top_margin_tablet_xlarge);
- mBaseSideMargin = getDimensionForDevice(context,
- R.dimen.recents_layout_side_margin_phone,
- R.dimen.recents_layout_side_margin_tablet,
- R.dimen.recents_layout_side_margin_tablet_xlarge);
- mBaseBottomMargin = res.getDimensionPixelSize(R.dimen.recents_layout_bottom_margin);
- mFreeformStackGap =
- res.getDimensionPixelSize(R.dimen.recents_freeform_layout_bottom_margin);
-
+ mTaskGridLayoutAlgorithm = new TaskGridLayoutAlgorithm(context);
reloadOnConfigurationChange(context);
}
@@ -381,6 +371,7 @@
R.dimen.recents_layout_initial_top_offset_tablet,
R.dimen.recents_layout_initial_top_offset_tablet,
R.dimen.recents_layout_initial_top_offset_tablet,
+ R.dimen.recents_layout_initial_top_offset_tablet,
R.dimen.recents_layout_initial_top_offset_tablet);
mBaseInitialBottomOffset = getDimensionForDevice(context,
R.dimen.recents_layout_initial_bottom_offset_phone_port,
@@ -388,8 +379,24 @@
R.dimen.recents_layout_initial_bottom_offset_tablet,
R.dimen.recents_layout_initial_bottom_offset_tablet,
R.dimen.recents_layout_initial_bottom_offset_tablet,
+ R.dimen.recents_layout_initial_bottom_offset_tablet,
R.dimen.recents_layout_initial_bottom_offset_tablet);
mFreeformLayoutAlgorithm.reloadOnConfigurationChange(context);
+ mTaskGridLayoutAlgorithm.reloadOnConfigurationChange(context);
+ mMinMargin = res.getDimensionPixelSize(R.dimen.recents_layout_min_margin);
+ mBaseTopMargin = getDimensionForDevice(context,
+ R.dimen.recents_layout_top_margin_phone,
+ R.dimen.recents_layout_top_margin_tablet,
+ R.dimen.recents_layout_top_margin_tablet_xlarge,
+ R.dimen.recents_layout_top_margin_tablet);
+ mBaseSideMargin = getDimensionForDevice(context,
+ R.dimen.recents_layout_side_margin_phone,
+ R.dimen.recents_layout_side_margin_tablet,
+ R.dimen.recents_layout_side_margin_tablet_xlarge,
+ R.dimen.recents_layout_side_margin_tablet);
+ mBaseBottomMargin = res.getDimensionPixelSize(R.dimen.recents_layout_bottom_margin);
+ mFreeformStackGap =
+ res.getDimensionPixelSize(R.dimen.recents_freeform_layout_bottom_margin);
}
/**
@@ -406,6 +413,7 @@
public boolean setSystemInsets(Rect systemInsets) {
boolean changed = !mSystemInsets.equals(systemInsets);
mSystemInsets.set(systemInsets);
+ mTaskGridLayoutAlgorithm.setSystemInsets(systemInsets);
return changed;
}
@@ -471,6 +479,9 @@
updateFrontBackTransforms();
}
+
+ // Initialize the grid layout
+ mTaskGridLayoutAlgorithm.initialize(displayRect, windowRect);
}
/**
@@ -722,6 +733,11 @@
}
}
+ public Rect getStackActionButtonRect(boolean useGridLayout) {
+ return useGridLayout
+ ? mTaskGridLayoutAlgorithm.getStackActionButtonRect() : mStackActionButtonRect;
+ }
+
/**
* Returns the TaskViewTransform that would put the task just off the back of the stack.
*/
@@ -826,24 +842,30 @@
* is what the view is measured and laid out with.
*/
public TaskViewTransform getStackTransform(Task task, float stackScroll,
- TaskViewTransform transformOut, TaskViewTransform frontTransform) {
+ TaskViewTransform transformOut, TaskViewTransform frontTransform,
+ boolean useGridLayout) {
return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
- false /* forceUpdate */, false /* ignoreTaskOverrides */);
+ false /* forceUpdate */, false /* ignoreTaskOverrides */, useGridLayout);
}
public TaskViewTransform getStackTransform(Task task, float stackScroll,
TaskViewTransform transformOut, TaskViewTransform frontTransform,
- boolean ignoreTaskOverrides) {
+ boolean ignoreTaskOverrides, boolean useGridLayout) {
return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
- false /* forceUpdate */, ignoreTaskOverrides);
+ false /* forceUpdate */, ignoreTaskOverrides, useGridLayout);
}
public TaskViewTransform getStackTransform(Task task, float stackScroll, int focusState,
TaskViewTransform transformOut, TaskViewTransform frontTransform, boolean forceUpdate,
- boolean ignoreTaskOverrides) {
+ boolean ignoreTaskOverrides, boolean useGridLayout) {
if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) {
mFreeformLayoutAlgorithm.getTransform(task, transformOut, this);
return transformOut;
+ } else if (useGridLayout) {
+ int taskIndex = mTaskIndexMap.get(task.key.id);
+ int taskCount = mTaskIndexMap.size();
+ mTaskGridLayoutAlgorithm.getTransform(taskIndex, taskCount, transformOut, this);
+ return transformOut;
} else {
// Return early if we have an invalid index
int nonOverrideTaskProgress = mTaskIndexMap.get(task.key.id, -1);
@@ -865,10 +887,10 @@
*/
public TaskViewTransform getStackTransformScreenCoordinates(Task task, float stackScroll,
TaskViewTransform transformOut, TaskViewTransform frontTransform,
- Rect windowOverrideRect) {
+ Rect windowOverrideRect, boolean useGridLayout) {
TaskViewTransform transform = getStackTransform(task, stackScroll, mFocusState,
transformOut, frontTransform, true /* forceUpdate */,
- false /* ignoreTaskOverrides */);
+ false /* ignoreTaskOverrides */, useGridLayout);
return transformToScreenCoordinates(transform, windowOverrideRect);
}
@@ -1089,9 +1111,9 @@
* Retrieves resources that are constant regardless of the current configuration of the device.
*/
public static int getDimensionForDevice(Context ctx, int phoneResId,
- int tabletResId, int xlargeTabletResId) {
+ int tabletResId, int xlargeTabletResId, int gridLayoutResId) {
return getDimensionForDevice(ctx, phoneResId, phoneResId, tabletResId, tabletResId,
- xlargeTabletResId, xlargeTabletResId);
+ xlargeTabletResId, xlargeTabletResId, gridLayoutResId);
}
/**
@@ -1099,12 +1121,14 @@
*/
public static int getDimensionForDevice(Context ctx, int phonePortResId, int phoneLandResId,
int tabletPortResId, int tabletLandResId, int xlargeTabletPortResId,
- int xlargeTabletLandResId) {
+ int xlargeTabletLandResId, int gridLayoutResId) {
RecentsConfiguration config = Recents.getConfiguration();
Resources res = ctx.getResources();
boolean isLandscape = Utilities.getAppConfiguration(ctx).orientation ==
Configuration.ORIENTATION_LANDSCAPE;
- if (config.isXLargeScreen) {
+ if (config.isGridEnabled) {
+ return res.getDimensionPixelSize(gridLayoutResId);
+ } else if (config.isXLargeScreen) {
return res.getDimensionPixelSize(isLandscape
? xlargeTabletLandResId
: xlargeTabletPortResId);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index fc580a5..8d91b4f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -92,6 +92,7 @@
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.recents.views.grid.GridTaskView;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -158,6 +159,7 @@
private int mTaskCornerRadiusPx;
private int mDividerSize;
private int mStartTimerIndicatorDuration;
+ private boolean mDraggingOverDockState;
@ViewDebug.ExportedProperty(category="recents")
private boolean mTaskViewsClipDirty = true;
@@ -279,6 +281,9 @@
}
});
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ if (ssp.hasFreeformWorkspaceSupport()) {
+ setWillNotDraw(false);
+ }
mFreeformWorkspaceBackground = (GradientDrawable) getContext().getDrawable(
R.drawable.recents_freeform_workspace_bg);
@@ -496,13 +501,13 @@
// Calculate the current and (if necessary) the target transform for the task
transform = mLayoutAlgorithm.getStackTransform(task, curStackScroll,
- taskTransforms.get(i), frontTransform, ignoreTaskOverrides);
+ taskTransforms.get(i), frontTransform, ignoreTaskOverrides, useGridLayout());
if (useTargetStackScroll && !transform.visible) {
// If we have a target stack scroll and the task is not currently visible, then we
// just update the transform at the new scroll
// TODO: Optimize this
- transformAtTarget = mLayoutAlgorithm.getStackTransform(task,
- targetStackScroll, new TaskViewTransform(), frontTransformAtTarget);
+ transformAtTarget = mLayoutAlgorithm.getStackTransform(task, targetStackScroll,
+ new TaskViewTransform(), frontTransformAtTarget, useGridLayout());
if (transformAtTarget.visible) {
transform.copyFrom(transformAtTarget);
}
@@ -733,7 +738,7 @@
} else {
mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(),
focusState, transform, null, true /* forceUpdate */,
- false /* ignoreTaskOverrides */);
+ false /* ignoreTaskOverrides */, useGridLayout());
}
transform.visible = true;
}
@@ -750,7 +755,7 @@
Task task = tasks.get(i);
TaskViewTransform transform = transformsOut.get(i);
mLayoutAlgorithm.getStackTransform(task, stackScroll, focusState, transform, null,
- true /* forceUpdate */, ignoreTaskOverrides);
+ true /* forceUpdate */, ignoreTaskOverrides, useGridLayout());
transform.visible = true;
}
}
@@ -779,6 +784,11 @@
* Updates the clip for each of the task views from back to front.
*/
private void clipTaskViews() {
+ // We never clip task views in grid layout
+ if (Recents.getConfiguration().isGridEnabled) {
+ return;
+ }
+
// Update the clip on each task child
List<TaskView> taskViews = getTaskViews();
TaskView tmpTv = null;
@@ -1335,14 +1345,7 @@
setFocusedTask(focusedTaskIndex, false /* scrollToTask */,
false /* requestViewFocus */);
}
-
- // Update the stack action button visibility
- if (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
- mStack.getTaskCount() > 0) {
- EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
- } else {
- EventBus.getDefault().send(new HideStackActionButtonEvent());
- }
+ updateStackActionButtonVisibility();
}
public boolean isTouchPointInView(float x, float y, TaskView tv) {
@@ -1503,7 +1506,11 @@
@Override
public TaskView createView(Context context) {
- return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
+ if (Recents.getConfiguration().isGridEnabled) {
+ return (GridTaskView) mInflater.inflate(R.layout.recents_grid_task_view, this, false);
+ } else {
+ return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
+ }
}
@Override
@@ -1560,11 +1567,6 @@
// Bind the task view to the new task
bindTaskView(tv, task);
- // If the doze trigger has already fired, then update the state for this task view
- if (mUIDozeTrigger.isAsleep()) {
- tv.setNoUserInteractionState();
- }
-
// Set the new state for this view, including the callbacks and view clipping
tv.setCallbacks(this);
tv.setTouchEnabled(true);
@@ -1594,6 +1596,12 @@
// Rebind the task and request that this task's data be filled into the TaskView
tv.onTaskBound(task, mTouchExplorationEnabled, mDisplayOrientation, mDisplayRect);
+ // If the doze trigger has already fired, then update the state for this task view
+ if (mUIDozeTrigger.isAsleep() ||
+ Recents.getSystemServices().hasFreeformWorkspaceSupport()) {
+ tv.setNoUserInteractionState();
+ }
+
// Load the task data
Recents.getTaskLoader().loadTaskData(task);
}
@@ -1632,7 +1640,8 @@
relayoutTaskViewsOnNextFrame(animation);
}
- if (mEnterAnimationComplete) {
+ // In grid layout, the stack action button always remains visible.
+ if (mEnterAnimationComplete && !useGridLayout()) {
if (prevScroll > SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
mStack.getTaskCount() > 0) {
@@ -1679,17 +1688,11 @@
return;
}
- int launchTaskIndex = mStack.indexOfStackTask(mStack.getLaunchTarget());
- if (launchTaskIndex != -1) {
- launchTaskIndex = Math.max(0, launchTaskIndex - 1);
- } else {
- launchTaskIndex = mStack.getTaskCount() - 1;
- }
- if (launchTaskIndex != -1) {
+ final Task launchTask = mStack.getNextLaunchTarget();
+ if (launchTask != null) {
// Stop all animations
cancelAllTaskViewAnimations();
- final Task launchTask = mStack.getStackTasks().get(launchTaskIndex);
float curScroll = mStackScroller.getStackScroll();
float targetScroll = mLayoutAlgorithm.getStackScrollForTaskAtInitialOffset(launchTask);
float absScrollDiff = Math.abs(targetScroll - curScroll);
@@ -1832,7 +1835,7 @@
// Enlarge the dragged view slightly
float finalScale = event.taskView.getScaleX() * DRAG_SCALE_FACTOR;
mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(),
- mTmpTransform, null);
+ mTmpTransform, null, useGridLayout());
mTmpTransform.scale = finalScale;
mTmpTransform.translationZ = mLayoutAlgorithm.mMaxTranslationZ + 1;
mTmpTransform.dimAlpha = 0f;
@@ -1853,6 +1856,7 @@
Interpolators.FAST_OUT_SLOW_IN);
boolean ignoreTaskOverrides = false;
if (event.dropTarget instanceof TaskStack.DockState) {
+ mDraggingOverDockState = true;
// Calculate the new task stack bounds that matches the window size that Recents will
// have after the drop
final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
@@ -1872,6 +1876,7 @@
updateLayoutAlgorithm(true /* boundScroll */);
ignoreTaskOverrides = true;
} else {
+ mDraggingOverDockState = false;
// Restore the pre-drag task stack bounds, but ensure that we don't layout the dragging
// task view, so add it back to the ignore set after updating the layout
removeIgnoreTask(event.task);
@@ -1882,6 +1887,7 @@
}
public final void onBusEvent(final DragEndEvent event) {
+ mDraggingOverDockState = false;
// We don't handle drops on the dock regions
if (event.dropTarget instanceof TaskStack.DockState) {
// However, we do need to reset the overrides, since the last state of this task stack
@@ -1969,7 +1975,9 @@
@Override
public void run() {
// Start the dozer to trigger to trigger any UI that shows after a timeout
- mUIDozeTrigger.startDozing();
+ if (!Recents.getSystemServices().hasFreeformWorkspaceSupport()) {
+ mUIDozeTrigger.startDozing();
+ }
// Update the focused state here -- since we only set the focused task without
// requesting view focus in onFirstLayout(), actually request view focus and
@@ -2049,6 +2057,9 @@
}
}
+ // Update the Clear All button in case we're switching in or out of grid layout.
+ updateStackActionButtonVisibility();
+
// Trigger a new layout and update to the initial state if necessary
if (event.fromMultiWindow) {
mInitialState = INITIAL_STATE_UPDATE_LAYOUT_ONLY;
@@ -2119,6 +2130,20 @@
}
/**
+ * Check whether we should use the grid layout.
+ * We use the grid layout for Recents iff all the following is true:
+ * 1. Grid-mode is enabled.
+ * 2. The activity is not in multi-window mode.
+ * 3. The user is not dragging a task view over the dock state.
+ * @return True if we should use the grid layout.
+ */
+ public boolean useGridLayout() {
+ return Recents.getConfiguration().isGridEnabled
+ && !((RecentsActivity) mContext).isInMultiWindowMode()
+ && !mDraggingOverDockState;
+ }
+
+ /**
* Reads current system flags related to accessibility and screen pinning.
*/
private void readSystemFlags() {
@@ -2128,6 +2153,17 @@
Settings.System.LOCK_TO_APP_ENABLED) != 0;
}
+ private void updateStackActionButtonVisibility() {
+ // Always show the button in grid layout.
+ if (useGridLayout() ||
+ (mStackScroller.getStackScroll() < SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
+ mStack.getTaskCount() > 0)) {
+ EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
+ } else {
+ EventBus.getDefault().send(new HideStackActionButtonEvent());
+ }
+ }
+
public void dump(String prefix, PrintWriter writer) {
String innerPrefix = prefix + " ";
String id = Integer.toHexString(System.identityHashCode(this));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 115e65a..93ab0bd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -148,7 +148,7 @@
private ArrayList<Animator> mTmpAnimators = new ArrayList<>();
@ViewDebug.ExportedProperty(deepExport=true, prefix="thumbnail_")
- TaskViewThumbnail mThumbnailView;
+ protected TaskViewThumbnail mThumbnailView;
@ViewDebug.ExportedProperty(deepExport=true, prefix="header_")
TaskViewHeader mHeaderView;
private View mActionButtonView;
@@ -176,8 +176,7 @@
super(context, attrs, defStyleAttr, defStyleRes);
RecentsConfiguration config = Recents.getConfiguration();
Resources res = context.getResources();
- mViewBounds = new AnimateableViewBounds(this, res.getDimensionPixelSize(
- R.dimen.recents_task_view_shadow_rounded_corners_radius));
+ mViewBounds = createOutlineProvider();
if (config.fakeShadows) {
setBackground(new FakeShadowDrawable(res, config));
}
@@ -194,7 +193,9 @@
* Called from RecentsActivity when it is relaunched.
*/
void onReload(boolean isResumingFromVisible) {
- resetNoUserInteractionState();
+ if (!Recents.getSystemServices().hasFreeformWorkspaceSupport()) {
+ resetNoUserInteractionState();
+ }
if (!isResumingFromVisible) {
resetViewProperties();
}
@@ -205,6 +206,12 @@
return mTask;
}
+ /* Create an outline provider to clip and outline the view */
+ protected AnimateableViewBounds createOutlineProvider() {
+ return new AnimateableViewBounds(this, mContext.getResources().getDimensionPixelSize(
+ R.dimen.recents_task_view_shadow_rounded_corners_radius));
+ }
+
/** Returns the view bounds. */
AnimateableViewBounds getViewBounds() {
return mViewBounds;
@@ -232,7 +239,7 @@
/**
* Update the task view when the configuration changes.
*/
- void onConfigurationChanged() {
+ protected void onConfigurationChanged() {
mHeaderView.onConfigurationChanged();
}
@@ -666,10 +673,16 @@
@Override
public boolean onLongClick(View v) {
SystemServicesProxy ssp = Recents.getSystemServices();
- // Since we are clipping the view to the bounds, manually do the hit test
+ boolean inBounds = false;
Rect clipBounds = new Rect(mViewBounds.mClipBounds);
- clipBounds.scale(getScaleX());
- boolean inBounds = clipBounds.contains(mDownTouchPos.x, mDownTouchPos.y);
+ if (!clipBounds.isEmpty()) {
+ // If we are clipping the view to the bounds, manually do the hit test.
+ clipBounds.scale(getScaleX());
+ inBounds = clipBounds.contains(mDownTouchPos.x, mDownTouchPos.y);
+ } else {
+ // Otherwise just make sure we're within the view's bounds.
+ inBounds = mDownTouchPos.x <= getWidth() && mDownTouchPos.y <= getHeight();
+ }
if (v == this && inBounds && !ssp.hasDockedTask()) {
// Start listening for drag events
setClipViewInStack(false);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 691e599..c0cc83f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -290,14 +290,16 @@
R.dimen.recents_task_view_header_height,
R.dimen.recents_task_view_header_height_tablet_land,
R.dimen.recents_task_view_header_height,
- R.dimen.recents_task_view_header_height_tablet_land);
+ R.dimen.recents_task_view_header_height_tablet_land,
+ R.dimen.recents_grid_task_view_header_height);
int headerButtonPadding = TaskStackLayoutAlgorithm.getDimensionForDevice(getContext(),
R.dimen.recents_task_view_header_button_padding,
R.dimen.recents_task_view_header_button_padding,
R.dimen.recents_task_view_header_button_padding,
R.dimen.recents_task_view_header_button_padding_tablet_land,
R.dimen.recents_task_view_header_button_padding,
- R.dimen.recents_task_view_header_button_padding_tablet_land);
+ R.dimen.recents_task_view_header_button_padding_tablet_land,
+ R.dimen.recents_grid_task_view_header_button_padding);
if (headerBarHeight != mHeaderBarHeight || headerButtonPadding != mHeaderButtonPadding) {
mHeaderBarHeight = headerBarHeight;
mHeaderButtonPadding = headerButtonPadding;
@@ -603,10 +605,7 @@
Constants.Metrics.DismissSourceHeaderButton);
} else if (v == mMoveTaskButton) {
TaskView tv = Utilities.findParent(this, TaskView.class);
- Rect bounds = mMoveTaskTargetStackId == FREEFORM_WORKSPACE_STACK_ID
- ? new Rect(mTaskViewRect)
- : new Rect();
- EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, bounds,
+ EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, null,
mMoveTaskTargetStackId, false));
} else if (v == mAppInfoView) {
EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index c46adf1..16521f7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -60,6 +60,8 @@
@ViewDebug.ExportedProperty(category="recents")
private float mThumbnailScale;
private float mFullscreenThumbnailScale;
+ private boolean mSizeToFit = false;
+ private boolean mOverlayHeaderOnThumbnailActionBar = true;
private ActivityManager.TaskThumbnailInfo mThumbnailInfo;
private int mCornerRadius;
@@ -133,10 +135,12 @@
(int) (mThumbnailRect.width() * mThumbnailScale));
int thumbnailHeight = Math.min(viewHeight,
(int) (mThumbnailRect.height() * mThumbnailScale));
+
if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
- int topOffset = mTaskBar != null
- ? mTaskBar.getHeight() - mCornerRadius
- : 0;
+ int topOffset = 0;
+ if (mTaskBar != null && mOverlayHeaderOnThumbnailActionBar) {
+ topOffset = mTaskBar.getHeight() - mCornerRadius;
+ }
// Draw the background, there will be some small overdraw with the thumbnail
if (thumbnailWidth < viewWidth) {
@@ -230,7 +234,7 @@
// If we haven't measured or the thumbnail is invalid, skip the thumbnail drawing
// and only draw the background color
mThumbnailScale = 0f;
- } else if (isStackTask) {
+ } else if (isStackTask && !mSizeToFit) {
float invThumbnailScale = 1f / mFullscreenThumbnailScale;
if (mDisplayOrientation == Configuration.ORIENTATION_PORTRAIT) {
if (mThumbnailInfo.screenOrientation == Configuration.ORIENTATION_PORTRAIT) {
@@ -262,6 +266,19 @@
}
}
+ /** Sets whether the thumbnail should be resized to fit the task view in all orientations. */
+ public void setSizeToFit(boolean flag) {
+ mSizeToFit = flag;
+ }
+
+ /**
+ * Sets whether the header should overlap (and hide) the action bar in the thumbnail, or
+ * be stacked just above it.
+ */
+ public void setOverlayHeaderOnThumbnailActionBar(boolean flag) {
+ mOverlayHeaderOnThumbnailActionBar = flag;
+ }
+
/** Updates the clip rect based on the given task bar. */
void updateClipToTaskBar(View taskBar) {
mTaskBar = taskBar;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java
new file mode 100644
index 0000000..a029478
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 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.grid;
+
+import android.view.View;
+import com.android.systemui.recents.views.AnimateableViewBounds;
+
+/* An outline provider for grid-based task views. */
+class AnimateableGridViewBounds extends AnimateableViewBounds {
+
+ public AnimateableGridViewBounds(View source, int cornerRadius) {
+ super(source, cornerRadius);
+ }
+
+ @Override
+ protected void updateClipBounds() {
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java
new file mode 100644
index 0000000..6300400
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 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.grid;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import com.android.systemui.R;
+import com.android.systemui.recents.views.AnimateableViewBounds;
+import com.android.systemui.recents.views.TaskView;
+
+public class GridTaskView extends TaskView {
+
+ /** The height, in pixels, of the header view. */
+ private int mHeaderHeight;
+
+ public GridTaskView(Context context) {
+ this(context, null);
+ }
+
+ public GridTaskView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public GridTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public GridTaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mHeaderHeight = context.getResources().getDimensionPixelSize(
+ R.dimen.recents_grid_task_view_header_height);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ // Show the full thumbnail and don't overlap with the header.
+ mThumbnailView.setSizeToFit(true);
+ mThumbnailView.setOverlayHeaderOnThumbnailActionBar(false);
+ mThumbnailView.updateThumbnailScale();
+ mThumbnailView.setTranslationY(mHeaderHeight);
+ }
+
+ @Override
+ protected AnimateableViewBounds createOutlineProvider() {
+ return new AnimateableGridViewBounds(this, mContext.getResources().getDimensionPixelSize(
+ R.dimen.recents_task_view_shadow_rounded_corners_radius));
+ }
+
+ @Override
+ protected void onConfigurationChanged() {
+ super.onConfigurationChanged();
+ mHeaderHeight = mContext.getResources().getDimensionPixelSize(
+ R.dimen.recents_grid_task_view_header_height);
+ mThumbnailView.setTranslationY(mHeaderHeight);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
new file mode 100644
index 0000000..65a8ee2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2016 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.grid;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
+import com.android.systemui.recents.views.TaskViewTransform;
+
+public class TaskGridLayoutAlgorithm {
+
+ private final String TAG = "TaskGridLayoutAlgorithm";
+ private final int MAX_LAYOUT_TASK_COUNT = 8;
+
+ /** The horizontal padding around the whole recents view. */
+ private int mPaddingLeftRight;
+ /** The vertical padding around the whole recents view. */
+ private int mPaddingTopBottom;
+ /** The padding between task views. */
+ private int mPaddingTaskView;
+
+ private Rect mDisplayRect;
+ private Rect mWindowRect;
+ private Point mScreenSize = new Point();
+
+ private Rect mTaskGridRect;
+
+ /** The height, in pixels, of each task view's title bar. */
+ private int mTitleBarHeight;
+
+ /** The aspect ratio of each task thumbnail, without the title bar. */
+ private float mAppAspectRatio;
+ private Rect mSystemInsets = new Rect();
+
+ public TaskGridLayoutAlgorithm(Context context) {
+ reloadOnConfigurationChange(context);
+ }
+
+ public void reloadOnConfigurationChange(Context context) {
+ Resources res = context.getResources();
+ mPaddingLeftRight = res.getDimensionPixelSize(R.dimen.recents_grid_padding_left_right);
+ mPaddingTopBottom = res.getDimensionPixelSize(R.dimen.recents_grid_padding_top_bottom);
+ mPaddingTaskView = res.getDimensionPixelSize(R.dimen.recents_grid_padding_task_view);
+
+ mTaskGridRect = new Rect();
+ mTitleBarHeight = res.getDimensionPixelSize(R.dimen.recents_grid_task_view_header_height);
+
+ WindowManager windowManager = (WindowManager) context
+ .getSystemService(Context.WINDOW_SERVICE);
+ windowManager.getDefaultDisplay().getRealSize(mScreenSize);
+
+ updateAppAspectRatio();
+ }
+
+ public TaskViewTransform getTransform(int taskIndex, int taskCount,
+ TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout) {
+
+ int layoutTaskCount = Math.min(MAX_LAYOUT_TASK_COUNT, taskCount);
+
+ // We also need to invert the index in order to display the most recent tasks first.
+ int taskLayoutIndex = taskCount - taskIndex - 1;
+
+ int tasksPerLine = layoutTaskCount < 2 ? 1 : (
+ layoutTaskCount < 5 ? 2 : (
+ layoutTaskCount < 7 ? 3 : 4));
+ int lines = layoutTaskCount < 3 ? 1 : 2;
+
+ int taskWidth, taskHeight;
+ int maxTaskWidth = (mDisplayRect.width() - 2 * mPaddingLeftRight
+ - (tasksPerLine - 1) * mPaddingTaskView) / tasksPerLine;
+ int maxTaskHeight = (mDisplayRect.height() - 2 * mPaddingTopBottom
+ - (lines - 1) * mPaddingTaskView) / lines;
+
+ if (maxTaskHeight >= maxTaskWidth / mAppAspectRatio + mTitleBarHeight) {
+ // Width bound.
+ taskWidth = maxTaskWidth;
+ taskHeight = (int) (maxTaskWidth / mAppAspectRatio + mTitleBarHeight);
+ } else {
+ // Height bound.
+ taskHeight = maxTaskHeight;
+ taskWidth = (int) ((taskHeight - mTitleBarHeight) * mAppAspectRatio);
+ }
+ int emptySpaceX = mDisplayRect.width() - 2 * mPaddingLeftRight
+ - (tasksPerLine * taskWidth) - (tasksPerLine - 1) * mPaddingTaskView;
+ int emptySpaceY = mDisplayRect.height() - 2 * mPaddingTopBottom
+ - (lines * taskHeight) - (lines - 1) * mPaddingTaskView;
+
+ mTaskGridRect.set(0, 0, taskWidth, taskHeight);
+
+ int xIndex = taskLayoutIndex % tasksPerLine;
+ int yIndex = taskLayoutIndex / tasksPerLine;
+ int x = emptySpaceX / 2 + mPaddingLeftRight + (taskWidth + mPaddingTaskView) * xIndex;
+ int y = emptySpaceY / 2 + mPaddingTopBottom + (taskHeight + mPaddingTaskView) * yIndex;
+ float z = stackLayout.mMaxTranslationZ;
+
+ float dimAlpha = 0f;
+ float viewOutlineAlpha = 0f;
+ boolean isTaskViewVisible = (taskLayoutIndex < MAX_LAYOUT_TASK_COUNT);
+
+ // Fill out the transform
+ transformOut.scale = 1f;
+ transformOut.alpha = isTaskViewVisible ? 1f : 0f;
+ transformOut.translationZ = z;
+ transformOut.dimAlpha = dimAlpha;
+ transformOut.viewOutlineAlpha = viewOutlineAlpha;
+ transformOut.rect.set(mTaskGridRect);
+ transformOut.rect.offset(x, y);
+ Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
+ // We only show the 8 most recent tasks.
+ transformOut.visible = isTaskViewVisible;
+ return transformOut;
+ }
+
+ public void initialize(Rect displayRect, Rect windowRect) {
+ mDisplayRect = displayRect;
+ mWindowRect = windowRect;
+ }
+
+ public void setSystemInsets(Rect systemInsets) {
+ mSystemInsets = systemInsets;
+ updateAppAspectRatio();
+ }
+
+ private void updateAppAspectRatio() {
+ int usableWidth = mScreenSize.x - mSystemInsets.left - mSystemInsets.right;
+ int usableHeight = mScreenSize.y - mSystemInsets.top - mSystemInsets.bottom;
+ mAppAspectRatio = (float) usableWidth / (float) usableHeight;
+ }
+
+ public Rect getStackActionButtonRect() {
+ Rect buttonRect = new Rect(mDisplayRect);
+ buttonRect.right -= mPaddingLeftRight;
+ buttonRect.left += mPaddingLeftRight;
+ buttonRect.bottom = buttonRect.top + mPaddingTopBottom;
+ return buttonRect;
+ }
+}