2 * Copyright (C) 2014 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.systemui.recents.views;
19 import android.annotation.IntDef;
20 import android.content.Context;
21 import android.content.res.Configuration;
22 import android.content.res.Resources;
23 import android.graphics.Path;
24 import android.graphics.Rect;
25 import android.util.ArraySet;
26 import android.util.MutableFloat;
27 import android.util.SparseArray;
28 import android.util.SparseIntArray;
29 import android.view.ViewDebug;
31 import com.android.systemui.R;
32 import com.android.systemui.recents.Recents;
33 import com.android.systemui.recents.RecentsActivityLaunchState;
34 import com.android.systemui.recents.RecentsConfiguration;
35 import com.android.systemui.recents.RecentsDebugFlags;
36 import com.android.systemui.recents.misc.FreePathInterpolator;
37 import com.android.systemui.recents.misc.SystemServicesProxy;
38 import com.android.systemui.recents.misc.Utilities;
39 import com.android.systemui.recents.model.Task;
40 import com.android.systemui.recents.model.TaskStack;
42 import java.io.PrintWriter;
43 import java.lang.annotation.Retention;
44 import java.lang.annotation.RetentionPolicy;
45 import java.util.ArrayList;
46 import java.util.List;
49 * Used to describe a visible range that can be normalized to [0, 1].
52 final float relativeMin;
53 final float relativeMax;
58 public Range(float relMin, float relMax) {
59 min = relativeMin = relMin;
60 max = relativeMax = relMax;
64 * Offsets this range to a given absolute position.
66 public void offset(float x) {
68 min = x + relativeMin;
69 max = x + relativeMax;
73 * Returns x normalized to the range 0 to 1 such that 0 = min, 0.5 = origin and 1 = max
75 * @param x is an absolute value in the same domain as origin
77 public float getNormalizedX(float x) {
79 return 0.5f + 0.5f * (x - origin) / -relativeMin;
81 return 0.5f + 0.5f * (x - origin) / relativeMax;
86 * Given a normalized {@param x} value in this range, projected onto the full range to get an
87 * absolute value about the given {@param origin}.
89 public float getAbsoluteX(float normX) {
91 return (normX - 0.5f) / 0.5f * -relativeMin;
93 return (normX - 0.5f) / 0.5f * relativeMax;
98 * Returns whether a value at an absolute x would be within range.
100 public boolean isInRange(float absX) {
101 return (absX >= Math.floor(min)) && (absX <= Math.ceil(max));
106 * The layout logic for a TaskStackView. This layout needs to be able to calculate the stack layout
107 * without an activity-specific context only with the information passed in. This layout can have
108 * two states focused and unfocused, and in the focused state, there is a task that is displayed
109 * more prominently in the stack.
111 public class TaskStackLayoutAlgorithm {
113 private static final String TAG = "TaskStackLayoutAlgorithm";
115 // The distribution of view bounds alpha
116 // XXX: This is a hack because you can currently set the max alpha to be > 1f
117 public static final float OUTLINE_ALPHA_MIN_VALUE = 0f;
118 public static final float OUTLINE_ALPHA_MAX_VALUE = 2f;
120 // The medium/maximum dim on the tasks
121 private static final float MED_DIM = 0.15f;
122 private static final float MAX_DIM = 0.25f;
124 // The various focus states
125 public static final int STATE_FOCUSED = 1;
126 public static final int STATE_UNFOCUSED = 0;
128 // The side that an offset is anchored
129 @Retention(RetentionPolicy.SOURCE)
130 @IntDef({FROM_TOP, FROM_BOTTOM})
131 public @interface AnchorSide {}
132 private static final int FROM_TOP = 0;
133 private static final int FROM_BOTTOM = 1;
135 // The extent that we care about when calculating fractions
136 @Retention(RetentionPolicy.SOURCE)
137 @IntDef({WIDTH, HEIGHT})
138 public @interface Extent {}
139 private static final int WIDTH = 0;
140 private static final int HEIGHT = 1;
142 public interface TaskStackLayoutAlgorithmCallbacks {
143 void onFocusStateChanged(int prevFocusState, int curFocusState);
147 * The various stack/freeform states.
149 public static class StackState {
151 public static final StackState FREEFORM_ONLY = new StackState(1f, 255);
152 public static final StackState STACK_ONLY = new StackState(0f, 0);
153 public static final StackState SPLIT = new StackState(0.5f, 255);
155 public final float freeformHeightPct;
156 public final int freeformBackgroundAlpha;
159 * @param freeformHeightPct the percentage of the stack height (not including paddings) to
160 * allocate to the freeform workspace
161 * @param freeformBackgroundAlpha the background alpha for the freeform workspace
163 private StackState(float freeformHeightPct, int freeformBackgroundAlpha) {
164 this.freeformHeightPct = freeformHeightPct;
165 this.freeformBackgroundAlpha = freeformBackgroundAlpha;
169 * Resolves the stack state for the layout given a task stack.
171 public static StackState getStackStateForStack(TaskStack stack) {
172 SystemServicesProxy ssp = Recents.getSystemServices();
173 boolean hasFreeformWorkspaces = ssp.hasFreeformWorkspaceSupport();
174 int freeformCount = stack.getFreeformTaskCount();
175 int stackCount = stack.getStackTaskCount();
176 if (hasFreeformWorkspaces && stackCount > 0 && freeformCount > 0) {
178 } else if (hasFreeformWorkspaces && freeformCount > 0) {
179 return FREEFORM_ONLY;
186 * Computes the freeform and stack rect for this state.
188 * @param freeformRectOut the freeform rect to be written out
189 * @param stackRectOut the stack rect, we only write out the top of the stack
190 * @param taskStackBounds the full rect that the freeform rect can take up
192 public void computeRects(Rect freeformRectOut, Rect stackRectOut,
193 Rect taskStackBounds, int topMargin, int freeformGap, int stackBottomOffset) {
194 // The freeform height is the visible height (not including system insets) - padding
195 // above freeform and below stack - gap between the freeform and stack
196 int availableHeight = taskStackBounds.height() - topMargin - stackBottomOffset;
197 int ffPaddedHeight = (int) (availableHeight * freeformHeightPct);
198 int ffHeight = Math.max(0, ffPaddedHeight - freeformGap);
199 freeformRectOut.set(taskStackBounds.left,
200 taskStackBounds.top + topMargin,
201 taskStackBounds.right,
202 taskStackBounds.top + topMargin + ffHeight);
203 stackRectOut.set(taskStackBounds.left,
205 taskStackBounds.right,
206 taskStackBounds.bottom);
207 if (ffPaddedHeight > 0) {
208 stackRectOut.top += ffPaddedHeight;
210 stackRectOut.top += topMargin;
215 // A report of the visibility state of the stack
216 public class VisibilityReport {
217 public int numVisibleTasks;
218 public int numVisibleThumbnails;
220 /** Package level ctor */
221 VisibilityReport(int tasks, int thumbnails) {
222 numVisibleTasks = tasks;
223 numVisibleThumbnails = thumbnails;
228 private StackState mState = StackState.SPLIT;
229 private TaskStackLayoutAlgorithmCallbacks mCb;
231 // The task bounds (untransformed) for layout. This rect is anchored at mTaskRoot.
232 @ViewDebug.ExportedProperty(category="recents")
233 public Rect mTaskRect = new Rect();
234 // The freeform workspace bounds, inset by the top system insets and is a fixed height
235 @ViewDebug.ExportedProperty(category="recents")
236 public Rect mFreeformRect = new Rect();
237 // The stack bounds, inset from the top system insets, and runs to the bottom of the screen
238 @ViewDebug.ExportedProperty(category="recents")
239 public Rect mStackRect = new Rect();
240 // This is the current system insets
241 @ViewDebug.ExportedProperty(category="recents")
242 public Rect mSystemInsets = new Rect();
243 // This is the bounds of the stack action above the stack rect
244 @ViewDebug.ExportedProperty(category="recents")
245 public Rect mStackActionButtonRect = new Rect();
247 // The visible ranges when the stack is focused and unfocused
248 private Range mUnfocusedRange;
249 private Range mFocusedRange;
251 // The base top margin for the stack from the system insets
252 @ViewDebug.ExportedProperty(category="recents")
253 private int mBaseTopMargin;
254 // The base side margin for the stack from the system insets
255 @ViewDebug.ExportedProperty(category="recents")
256 private int mBaseSideMargin;
257 // The base bottom margin for the stack from the system insets
258 @ViewDebug.ExportedProperty(category="recents")
259 private int mBaseBottomMargin;
260 private int mMinMargin;
262 // The gap between the freeform and stack layouts
263 @ViewDebug.ExportedProperty(category="recents")
264 private int mFreeformStackGap;
266 // The initial offset that the focused task is from the top
267 @ViewDebug.ExportedProperty(category="recents")
268 private int mInitialTopOffset;
269 private int mBaseInitialTopOffset;
270 // The initial offset that the launch-from task is from the bottom
271 @ViewDebug.ExportedProperty(category="recents")
272 private int mInitialBottomOffset;
273 private int mBaseInitialBottomOffset;
275 // The height between the top margin and the top of the focused task
276 @ViewDebug.ExportedProperty(category="recents")
277 private int mFocusedTopPeekHeight;
278 // The height between the bottom margin and the top of task in front of the focused task
279 @ViewDebug.ExportedProperty(category="recents")
280 private int mFocusedBottomPeekHeight;
282 // The offset from the bottom of the stack to the bottom of the bounds when the stack is
283 // scrolled to the front
284 @ViewDebug.ExportedProperty(category="recents")
285 private int mStackBottomOffset;
287 // The paths defining the motion of the tasks when the stack is focused and unfocused
288 private Path mUnfocusedCurve;
289 private Path mFocusedCurve;
290 private FreePathInterpolator mUnfocusedCurveInterpolator;
291 private FreePathInterpolator mFocusedCurveInterpolator;
293 // The paths defining the distribution of the dim to apply to tasks in the stack when focused
295 private Path mUnfocusedDimCurve;
296 private Path mFocusedDimCurve;
297 private FreePathInterpolator mUnfocusedDimCurveInterpolator;
298 private FreePathInterpolator mFocusedDimCurveInterpolator;
300 // The state of the stack focus (0..1), which controls the transition of the stack from the
301 // focused to non-focused state
302 @ViewDebug.ExportedProperty(category="recents")
303 private int mFocusState;
305 // The smallest scroll progress, at this value, the back most task will be visible
306 @ViewDebug.ExportedProperty(category="recents")
308 // The largest scroll progress, at this value, the front most task will be visible above the
310 @ViewDebug.ExportedProperty(category="recents")
312 // The initial progress that the scroller is set when you first enter recents
313 @ViewDebug.ExportedProperty(category="recents")
314 float mInitialScrollP;
315 // The task progress for the front-most task in the stack
316 @ViewDebug.ExportedProperty(category="recents")
317 float mFrontMostTaskP;
319 // The last computed task counts
320 @ViewDebug.ExportedProperty(category="recents")
322 @ViewDebug.ExportedProperty(category="recents")
323 int mNumFreeformTasks;
325 // The min/max z translations
326 @ViewDebug.ExportedProperty(category="recents")
327 int mMinTranslationZ;
328 @ViewDebug.ExportedProperty(category="recents")
329 int mMaxTranslationZ;
331 // Optimization, allows for quick lookup of task -> index
332 private SparseIntArray mTaskIndexMap = new SparseIntArray();
333 private SparseArray<Float> mTaskIndexOverrideMap = new SparseArray<>();
335 // The freeform workspace layout
336 FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm;
338 // The transform to place TaskViews at the front and back of the stack respectively
339 TaskViewTransform mBackOfStackTransform = new TaskViewTransform();
340 TaskViewTransform mFrontOfStackTransform = new TaskViewTransform();
342 public TaskStackLayoutAlgorithm(Context context, TaskStackLayoutAlgorithmCallbacks cb) {
343 Resources res = context.getResources();
346 mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm(context);
347 mMinMargin = res.getDimensionPixelSize(R.dimen.recents_layout_min_margin);
348 mBaseTopMargin = getDimensionForDevice(context,
349 R.dimen.recents_layout_top_margin_phone,
350 R.dimen.recents_layout_top_margin_tablet,
351 R.dimen.recents_layout_top_margin_tablet_xlarge);
352 mBaseSideMargin = getDimensionForDevice(context,
353 R.dimen.recents_layout_side_margin_phone,
354 R.dimen.recents_layout_side_margin_tablet,
355 R.dimen.recents_layout_side_margin_tablet_xlarge);
356 mBaseBottomMargin = res.getDimensionPixelSize(R.dimen.recents_layout_bottom_margin);
358 res.getDimensionPixelSize(R.dimen.recents_freeform_layout_bottom_margin);
360 reloadOnConfigurationChange(context);
364 * Reloads the layout for the current configuration.
366 public void reloadOnConfigurationChange(Context context) {
367 Resources res = context.getResources();
368 mFocusedRange = new Range(res.getFloat(R.integer.recents_layout_focused_range_min),
369 res.getFloat(R.integer.recents_layout_focused_range_max));
370 mUnfocusedRange = new Range(res.getFloat(R.integer.recents_layout_unfocused_range_min),
371 res.getFloat(R.integer.recents_layout_unfocused_range_max));
372 mFocusState = getInitialFocusState();
373 mFocusedTopPeekHeight = res.getDimensionPixelSize(R.dimen.recents_layout_top_peek_size);
374 mFocusedBottomPeekHeight =
375 res.getDimensionPixelSize(R.dimen.recents_layout_bottom_peek_size);
376 mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_layout_z_min);
377 mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_layout_z_max);
378 mBaseInitialTopOffset = getDimensionForDevice(context,
379 R.dimen.recents_layout_initial_top_offset_phone_port,
380 R.dimen.recents_layout_initial_top_offset_phone_land,
381 R.dimen.recents_layout_initial_top_offset_tablet,
382 R.dimen.recents_layout_initial_top_offset_tablet,
383 R.dimen.recents_layout_initial_top_offset_tablet,
384 R.dimen.recents_layout_initial_top_offset_tablet);
385 mBaseInitialBottomOffset = getDimensionForDevice(context,
386 R.dimen.recents_layout_initial_bottom_offset_phone_port,
387 R.dimen.recents_layout_initial_bottom_offset_phone_land,
388 R.dimen.recents_layout_initial_bottom_offset_tablet,
389 R.dimen.recents_layout_initial_bottom_offset_tablet,
390 R.dimen.recents_layout_initial_bottom_offset_tablet,
391 R.dimen.recents_layout_initial_bottom_offset_tablet);
392 mFreeformLayoutAlgorithm.reloadOnConfigurationChange(context);
396 * Resets this layout when the stack view is reset.
398 public void reset() {
399 mTaskIndexOverrideMap.clear();
400 setFocusState(getInitialFocusState());
404 * Sets the system insets.
406 public boolean setSystemInsets(Rect systemInsets) {
407 boolean changed = !mSystemInsets.equals(systemInsets);
408 mSystemInsets.set(systemInsets);
413 * Sets the focused state.
415 public void setFocusState(int focusState) {
416 int prevFocusState = mFocusState;
417 mFocusState = focusState;
418 updateFrontBackTransforms();
420 mCb.onFocusStateChanged(prevFocusState, focusState);
425 * Gets the focused state.
427 public int getFocusState() {
432 * Computes the stack and task rects. The given task stack bounds already has the top/right
433 * insets and left/right padding already applied.
435 public void initialize(Rect displayRect, Rect windowRect, Rect taskStackBounds,
437 Rect lastStackRect = new Rect(mStackRect);
439 int topMargin = getScaleForExtent(windowRect, displayRect, mBaseTopMargin, mMinMargin, HEIGHT);
440 int bottomMargin = getScaleForExtent(windowRect, displayRect, mBaseBottomMargin, mMinMargin,
442 mInitialTopOffset = getScaleForExtent(windowRect, displayRect, mBaseInitialTopOffset,
444 mInitialBottomOffset = mBaseInitialBottomOffset;
446 // Compute the stack bounds
448 mStackBottomOffset = mSystemInsets.bottom + bottomMargin;
449 state.computeRects(mFreeformRect, mStackRect, taskStackBounds, topMargin,
450 mFreeformStackGap, mStackBottomOffset);
452 // The stack action button will take the full un-padded header space above the stack
453 mStackActionButtonRect.set(mStackRect.left, mStackRect.top - topMargin,
454 mStackRect.right, mStackRect.top + mFocusedTopPeekHeight);
456 // Anchor the task rect top aligned to the stack rect
457 int height = mStackRect.height() - mInitialTopOffset - mStackBottomOffset;
458 mTaskRect.set(mStackRect.left, mStackRect.top, mStackRect.right, mStackRect.top + height);
460 // Short circuit here if the stack rects haven't changed so we don't do all the work below
461 if (!lastStackRect.equals(mStackRect)) {
462 // Reinitialize the focused and unfocused curves
463 mUnfocusedCurve = constructUnfocusedCurve();
464 mUnfocusedCurveInterpolator = new FreePathInterpolator(mUnfocusedCurve);
465 mFocusedCurve = constructFocusedCurve();
466 mFocusedCurveInterpolator = new FreePathInterpolator(mFocusedCurve);
467 mUnfocusedDimCurve = constructUnfocusedDimCurve();
468 mUnfocusedDimCurveInterpolator = new FreePathInterpolator(mUnfocusedDimCurve);
469 mFocusedDimCurve = constructFocusedDimCurve();
470 mFocusedDimCurveInterpolator = new FreePathInterpolator(mFocusedDimCurve);
472 updateFrontBackTransforms();
477 * Computes the minimum and maximum scroll progress values and the progress values for each task
480 void update(TaskStack stack, ArraySet<Task.TaskKey> ignoreTasksSet) {
481 SystemServicesProxy ssp = Recents.getSystemServices();
482 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
484 // Clear the progress map
485 mTaskIndexMap.clear();
487 // Return early if we have no tasks
488 ArrayList<Task> tasks = stack.getStackTasks();
489 if (tasks.isEmpty()) {
491 mMinScrollP = mMaxScrollP = mInitialScrollP = 0;
492 mNumStackTasks = mNumFreeformTasks = 0;
496 // Filter the set of freeform and stack tasks
497 ArrayList<Task> freeformTasks = new ArrayList<>();
498 ArrayList<Task> stackTasks = new ArrayList<>();
499 for (int i = 0; i < tasks.size(); i++) {
500 Task task = tasks.get(i);
501 if (ignoreTasksSet.contains(task.key)) {
504 if (task.isFreeformTask()) {
505 freeformTasks.add(task);
507 stackTasks.add(task);
510 mNumStackTasks = stackTasks.size();
511 mNumFreeformTasks = freeformTasks.size();
513 // Put each of the tasks in the progress map at a fixed index (does not need to actually
514 // map to a scroll position, just by index)
515 int taskCount = stackTasks.size();
516 for (int i = 0; i < taskCount; i++) {
517 Task task = stackTasks.get(i);
518 mTaskIndexMap.put(task.key.id, i);
521 // Update the freeform tasks
522 if (!freeformTasks.isEmpty()) {
523 mFreeformLayoutAlgorithm.update(freeformTasks, this);
526 // Calculate the min/max/initial scroll
527 Task launchTask = stack.getLaunchTarget();
528 int launchTaskIndex = launchTask != null
529 ? stack.indexOfStackTask(launchTask)
530 : mNumStackTasks - 1;
531 if (getInitialFocusState() == STATE_FOCUSED) {
532 int maxBottomOffset = mStackBottomOffset + mTaskRect.height();
533 float maxBottomNormX = getNormalizedXFromFocusedY(maxBottomOffset, FROM_BOTTOM);
534 mFocusedRange.offset(0f);
536 mMaxScrollP = Math.max(mMinScrollP, (mNumStackTasks - 1) -
537 Math.max(0, mFocusedRange.getAbsoluteX(maxBottomNormX)));
538 if (launchState.launchedFromHome) {
539 mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
541 mInitialScrollP = Utilities.clamp(launchTaskIndex - 1, mMinScrollP, mMaxScrollP);
543 } else if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
544 // If there is one stack task, ignore the min/max/initial scroll positions
549 // Set the max scroll to be the point where the front most task is visible with the
550 // stack bottom offset
551 int maxBottomOffset = mStackBottomOffset + mTaskRect.height();
552 float maxBottomNormX = getNormalizedXFromUnfocusedY(maxBottomOffset, FROM_BOTTOM);
553 mUnfocusedRange.offset(0f);
555 mMaxScrollP = Math.max(mMinScrollP, (mNumStackTasks - 1) -
556 Math.max(0, mUnfocusedRange.getAbsoluteX(maxBottomNormX)));
557 boolean scrollToFront = launchState.launchedFromHome ||
558 launchState.launchedViaDockGesture;
559 if (launchState.launchedWithAltTab) {
560 mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
561 } else if (scrollToFront) {
562 mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
564 // We are overriding the initial two task positions, so set the initial scroll
565 // position to match the second task (aka focused task) position
566 float initialTopNormX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
567 mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, (mNumStackTasks - 2))
568 - Math.max(0, mUnfocusedRange.getAbsoluteX(initialTopNormX)));
574 * Creates task overrides to ensure the initial stack layout if necessary.
576 public void setTaskOverridesForInitialState(TaskStack stack, boolean ignoreScrollToFront) {
577 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
579 mTaskIndexOverrideMap.clear();
581 boolean scrollToFront = launchState.launchedFromHome ||
582 launchState.launchedViaDockGesture;
583 if (getInitialFocusState() == STATE_UNFOCUSED && mNumStackTasks > 1) {
584 if (ignoreScrollToFront || (!launchState.launchedWithAltTab && !scrollToFront)) {
585 // Set the initial scroll to the predefined state (which differs from the stack)
586 float [] initialNormX = null;
587 float minBottomTaskNormX = getNormalizedXFromUnfocusedY(mSystemInsets.bottom +
588 mInitialBottomOffset, FROM_BOTTOM);
589 float maxBottomTaskNormX = getNormalizedXFromUnfocusedY(mFocusedTopPeekHeight +
590 mTaskRect.height() - mMinMargin, FROM_TOP);
591 if (mNumStackTasks <= 2) {
592 // For small stacks, position the tasks so that they are top aligned to under
593 // the action button, but ensure that it is at least a certain offset from the
594 // bottom of the stack
595 initialNormX = new float[] {
596 Math.min(maxBottomTaskNormX, minBottomTaskNormX),
597 getNormalizedXFromUnfocusedY(mFocusedTopPeekHeight, FROM_TOP)
600 initialNormX = new float[] {
602 getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP)
606 mUnfocusedRange.offset(0f);
607 List<Task> tasks = stack.getStackTasks();
608 int taskCount = tasks.size();
609 for (int i = taskCount - 1; i >= 0; i--) {
610 int indexFromFront = taskCount - i - 1;
611 if (indexFromFront >= initialNormX.length) {
614 float newTaskProgress = mInitialScrollP +
615 mUnfocusedRange.getAbsoluteX(initialNormX[indexFromFront]);
616 mTaskIndexOverrideMap.put(tasks.get(i).key.id, newTaskProgress);
623 * Adds and override task progress for the given task when transitioning from focused to
626 public void addUnfocusedTaskOverride(Task task, float stackScroll) {
627 if (mFocusState != STATE_UNFOCUSED) {
628 mFocusedRange.offset(stackScroll);
629 mUnfocusedRange.offset(stackScroll);
630 float focusedRangeX = mFocusedRange.getNormalizedX(mTaskIndexMap.get(task.key.id));
631 float focusedY = mFocusedCurveInterpolator.getInterpolation(focusedRangeX);
632 float unfocusedRangeX = mUnfocusedCurveInterpolator.getX(focusedY);
633 float unfocusedTaskProgress = stackScroll + mUnfocusedRange.getAbsoluteX(unfocusedRangeX);
634 if (Float.compare(focusedRangeX, unfocusedRangeX) != 0) {
635 mTaskIndexOverrideMap.put(task.key.id, unfocusedTaskProgress);
641 * Adds and override task progress for the given task when transitioning from focused to
644 public void addUnfocusedTaskOverride(TaskView taskView, float stackScroll) {
645 mFocusedRange.offset(stackScroll);
646 mUnfocusedRange.offset(stackScroll);
648 Task task = taskView.getTask();
649 int top = taskView.getTop() - mTaskRect.top;
650 float focusedRangeX = getNormalizedXFromFocusedY(top, FROM_TOP);
651 float unfocusedRangeX = getNormalizedXFromUnfocusedY(top, FROM_TOP);
652 float unfocusedTaskProgress = stackScroll + mUnfocusedRange.getAbsoluteX(unfocusedRangeX);
653 if (Float.compare(focusedRangeX, unfocusedRangeX) != 0) {
654 mTaskIndexOverrideMap.put(task.key.id, unfocusedTaskProgress);
658 public void clearUnfocusedTaskOverrides() {
659 mTaskIndexOverrideMap.clear();
663 * Updates this stack when a scroll happens.
666 public float updateFocusStateOnScroll(float lastTargetStackScroll, float targetStackScroll,
667 float lastStackScroll) {
668 if (targetStackScroll == lastStackScroll) {
669 return targetStackScroll;
672 float deltaScroll = targetStackScroll - lastStackScroll;
673 float deltaTargetScroll = targetStackScroll - lastTargetStackScroll;
674 float newScroll = targetStackScroll;
675 mUnfocusedRange.offset(targetStackScroll);
676 for (int i = mTaskIndexOverrideMap.size() - 1; i >= 0; i--) {
677 int taskId = mTaskIndexOverrideMap.keyAt(i);
678 float x = mTaskIndexMap.get(taskId);
679 float overrideX = mTaskIndexOverrideMap.get(taskId, 0f);
680 float newOverrideX = overrideX + deltaScroll;
681 if (isInvalidOverrideX(x, overrideX, newOverrideX)) {
682 // Remove the override once we reach the original task index
683 mTaskIndexOverrideMap.removeAt(i);
684 } else if ((overrideX >= x && deltaScroll <= 0f) ||
685 (overrideX <= x && deltaScroll >= 0f)) {
686 // Scrolling from override x towards x, then lock the task in place
687 mTaskIndexOverrideMap.put(taskId, newOverrideX);
689 // Scrolling override x away from x, we should still move the scroll towards x
690 newScroll = lastStackScroll;
691 newOverrideX = overrideX - deltaTargetScroll;
692 if (isInvalidOverrideX(x, overrideX, newOverrideX)) {
693 mTaskIndexOverrideMap.removeAt(i);
695 mTaskIndexOverrideMap.put(taskId, newOverrideX);
702 private boolean isInvalidOverrideX(float x, float overrideX, float newOverrideX) {
703 boolean outOfBounds = mUnfocusedRange.getNormalizedX(newOverrideX) < 0f ||
704 mUnfocusedRange.getNormalizedX(newOverrideX) > 1f;
705 return outOfBounds || (overrideX >= x && x >= newOverrideX) ||
706 (overrideX <= x && x <= newOverrideX);
710 * Returns the default focus state.
712 public int getInitialFocusState() {
713 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
714 RecentsDebugFlags debugFlags = Recents.getDebugFlags();
715 if (debugFlags.isPagingEnabled() || launchState.launchedWithAltTab) {
716 return STATE_FOCUSED;
718 return STATE_UNFOCUSED;
723 * Returns the TaskViewTransform that would put the task just off the back of the stack.
725 public TaskViewTransform getBackOfStackTransform() {
726 return mBackOfStackTransform;
730 * Returns the TaskViewTransform that would put the task just off the front of the stack.
732 public TaskViewTransform getFrontOfStackTransform() {
733 return mFrontOfStackTransform;
737 * Returns the current stack state.
739 public StackState getStackState() {
744 * Returns whether this stack layout has been initialized.
746 public boolean isInitialized() {
747 return !mStackRect.isEmpty();
751 * Computes the maximum number of visible tasks and thumbnails when the scroll is at the initial
752 * stack scroll. Requires that update() is called first.
754 public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
755 // Ensure minimum visibility count
756 if (tasks.size() <= 1) {
757 return new VisibilityReport(1, 1);
760 // Quick return when there are no stack tasks
761 if (mNumStackTasks == 0) {
762 return new VisibilityReport(Math.max(mNumFreeformTasks, 1),
763 Math.max(mNumFreeformTasks, 1));
766 // Otherwise, walk backwards in the stack and count the number of tasks and visible
767 // thumbnails and add that to the total freeform task count
768 TaskViewTransform tmpTransform = new TaskViewTransform();
769 Range currentRange = getInitialFocusState() > 0f ? mFocusedRange : mUnfocusedRange;
770 currentRange.offset(mInitialScrollP);
771 int taskBarHeight = mContext.getResources().getDimensionPixelSize(
772 R.dimen.recents_task_view_header_height);
773 int numVisibleTasks = Math.max(mNumFreeformTasks, 1);
774 int numVisibleThumbnails = Math.max(mNumFreeformTasks, 1);
775 float prevScreenY = Integer.MAX_VALUE;
776 for (int i = tasks.size() - 1; i >= 0; i--) {
777 Task task = tasks.get(i);
780 if (task.isFreeformTask()) {
785 float taskProgress = getStackScrollForTask(task);
786 if (!currentRange.isInRange(taskProgress)) {
790 boolean isFrontMostTaskInGroup = task.group == null || task.group.isFrontMostTask(task);
791 if (isFrontMostTaskInGroup) {
792 getStackTransform(taskProgress, taskProgress, mInitialScrollP, mFocusState,
793 tmpTransform, null, false /* ignoreSingleTaskCase */,
794 false /* forceUpdate */);
795 float screenY = tmpTransform.rect.top;
796 boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
797 if (hasVisibleThumbnail) {
798 numVisibleThumbnails++;
800 prevScreenY = screenY;
802 // Once we hit the next front most task that does not have a visible thumbnail,
803 // walk through remaining visible set
804 for (int j = i; j >= 0; j--) {
806 taskProgress = getStackScrollForTask(tasks.get(j));
807 if (!currentRange.isInRange(taskProgress)) {
813 } else if (!isFrontMostTaskInGroup) {
814 // Affiliated task, no thumbnail
818 return new VisibilityReport(numVisibleTasks, numVisibleThumbnails);
822 * Returns the transform for the given task. This transform is relative to the mTaskRect, which
823 * is what the view is measured and laid out with.
825 public TaskViewTransform getStackTransform(Task task, float stackScroll,
826 TaskViewTransform transformOut, TaskViewTransform frontTransform) {
827 return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
828 false /* forceUpdate */, false /* ignoreTaskOverrides */);
831 public TaskViewTransform getStackTransform(Task task, float stackScroll,
832 TaskViewTransform transformOut, TaskViewTransform frontTransform,
833 boolean ignoreTaskOverrides) {
834 return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
835 false /* forceUpdate */, ignoreTaskOverrides);
838 public TaskViewTransform getStackTransform(Task task, float stackScroll, int focusState,
839 TaskViewTransform transformOut, TaskViewTransform frontTransform, boolean forceUpdate,
840 boolean ignoreTaskOverrides) {
841 if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) {
842 mFreeformLayoutAlgorithm.getTransform(task, transformOut, this);
845 // Return early if we have an invalid index
846 int nonOverrideTaskProgress = mTaskIndexMap.get(task.key.id, -1);
847 if (task == null || nonOverrideTaskProgress == -1) {
848 transformOut.reset();
851 float taskProgress = ignoreTaskOverrides
852 ? nonOverrideTaskProgress
853 : getStackScrollForTask(task);
854 getStackTransform(taskProgress, nonOverrideTaskProgress, stackScroll, focusState,
855 transformOut, frontTransform, false /* ignoreSingleTaskCase */, forceUpdate);
861 * Like {@link #getStackTransform}, but in screen coordinates
863 public TaskViewTransform getStackTransformScreenCoordinates(Task task, float stackScroll,
864 TaskViewTransform transformOut, TaskViewTransform frontTransform,
865 Rect windowOverrideRect) {
866 TaskViewTransform transform = getStackTransform(task, stackScroll, mFocusState,
867 transformOut, frontTransform, true /* forceUpdate */,
868 false /* ignoreTaskOverrides */);
869 return transformToScreenCoordinates(transform, windowOverrideRect);
873 * Transforms the given {@param transformOut} to the screen coordinates, overriding the current
874 * window rectangle with {@param windowOverrideRect} if non-null.
876 public TaskViewTransform transformToScreenCoordinates(TaskViewTransform transformOut,
877 Rect windowOverrideRect) {
878 Rect windowRect = windowOverrideRect != null
880 : Recents.getSystemServices().getWindowRect();
881 transformOut.rect.offset(windowRect.left, windowRect.top);
886 * Update/get the transform.
888 * @param ignoreSingleTaskCase When set, will ensure that the transform computed does not take
889 * into account the special single-task case. This is only used
890 * internally to ensure that we can calculate the transform for any
891 * position in the stack.
893 public void getStackTransform(float taskProgress, float nonOverrideTaskProgress,
894 float stackScroll, int focusState, TaskViewTransform transformOut,
895 TaskViewTransform frontTransform, boolean ignoreSingleTaskCase, boolean forceUpdate) {
896 SystemServicesProxy ssp = Recents.getSystemServices();
898 // Ensure that the task is in range
899 mUnfocusedRange.offset(stackScroll);
900 mFocusedRange.offset(stackScroll);
901 boolean unfocusedVisible = mUnfocusedRange.isInRange(taskProgress);
902 boolean focusedVisible = mFocusedRange.isInRange(taskProgress);
904 // Skip if the task is not visible
905 if (!forceUpdate && !unfocusedVisible && !focusedVisible) {
906 transformOut.reset();
910 // Map the absolute task progress to the normalized x at the stack scroll. We use this to
911 // calculate positions along the curve.
912 mUnfocusedRange.offset(stackScroll);
913 mFocusedRange.offset(stackScroll);
914 float unfocusedRangeX = mUnfocusedRange.getNormalizedX(taskProgress);
915 float focusedRangeX = mFocusedRange.getNormalizedX(taskProgress);
917 // Map the absolute task progress to the normalized x at the bounded stack scroll. We use
918 // this to calculate bounded properties, like translationZ and outline alpha.
919 float boundedStackScroll = Utilities.clamp(stackScroll, mMinScrollP, mMaxScrollP);
920 mUnfocusedRange.offset(boundedStackScroll);
921 mFocusedRange.offset(boundedStackScroll);
922 float boundedScrollUnfocusedRangeX = mUnfocusedRange.getNormalizedX(taskProgress);
923 float boundedScrollUnfocusedNonOverrideRangeX =
924 mUnfocusedRange.getNormalizedX(nonOverrideTaskProgress);
926 // Map the absolute task progress to the normalized x at the upper bounded stack scroll.
927 // We use this to calculate the dim, which is bounded only on one end.
928 float lowerBoundedStackScroll = Utilities.clamp(stackScroll, -Float.MAX_VALUE, mMaxScrollP);
929 mUnfocusedRange.offset(lowerBoundedStackScroll);
930 mFocusedRange.offset(lowerBoundedStackScroll);
931 float lowerBoundedUnfocusedRangeX = mUnfocusedRange.getNormalizedX(taskProgress);
932 float lowerBoundedFocusedRangeX = mFocusedRange.getNormalizedX(taskProgress);
934 int x = (mStackRect.width() - mTaskRect.width()) / 2;
938 float viewOutlineAlpha;
939 if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1 && !ignoreSingleTaskCase) {
940 // When there is exactly one task, then decouple the task from the stack and just move
942 float tmpP = (mMinScrollP - stackScroll) / mNumStackTasks;
943 int centerYOffset = (mStackRect.top - mTaskRect.top) +
944 (mStackRect.height() - mSystemInsets.bottom - mTaskRect.height()) / 2;
945 y = centerYOffset + getYForDeltaP(tmpP, 0);
946 z = mMaxTranslationZ;
948 viewOutlineAlpha = OUTLINE_ALPHA_MIN_VALUE +
949 (OUTLINE_ALPHA_MAX_VALUE - OUTLINE_ALPHA_MIN_VALUE) / 2f;
952 // Otherwise, update the task to the stack layout
953 int unfocusedY = (int) ((1f - mUnfocusedCurveInterpolator.getInterpolation(
954 unfocusedRangeX)) * mStackRect.height());
955 int focusedY = (int) ((1f - mFocusedCurveInterpolator.getInterpolation(
956 focusedRangeX)) * mStackRect.height());
957 float unfocusedDim = mUnfocusedDimCurveInterpolator.getInterpolation(
958 lowerBoundedUnfocusedRangeX);
959 float focusedDim = mFocusedDimCurveInterpolator.getInterpolation(
960 lowerBoundedFocusedRangeX);
962 // Special case, because we override the initial task positions differently for small
963 // stacks, we clamp the dim to 0 in the initial position, and then only modulate the
964 // dim when the task is scrolled back towards the top of the screen
965 if (mNumStackTasks <= 2 && nonOverrideTaskProgress == 0f) {
966 if (boundedScrollUnfocusedRangeX >= 0.5f) {
969 float offset = mUnfocusedDimCurveInterpolator.getInterpolation(0.5f);
970 unfocusedDim -= offset;
971 unfocusedDim *= MAX_DIM / (MAX_DIM - offset);
975 y = (mStackRect.top - mTaskRect.top) +
976 (int) Utilities.mapRange(focusState, unfocusedY, focusedY);
977 z = Utilities.mapRange(Utilities.clamp01(boundedScrollUnfocusedNonOverrideRangeX),
978 mMinTranslationZ, mMaxTranslationZ);
979 dimAlpha = Utilities.mapRange(focusState, unfocusedDim, focusedDim);
980 viewOutlineAlpha = Utilities.mapRange(Utilities.clamp01(boundedScrollUnfocusedRangeX),
981 OUTLINE_ALPHA_MIN_VALUE, OUTLINE_ALPHA_MAX_VALUE);
984 // Fill out the transform
985 transformOut.scale = 1f;
986 transformOut.alpha = 1f;
987 transformOut.translationZ = z;
988 transformOut.dimAlpha = dimAlpha;
989 transformOut.viewOutlineAlpha = viewOutlineAlpha;
990 transformOut.rect.set(mTaskRect);
991 transformOut.rect.offset(x, y);
992 Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
993 transformOut.visible = (transformOut.rect.top < mStackRect.bottom) &&
994 (frontTransform == null || transformOut.rect.top != frontTransform.rect.top);
998 * Returns the untransformed task view bounds.
1000 public Rect getUntransformedTaskViewBounds() {
1001 return new Rect(mTaskRect);
1005 * Returns the scroll progress to scroll to such that the top of the task is at the top of the
1008 float getStackScrollForTask(Task t) {
1009 Float overrideP = mTaskIndexOverrideMap.get(t.key.id, null);
1010 if (overrideP == null) {
1011 return (float) mTaskIndexMap.get(t.key.id, 0);
1017 * Returns the original scroll progress to scroll to such that the top of the task is at the top
1020 float getStackScrollForTaskIgnoreOverrides(Task t) {
1021 return (float) mTaskIndexMap.get(t.key.id, 0);
1025 * Returns the scroll progress to scroll to such that the top of the task at the initial top
1026 * offset (which is at the task's brightest point).
1028 float getStackScrollForTaskAtInitialOffset(Task t) {
1029 float normX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
1030 mUnfocusedRange.offset(0f);
1031 return Utilities.clamp((float) mTaskIndexMap.get(t.key.id, 0) - Math.max(0,
1032 mUnfocusedRange.getAbsoluteX(normX)), mMinScrollP, mMaxScrollP);
1036 * Maps a movement in screen y, relative to {@param downY}, to a movement in along the arc
1037 * length of the curve. We know the curve is mostly flat, so we just map the length of the
1038 * screen along the arc-length proportionally (1/arclength).
1040 public float getDeltaPForY(int downY, int y) {
1041 float deltaP = (float) (y - downY) / mStackRect.height() *
1042 mUnfocusedCurveInterpolator.getArcLength();
1047 * This is the inverse of {@link #getDeltaPForY}. Given a movement along the arc length
1048 * of the curve, map back to the screen y.
1050 public int getYForDeltaP(float downScrollP, float p) {
1051 int y = (int) ((p - downScrollP) * mStackRect.height() *
1052 (1f / mUnfocusedCurveInterpolator.getArcLength()));
1057 * Returns the task stack bounds in the current orientation. This rect takes into account the
1058 * top and right system insets (but not the bottom inset) and left/right paddings, but _not_
1059 * the top/bottom padding or insets.
1061 public void getTaskStackBounds(Rect displayRect, Rect windowRect, int topInset, int rightInset,
1062 Rect taskStackBounds) {
1063 taskStackBounds.set(windowRect.left, windowRect.top + topInset,
1064 windowRect.right - rightInset, windowRect.bottom);
1066 // Ensure that the new width is at most the smaller display edge size
1067 int sideMargin = getScaleForExtent(windowRect, displayRect, mBaseSideMargin, mMinMargin,
1069 int targetStackWidth = taskStackBounds.width() - 2 * sideMargin;
1070 if (Utilities.getAppConfiguration(mContext).orientation
1071 == Configuration.ORIENTATION_LANDSCAPE) {
1072 // If we are in landscape, calculate the width of the stack in portrait and ensure that
1073 // we are not larger than that size
1074 Rect portraitDisplayRect = new Rect(0, 0,
1075 Math.min(displayRect.width(), displayRect.height()),
1076 Math.max(displayRect.width(), displayRect.height()));
1077 int portraitSideMargin = getScaleForExtent(portraitDisplayRect, portraitDisplayRect,
1078 mBaseSideMargin, mMinMargin, WIDTH);
1079 targetStackWidth = Math.min(targetStackWidth,
1080 portraitDisplayRect.width() - 2 * portraitSideMargin);
1082 taskStackBounds.inset((taskStackBounds.width() - targetStackWidth) / 2, 0);
1086 * Retrieves resources that are constant regardless of the current configuration of the device.
1088 public static int getDimensionForDevice(Context ctx, int phoneResId,
1089 int tabletResId, int xlargeTabletResId) {
1090 return getDimensionForDevice(ctx, phoneResId, phoneResId, tabletResId, tabletResId,
1091 xlargeTabletResId, xlargeTabletResId);
1095 * Retrieves resources that are constant regardless of the current configuration of the device.
1097 public static int getDimensionForDevice(Context ctx, int phonePortResId, int phoneLandResId,
1098 int tabletPortResId, int tabletLandResId, int xlargeTabletPortResId,
1099 int xlargeTabletLandResId) {
1100 RecentsConfiguration config = Recents.getConfiguration();
1101 Resources res = ctx.getResources();
1102 boolean isLandscape = Utilities.getAppConfiguration(ctx).orientation ==
1103 Configuration.ORIENTATION_LANDSCAPE;
1104 if (config.isXLargeScreen) {
1105 return res.getDimensionPixelSize(isLandscape
1106 ? xlargeTabletLandResId
1107 : xlargeTabletPortResId);
1108 } else if (config.isLargeScreen) {
1109 return res.getDimensionPixelSize(isLandscape
1113 return res.getDimensionPixelSize(isLandscape
1120 * Returns the normalized x on the unfocused curve given an absolute Y position (relative to the
1123 private float getNormalizedXFromUnfocusedY(float y, @AnchorSide int fromSide) {
1124 float offset = (fromSide == FROM_TOP)
1125 ? mStackRect.height() - y
1127 float offsetPct = offset / mStackRect.height();
1128 return mUnfocusedCurveInterpolator.getX(offsetPct);
1132 * Returns the normalized x on the focused curve given an absolute Y position (relative to the
1135 private float getNormalizedXFromFocusedY(float y, @AnchorSide int fromSide) {
1136 float offset = (fromSide == FROM_TOP)
1137 ? mStackRect.height() - y
1139 float offsetPct = offset / mStackRect.height();
1140 return mFocusedCurveInterpolator.getX(offsetPct);
1144 * Creates a new path for the focused curve.
1146 private Path constructFocusedCurve() {
1147 // Initialize the focused curve. This curve is a piecewise curve composed of several
1148 // linear pieces that goes from (0,1) through (0.5, peek height offset),
1149 // (0.5, bottom task offsets), and (1,0).
1150 float topPeekHeightPct = (float) mFocusedTopPeekHeight / mStackRect.height();
1151 float bottomPeekHeightPct = (float) (mStackBottomOffset + mFocusedBottomPeekHeight) /
1152 mStackRect.height();
1153 float minBottomPeekHeightPct = (float) (mFocusedTopPeekHeight + mTaskRect.height() -
1154 mMinMargin) / mStackRect.height();
1155 Path p = new Path();
1157 p.lineTo(0.5f, 1f - topPeekHeightPct);
1158 p.lineTo(1f - (0.5f / mFocusedRange.relativeMax), Math.max(1f - minBottomPeekHeightPct,
1159 bottomPeekHeightPct));
1165 * Creates a new path for the unfocused curve.
1167 private Path constructUnfocusedCurve() {
1168 // Initialize the unfocused curve. This curve is a piecewise curve composed of two quadradic
1169 // beziers that goes from (0,1) through (0.5, peek height offset) and ends at (1,0). This
1170 // ensures that we match the range, at which 0.5 represents the stack scroll at the current
1171 // task progress. Because the height offset can change depending on a resource, we compute
1172 // the control point of the second bezier such that between it and a first known point,
1173 // there is a tangent at (0.5, peek height offset).
1174 float cpoint1X = 0.4f;
1175 float cpoint1Y = 0.975f;
1176 float topPeekHeightPct = (float) mFocusedTopPeekHeight / mStackRect.height();
1177 float slope = ((1f - topPeekHeightPct) - cpoint1Y) / (0.5f - cpoint1X);
1178 float b = 1f - slope * cpoint1X;
1179 float cpoint2X = 0.65f;
1180 float cpoint2Y = slope * cpoint2X + b;
1181 Path p = new Path();
1183 p.cubicTo(0f, 1f, cpoint1X, cpoint1Y, 0.5f, 1f - topPeekHeightPct);
1184 p.cubicTo(0.5f, 1f - topPeekHeightPct, cpoint2X, cpoint2Y, 1f, 0f);
1189 * Creates a new path for the focused dim curve.
1191 private Path constructFocusedDimCurve() {
1192 Path p = new Path();
1193 // The focused dim interpolator starts at max dim, reduces to zero at 0.5 (the focused
1194 // task), then goes back to max dim at the next task
1195 p.moveTo(0f, MAX_DIM);
1197 p.lineTo(0.5f + (0.5f / mFocusedRange.relativeMax), MAX_DIM);
1198 p.lineTo(1f, MAX_DIM);
1203 * Creates a new path for the unfocused dim curve.
1205 private Path constructUnfocusedDimCurve() {
1206 float focusX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
1207 float cpoint2X = focusX + (1f - focusX) / 2;
1208 Path p = new Path();
1209 // The unfocused dim interpolator starts at max dim, reduces to zero at 0.5 (the focused
1210 // task), then goes back to max dim towards the front of the stack
1211 p.moveTo(0f, MAX_DIM);
1212 p.cubicTo(focusX * 0.5f, MAX_DIM, focusX * 0.75f, MAX_DIM * 0.75f, focusX, 0f);
1213 p.cubicTo(cpoint2X, 0f, cpoint2X, MED_DIM, 1f, MED_DIM);
1218 * Scales the given {@param value} to the scale of the {@param instance} rect relative to the
1219 * {@param other} rect in the {@param extent} side.
1221 private int getScaleForExtent(Rect instance, Rect other, int value, int minValue,
1222 @Extent int extent) {
1223 if (extent == WIDTH) {
1224 float scale = Utilities.clamp01((float) instance.width() / other.width());
1225 return Math.max(minValue, (int) (scale * value));
1226 } else if (extent == HEIGHT) {
1227 float scale = Utilities.clamp01((float) instance.height() / other.height());
1228 return Math.max(minValue, (int) (scale * value));
1234 * Updates the current transforms that would put a TaskView at the front and back of the stack.
1236 private void updateFrontBackTransforms() {
1237 // Return early if we have not yet initialized
1238 if (mStackRect.isEmpty()) {
1242 float min = Utilities.mapRange(mFocusState, mUnfocusedRange.relativeMin,
1243 mFocusedRange.relativeMin);
1244 float max = Utilities.mapRange(mFocusState, mUnfocusedRange.relativeMax,
1245 mFocusedRange.relativeMax);
1246 getStackTransform(min, min, 0f, mFocusState, mBackOfStackTransform, null,
1247 true /* ignoreSingleTaskCase */, true /* forceUpdate */);
1248 getStackTransform(max, max, 0f, mFocusState, mFrontOfStackTransform, null,
1249 true /* ignoreSingleTaskCase */, true /* forceUpdate */);
1250 mBackOfStackTransform.visible = true;
1251 mFrontOfStackTransform.visible = true;
1254 public void dump(String prefix, PrintWriter writer) {
1255 String innerPrefix = prefix + " ";
1257 writer.print(prefix); writer.print(TAG);
1258 writer.write(" numStackTasks="); writer.write(mNumStackTasks);
1261 writer.print(innerPrefix);
1262 writer.print("insets="); writer.print(Utilities.dumpRect(mSystemInsets));
1263 writer.print(" stack="); writer.print(Utilities.dumpRect(mStackRect));
1264 writer.print(" task="); writer.print(Utilities.dumpRect(mTaskRect));
1265 writer.print(" freeform="); writer.print(Utilities.dumpRect(mFreeformRect));
1266 writer.print(" actionButton="); writer.print(Utilities.dumpRect(mStackActionButtonRect));
1269 writer.print(innerPrefix);
1270 writer.print("minScroll="); writer.print(mMinScrollP);
1271 writer.print(" maxScroll="); writer.print(mMaxScrollP);
1272 writer.print(" initialScroll="); writer.print(mInitialScrollP);
1275 writer.print(innerPrefix);
1276 writer.print("focusState="); writer.print(mFocusState);
1279 if (mTaskIndexOverrideMap.size() > 0) {
1280 for (int i = mTaskIndexOverrideMap.size() - 1; i >= 0; i--) {
1281 int taskId = mTaskIndexOverrideMap.keyAt(i);
1282 float x = mTaskIndexMap.get(taskId);
1283 float overrideX = mTaskIndexOverrideMap.get(taskId, 0f);
1285 writer.print(innerPrefix);
1286 writer.print("taskId= "); writer.print(taskId);
1287 writer.print(" x= "); writer.print(x);
1288 writer.print(" overrideX= "); writer.print(overrideX);