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.launchedFromBlacklistedApp) {
560 mInitialScrollP = mMaxScrollP;
561 } else if (launchState.launchedWithAltTab) {
562 mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
563 } else if (scrollToFront) {
564 mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
566 // We are overriding the initial two task positions, so set the initial scroll
567 // position to match the second task (aka focused task) position
568 float initialTopNormX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
569 mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP, (mNumStackTasks - 2))
570 - Math.max(0, mUnfocusedRange.getAbsoluteX(initialTopNormX)));
576 * Creates task overrides to ensure the initial stack layout if necessary.
578 public void setTaskOverridesForInitialState(TaskStack stack, boolean ignoreScrollToFront) {
579 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
581 mTaskIndexOverrideMap.clear();
583 boolean scrollToFront = launchState.launchedFromHome ||
584 launchState.launchedFromBlacklistedApp ||
585 launchState.launchedViaDockGesture;
586 if (getInitialFocusState() == STATE_UNFOCUSED && mNumStackTasks > 1) {
587 if (ignoreScrollToFront || (!launchState.launchedWithAltTab && !scrollToFront)) {
588 // Set the initial scroll to the predefined state (which differs from the stack)
589 float [] initialNormX = null;
590 float minBottomTaskNormX = getNormalizedXFromUnfocusedY(mSystemInsets.bottom +
591 mInitialBottomOffset, FROM_BOTTOM);
592 float maxBottomTaskNormX = getNormalizedXFromUnfocusedY(mFocusedTopPeekHeight +
593 mTaskRect.height() - mMinMargin, FROM_TOP);
594 if (mNumStackTasks <= 2) {
595 // For small stacks, position the tasks so that they are top aligned to under
596 // the action button, but ensure that it is at least a certain offset from the
597 // bottom of the stack
598 initialNormX = new float[] {
599 Math.min(maxBottomTaskNormX, minBottomTaskNormX),
600 getNormalizedXFromUnfocusedY(mFocusedTopPeekHeight, FROM_TOP)
603 initialNormX = new float[] {
605 getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP)
609 mUnfocusedRange.offset(0f);
610 List<Task> tasks = stack.getStackTasks();
611 int taskCount = tasks.size();
612 for (int i = taskCount - 1; i >= 0; i--) {
613 int indexFromFront = taskCount - i - 1;
614 if (indexFromFront >= initialNormX.length) {
617 float newTaskProgress = mInitialScrollP +
618 mUnfocusedRange.getAbsoluteX(initialNormX[indexFromFront]);
619 mTaskIndexOverrideMap.put(tasks.get(i).key.id, newTaskProgress);
626 * Adds and override task progress for the given task when transitioning from focused to
629 public void addUnfocusedTaskOverride(Task task, float stackScroll) {
630 if (mFocusState != STATE_UNFOCUSED) {
631 mFocusedRange.offset(stackScroll);
632 mUnfocusedRange.offset(stackScroll);
633 float focusedRangeX = mFocusedRange.getNormalizedX(mTaskIndexMap.get(task.key.id));
634 float focusedY = mFocusedCurveInterpolator.getInterpolation(focusedRangeX);
635 float unfocusedRangeX = mUnfocusedCurveInterpolator.getX(focusedY);
636 float unfocusedTaskProgress = stackScroll + mUnfocusedRange.getAbsoluteX(unfocusedRangeX);
637 if (Float.compare(focusedRangeX, unfocusedRangeX) != 0) {
638 mTaskIndexOverrideMap.put(task.key.id, unfocusedTaskProgress);
644 * Adds and override task progress for the given task when transitioning from focused to
647 public void addUnfocusedTaskOverride(TaskView taskView, float stackScroll) {
648 mFocusedRange.offset(stackScroll);
649 mUnfocusedRange.offset(stackScroll);
651 Task task = taskView.getTask();
652 int top = taskView.getTop() - mTaskRect.top;
653 float focusedRangeX = getNormalizedXFromFocusedY(top, FROM_TOP);
654 float unfocusedRangeX = getNormalizedXFromUnfocusedY(top, FROM_TOP);
655 float unfocusedTaskProgress = stackScroll + mUnfocusedRange.getAbsoluteX(unfocusedRangeX);
656 if (Float.compare(focusedRangeX, unfocusedRangeX) != 0) {
657 mTaskIndexOverrideMap.put(task.key.id, unfocusedTaskProgress);
661 public void clearUnfocusedTaskOverrides() {
662 mTaskIndexOverrideMap.clear();
666 * Updates this stack when a scroll happens.
669 public float updateFocusStateOnScroll(float lastTargetStackScroll, float targetStackScroll,
670 float lastStackScroll) {
671 if (targetStackScroll == lastStackScroll) {
672 return targetStackScroll;
675 float deltaScroll = targetStackScroll - lastStackScroll;
676 float deltaTargetScroll = targetStackScroll - lastTargetStackScroll;
677 float newScroll = targetStackScroll;
678 mUnfocusedRange.offset(targetStackScroll);
679 for (int i = mTaskIndexOverrideMap.size() - 1; i >= 0; i--) {
680 int taskId = mTaskIndexOverrideMap.keyAt(i);
681 float x = mTaskIndexMap.get(taskId);
682 float overrideX = mTaskIndexOverrideMap.get(taskId, 0f);
683 float newOverrideX = overrideX + deltaScroll;
684 if (isInvalidOverrideX(x, overrideX, newOverrideX)) {
685 // Remove the override once we reach the original task index
686 mTaskIndexOverrideMap.removeAt(i);
687 } else if ((overrideX >= x && deltaScroll <= 0f) ||
688 (overrideX <= x && deltaScroll >= 0f)) {
689 // Scrolling from override x towards x, then lock the task in place
690 mTaskIndexOverrideMap.put(taskId, newOverrideX);
692 // Scrolling override x away from x, we should still move the scroll towards x
693 newScroll = lastStackScroll;
694 newOverrideX = overrideX - deltaTargetScroll;
695 if (isInvalidOverrideX(x, overrideX, newOverrideX)) {
696 mTaskIndexOverrideMap.removeAt(i);
698 mTaskIndexOverrideMap.put(taskId, newOverrideX);
705 private boolean isInvalidOverrideX(float x, float overrideX, float newOverrideX) {
706 boolean outOfBounds = mUnfocusedRange.getNormalizedX(newOverrideX) < 0f ||
707 mUnfocusedRange.getNormalizedX(newOverrideX) > 1f;
708 return outOfBounds || (overrideX >= x && x >= newOverrideX) ||
709 (overrideX <= x && x <= newOverrideX);
713 * Returns the default focus state.
715 public int getInitialFocusState() {
716 RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
717 RecentsDebugFlags debugFlags = Recents.getDebugFlags();
718 if (debugFlags.isPagingEnabled() || launchState.launchedWithAltTab) {
719 return STATE_FOCUSED;
721 return STATE_UNFOCUSED;
726 * Returns the TaskViewTransform that would put the task just off the back of the stack.
728 public TaskViewTransform getBackOfStackTransform() {
729 return mBackOfStackTransform;
733 * Returns the TaskViewTransform that would put the task just off the front of the stack.
735 public TaskViewTransform getFrontOfStackTransform() {
736 return mFrontOfStackTransform;
740 * Returns the current stack state.
742 public StackState getStackState() {
747 * Returns whether this stack layout has been initialized.
749 public boolean isInitialized() {
750 return !mStackRect.isEmpty();
754 * Computes the maximum number of visible tasks and thumbnails when the scroll is at the initial
755 * stack scroll. Requires that update() is called first.
757 public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
758 // Ensure minimum visibility count
759 if (tasks.size() <= 1) {
760 return new VisibilityReport(1, 1);
763 // Quick return when there are no stack tasks
764 if (mNumStackTasks == 0) {
765 return new VisibilityReport(Math.max(mNumFreeformTasks, 1),
766 Math.max(mNumFreeformTasks, 1));
769 // Otherwise, walk backwards in the stack and count the number of tasks and visible
770 // thumbnails and add that to the total freeform task count
771 TaskViewTransform tmpTransform = new TaskViewTransform();
772 Range currentRange = getInitialFocusState() > 0f ? mFocusedRange : mUnfocusedRange;
773 currentRange.offset(mInitialScrollP);
774 int taskBarHeight = mContext.getResources().getDimensionPixelSize(
775 R.dimen.recents_task_view_header_height);
776 int numVisibleTasks = Math.max(mNumFreeformTasks, 1);
777 int numVisibleThumbnails = Math.max(mNumFreeformTasks, 1);
778 float prevScreenY = Integer.MAX_VALUE;
779 for (int i = tasks.size() - 1; i >= 0; i--) {
780 Task task = tasks.get(i);
783 if (task.isFreeformTask()) {
788 float taskProgress = getStackScrollForTask(task);
789 if (!currentRange.isInRange(taskProgress)) {
793 boolean isFrontMostTaskInGroup = task.group == null || task.group.isFrontMostTask(task);
794 if (isFrontMostTaskInGroup) {
795 getStackTransform(taskProgress, taskProgress, mInitialScrollP, mFocusState,
796 tmpTransform, null, false /* ignoreSingleTaskCase */,
797 false /* forceUpdate */);
798 float screenY = tmpTransform.rect.top;
799 boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight;
800 if (hasVisibleThumbnail) {
801 numVisibleThumbnails++;
803 prevScreenY = screenY;
805 // Once we hit the next front most task that does not have a visible thumbnail,
806 // walk through remaining visible set
807 for (int j = i; j >= 0; j--) {
809 taskProgress = getStackScrollForTask(tasks.get(j));
810 if (!currentRange.isInRange(taskProgress)) {
816 } else if (!isFrontMostTaskInGroup) {
817 // Affiliated task, no thumbnail
821 return new VisibilityReport(numVisibleTasks, numVisibleThumbnails);
825 * Returns the transform for the given task. This transform is relative to the mTaskRect, which
826 * is what the view is measured and laid out with.
828 public TaskViewTransform getStackTransform(Task task, float stackScroll,
829 TaskViewTransform transformOut, TaskViewTransform frontTransform) {
830 return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
831 false /* forceUpdate */, false /* ignoreTaskOverrides */);
834 public TaskViewTransform getStackTransform(Task task, float stackScroll,
835 TaskViewTransform transformOut, TaskViewTransform frontTransform,
836 boolean ignoreTaskOverrides) {
837 return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
838 false /* forceUpdate */, ignoreTaskOverrides);
841 public TaskViewTransform getStackTransform(Task task, float stackScroll, int focusState,
842 TaskViewTransform transformOut, TaskViewTransform frontTransform, boolean forceUpdate,
843 boolean ignoreTaskOverrides) {
844 if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) {
845 mFreeformLayoutAlgorithm.getTransform(task, transformOut, this);
848 // Return early if we have an invalid index
849 int nonOverrideTaskProgress = mTaskIndexMap.get(task.key.id, -1);
850 if (task == null || nonOverrideTaskProgress == -1) {
851 transformOut.reset();
854 float taskProgress = ignoreTaskOverrides
855 ? nonOverrideTaskProgress
856 : getStackScrollForTask(task);
857 getStackTransform(taskProgress, nonOverrideTaskProgress, stackScroll, focusState,
858 transformOut, frontTransform, false /* ignoreSingleTaskCase */, forceUpdate);
864 * Like {@link #getStackTransform}, but in screen coordinates
866 public TaskViewTransform getStackTransformScreenCoordinates(Task task, float stackScroll,
867 TaskViewTransform transformOut, TaskViewTransform frontTransform,
868 Rect windowOverrideRect) {
869 TaskViewTransform transform = getStackTransform(task, stackScroll, mFocusState,
870 transformOut, frontTransform, true /* forceUpdate */,
871 false /* ignoreTaskOverrides */);
872 return transformToScreenCoordinates(transform, windowOverrideRect);
876 * Transforms the given {@param transformOut} to the screen coordinates, overriding the current
877 * window rectangle with {@param windowOverrideRect} if non-null.
879 public TaskViewTransform transformToScreenCoordinates(TaskViewTransform transformOut,
880 Rect windowOverrideRect) {
881 Rect windowRect = windowOverrideRect != null
883 : Recents.getSystemServices().getWindowRect();
884 transformOut.rect.offset(windowRect.left, windowRect.top);
889 * Update/get the transform.
891 * @param ignoreSingleTaskCase When set, will ensure that the transform computed does not take
892 * into account the special single-task case. This is only used
893 * internally to ensure that we can calculate the transform for any
894 * position in the stack.
896 public void getStackTransform(float taskProgress, float nonOverrideTaskProgress,
897 float stackScroll, int focusState, TaskViewTransform transformOut,
898 TaskViewTransform frontTransform, boolean ignoreSingleTaskCase, boolean forceUpdate) {
899 SystemServicesProxy ssp = Recents.getSystemServices();
901 // Ensure that the task is in range
902 mUnfocusedRange.offset(stackScroll);
903 mFocusedRange.offset(stackScroll);
904 boolean unfocusedVisible = mUnfocusedRange.isInRange(taskProgress);
905 boolean focusedVisible = mFocusedRange.isInRange(taskProgress);
907 // Skip if the task is not visible
908 if (!forceUpdate && !unfocusedVisible && !focusedVisible) {
909 transformOut.reset();
913 // Map the absolute task progress to the normalized x at the stack scroll. We use this to
914 // calculate positions along the curve.
915 mUnfocusedRange.offset(stackScroll);
916 mFocusedRange.offset(stackScroll);
917 float unfocusedRangeX = mUnfocusedRange.getNormalizedX(taskProgress);
918 float focusedRangeX = mFocusedRange.getNormalizedX(taskProgress);
920 // Map the absolute task progress to the normalized x at the bounded stack scroll. We use
921 // this to calculate bounded properties, like translationZ and outline alpha.
922 float boundedStackScroll = Utilities.clamp(stackScroll, mMinScrollP, mMaxScrollP);
923 mUnfocusedRange.offset(boundedStackScroll);
924 mFocusedRange.offset(boundedStackScroll);
925 float boundedScrollUnfocusedRangeX = mUnfocusedRange.getNormalizedX(taskProgress);
926 float boundedScrollUnfocusedNonOverrideRangeX =
927 mUnfocusedRange.getNormalizedX(nonOverrideTaskProgress);
929 // Map the absolute task progress to the normalized x at the upper bounded stack scroll.
930 // We use this to calculate the dim, which is bounded only on one end.
931 float lowerBoundedStackScroll = Utilities.clamp(stackScroll, -Float.MAX_VALUE, mMaxScrollP);
932 mUnfocusedRange.offset(lowerBoundedStackScroll);
933 mFocusedRange.offset(lowerBoundedStackScroll);
934 float lowerBoundedUnfocusedRangeX = mUnfocusedRange.getNormalizedX(taskProgress);
935 float lowerBoundedFocusedRangeX = mFocusedRange.getNormalizedX(taskProgress);
937 int x = (mStackRect.width() - mTaskRect.width()) / 2;
941 float viewOutlineAlpha;
942 if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1 && !ignoreSingleTaskCase) {
943 // When there is exactly one task, then decouple the task from the stack and just move
945 float tmpP = (mMinScrollP - stackScroll) / mNumStackTasks;
946 int centerYOffset = (mStackRect.top - mTaskRect.top) +
947 (mStackRect.height() - mSystemInsets.bottom - mTaskRect.height()) / 2;
948 y = centerYOffset + getYForDeltaP(tmpP, 0);
949 z = mMaxTranslationZ;
951 viewOutlineAlpha = OUTLINE_ALPHA_MIN_VALUE +
952 (OUTLINE_ALPHA_MAX_VALUE - OUTLINE_ALPHA_MIN_VALUE) / 2f;
955 // Otherwise, update the task to the stack layout
956 int unfocusedY = (int) ((1f - mUnfocusedCurveInterpolator.getInterpolation(
957 unfocusedRangeX)) * mStackRect.height());
958 int focusedY = (int) ((1f - mFocusedCurveInterpolator.getInterpolation(
959 focusedRangeX)) * mStackRect.height());
960 float unfocusedDim = mUnfocusedDimCurveInterpolator.getInterpolation(
961 lowerBoundedUnfocusedRangeX);
962 float focusedDim = mFocusedDimCurveInterpolator.getInterpolation(
963 lowerBoundedFocusedRangeX);
965 // Special case, because we override the initial task positions differently for small
966 // stacks, we clamp the dim to 0 in the initial position, and then only modulate the
967 // dim when the task is scrolled back towards the top of the screen
968 if (mNumStackTasks <= 2 && nonOverrideTaskProgress == 0f) {
969 if (boundedScrollUnfocusedRangeX >= 0.5f) {
972 float offset = mUnfocusedDimCurveInterpolator.getInterpolation(0.5f);
973 unfocusedDim -= offset;
974 unfocusedDim *= MAX_DIM / (MAX_DIM - offset);
978 y = (mStackRect.top - mTaskRect.top) +
979 (int) Utilities.mapRange(focusState, unfocusedY, focusedY);
980 z = Utilities.mapRange(Utilities.clamp01(boundedScrollUnfocusedNonOverrideRangeX),
981 mMinTranslationZ, mMaxTranslationZ);
982 dimAlpha = Utilities.mapRange(focusState, unfocusedDim, focusedDim);
983 viewOutlineAlpha = Utilities.mapRange(Utilities.clamp01(boundedScrollUnfocusedRangeX),
984 OUTLINE_ALPHA_MIN_VALUE, OUTLINE_ALPHA_MAX_VALUE);
987 // Fill out the transform
988 transformOut.scale = 1f;
989 transformOut.alpha = 1f;
990 transformOut.translationZ = z;
991 transformOut.dimAlpha = dimAlpha;
992 transformOut.viewOutlineAlpha = viewOutlineAlpha;
993 transformOut.rect.set(mTaskRect);
994 transformOut.rect.offset(x, y);
995 Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
996 transformOut.visible = (transformOut.rect.top < mStackRect.bottom) &&
997 (frontTransform == null || transformOut.rect.top != frontTransform.rect.top);
1001 * Returns the untransformed task view bounds.
1003 public Rect getUntransformedTaskViewBounds() {
1004 return new Rect(mTaskRect);
1008 * Returns the scroll progress to scroll to such that the top of the task is at the top of the
1011 float getStackScrollForTask(Task t) {
1012 Float overrideP = mTaskIndexOverrideMap.get(t.key.id, null);
1013 if (overrideP == null) {
1014 return (float) mTaskIndexMap.get(t.key.id, 0);
1020 * Returns the original scroll progress to scroll to such that the top of the task is at the top
1023 float getStackScrollForTaskIgnoreOverrides(Task t) {
1024 return (float) mTaskIndexMap.get(t.key.id, 0);
1028 * Returns the scroll progress to scroll to such that the top of the task at the initial top
1029 * offset (which is at the task's brightest point).
1031 float getStackScrollForTaskAtInitialOffset(Task t) {
1032 float normX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
1033 mUnfocusedRange.offset(0f);
1034 return Utilities.clamp((float) mTaskIndexMap.get(t.key.id, 0) - Math.max(0,
1035 mUnfocusedRange.getAbsoluteX(normX)), mMinScrollP, mMaxScrollP);
1039 * Maps a movement in screen y, relative to {@param downY}, to a movement in along the arc
1040 * length of the curve. We know the curve is mostly flat, so we just map the length of the
1041 * screen along the arc-length proportionally (1/arclength).
1043 public float getDeltaPForY(int downY, int y) {
1044 float deltaP = (float) (y - downY) / mStackRect.height() *
1045 mUnfocusedCurveInterpolator.getArcLength();
1050 * This is the inverse of {@link #getDeltaPForY}. Given a movement along the arc length
1051 * of the curve, map back to the screen y.
1053 public int getYForDeltaP(float downScrollP, float p) {
1054 int y = (int) ((p - downScrollP) * mStackRect.height() *
1055 (1f / mUnfocusedCurveInterpolator.getArcLength()));
1060 * Returns the task stack bounds in the current orientation. This rect takes into account the
1061 * top and right system insets (but not the bottom inset) and left/right paddings, but _not_
1062 * the top/bottom padding or insets.
1064 public void getTaskStackBounds(Rect displayRect, Rect windowRect, int topInset, int leftInset,
1065 int rightInset, Rect taskStackBounds) {
1066 taskStackBounds.set(windowRect.left + leftInset, windowRect.top + topInset,
1067 windowRect.right - rightInset, windowRect.bottom);
1069 // Ensure that the new width is at most the smaller display edge size
1070 int sideMargin = getScaleForExtent(windowRect, displayRect, mBaseSideMargin, mMinMargin,
1072 int targetStackWidth = taskStackBounds.width() - 2 * sideMargin;
1073 if (Utilities.getAppConfiguration(mContext).orientation
1074 == Configuration.ORIENTATION_LANDSCAPE) {
1075 // If we are in landscape, calculate the width of the stack in portrait and ensure that
1076 // we are not larger than that size
1077 Rect portraitDisplayRect = new Rect(0, 0,
1078 Math.min(displayRect.width(), displayRect.height()),
1079 Math.max(displayRect.width(), displayRect.height()));
1080 int portraitSideMargin = getScaleForExtent(portraitDisplayRect, portraitDisplayRect,
1081 mBaseSideMargin, mMinMargin, WIDTH);
1082 targetStackWidth = Math.min(targetStackWidth,
1083 portraitDisplayRect.width() - 2 * portraitSideMargin);
1085 taskStackBounds.inset((taskStackBounds.width() - targetStackWidth) / 2, 0);
1089 * Retrieves resources that are constant regardless of the current configuration of the device.
1091 public static int getDimensionForDevice(Context ctx, int phoneResId,
1092 int tabletResId, int xlargeTabletResId) {
1093 return getDimensionForDevice(ctx, phoneResId, phoneResId, tabletResId, tabletResId,
1094 xlargeTabletResId, xlargeTabletResId);
1098 * Retrieves resources that are constant regardless of the current configuration of the device.
1100 public static int getDimensionForDevice(Context ctx, int phonePortResId, int phoneLandResId,
1101 int tabletPortResId, int tabletLandResId, int xlargeTabletPortResId,
1102 int xlargeTabletLandResId) {
1103 RecentsConfiguration config = Recents.getConfiguration();
1104 Resources res = ctx.getResources();
1105 boolean isLandscape = Utilities.getAppConfiguration(ctx).orientation ==
1106 Configuration.ORIENTATION_LANDSCAPE;
1107 if (config.isXLargeScreen) {
1108 return res.getDimensionPixelSize(isLandscape
1109 ? xlargeTabletLandResId
1110 : xlargeTabletPortResId);
1111 } else if (config.isLargeScreen) {
1112 return res.getDimensionPixelSize(isLandscape
1116 return res.getDimensionPixelSize(isLandscape
1123 * Returns the normalized x on the unfocused curve given an absolute Y position (relative to the
1126 private float getNormalizedXFromUnfocusedY(float y, @AnchorSide int fromSide) {
1127 float offset = (fromSide == FROM_TOP)
1128 ? mStackRect.height() - y
1130 float offsetPct = offset / mStackRect.height();
1131 return mUnfocusedCurveInterpolator.getX(offsetPct);
1135 * Returns the normalized x on the focused curve given an absolute Y position (relative to the
1138 private float getNormalizedXFromFocusedY(float y, @AnchorSide int fromSide) {
1139 float offset = (fromSide == FROM_TOP)
1140 ? mStackRect.height() - y
1142 float offsetPct = offset / mStackRect.height();
1143 return mFocusedCurveInterpolator.getX(offsetPct);
1147 * Creates a new path for the focused curve.
1149 private Path constructFocusedCurve() {
1150 // Initialize the focused curve. This curve is a piecewise curve composed of several
1151 // linear pieces that goes from (0,1) through (0.5, peek height offset),
1152 // (0.5, bottom task offsets), and (1,0).
1153 float topPeekHeightPct = (float) mFocusedTopPeekHeight / mStackRect.height();
1154 float bottomPeekHeightPct = (float) (mStackBottomOffset + mFocusedBottomPeekHeight) /
1155 mStackRect.height();
1156 float minBottomPeekHeightPct = (float) (mFocusedTopPeekHeight + mTaskRect.height() -
1157 mMinMargin) / mStackRect.height();
1158 Path p = new Path();
1160 p.lineTo(0.5f, 1f - topPeekHeightPct);
1161 p.lineTo(1f - (0.5f / mFocusedRange.relativeMax), Math.max(1f - minBottomPeekHeightPct,
1162 bottomPeekHeightPct));
1168 * Creates a new path for the unfocused curve.
1170 private Path constructUnfocusedCurve() {
1171 // Initialize the unfocused curve. This curve is a piecewise curve composed of two quadradic
1172 // beziers that goes from (0,1) through (0.5, peek height offset) and ends at (1,0). This
1173 // ensures that we match the range, at which 0.5 represents the stack scroll at the current
1174 // task progress. Because the height offset can change depending on a resource, we compute
1175 // the control point of the second bezier such that between it and a first known point,
1176 // there is a tangent at (0.5, peek height offset).
1177 float cpoint1X = 0.4f;
1178 float cpoint1Y = 0.975f;
1179 float topPeekHeightPct = (float) mFocusedTopPeekHeight / mStackRect.height();
1180 float slope = ((1f - topPeekHeightPct) - cpoint1Y) / (0.5f - cpoint1X);
1181 float b = 1f - slope * cpoint1X;
1182 float cpoint2X = 0.65f;
1183 float cpoint2Y = slope * cpoint2X + b;
1184 Path p = new Path();
1186 p.cubicTo(0f, 1f, cpoint1X, cpoint1Y, 0.5f, 1f - topPeekHeightPct);
1187 p.cubicTo(0.5f, 1f - topPeekHeightPct, cpoint2X, cpoint2Y, 1f, 0f);
1192 * Creates a new path for the focused dim curve.
1194 private Path constructFocusedDimCurve() {
1195 Path p = new Path();
1196 // The focused dim interpolator starts at max dim, reduces to zero at 0.5 (the focused
1197 // task), then goes back to max dim at the next task
1198 p.moveTo(0f, MAX_DIM);
1200 p.lineTo(0.5f + (0.5f / mFocusedRange.relativeMax), MAX_DIM);
1201 p.lineTo(1f, MAX_DIM);
1206 * Creates a new path for the unfocused dim curve.
1208 private Path constructUnfocusedDimCurve() {
1209 float focusX = getNormalizedXFromUnfocusedY(mInitialTopOffset, FROM_TOP);
1210 float cpoint2X = focusX + (1f - focusX) / 2;
1211 Path p = new Path();
1212 // The unfocused dim interpolator starts at max dim, reduces to zero at 0.5 (the focused
1213 // task), then goes back to max dim towards the front of the stack
1214 p.moveTo(0f, MAX_DIM);
1215 p.cubicTo(focusX * 0.5f, MAX_DIM, focusX * 0.75f, MAX_DIM * 0.75f, focusX, 0f);
1216 p.cubicTo(cpoint2X, 0f, cpoint2X, MED_DIM, 1f, MED_DIM);
1221 * Scales the given {@param value} to the scale of the {@param instance} rect relative to the
1222 * {@param other} rect in the {@param extent} side.
1224 private int getScaleForExtent(Rect instance, Rect other, int value, int minValue,
1225 @Extent int extent) {
1226 if (extent == WIDTH) {
1227 float scale = Utilities.clamp01((float) instance.width() / other.width());
1228 return Math.max(minValue, (int) (scale * value));
1229 } else if (extent == HEIGHT) {
1230 float scale = Utilities.clamp01((float) instance.height() / other.height());
1231 return Math.max(minValue, (int) (scale * value));
1237 * Updates the current transforms that would put a TaskView at the front and back of the stack.
1239 private void updateFrontBackTransforms() {
1240 // Return early if we have not yet initialized
1241 if (mStackRect.isEmpty()) {
1245 float min = Utilities.mapRange(mFocusState, mUnfocusedRange.relativeMin,
1246 mFocusedRange.relativeMin);
1247 float max = Utilities.mapRange(mFocusState, mUnfocusedRange.relativeMax,
1248 mFocusedRange.relativeMax);
1249 getStackTransform(min, min, 0f, mFocusState, mBackOfStackTransform, null,
1250 true /* ignoreSingleTaskCase */, true /* forceUpdate */);
1251 getStackTransform(max, max, 0f, mFocusState, mFrontOfStackTransform, null,
1252 true /* ignoreSingleTaskCase */, true /* forceUpdate */);
1253 mBackOfStackTransform.visible = true;
1254 mFrontOfStackTransform.visible = true;
1257 public void dump(String prefix, PrintWriter writer) {
1258 String innerPrefix = prefix + " ";
1260 writer.print(prefix); writer.print(TAG);
1261 writer.write(" numStackTasks="); writer.write(mNumStackTasks);
1264 writer.print(innerPrefix);
1265 writer.print("insets="); writer.print(Utilities.dumpRect(mSystemInsets));
1266 writer.print(" stack="); writer.print(Utilities.dumpRect(mStackRect));
1267 writer.print(" task="); writer.print(Utilities.dumpRect(mTaskRect));
1268 writer.print(" freeform="); writer.print(Utilities.dumpRect(mFreeformRect));
1269 writer.print(" actionButton="); writer.print(Utilities.dumpRect(mStackActionButtonRect));
1272 writer.print(innerPrefix);
1273 writer.print("minScroll="); writer.print(mMinScrollP);
1274 writer.print(" maxScroll="); writer.print(mMaxScrollP);
1275 writer.print(" initialScroll="); writer.print(mInitialScrollP);
1278 writer.print(innerPrefix);
1279 writer.print("focusState="); writer.print(mFocusState);
1282 if (mTaskIndexOverrideMap.size() > 0) {
1283 for (int i = mTaskIndexOverrideMap.size() - 1; i >= 0; i--) {
1284 int taskId = mTaskIndexOverrideMap.keyAt(i);
1285 float x = mTaskIndexMap.get(taskId);
1286 float overrideX = mTaskIndexOverrideMap.get(taskId, 0f);
1288 writer.print(innerPrefix);
1289 writer.print("taskId= "); writer.print(taskId);
1290 writer.print(" x= "); writer.print(x);
1291 writer.print(" overrideX= "); writer.print(overrideX);