blob: 89789bce6cf4e51d7421178d7aef8ad873790e1d [file] [log] [blame]
/*
* 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.annotation.IntDef;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Path;
import android.graphics.Rect;
import android.util.ArraySet;
import android.util.MutableFloat;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.ViewDebug;
import com.android.systemui.R;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.RecentsDebugFlags;
import com.android.systemui.recents.misc.FreePathInterpolator;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
/**
* Used to describe a visible range that can be normalized to [0, 1].
*/
class Range {
final float relativeMin;
final float relativeMax;
float origin;
float min;
float max;
public Range(float relMin, float relMax) {
min = relativeMin = relMin;
max = relativeMax = relMax;
}
/**
* Offsets this range to a given absolute position.
*/
public void offset(float x) {
this.origin = x;
min = x + relativeMin;
max = x + relativeMax;
}
/**
* Returns x normalized to the range 0 to 1 such that 0 = min, 0.5 = origin and 1 = max
*
* @param x is an absolute value in the same domain as origin
*/
public float getNormalizedX(float x) {
if (x < origin) {
return 0.5f + 0.5f * (x - origin) / -relativeMin;
} else {
return 0.5f + 0.5f * (x - origin) / relativeMax;
}
}
/**
* Given a normalized {@param x} value in this range, projected onto the full range to get an
* absolute value about the given {@param origin}.
*/
public float getAbsoluteX(float normX) {
if (normX < 0.5f) {
return (normX - 0.5f) / 0.5f * -relativeMin;
} else {
return (normX - 0.5f) / 0.5f * relativeMax;
}
}
/**
* Returns whether a value at an absolute x would be within range.
*/
public boolean isInRange(float absX) {
return (absX >= Math.floor(min)) && (absX <= Math.ceil(max));
}
}
/**
* The layout logic for a TaskStackView. This layout needs to be able to calculate the stack layout
* without an activity-specific context only with the information passed in. This layout can have
* two states focused and unfocused, and in the focused state, there is a task that is displayed
* more prominently in the stack.
*/
public class TaskStackLayoutAlgorithm {
private static final String TAG = "TaskStackLayoutAlgorithm";
// The distribution of view bounds alpha
// XXX: This is a hack because you can currently set the max alpha to be > 1f
public static final float OUTLINE_ALPHA_MIN_VALUE = 0f;
public static final float OUTLINE_ALPHA_MAX_VALUE = 2f;
// The medium/maximum dim on the tasks
private static final float MED_DIM = 0.15f;
private static final float MAX_DIM = 0.25f;
// The various focus states
public static final int STATE_FOCUSED = 1;
public static final int STATE_UNFOCUSED = 0;
// The side that an offset is anchored
@Retention(RetentionPolicy.SOURCE)
@IntDef({FROM_TOP, FROM_BOTTOM})
public @interface AnchorSide {}
private static final int FROM_TOP = 0;
private static final int FROM_BOTTOM = 1;
// The extent that we care about when calculating fractions
@Retention(RetentionPolicy.SOURCE)
@IntDef({WIDTH, HEIGHT})
public @interface Extent {}
private static final int WIDTH = 0;
private static final int HEIGHT = 1;
public interface TaskStackLayoutAlgorithmCallbacks {
void onFocusStateChanged(int prevFocusState, int curFocusState);
}
/**
* The various stack/freeform states.
*/
public static class StackState {
public static final StackState FREEFORM_ONLY = new StackState(1f, 255);
public static final StackState STACK_ONLY = new StackState(0f, 0);
public static final StackState SPLIT = new StackState(0.5f, 255);
public final float freeformHeightPct;
public final int freeformBackgroundAlpha;
/**
* @param freeformHeightPct the percentage of the stack height (not including paddings) to
* allocate to the freeform workspace
* @param freeformBackgroundAlpha the background alpha for the freeform workspace
*/
private StackState(float freeformHeightPct, int freeformBackgroundAlpha) {
this.freeformHeightPct = freeformHeightPct;
this.freeformBackgroundAlpha = freeformBackgroundAlpha;
}
/**
* Resolves the stack state for the layout given a task stack.
*/
public static StackState getStackStateForStack(TaskStack stack) {
SystemServicesProxy ssp = Recents.getSystemServices();
boolean hasFreeformWorkspaces = ssp.hasFreeformWorkspaceSupport();
int freeformCount = stack.getFreeformTaskCount();
int stackCount = stack.getStackTaskCount();
if (hasFreeformWorkspaces && stackCount > 0 && freeformCount > 0) {
return SPLIT;
} else if (hasFreeformWorkspaces && freeformCount > 0) {
return FREEFORM_ONLY;
} else {
return STACK_ONLY;
}
}
/**
* Computes the freeform and stack rect for this state.
*
* @param freeformRectOut the freeform rect to be written out
* @param stackRectOut the stack rect, we only write out the top of the stack
* @param taskStackBounds the full rect that the freeform rect can take up
*/
public void computeRects(Rect freeformRectOut, Rect stackRectOut,
Rect taskStackBounds, int topMargin, int freeformGap, int stackBottomOffset) {
// The freeform height is the visible height (not including system insets) - padding
// above freeform and below stack - gap between the freeform and stack
int availableHeight = taskStackBounds.height() - topMargin - stackBottomOffset;
int ffPaddedHeight = (int) (availableHeight * freeformHeightPct);
int ffHeight = Math.max(0, ffPaddedHeight - freeformGap);
freeformRectOut.set(taskStackBounds.left,
taskStackBounds.top + topMargin,
taskStackBounds.right,
taskStackBounds.top + topMargin + ffHeight);
stackRectOut.set(taskStackBounds.left,
taskStackBounds.top,
taskStackBounds.right,
taskStackBounds.bottom);
if (ffPaddedHeight > 0) {
stackRectOut.top += ffPaddedHeight;
} else {
stackRectOut.top += topMargin;
}
}
}
// A report of the visibility state of the stack
public class VisibilityReport {
public int numVisibleTasks;
public int numVisibleThumbnails;
/** Package level ctor */
VisibilityReport(int tasks, int thumbnails) {
numVisibleTasks = tasks;
numVisibleThumbnails = thumbnails;
}
}
Context mContext;
private StackState mState = StackState.SPLIT;
private TaskStackLayoutAlgorithmCallbacks mCb;
// The task bounds (untransformed) for layout. This rect is anchored at mTaskRoot.
@ViewDebug.ExportedProperty(category="recents")
public Rect mTaskRect = new Rect();
// The freeform workspace bounds, inset by the top system insets and is a fixed height
@ViewDebug.ExportedProperty(category="recents")
public Rect mFreeformRect = new Rect();
// The stack bounds, inset from the top system insets, and runs to the bottom of the screen
@ViewDebug.ExportedProperty(category="recents")
public Rect mStackRect = new Rect();
// 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;
// The base top margin for the stack from the system insets
@ViewDebug.ExportedProperty(category="recents")
private int mBaseTopMargin;
// The base side margin for the stack from the system insets
@ViewDebug.ExportedProperty(category="recents")
private int mBaseSideMargin;
// The base bottom margin for the stack from the system insets
@ViewDebug.ExportedProperty(category="recents")
private int mBaseBottomMargin;
private int mMinMargin;
// The gap between the freeform and stack layouts
@ViewDebug.ExportedProperty(category="recents")
private int mFreeformStackGap;
// The initial offset that the focused task is from the top
@ViewDebug.ExportedProperty(category="recents")
private int mInitialTopOffset;
private int mBaseInitialTopOffset;
// The initial offset that the launch-from task is from the bottom
@ViewDebug.ExportedProperty(category="recents")
private int mInitialBottomOffset;
private int mBaseInitialBottomOffset;
// The height between the top margin and the top of the focused task
@ViewDebug.ExportedProperty(category="recents")
private int mFocusedTopPeekHeight;
// The height between the bottom margin and the top of task in front of the focused task
@ViewDebug.ExportedProperty(category="recents")
private int mFocusedBottomPeekHeight;
// The offset from the bottom of the stack to the bottom of the bounds when the stack is
// scrolled to the front
@ViewDebug.ExportedProperty(category="recents")
private int mStackBottomOffset;
// The paths defining the motion of the tasks when the stack is focused and unfocused
private Path mUnfocusedCurve;
private Path mFocusedCurve;
private FreePathInterpolator mUnfocusedCurveInterpolator;
private FreePathInterpolator mFocusedCurveInterpolator;
// The paths defining the distribution of the dim to apply to tasks in the stack when focused
// and unfocused
private Path mUnfocusedDimCurve;
private Path mFocusedDimCurve;
private FreePathInterpolator mUnfocusedDimCurveInterpolator;
private FreePathInterpolator mFocusedDimCurveInterpolator;
// The state of the stack focus (0..1), which controls the transition of the stack from the
// focused to non-focused state
@ViewDebug.ExportedProperty(category="recents")
private int mFocusState;
// The smallest scroll progress, at this value, the back most task will be visible
@ViewDebug.ExportedProperty(category="recents")
float mMinScrollP;
// The largest scroll progress, at this value, the front most task will be visible above the
// navigation bar
@ViewDebug.ExportedProperty(category="recents")
float mMaxScrollP;
// The initial progress that the scroller is set when you first enter recents
@ViewDebug.ExportedProperty(category="recents")
float mInitialScrollP;
// The task progress for the front-most task in the stack
@ViewDebug.ExportedProperty(category="recents")
float mFrontMostTaskP;
// The last computed task counts
@ViewDebug.ExportedProperty(category="recents")
int mNumStackTasks;
@ViewDebug.ExportedProperty(category="recents")
int mNumFreeformTasks;
// The min/max z translations
@ViewDebug.ExportedProperty(category="recents")
int mMinTranslationZ;
@ViewDebug.ExportedProperty(category="recents")
int mMaxTranslationZ;
// Optimization, allows for quick lookup of task -> index
private SparseIntArray mTaskIndexMap = new SparseIntArray();
private SparseArray<Float> mTaskIndexOverrideMap = new SparseArray<>();
// The freeform workspace layout
FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm;
// The transform to place TaskViews at the front and back of the stack respectively
TaskViewTransform mBackOfStackTransform = new TaskViewTransform();
TaskViewTransform mFrontOfStackTransform = new TaskViewTransform();
public TaskStackLayoutAlgorithm(Context context, TaskStackLayoutAlgorithmCallbacks cb) {
Resources res = context.getResources();
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);
reloadOnConfigurationChange(context);
}
/**
* Reloads the layout for the current configuration.
*/
public void reloadOnConfigurationChange(Context context) {
Resources res = context.getResources();
mFocusedRange = new Range(res.getFloat(R.integer.recents_layout_focused_range_min),
res.getFloat(R.integer.recents_layout_focused_range_max));
mUnfocusedRange = new Range(res.getFloat(R.integer.recents_layout_unfocused_range_min),
res.getFloat(R.integer.recents_layout_unfocused_range_max));
mFocusState = getInitialFocusState();
mFocusedTopPeekHeight = res.getDimensionPixelSize(R.dimen.recents_layout_top_peek_size);
mFocusedBottomPeekHeight =
res.getDimensionPixelSize(R.dimen.recents_layout_bottom_peek_size);
mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_layout_z_min);
mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_layout_z_max);
mBaseInitialTopOffset = getDimensionForDevice(context,
R.dimen.recents_layout_initial_top_offset_phone_port,
R.dimen.recents_layout_initial_top_offset_phone_land,
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,
R.dimen.recents_layout_initial_bottom_offset_phone_land,
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);
}
/**
* Resets this layout when the stack view is reset.
*/
public void reset() {
mTaskIndexOverrideMap.clear();
setFocusState(getInitialFocusState());
}
/**
* Sets the system insets.
*/
public boolean setSystemInsets(Rect systemInsets) {
boolean changed = !mSystemInsets.equals(systemInsets);
mSystemInsets.set(systemInsets);
return changed;
}
/**
* Sets the focused state.
*/
public void setFocusState(int focusState) {
int prevFocusState = mFocusState;
mFocusState = focusState;
updateFrontBackTransforms();
if (mCb != null) {
mCb.onFocusStateChanged(prevFocusState, focusState);
}
}
/**
* Gets the focused state.
*/
public int getFocusState() {
return mFocusState;
}
/**
* Computes the stack and task rects. The given task stack bounds already has the top/right
* insets and left/right padding already applied.
*/
public void initialize(Rect displayRect, Rect windowRect, Rect taskStackBounds,
StackState state) {
Rect lastStackRect = new Rect(mStackRect);
int topMargin = getScaleForExtent(windowRect, displayRect, mBaseTopMargin, mMinMargin, HEIGHT);
int bottomMargin = getScaleForExtent(windowRect, displayRect, mBaseBottomMargin, mMinMargin,
HEIGHT);
mInitialTopOffset = getScaleForExtent(windowRect, displayRect, mBaseInitialTopOffset,
mMinMargin, HEIGHT);
mInitialBottomOffset = mBaseInitialBottomOffset;
// Compute the stack bounds
mState = state;
mStackBottomOffset = mSystemInsets.bottom + bottomMargin;
state.computeRects(mFreeformRect, mStackRect, taskStackBounds, topMargin,
mFreeformStackGap, mStackBottomOffset);
// The stack action button will take the full un-padded header space above the stack
mStackActionButtonRect.set(mStackRect.left, mStackRect.top - topMargin,
mStackRect.right, mStackRect.top + mFocusedTopPeekHeight);
// Anchor the task rect top aligned to the stack rect
int height = mStackRect.height() - mInitialTopOffset - mStackBottomOffset;
mTaskRect.set(mStackRect.left, mStackRect.top, mStackRect.right, mStackRect.top + height);
// Short circuit here if the stack rects haven't changed so we don't do all the work below
if (!lastStackRect.equals(mStackRect)) {
// Reinitialize the focused and unfocused curves
mUnfocusedCurve = constructUnfocusedCurve();
mUnfocusedCurveInterpolator = new FreePathInterpolator(mUnfocusedCurve);
mFocusedCurve = constructFocusedCurve();
mFocusedCurveInterpolator = new FreePathInterpolator(mFocusedCurve);
mUnfocusedDimCurve = constructUnfocusedDimCurve();
mUnfocusedDimCurveInterpolator = new FreePathInterpolator(mUnfocusedDimCurve);
mFocusedDimCurve = constructFocusedDimCurve();
mFocusedDimCurveInterpolator = new FreePathInterpolator(mFocusedDimCurve);
updateFrontBackTransforms();
}
}
/**
* Computes the minimum and maximum scroll progress values and the progress values for each task
* in the stack.
*/
void update(TaskStack stack, ArraySet<Task.TaskKey> ignoreTasksSet) {
SystemServicesProxy ssp = Recents.getSystemServices();
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
// Clear the progress map
mTaskIndexMap.clear();
// Return early if we have no tasks
ArrayList<Task> tasks = stack.getStackTasks();
if (tasks.isEmpty()) {
mFrontMostTaskP = 0;
mMinScrollP = mMaxScrollP = mInitialScrollP = 0;
mNumStackTasks = mNumFreeformTasks = 0;
return;
}
// Filter the set of freeform and stack tasks
ArrayList<Task> freeformTasks = new ArrayList<>();
ArrayList<Task> stackTasks = new ArrayList<>();
for (int i = 0; i < tasks.size(); i++) {
Task task = tasks.get(i);
if (ignoreTasksSet.contains(task.key)) {
continue;
}
if (task.isFreeformTask()) {
freeformTasks.add(task);
} else {
stackTasks.add(task);
}
}
mNumStackTasks = stackTasks.size();
mNumFreeformTasks = freeformTasks.size();
// Put each of the tasks in the progress map at a fixed index (does not need to actually
// map to a scroll position, just by index)
int taskCount = stackTasks.size();
for (int i = 0; i < taskCount; i++) {
Task task = stackTasks.get(i);
mTaskIndexMap.put(task.key.id, i);
}
// Update the freeform tasks
if (!freeformTasks.isEmpty()) {
mFreeformLayoutAlgorithm.update(freeformTasks, this);
}
// Calculate the min/max/initial scroll
Task launchTask = stack.getLaunchTarget();
int launchTaskIndex = launchTask != null
? stack.indexOfStackTask(launchTask)
: mNumStackTasks - 1;
if (getInitialFocusState() == STATE_FOCUSED) {
int maxBottomOffset = mStackBottomOffset + mTaskRect.height();
float maxBottomNormX = getNormalizedXFromFocusedY(maxBottomOffset, FROM_BOTTOM);
mFocusedRange.offset(0f);
mMinScrollP = 0;
mMaxScrollP = Math.max(mMinScrollP, (mNumStackTasks - 1) -
Math.max(0, mFocusedRange.getAbsoluteX(maxBottomNormX)));
if (launchState.launchedFromHome) {
mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
} else {
mInitialScrollP = Utilities.clamp(launchTaskIndex - 1, mMinScrollP, mMaxScrollP);
}
} else if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
// If there is one stack task, ignore the min/max/initial scroll positions
mMinScrollP = 0;
mMaxScrollP = 0;
mInitialScrollP = 0;
} else {
// Set the max scroll to be the point where the front most task is visible with the
// stack bottom offset
int maxBottomOffset = mStackBottomOffset + mTaskRect.height();
float maxBottomNormX = getNormalizedXFromUnfocusedY(maxBottomOffset, FROM_BOTTOM);
mUnfocusedRange.offset(0f);
mMinScrollP = 0;
mMaxScrollP = Math.max(mMinScrollP, (mNumStackTasks - 1) -
Math.max(0, mUnfocusedRange.getAbsoluteX(maxBottomNormX)));
boolean scrollToFront = launchState.launchedFromHome ||
launchState.launchedViaDockGesture;
if (launchState.launchedWithAltTab) {
mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
} else if (scrollToFront) {
mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
} else {
// We are overriding the initial two task positions, so set the initial scroll
// position to match the second task (aka focused task) position
float initialTopNormX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, (mNumStackTasks - 2))
- Math.max(0, mUnfocusedRange.getAbsoluteX(initialTopNormX)));
}
}
}
/**
* Creates task overrides to ensure the initial stack layout if necessary.
*/
public void setTaskOverridesForInitialState(TaskStack stack, boolean ignoreScrollToFront) {
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
mTaskIndexOverrideMap.clear();
boolean scrollToFront = launchState.launchedFromHome ||
launchState.launchedViaDockGesture;
if (getInitialFocusState() == STATE_UNFOCUSED && mNumStackTasks > 1) {
if (ignoreScrollToFront || (!launchState.launchedWithAltTab && !scrollToFront)) {
// Set the initial scroll to the predefined state (which differs from the stack)
float [] initialNormX = null;
float minBottomTaskNormX = getNormalizedXFromUnfocusedY(mSystemInsets.bottom +
mInitialBottomOffset, FROM_BOTTOM);
float maxBottomTaskNormX = getNormalizedXFromUnfocusedY(mFocusedTopPeekHeight +
mTaskRect.height() - mMinMargin, FROM_TOP);
if (mNumStackTasks <= 2) {
// For small stacks, position the tasks so that they are top aligned to under
// the action button, but ensure that it is at least a certain offset from the
// bottom of the stack
initialNormX = new float[] {
Math.min(maxBottomTaskNormX, minBottomTaskNormX),
getNormalizedXFromUnfocusedY(mFocusedTopPeekHeight, FROM_TOP)
};
} else {
initialNormX = new float[] {
minBottomTaskNormX,
getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP)
};
}
mUnfocusedRange.offset(0f);
List<Task> tasks = stack.getStackTasks();
int taskCount = tasks.size();
for (int i = taskCount - 1; i >= 0; i--) {
int indexFromFront = taskCount - i - 1;
if (indexFromFront >= initialNormX.length) {
break;
}
float newTaskProgress = mInitialScrollP +
mUnfocusedRange.getAbsoluteX(initialNormX[indexFromFront]);
mTaskIndexOverrideMap.put(tasks.get(i).key.id, newTaskProgress);
}
}
}
}
/**
* Adds and override task progress for the given task when transitioning from focused to
* unfocused state.
*/
public void addUnfocusedTaskOverride(Task task, float stackScroll) {
if (mFocusState != STATE_UNFOCUSED) {
mFocusedRange.offset(stackScroll);
mUnfocusedRange.offset(stackScroll);
float focusedRangeX = mFocusedRange.getNormalizedX(mTaskIndexMap.get(task.key.id));
float focusedY = mFocusedCurveInterpolator.getInterpolation(focusedRangeX);
float unfocusedRangeX = mUnfocusedCurveInterpolator.getX(focusedY);
float unfocusedTaskProgress = stackScroll + mUnfocusedRange.getAbsoluteX(unfocusedRangeX);
if (Float.compare(focusedRangeX, unfocusedRangeX) != 0) {
mTaskIndexOverrideMap.put(task.key.id, unfocusedTaskProgress);
}
}
}
/**
* Adds and override task progress for the given task when transitioning from focused to
* unfocused state.
*/
public void addUnfocusedTaskOverride(TaskView taskView, float stackScroll) {
mFocusedRange.offset(stackScroll);
mUnfocusedRange.offset(stackScroll);
Task task = taskView.getTask();
int top = taskView.getTop() - mTaskRect.top;
float focusedRangeX = getNormalizedXFromFocusedY(top, FROM_TOP);
float unfocusedRangeX = getNormalizedXFromUnfocusedY(top, FROM_TOP);
float unfocusedTaskProgress = stackScroll + mUnfocusedRange.getAbsoluteX(unfocusedRangeX);
if (Float.compare(focusedRangeX, unfocusedRangeX) != 0) {
mTaskIndexOverrideMap.put(task.key.id, unfocusedTaskProgress);
}
}
public void clearUnfocusedTaskOverrides() {
mTaskIndexOverrideMap.clear();
}
/**
* Updates this stack when a scroll happens.
*
*/
public float updateFocusStateOnScroll(float lastTargetStackScroll, float targetStackScroll,
float lastStackScroll) {
if (targetStackScroll == lastStackScroll) {
return targetStackScroll;
}
float deltaScroll = targetStackScroll - lastStackScroll;
float deltaTargetScroll = targetStackScroll - lastTargetStackScroll;
float newScroll = targetStackScroll;
mUnfocusedRange.offset(targetStackScroll);
for (int i = mTaskIndexOverrideMap.size() - 1; i >= 0; i--) {
int taskId = mTaskIndexOverrideMap.keyAt(i);
float x = mTaskIndexMap.get(taskId);
float overrideX = mTaskIndexOverrideMap.get(taskId, 0f);
float newOverrideX = overrideX + deltaScroll;
if (isInvalidOverrideX(x, overrideX, newOverrideX)) {
// Remove the override once we reach the original task index
mTaskIndexOverrideMap.removeAt(i);
} else if ((overrideX >= x && deltaScroll <= 0f) ||
(overrideX <= x && deltaScroll >= 0f)) {
// Scrolling from override x towards x, then lock the task in place
mTaskIndexOverrideMap.put(taskId, newOverrideX);
} else {
// Scrolling override x away from x, we should still move the scroll towards x
newScroll = lastStackScroll;
newOverrideX = overrideX - deltaTargetScroll;
if (isInvalidOverrideX(x, overrideX, newOverrideX)) {
mTaskIndexOverrideMap.removeAt(i);
} else{
mTaskIndexOverrideMap.put(taskId, newOverrideX);
}
}
}
return newScroll;
}
private boolean isInvalidOverrideX(float x, float overrideX, float newOverrideX) {
boolean outOfBounds = mUnfocusedRange.getNormalizedX(newOverrideX) < 0f ||
mUnfocusedRange.getNormalizedX(newOverrideX) > 1f;
return outOfBounds || (overrideX >= x && x >= newOverrideX) ||
(overrideX <= x && x <= newOverrideX);
}
/**
* Returns the default focus state.
*/
public int getInitialFocusState() {
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
RecentsDebugFlags debugFlags = Recents.getDebugFlags();
if (debugFlags.isPagingEnabled() || launchState.launchedWithAltTab) {
return STATE_FOCUSED;
} else {
return STATE_UNFOCUSED;
}
}
/**
* Returns the TaskViewTransform that would put the task just off the back of the stack.
*/
public TaskViewTransform getBackOfStackTransform() {
return mBackOfStackTransform;
}
/**
* Returns the TaskViewTransform that would put the task just off the front of the stack.
*/
public TaskViewTransform getFrontOfStackTransform() {
return mFrontOfStackTransform;
}
/**
* Returns the current stack state.
*/
public StackState getStackState() {
return mState;
}
/**
* Returns whether this stack layout has been initialized.
*/
public boolean isInitialized() {
return !mStackRect.isEmpty();
}
/**
* Computes the maximum number of visible tasks and thumbnails when the scroll is at the initial
* stack scroll. Requires that update() is called first.
*/
public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
// Ensure minimum visibility count
if (tasks.size() <= 1) {
return new VisibilityReport(1, 1);
}
// Quick return when there are no stack tasks
if (mNumStackTasks == 0) {
return new VisibilityReport(Math.max(mNumFreeformTasks, 1),
Math.max(mNumFreeformTasks, 1));
}
// Otherwise, walk backwards in the stack and count the number of tasks and visible
// thumbnails and add that to the total freeform task count
TaskViewTransform tmpTransform = new TaskViewTransform();
Range currentRange = getInitialFocusState() > 0f ? mFocusedRange : mUnfocusedRange;
currentRange.offset(mInitialScrollP);
int taskBarHeight = mContext.getResources().getDimensionPixelSize(
R.dimen.recents_task_view_header_height);
int numVisibleTasks = Math.max(mNumFreeformTasks, 1);
int numVisibleThumbnails = Math.max(mNumFreeformTasks, 1);
float prevScreenY = Integer.MAX_VALUE;
for (int i = tasks.size() - 1; i >= 0; i--) {
Task task = tasks.get(i);
// Skip freeform
if (task.isFreeformTask()) {
continue;
}
// Skip invisible
float taskProgress = getStackScrollForTask(task);
if (!currentRange.isInRange(taskProgress)) {
continue;
}
boolean isFrontMostTaskInGroup = task.group == null || task.group.isFrontMostTask(task);
if (isFrontMostTaskInGroup) {
getStackTransform(taskProgress, taskProgress, mInitialScrollP, mFocusState,
tmpTransform, null, false /* ignoreSingleTaskCase */,
false /* forceUpdate */);
float screenY = tmpTransform.rect.top;
boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
if (hasVisibleThumbnail) {
numVisibleThumbnails++;
numVisibleTasks++;
prevScreenY = screenY;
} else {
// Once we hit the next front most task that does not have a visible thumbnail,
// walk through remaining visible set
for (int j = i; j >= 0; j--) {
numVisibleTasks++;
taskProgress = getStackScrollForTask(tasks.get(j));
if (!currentRange.isInRange(taskProgress)) {
continue;
}
}
break;
}
} else if (!isFrontMostTaskInGroup) {
// Affiliated task, no thumbnail
numVisibleTasks++;
}
}
return new VisibilityReport(numVisibleTasks, numVisibleThumbnails);
}
/**
* Returns the transform for the given task. This transform is relative to the mTaskRect, which
* is what the view is measured and laid out with.
*/
public TaskViewTransform getStackTransform(Task task, float stackScroll,
TaskViewTransform transformOut, TaskViewTransform frontTransform) {
return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
false /* forceUpdate */, false /* ignoreTaskOverrides */);
}
public TaskViewTransform getStackTransform(Task task, float stackScroll,
TaskViewTransform transformOut, TaskViewTransform frontTransform,
boolean ignoreTaskOverrides) {
return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
false /* forceUpdate */, ignoreTaskOverrides);
}
public TaskViewTransform getStackTransform(Task task, float stackScroll, int focusState,
TaskViewTransform transformOut, TaskViewTransform frontTransform, boolean forceUpdate,
boolean ignoreTaskOverrides) {
if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) {
mFreeformLayoutAlgorithm.getTransform(task, transformOut, this);
return transformOut;
} else {
// Return early if we have an invalid index
int nonOverrideTaskProgress = mTaskIndexMap.get(task.key.id, -1);
if (task == null || nonOverrideTaskProgress == -1) {
transformOut.reset();
return transformOut;
}
float taskProgress = ignoreTaskOverrides
? nonOverrideTaskProgress
: getStackScrollForTask(task);
getStackTransform(taskProgress, nonOverrideTaskProgress, stackScroll, focusState,
transformOut, frontTransform, false /* ignoreSingleTaskCase */, forceUpdate);
return transformOut;
}
}
/**
* Like {@link #getStackTransform}, but in screen coordinates
*/
public TaskViewTransform getStackTransformScreenCoordinates(Task task, float stackScroll,
TaskViewTransform transformOut, TaskViewTransform frontTransform,
Rect windowOverrideRect) {
TaskViewTransform transform = getStackTransform(task, stackScroll, mFocusState,
transformOut, frontTransform, true /* forceUpdate */,
false /* ignoreTaskOverrides */);
return transformToScreenCoordinates(transform, windowOverrideRect);
}
/**
* Transforms the given {@param transformOut} to the screen coordinates, overriding the current
* window rectangle with {@param windowOverrideRect} if non-null.
*/
public TaskViewTransform transformToScreenCoordinates(TaskViewTransform transformOut,
Rect windowOverrideRect) {
Rect windowRect = windowOverrideRect != null
? windowOverrideRect
: Recents.getSystemServices().getWindowRect();
transformOut.rect.offset(windowRect.left, windowRect.top);
return transformOut;
}
/**
* Update/get the transform.
*
* @param ignoreSingleTaskCase When set, will ensure that the transform computed does not take
* into account the special single-task case. This is only used
* internally to ensure that we can calculate the transform for any
* position in the stack.
*/
public void getStackTransform(float taskProgress, float nonOverrideTaskProgress,
float stackScroll, int focusState, TaskViewTransform transformOut,
TaskViewTransform frontTransform, boolean ignoreSingleTaskCase, boolean forceUpdate) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Ensure that the task is in range
mUnfocusedRange.offset(stackScroll);
mFocusedRange.offset(stackScroll);
boolean unfocusedVisible = mUnfocusedRange.isInRange(taskProgress);
boolean focusedVisible = mFocusedRange.isInRange(taskProgress);
// Skip if the task is not visible
if (!forceUpdate && !unfocusedVisible && !focusedVisible) {
transformOut.reset();
return;
}
// Map the absolute task progress to the normalized x at the stack scroll. We use this to
// calculate positions along the curve.
mUnfocusedRange.offset(stackScroll);
mFocusedRange.offset(stackScroll);
float unfocusedRangeX = mUnfocusedRange.getNormalizedX(taskProgress);
float focusedRangeX = mFocusedRange.getNormalizedX(taskProgress);
// Map the absolute task progress to the normalized x at the bounded stack scroll. We use
// this to calculate bounded properties, like translationZ and outline alpha.
float boundedStackScroll = Utilities.clamp(stackScroll, mMinScrollP, mMaxScrollP);
mUnfocusedRange.offset(boundedStackScroll);
mFocusedRange.offset(boundedStackScroll);
float boundedScrollUnfocusedRangeX = mUnfocusedRange.getNormalizedX(taskProgress);
float boundedScrollUnfocusedNonOverrideRangeX =
mUnfocusedRange.getNormalizedX(nonOverrideTaskProgress);
// Map the absolute task progress to the normalized x at the upper bounded stack scroll.
// We use this to calculate the dim, which is bounded only on one end.
float lowerBoundedStackScroll = Utilities.clamp(stackScroll, -Float.MAX_VALUE, mMaxScrollP);
mUnfocusedRange.offset(lowerBoundedStackScroll);
mFocusedRange.offset(lowerBoundedStackScroll);
float lowerBoundedUnfocusedRangeX = mUnfocusedRange.getNormalizedX(taskProgress);
float lowerBoundedFocusedRangeX = mFocusedRange.getNormalizedX(taskProgress);
int x = (mStackRect.width() - mTaskRect.width()) / 2;
int y;
float z;
float dimAlpha;
float viewOutlineAlpha;
if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1 && !ignoreSingleTaskCase) {
// When there is exactly one task, then decouple the task from the stack and just move
// in screen space
float tmpP = (mMinScrollP - stackScroll) / mNumStackTasks;
int centerYOffset = (mStackRect.top - mTaskRect.top) +
(mStackRect.height() - mSystemInsets.bottom - mTaskRect.height()) / 2;
y = centerYOffset + getYForDeltaP(tmpP, 0);
z = mMaxTranslationZ;
dimAlpha = 0f;
viewOutlineAlpha = OUTLINE_ALPHA_MIN_VALUE +
(OUTLINE_ALPHA_MAX_VALUE - OUTLINE_ALPHA_MIN_VALUE) / 2f;
} else {
// Otherwise, update the task to the stack layout
int unfocusedY = (int) ((1f - mUnfocusedCurveInterpolator.getInterpolation(
unfocusedRangeX)) * mStackRect.height());
int focusedY = (int) ((1f - mFocusedCurveInterpolator.getInterpolation(
focusedRangeX)) * mStackRect.height());
float unfocusedDim = mUnfocusedDimCurveInterpolator.getInterpolation(
lowerBoundedUnfocusedRangeX);
float focusedDim = mFocusedDimCurveInterpolator.getInterpolation(
lowerBoundedFocusedRangeX);
// Special case, because we override the initial task positions differently for small
// stacks, we clamp the dim to 0 in the initial position, and then only modulate the
// dim when the task is scrolled back towards the top of the screen
if (mNumStackTasks <= 2 && nonOverrideTaskProgress == 0f) {
if (boundedScrollUnfocusedRangeX >= 0.5f) {
unfocusedDim = 0f;
} else {
float offset = mUnfocusedDimCurveInterpolator.getInterpolation(0.5f);
unfocusedDim -= offset;
unfocusedDim *= MAX_DIM / (MAX_DIM - offset);
}
}
y = (mStackRect.top - mTaskRect.top) +
(int) Utilities.mapRange(focusState, unfocusedY, focusedY);
z = Utilities.mapRange(Utilities.clamp01(boundedScrollUnfocusedNonOverrideRangeX),
mMinTranslationZ, mMaxTranslationZ);
dimAlpha = Utilities.mapRange(focusState, unfocusedDim, focusedDim);
viewOutlineAlpha = Utilities.mapRange(Utilities.clamp01(boundedScrollUnfocusedRangeX),
OUTLINE_ALPHA_MIN_VALUE, OUTLINE_ALPHA_MAX_VALUE);
}
// Fill out the transform
transformOut.scale = 1f;
transformOut.alpha = 1f;
transformOut.translationZ = z;
transformOut.dimAlpha = dimAlpha;
transformOut.viewOutlineAlpha = viewOutlineAlpha;
transformOut.rect.set(mTaskRect);
transformOut.rect.offset(x, y);
Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
transformOut.visible = (transformOut.rect.top < mStackRect.bottom) &&
(frontTransform == null || transformOut.rect.top != frontTransform.rect.top);
}
/**
* Returns the untransformed task view bounds.
*/
public Rect getUntransformedTaskViewBounds() {
return new Rect(mTaskRect);
}
/**
* Returns the scroll progress to scroll to such that the top of the task is at the top of the
* stack.
*/
float getStackScrollForTask(Task t) {
Float overrideP = mTaskIndexOverrideMap.get(t.key.id, null);
if (overrideP == null) {
return (float) mTaskIndexMap.get(t.key.id, 0);
}
return overrideP;
}
/**
* Returns the original scroll progress to scroll to such that the top of the task is at the top
* of the stack.
*/
float getStackScrollForTaskIgnoreOverrides(Task t) {
return (float) mTaskIndexMap.get(t.key.id, 0);
}
/**
* Returns the scroll progress to scroll to such that the top of the task at the initial top
* offset (which is at the task's brightest point).
*/
float getStackScrollForTaskAtInitialOffset(Task t) {
float normX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
mUnfocusedRange.offset(0f);
return Utilities.clamp((float) mTaskIndexMap.get(t.key.id, 0) - Math.max(0,
mUnfocusedRange.getAbsoluteX(normX)), mMinScrollP, mMaxScrollP);
}
/**
* Maps a movement in screen y, relative to {@param downY}, to a movement in along the arc
* length of the curve. We know the curve is mostly flat, so we just map the length of the
* screen along the arc-length proportionally (1/arclength).
*/
public float getDeltaPForY(int downY, int y) {
float deltaP = (float) (y - downY) / mStackRect.height() *
mUnfocusedCurveInterpolator.getArcLength();
return -deltaP;
}
/**
* This is the inverse of {@link #getDeltaPForY}. Given a movement along the arc length
* of the curve, map back to the screen y.
*/
public int getYForDeltaP(float downScrollP, float p) {
int y = (int) ((p - downScrollP) * mStackRect.height() *
(1f / mUnfocusedCurveInterpolator.getArcLength()));
return -y;
}
/**
* Returns the task stack bounds in the current orientation. This rect takes into account the
* top and right system insets (but not the bottom inset) and left/right paddings, but _not_
* the top/bottom padding or insets.
*/
public void getTaskStackBounds(Rect displayRect, Rect windowRect, int topInset, int leftInset,
int rightInset, Rect taskStackBounds) {
taskStackBounds.set(windowRect.left + leftInset, windowRect.top + topInset,
windowRect.right - rightInset, windowRect.bottom);
// Ensure that the new width is at most the smaller display edge size
int sideMargin = getScaleForExtent(windowRect, displayRect, mBaseSideMargin, mMinMargin,
WIDTH);
int targetStackWidth = taskStackBounds.width() - 2 * sideMargin;
if (Utilities.getAppConfiguration(mContext).orientation
== Configuration.ORIENTATION_LANDSCAPE) {
// If we are in landscape, calculate the width of the stack in portrait and ensure that
// we are not larger than that size
Rect portraitDisplayRect = new Rect(0, 0,
Math.min(displayRect.width(), displayRect.height()),
Math.max(displayRect.width(), displayRect.height()));
int portraitSideMargin = getScaleForExtent(portraitDisplayRect, portraitDisplayRect,
mBaseSideMargin, mMinMargin, WIDTH);
targetStackWidth = Math.min(targetStackWidth,
portraitDisplayRect.width() - 2 * portraitSideMargin);
}
taskStackBounds.inset((taskStackBounds.width() - targetStackWidth) / 2, 0);
}
/**
* 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) {
return getDimensionForDevice(ctx, phoneResId, phoneResId, tabletResId, tabletResId,
xlargeTabletResId, xlargeTabletResId);
}
/**
* Retrieves resources that are constant regardless of the current configuration of the device.
*/
public static int getDimensionForDevice(Context ctx, int phonePortResId, int phoneLandResId,
int tabletPortResId, int tabletLandResId, int xlargeTabletPortResId,
int xlargeTabletLandResId) {
RecentsConfiguration config = Recents.getConfiguration();
Resources res = ctx.getResources();
boolean isLandscape = Utilities.getAppConfiguration(ctx).orientation ==
Configuration.ORIENTATION_LANDSCAPE;
if (config.isXLargeScreen) {
return res.getDimensionPixelSize(isLandscape
? xlargeTabletLandResId
: xlargeTabletPortResId);
} else if (config.isLargeScreen) {
return res.getDimensionPixelSize(isLandscape
? tabletLandResId
: tabletPortResId);
} else {
return res.getDimensionPixelSize(isLandscape
? phoneLandResId
: phonePortResId);
}
}
/**
* Returns the normalized x on the unfocused curve given an absolute Y position (relative to the
* stack height).
*/
private float getNormalizedXFromUnfocusedY(float y, @AnchorSide int fromSide) {
float offset = (fromSide == FROM_TOP)
? mStackRect.height() - y
: y;
float offsetPct = offset / mStackRect.height();
return mUnfocusedCurveInterpolator.getX(offsetPct);
}
/**
* Returns the normalized x on the focused curve given an absolute Y position (relative to the
* stack height).
*/
private float getNormalizedXFromFocusedY(float y, @AnchorSide int fromSide) {
float offset = (fromSide == FROM_TOP)
? mStackRect.height() - y
: y;
float offsetPct = offset / mStackRect.height();
return mFocusedCurveInterpolator.getX(offsetPct);
}
/**
* Creates a new path for the focused curve.
*/
private Path constructFocusedCurve() {
// Initialize the focused curve. This curve is a piecewise curve composed of several
// linear pieces that goes from (0,1) through (0.5, peek height offset),
// (0.5, bottom task offsets), and (1,0).
float topPeekHeightPct = (float) mFocusedTopPeekHeight / mStackRect.height();
float bottomPeekHeightPct = (float) (mStackBottomOffset + mFocusedBottomPeekHeight) /
mStackRect.height();
float minBottomPeekHeightPct = (float) (mFocusedTopPeekHeight + mTaskRect.height() -
mMinMargin) / mStackRect.height();
Path p = new Path();
p.moveTo(0f, 1f);
p.lineTo(0.5f, 1f - topPeekHeightPct);
p.lineTo(1f - (0.5f / mFocusedRange.relativeMax), Math.max(1f - minBottomPeekHeightPct,
bottomPeekHeightPct));
p.lineTo(1f, 0f);
return p;
}
/**
* Creates a new path for the unfocused curve.
*/
private Path constructUnfocusedCurve() {
// Initialize the unfocused curve. This curve is a piecewise curve composed of two quadradic
// beziers that goes from (0,1) through (0.5, peek height offset) and ends at (1,0). This
// ensures that we match the range, at which 0.5 represents the stack scroll at the current
// task progress. Because the height offset can change depending on a resource, we compute
// the control point of the second bezier such that between it and a first known point,
// there is a tangent at (0.5, peek height offset).
float cpoint1X = 0.4f;
float cpoint1Y = 0.975f;
float topPeekHeightPct = (float) mFocusedTopPeekHeight / mStackRect.height();
float slope = ((1f - topPeekHeightPct) - cpoint1Y) / (0.5f - cpoint1X);
float b = 1f - slope * cpoint1X;
float cpoint2X = 0.65f;
float cpoint2Y = slope * cpoint2X + b;
Path p = new Path();
p.moveTo(0f, 1f);
p.cubicTo(0f, 1f, cpoint1X, cpoint1Y, 0.5f, 1f - topPeekHeightPct);
p.cubicTo(0.5f, 1f - topPeekHeightPct, cpoint2X, cpoint2Y, 1f, 0f);
return p;
}
/**
* Creates a new path for the focused dim curve.
*/
private Path constructFocusedDimCurve() {
Path p = new Path();
// The focused dim interpolator starts at max dim, reduces to zero at 0.5 (the focused
// task), then goes back to max dim at the next task
p.moveTo(0f, MAX_DIM);
p.lineTo(0.5f, 0f);
p.lineTo(0.5f + (0.5f / mFocusedRange.relativeMax), MAX_DIM);
p.lineTo(1f, MAX_DIM);
return p;
}
/**
* Creates a new path for the unfocused dim curve.
*/
private Path constructUnfocusedDimCurve() {
float focusX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
float cpoint2X = focusX + (1f - focusX) / 2;
Path p = new Path();
// The unfocused dim interpolator starts at max dim, reduces to zero at 0.5 (the focused
// task), then goes back to max dim towards the front of the stack
p.moveTo(0f, MAX_DIM);
p.cubicTo(focusX * 0.5f, MAX_DIM, focusX * 0.75f, MAX_DIM * 0.75f, focusX, 0f);
p.cubicTo(cpoint2X, 0f, cpoint2X, MED_DIM, 1f, MED_DIM);
return p;
}
/**
* Scales the given {@param value} to the scale of the {@param instance} rect relative to the
* {@param other} rect in the {@param extent} side.
*/
private int getScaleForExtent(Rect instance, Rect other, int value, int minValue,
@Extent int extent) {
if (extent == WIDTH) {
float scale = Utilities.clamp01((float) instance.width() / other.width());
return Math.max(minValue, (int) (scale * value));
} else if (extent == HEIGHT) {
float scale = Utilities.clamp01((float) instance.height() / other.height());
return Math.max(minValue, (int) (scale * value));
}
return value;
}
/**
* Updates the current transforms that would put a TaskView at the front and back of the stack.
*/
private void updateFrontBackTransforms() {
// Return early if we have not yet initialized
if (mStackRect.isEmpty()) {
return;
}
float min = Utilities.mapRange(mFocusState, mUnfocusedRange.relativeMin,
mFocusedRange.relativeMin);
float max = Utilities.mapRange(mFocusState, mUnfocusedRange.relativeMax,
mFocusedRange.relativeMax);
getStackTransform(min, min, 0f, mFocusState, mBackOfStackTransform, null,
true /* ignoreSingleTaskCase */, true /* forceUpdate */);
getStackTransform(max, max, 0f, mFocusState, mFrontOfStackTransform, null,
true /* ignoreSingleTaskCase */, true /* forceUpdate */);
mBackOfStackTransform.visible = true;
mFrontOfStackTransform.visible = true;
}
public void dump(String prefix, PrintWriter writer) {
String innerPrefix = prefix + " ";
writer.print(prefix); writer.print(TAG);
writer.write(" numStackTasks="); writer.write(mNumStackTasks);
writer.println();
writer.print(innerPrefix);
writer.print("insets="); writer.print(Utilities.dumpRect(mSystemInsets));
writer.print(" stack="); writer.print(Utilities.dumpRect(mStackRect));
writer.print(" task="); writer.print(Utilities.dumpRect(mTaskRect));
writer.print(" freeform="); writer.print(Utilities.dumpRect(mFreeformRect));
writer.print(" actionButton="); writer.print(Utilities.dumpRect(mStackActionButtonRect));
writer.println();
writer.print(innerPrefix);
writer.print("minScroll="); writer.print(mMinScrollP);
writer.print(" maxScroll="); writer.print(mMaxScrollP);
writer.print(" initialScroll="); writer.print(mInitialScrollP);
writer.println();
writer.print(innerPrefix);
writer.print("focusState="); writer.print(mFocusState);
writer.println();
if (mTaskIndexOverrideMap.size() > 0) {
for (int i = mTaskIndexOverrideMap.size() - 1; i >= 0; i--) {
int taskId = mTaskIndexOverrideMap.keyAt(i);
float x = mTaskIndexMap.get(taskId);
float overrideX = mTaskIndexOverrideMap.get(taskId, 0f);
writer.print(innerPrefix);
writer.print("taskId= "); writer.print(taskId);
writer.print(" x= "); writer.print(x);
writer.print(" overrideX= "); writer.print(overrideX);
writer.println();
}
}
}
}