diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index cbc92f2d985b3..bd7f45bd0e7a5 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -230,6 +230,9 @@ 28dp + + 24dp + 12dp diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java index 6668df93511d6..ebfacac1b1fb8 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java @@ -64,8 +64,6 @@ public class Constants { } public static class TaskStackView { - public static final int TaskStackMinOverscrollRange = 32; - public static final int TaskStackMaxOverscrollRange = 128; public static final int FilterStartDelay = 25; } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index b05e1fe4adbaf..23836ab762165 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -469,7 +469,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm(); Rect taskStackBounds = new Rect(mTaskStackBounds); algo.setSystemInsets(systemInsets); - algo.computeRects(windowRect.width(), windowRect.height(), taskStackBounds); + algo.computeRects(taskStackBounds); Rect taskViewBounds = algo.getUntransformedTaskViewBounds(); if (!taskViewBounds.equals(mLastTaskViewBounds)) { mLastTaskViewBounds.set(taskViewBounds); diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java index 8e85bfc4aca6f..ea6821fe16961 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/ParametricCurve.java @@ -48,6 +48,7 @@ public class ParametricCurve { float[] xp; float[] px; + float mLength; CurveFunction mFn; ParametricCurveFunction mScaleFn; @@ -79,6 +80,7 @@ public class ParametricCurve { dx[xStep] = (float) Math.hypot(fx[xStep] - fx[xStep - 1], step); pLength += dx[xStep]; } + mLength = pLength; // Approximate p(x), a function of cumulative progress with x, normalized to 0..1 float p = 0; px[0] = 0f; @@ -260,4 +262,11 @@ public class ParametricCurve { } return 1f - xToP(maxX, bounds); } + + /** + * Returns the length of this curve. + */ + public float getArcLength() { + return mLength; + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java index 0793180549a9a..60051b83ad437 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java @@ -170,6 +170,12 @@ public class Task { } } + public boolean isFreeformTask() { + // Temporarily disable: + return false; + // return SystemServicesProxy.isFreeformStack(key.stackId); + } + /** Notifies the callback listeners that this task has been loaded */ public void notifyTaskDataLoaded(Bitmap thumbnail, Drawable applicationIcon) { this.applicationIcon = applicationIcon; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java index e04699c10fddd..b7c1de36279fb 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/SwipeHelper.java @@ -68,6 +68,7 @@ public class SwipeHelper { private float mInitialTouchPos; private boolean mDragging; + private float mSnapBackTranslationX; private View mCurrView; private boolean mCanCurrViewBeDimissed; @@ -92,6 +93,10 @@ public class SwipeHelper { mDensityScale = densityScale; } + public void setSnapBackTranslationX(float translationX) { + mSnapBackTranslationX = translationX; + } + public void setPagingTouchSlop(float pagingTouchSlop) { mPagingTouchSlop = pagingTouchSlop; } @@ -267,7 +272,7 @@ public class SwipeHelper { private void snapChild(final View view, float velocity) { final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(view); - ValueAnimator anim = createTranslationAnimation(view, 0); + ValueAnimator anim = createTranslationAnimation(view, mSnapBackTranslationX); int duration = SNAP_ANIM_LEN; anim.setDuration(duration); anim.setInterpolator(mLinearOutSlowInInterpolator); diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 757e695f283bf..21ceb338677d1 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -124,7 +124,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mConfig = RecentsConfiguration.getInstance(); mViewPool = new ViewPool<>(context, this); mInflater = LayoutInflater.from(context); - mLayoutAlgorithm = new TaskStackViewLayoutAlgorithm(context, mConfig); + mLayoutAlgorithm = new TaskStackViewLayoutAlgorithm(context); mFilterAlgorithm = new TaskStackViewFilterAlgorithm(this, mViewPool); mStackScroller = new TaskStackViewScroller(context, mLayoutAlgorithm); mStackScroller.setCallbacks(this); @@ -633,10 +633,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } /** Computes the stack and task rects */ - public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds, - boolean launchedWithAltTab, boolean launchedFromHome) { + public void computeRects(Rect taskStackBounds) { // Compute the rects in the stack algorithm - mLayoutAlgorithm.computeRects(windowWidth, windowHeight, taskStackBounds); + mLayoutAlgorithm.computeRects(taskStackBounds); // Update the scroll bounds updateMinMaxScroll(false); @@ -674,9 +673,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal int height = MeasureSpec.getSize(heightMeasureSpec); // Compute our stack/task rects - RecentsActivityLaunchState launchState = mConfig.getLaunchState(); - computeRects(width, height, mTaskStackBounds, launchState.launchedWithAltTab, - launchState.launchedFromHome); + computeRects(mTaskStackBounds); // If this is the first layout, then scroll to the front of the stack and synchronize the // stack views immediately to load all the views diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java index 9a5d9bdf79021..d81f3f6ea6a59 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java @@ -21,6 +21,7 @@ import android.content.res.Resources; import android.graphics.Rect; import android.util.Log; import com.android.systemui.R; +import com.android.systemui.recents.Recents; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.misc.ParametricCurve; import com.android.systemui.recents.misc.Utilities; @@ -32,14 +33,15 @@ import java.util.HashMap; /** * The layout logic for a TaskStackView. + * */ public class TaskStackViewLayoutAlgorithm { - private static final boolean DEBUG = false; private static final String TAG = "TaskStackViewLayoutAlgorithm"; + private static final boolean DEBUG = false; // The min scale of the last task at the top of the curve - private static final float STACK_PEEK_MIN_SCALE = 0.75f; + private static final float STACK_PEEK_MIN_SCALE = 0.85f; // The scale of the last task private static final float SINGLE_TASK_SCALE = 0.95f; // The percentage of height of task to show between tasks @@ -58,14 +60,17 @@ public class TaskStackViewLayoutAlgorithm { } Context mContext; - RecentsConfiguration mConfig; // This is the view bounds inset exactly by the search bar, but without the bottom inset // see RecentsConfiguration.getTaskStackBounds() public Rect mStackRect = new Rect(); + // This is the task view bounds for layout (untransformed), the rect is top-aligned to the top // of the stack rect public Rect mTaskRect = new Rect(); + + // The bounds of the freeform workspace, the rect is top-aligned to the top of the stack rect + public Rect mFreeformRect = new Rect(); // This is the current system insets public Rect mSystemInsets = new Rect(); @@ -74,8 +79,13 @@ public class TaskStackViewLayoutAlgorithm { // The largest scroll progress, at this value, the front most task will be visible above the // navigation bar float mMaxScrollP; + // The scroll progress at which the stack scroll ends and the overscroll begins. This serves + // as the point at which we can show the freeform space. + float mMaxStackScrollP; // The initial progress that the scroller is set float mInitialScrollP; + // The task progress for the front-most task in the stack + float mFrontMostTaskP; // The relative progress to ensure that the height between affiliated tasks is respected float mWithinAffiliationPOffset; @@ -89,11 +99,21 @@ public class TaskStackViewLayoutAlgorithm { // The relative progress to ensure that the offset from the bottom of the stack to the bottom // of the task is respected float mTaskBottomPOffset; + // The relative progress to ensure that the freeform workspace height is respected + float mFreeformWorkspacePOffset; // The front-most task bottom offset int mTaskBottomOffset; - // The last computed task count - int mNumTasks; + // The number of cells in the freeform workspace + int mFreeformCellXCount; + int mFreeformCellYCount; + // The width and height of the cells in the freeform workspace + int mFreeformCellWidth; + int mFreeformCellHeight; + + // The last computed task counts + int mNumStackTasks; + int mNumFreeformTasks; // The min/max z translations int mMinTranslationZ; int mMaxTranslationZ; @@ -104,12 +124,11 @@ public class TaskStackViewLayoutAlgorithm { // Log function static ParametricCurve sCurve; - public TaskStackViewLayoutAlgorithm(Context context, RecentsConfiguration config) { + public TaskStackViewLayoutAlgorithm(Context context) { Resources res = context.getResources(); mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_min); mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_max); mContext = context; - mConfig = config; if (sCurve == null) { sCurve = new ParametricCurve(new ParametricCurve.CurveFunction() { // The large the XScale, the longer the flat area of the curve @@ -153,23 +172,25 @@ public class TaskStackViewLayoutAlgorithm { } /** - * Computes the stack and task rects + * Computes the stack and task rects. */ - public void computeRects(int windowWidth, int windowHeight, Rect taskStackBounds) { - int widthPadding = (int) (mConfig.taskStackWidthPaddingPct * taskStackBounds.width()); + public void computeRects(Rect taskStackBounds) { + RecentsConfiguration config = RecentsConfiguration.getInstance(); + int widthPadding = (int) (config.taskStackWidthPaddingPct * taskStackBounds.width()); int heightPadding = mContext.getResources().getDimensionPixelSize( R.dimen.recents_stack_top_padding); // Compute the stack rect, inset from the given task stack bounds mStackRect.set(taskStackBounds.left + widthPadding, taskStackBounds.top + heightPadding, - taskStackBounds.right - widthPadding, windowHeight); + taskStackBounds.right - widthPadding, taskStackBounds.bottom); mTaskBottomOffset = mSystemInsets.bottom + heightPadding; // Compute the task rect, align it to the top-center square in the stack rect - int size = Math.min(mStackRect.width(), taskStackBounds.height() - mTaskBottomOffset); + int size = Math.min(mStackRect.width(), mStackRect.height() - mTaskBottomOffset); int xOffset = (mStackRect.width() - size) / 2; mTaskRect.set(mStackRect.left + xOffset, mStackRect.top, mStackRect.right - xOffset, mStackRect.top + size); + mFreeformRect.set(mTaskRect); // Compute the progress offsets int withinAffiliationOffset = mContext.getResources().getDimensionPixelSize( @@ -184,9 +205,12 @@ public class TaskStackViewLayoutAlgorithm { mTaskHalfHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height() / 2, mStackRect); mTaskBottomPOffset = sCurve.computePOffsetForHeight(mTaskBottomOffset, mStackRect); + mFreeformWorkspacePOffset = sCurve.computePOffsetForHeight(mFreeformRect.height(), + mStackRect); if (DEBUG) { Log.d(TAG, "computeRects"); + Log.d(TAG, "\tarclength: " + sCurve.getArcLength()); Log.d(TAG, "\tmStackRect: " + mStackRect); Log.d(TAG, "\tmTaskRect: " + mTaskRect); Log.d(TAG, "\tmSystemInsets: " + mSystemInsets); @@ -222,29 +246,38 @@ public class TaskStackViewLayoutAlgorithm { // Clear the progress map mTaskProgressMap.clear(); - mNumTasks = tasks.size(); // Return early if we have no tasks if (tasks.isEmpty()) { - mMinScrollP = mMaxScrollP = 0; + mMinScrollP = mMaxScrollP = mMaxStackScrollP = 0; + mNumStackTasks = mNumFreeformTasks = 0; return; } - // We calculate the progress by taking the progress of the element from the bottom of the - // screen - if (mNumTasks == 1) { - // Just center the task in the visible stack rect - mMinScrollP = mMaxScrollP = mInitialScrollP = 0f; - mTaskProgressMap.put(tasks.get(0).key, 0f); - } else { - // Update the tasks from back to front with the new progresses. We set the initial - // progress to the progress at which the top of the last task is near the center of the - // visible stack rect. + // Filter the set of freeform and stack tasks + ArrayList freeformTasks = new ArrayList<>(); + ArrayList stackTasks = new ArrayList<>(); + for (int i = 0; i < tasks.size(); i++) { + Task task = tasks.get(i); + if (task.isFreeformTask()) { + freeformTasks.add(task); + } else { + stackTasks.add(task); + } + } + mNumStackTasks = stackTasks.size(); + mNumFreeformTasks = freeformTasks.size(); + + // TODO: In the case where there is only freeform tasks, then the scrolls should be set to + // zero + + if (!stackTasks.isEmpty()) { + // Update the for each task from back to front. float pAtBackMostTaskTop = 0; float pAtFrontMostTaskTop = pAtBackMostTaskTop; - int taskCount = tasks.size(); + int taskCount = stackTasks.size(); for (int i = 0; i < taskCount; i++) { - Task task = tasks.get(i); + Task task = stackTasks.get(i); mTaskProgressMap.put(task.key, pAtFrontMostTaskTop); if (i < (taskCount - 1)) { @@ -255,15 +288,47 @@ public class TaskStackViewLayoutAlgorithm { } } - // Set the max scroll progress to the point at which the bottom of the front-most task - // is aligned to the bottom of the stack (including nav bar and stack padding) - mMaxScrollP = pAtFrontMostTaskTop - 1f + mTaskBottomPOffset + mTaskHeightPOffset; + mFrontMostTaskP = pAtFrontMostTaskTop; + // Set the max scroll progress to the point at which the top of the front-most task + // is aligned to the bottom of the stack (offset by nav bar, padding, and task height) + mMaxStackScrollP = getBottomAlignedScrollProgress(pAtFrontMostTaskTop, + mTaskBottomPOffset + mTaskHeightPOffset); // Basically align the back-most task such that its progress is the same as the top of - // the front most task at the max scroll - mMinScrollP = pAtBackMostTaskTop - 1f + mTaskBottomPOffset + mTaskHeightPOffset; - // The offset the inital scroll position to the front of the stack, with half the front - // task height visible - mInitialScrollP = Math.max(mMinScrollP, mMaxScrollP - mTaskHalfHeightPOffset); + // the front most task at the max stack scroll + mMinScrollP = getBottomAlignedScrollProgress(pAtBackMostTaskTop, + mTaskBottomPOffset + mTaskHeightPOffset); + } + + if (!freeformTasks.isEmpty()) { + // Calculate the cell width/height depending on the number of freeform tasks + mFreeformCellXCount = Math.max(2, (int) Math.ceil(Math.sqrt(mNumFreeformTasks))); + mFreeformCellYCount = Math.max(2, (int) Math.ceil((float) mNumFreeformTasks / mFreeformCellXCount)); + mFreeformCellWidth = mFreeformRect.width() / mFreeformCellXCount; + mFreeformCellHeight = mFreeformRect.height() / mFreeformCellYCount; + + // Put each of the tasks in the progress map at a fixed index (does not need to actually + // map to a scroll position, just by index) + int taskCount = freeformTasks.size(); + for (int i = 0; i < taskCount; i++) { + Task task = freeformTasks.get(i); + mTaskProgressMap.put(task.key, (mFrontMostTaskP + 1) + i); + } + + // The max scroll includes the freeform workspace offset. As the scroll progress exceeds + // mMaxStackScrollP up to mMaxScrollP, the stack will translate upwards and the freeform + // workspace will be visible + mMaxScrollP = mMaxStackScrollP + mFreeformWorkspacePOffset; + mInitialScrollP = mMaxScrollP; + } else { + mMaxScrollP = mMaxStackScrollP; + mInitialScrollP = Math.max(mMinScrollP, mMaxStackScrollP - mTaskHalfHeightPOffset); + } + if (DEBUG) { + Log.d(TAG, "mNumStackTasks: " + mNumStackTasks); + Log.d(TAG, "mNumFreeformTasks: " + mNumFreeformTasks); + Log.d(TAG, "mMinScrollP: " + mMinScrollP); + Log.d(TAG, "mMaxStackScrollP: " + mMaxStackScrollP); + Log.d(TAG, "mMaxScrollP: " + mMaxScrollP); } } @@ -272,11 +337,24 @@ public class TaskStackViewLayoutAlgorithm { * computeMinMaxScroll() is called first. */ public VisibilityReport computeStackVisibilityReport(ArrayList tasks) { + // Ensure minimum visibility count if (tasks.size() <= 1) { return new VisibilityReport(1, 1); } - // Walk backwards in the task stack and count the number of tasks and visible thumbnails + // If there are freeform tasks, then they will be the only ones visible + int freeformTaskCount = 0; + for (Task t : tasks) { + if (t.isFreeformTask()) { + freeformTaskCount++; + } + } + if (freeformTaskCount > 0) { + return new VisibilityReport(freeformTaskCount, freeformTaskCount); + } + + // Otherwise, walk backwards in the stack and count the number of tasks and visible + // thumbnails int taskHeight = mTaskRect.height(); int taskBarHeight = mContext.getResources().getDimensionPixelSize( R.dimen.recents_task_bar_height); @@ -338,18 +416,38 @@ public class TaskStackViewLayoutAlgorithm { /** Update/get the transform */ public TaskViewTransform getStackTransform(float taskProgress, float stackScroll, TaskViewTransform transformOut, TaskViewTransform prevTransform) { - if (DEBUG) { - Log.d(TAG, "getStackTransform: " + stackScroll); + float stackOverscroll = (stackScroll - mMaxStackScrollP) / mFreeformWorkspacePOffset; + int overscrollYOffset = 0; + if (mNumFreeformTasks > 0) { + overscrollYOffset = (int) (Math.max(0, stackOverscroll) * mFreeformRect.height()); } - if (mNumTasks == 1) { + if ((mNumFreeformTasks > 0) && (stackScroll > mMaxStackScrollP) && + (taskProgress > mFrontMostTaskP)) { + // This is a freeform task, so lay it out in the freeform workspace + int taskIndex = Math.round(taskProgress - (mFrontMostTaskP + 1)); + int x = taskIndex % mFreeformCellXCount; + int y = taskIndex / mFreeformCellXCount; + int frontTaskBottom = mStackRect.height() - mTaskBottomOffset; + float scale = (float) mFreeformCellWidth / mTaskRect.width(); + int scaleXOffset = (int) (((1f - scale) * mTaskRect.width()) / 2); + int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2); + transformOut.scale = scale; + transformOut.translationX = x * mFreeformCellWidth - scaleXOffset; + transformOut.translationY = frontTaskBottom - overscrollYOffset + + (y * mFreeformCellHeight) - scaleYOffset; + transformOut.visible = true; + return transformOut; + + } else if (mNumStackTasks == 1) { // Center the task in the stack, changing the scale will not follow the curve, but just // modulate some values directly - float pTaskRelative = -stackScroll; + float pTaskRelative = mMinScrollP - stackScroll; float scale = SINGLE_TASK_SCALE; int topOffset = (mStackRect.height() - mTaskBottomOffset - mTaskRect.height()) / 2; transformOut.scale = scale; - transformOut.translationY = (int) (topOffset + (pTaskRelative * mStackRect.height())); + transformOut.translationY = (int) (topOffset + (pTaskRelative * mStackRect.height())) - + overscrollYOffset; transformOut.translationZ = mMaxTranslationZ; transformOut.rect.set(mTaskRect); transformOut.rect.offset(0, transformOut.translationY); @@ -357,8 +455,12 @@ public class TaskStackViewLayoutAlgorithm { transformOut.visible = true; transformOut.p = pTaskRelative; return transformOut; + } else { float pTaskRelative = taskProgress - stackScroll; + if (mNumFreeformTasks > 0) { + pTaskRelative = Math.min(mMaxStackScrollP, pTaskRelative); + } float pBounded = Math.max(0, Math.min(pTaskRelative, 1f)); // If the task top is outside of the bounds below the screen, then immediately reset it if (pTaskRelative > 1f) { @@ -379,7 +481,7 @@ public class TaskStackViewLayoutAlgorithm { int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2); transformOut.scale = scale; transformOut.translationY = sCurve.pToX(pBounded, mStackRect) - mStackRect.top - - scaleYOffset; + scaleYOffset - overscrollYOffset; transformOut.translationZ = Math.max(mMinTranslationZ, mMinTranslationZ + (pBounded * (mMaxTranslationZ - mMinTranslationZ))); transformOut.rect.set(mTaskRect); @@ -391,6 +493,27 @@ public class TaskStackViewLayoutAlgorithm { } } + /** + * Update/get the transform + */ + public TaskViewTransform getFreeformWorkspaceBounds(float stackScroll, + TaskViewTransform transformOut) { + transformOut.reset(); + if (mNumFreeformTasks == 0) { + return transformOut; + } + + if (stackScroll > mMaxStackScrollP) { + float stackOverscroll = (stackScroll - mMaxStackScrollP) / mFreeformWorkspacePOffset; + int overscrollYOffset = (int) (stackOverscroll * mFreeformRect.height()); + int frontTaskBottom = mStackRect.height() - mTaskBottomOffset; + transformOut.visible = true; + transformOut.alpha = + transformOut.translationY = frontTaskBottom - overscrollYOffset; + } + return transformOut; + } + /** * Returns the untransformed task view bounds. */ @@ -406,4 +529,29 @@ public class TaskStackViewLayoutAlgorithm { if (!mTaskProgressMap.containsKey(t.key)) return 0f; return mTaskProgressMap.get(t.key); } + + /** + * Maps a movement in screen y, relative to {@param downY}, to a movement in along the arc + * length of the curve. We know the curve is mostly flat, so we just map the length of the + * screen along the arc-length proportionally (1/arclength). + */ + public float getDeltaPForY(int downY, int y) { + float deltaP = (float) (y - downY) / mStackRect.height() * (1f / sCurve.getArcLength()); + return -deltaP; + } + + /** + * This is the inverse of {@link #getDeltaPForY}. Given a movement along the arc length + * of the curve, map back to the screen y. + */ + public int getYForDeltaP(float downScrollP, float p) { + int y = (int) ((p - downScrollP) * mStackRect.height() * sCurve.getArcLength()); + return -y; + } + + private float getBottomAlignedScrollProgress(float p, float pOffsetFromBottom) { + // At scroll progress == p, then p is at the top of the stack + // At scroll progress == p + 1, then p is at the bottom of the stack + return p - (1 - pOffsetFromBottom); + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java index 3d3b13dd3290c..15fcab4c67cd6 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java @@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; +import android.util.Log; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.OverScroller; @@ -29,8 +30,12 @@ import com.android.systemui.recents.misc.Utilities; /* The scrolling logic for a TaskStackView */ public class TaskStackViewScroller { + + private static final String TAG = "TaskStackViewScroller"; + private static final boolean DEBUG = false; + public interface TaskStackViewScrollerCallbacks { - public void onScrollChanged(float p); + void onScrollChanged(float p); } Context mContext; @@ -38,6 +43,8 @@ public class TaskStackViewScroller { TaskStackViewScrollerCallbacks mCb; float mStackScrollP; + float mFlingDownScrollP; + int mFlingDownY; OverScroller mScroller; ObjectAnimator mScrollAnimator; @@ -77,11 +84,6 @@ public class TaskStackViewScroller { } } - /** Sets the current stack scroll without calling the callback. */ - void setStackScrollRaw(float s) { - mStackScrollP = s; - } - /** * Sets the current stack scroll to the initial state when you first enter recents. * @return whether the stack progress changed. @@ -92,6 +94,20 @@ public class TaskStackViewScroller { return Float.compare(prevStackScrollP, mStackScrollP) != 0; } + /** + * Starts a fling that is coordinated with the {@link TaskStackViewTouchHandler}. + */ + public void fling(float downScrollP, int downY, int y, int velY, int minY, int maxY, + int overscroll) { + if (DEBUG) { + Log.d(TAG, "fling: " + downScrollP + ", downY: " + downY + ", y: " + y + + ", velY: " + velY + ", minY: " + minY + ", maxY: " + maxY); + } + mFlingDownScrollP = downScrollP; + mFlingDownY = downY; + mScroller.fling(0, y, 0, velY, 0, 0, minY, maxY, 0, overscroll); + } + /** Bounds the current scroll if necessary */ public boolean boundScroll() { float curScroll = getStackScroll(); @@ -174,21 +190,20 @@ public class TaskStackViewScroller { /**** OverScroller ****/ + // TODO: Remove + @Deprecated int progressToScrollRange(float p) { return (int) (p * mLayoutAlgorithm.mStackRect.height()); } - float scrollRangeToProgress(int s) { - return (float) s / mLayoutAlgorithm.mStackRect.height(); - } - /** Called from the view draw, computes the next scroll. */ boolean computeScroll() { if (mScroller.computeScrollOffset()) { - float scroll = scrollRangeToProgress(mScroller.getCurrY()); - setStackScrollRaw(scroll); - if (mCb != null) { - mCb.onScrollChanged(scroll); + float deltaP = mLayoutAlgorithm.getDeltaPForY(mFlingDownY, mScroller.getCurrY()); + float scroll = mFlingDownScrollP + deltaP; + setStackScroll(scroll); + if (DEBUG) { + Log.d(TAG, "computeScroll: " + scroll); } return true; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java index 3a1a9876926f6..08889c53161cc 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -17,6 +17,7 @@ package com.android.systemui.recents.views; import android.content.Context; +import android.content.res.Resources; import android.view.InputDevice; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -34,7 +35,11 @@ import java.util.List; /* Handles touch events for a TaskStackView. */ class TaskStackViewTouchHandler implements SwipeHelper.Callback { - static int INACTIVE_POINTER_ID = -1; + + private static final String TAG = "TaskStackViewTouchHandler"; + private static final boolean DEBUG = true; + + private static int INACTIVE_POINTER_ID = -1; Context mContext; TaskStackView mSv; @@ -42,21 +47,16 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { VelocityTracker mVelocityTracker; boolean mIsScrolling; - - float mInitialP; - float mLastP; - float mTotalPMotion; - int mInitialMotionX, mInitialMotionY; - int mLastMotionX, mLastMotionY; + float mDownScrollP; + int mDownX, mDownY; int mActivePointerId = INACTIVE_POINTER_ID; + int mOverscrollSize; TaskView mActiveTaskView = null; int mMinimumVelocity; int mMaximumVelocity; // The scroll touch slop is used to calculate when we start scrolling int mScrollTouchSlop; - // The page touch slop is used to calculate when we start swiping - float mPagingTouchSlop; // Used to calculate when a tap is outside a task view rectangle. final int mWindowTouchSlop; @@ -65,18 +65,20 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { public TaskStackViewTouchHandler(Context context, TaskStackView sv, TaskStackViewScroller scroller) { - mContext = context; + Resources res = context.getResources(); ViewConfiguration configuration = ViewConfiguration.get(context); + mContext = context; mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); mScrollTouchSlop = configuration.getScaledTouchSlop(); - mPagingTouchSlop = configuration.getScaledPagingTouchSlop(); mWindowTouchSlop = configuration.getScaledWindowTouchSlop(); mSv = sv; mScroller = scroller; - float densityScale = context.getResources().getDisplayMetrics().density; - mSwipeHelper = new SwipeHelper(context, SwipeHelper.X, this, densityScale, mPagingTouchSlop); + float densityScale = res.getDisplayMetrics().density; + mOverscrollSize = res.getDimensionPixelSize(R.dimen.recents_stack_overscroll); + mSwipeHelper = new SwipeHelper(context, SwipeHelper.X, this, densityScale, + configuration.getScaledPagingTouchSlop()); mSwipeHelper.setMinAlpha(1f); } @@ -88,11 +90,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { mVelocityTracker.clear(); } } - void initVelocityTrackerIfNotExists() { - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - } void recycleVelocityTracker() { if (mVelocityTracker != null) { mVelocityTracker.recycle(); @@ -115,175 +112,70 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { return null; } - /** Constructs a simulated motion event for the current stack scroll. */ - MotionEvent createMotionEventForStackScroll(MotionEvent ev) { - MotionEvent pev = MotionEvent.obtainNoHistory(ev); - pev.setLocation(0, mScroller.progressToScrollRange(mScroller.getStackScroll())); - return pev; - } - /** Touch preprocessing for handling below */ public boolean onInterceptTouchEvent(MotionEvent ev) { - // Return early if we have no children - boolean hasTaskViews = (mSv.getTaskViews().size() > 0); - if (!hasTaskViews) { - return false; - } - // Pass through to swipe helper if we are swiping mInterceptedBySwipeHelper = mSwipeHelper.onInterceptTouchEvent(ev); if (mInterceptedBySwipeHelper) { return true; } - TaskStackViewLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm; - boolean wasScrolling = mScroller.isScrolling() || - (mScroller.mScrollAnimator != null && mScroller.mScrollAnimator.isRunning()); - int action = ev.getAction(); - switch (action & MotionEvent.ACTION_MASK) { - case MotionEvent.ACTION_DOWN: { - // Save the touch down info - mInitialMotionX = mLastMotionX = (int) ev.getX(); - mInitialMotionY = mLastMotionY = (int) ev.getY(); - mInitialP = mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, - layoutAlgorithm.mStackRect); - mActivePointerId = ev.getPointerId(0); - mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY); - // Stop the current scroll if it is still flinging - mScroller.stopScroller(); - mScroller.stopBoundScrollAnimation(); - // Initialize the velocity tracker - initOrResetVelocityTracker(); - mVelocityTracker.addMovement(createMotionEventForStackScroll(ev)); - break; - } - case MotionEvent.ACTION_POINTER_DOWN: { - final int index = ev.getActionIndex(); - mActivePointerId = ev.getPointerId(index); - mLastMotionX = (int) ev.getX(index); - mLastMotionY = (int) ev.getY(index); - mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect); - break; - } - case MotionEvent.ACTION_MOVE: { - if (mActivePointerId == INACTIVE_POINTER_ID) break; - - // Initialize the velocity tracker if necessary - initVelocityTrackerIfNotExists(); - mVelocityTracker.addMovement(createMotionEventForStackScroll(ev)); - - int activePointerIndex = ev.findPointerIndex(mActivePointerId); - int y = (int) ev.getY(activePointerIndex); - int x = (int) ev.getX(activePointerIndex); - if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) { - // Save the touch move info - mIsScrolling = true; - // Disallow parents from intercepting touch events - final ViewParent parent = mSv.getParent(); - if (parent != null) { - parent.requestDisallowInterceptTouchEvent(true); - } - } - - mLastMotionX = x; - mLastMotionY = y; - mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect); - break; - } - case MotionEvent.ACTION_POINTER_UP: { - int pointerIndex = ev.getActionIndex(); - int pointerId = ev.getPointerId(pointerIndex); - if (pointerId == mActivePointerId) { - // Select a new active pointer id and reset the motion state - final int newPointerIndex = (pointerIndex == 0) ? 1 : 0; - mActivePointerId = ev.getPointerId(newPointerIndex); - mLastMotionX = (int) ev.getX(newPointerIndex); - mLastMotionY = (int) ev.getY(newPointerIndex); - mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect); - mVelocityTracker.clear(); - } - break; - } - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: { - // Animate the scroll back if we've cancelled - mScroller.animateBoundScroll(); - // Reset the drag state and the velocity tracker - mIsScrolling = false; - mActivePointerId = INACTIVE_POINTER_ID; - mActiveTaskView = null; - mTotalPMotion = 0; - recycleVelocityTracker(); - break; - } - } - - return wasScrolling || mIsScrolling; + return handleTouchEvent(ev); } /** Handles touch events once we have intercepted them */ public boolean onTouchEvent(MotionEvent ev) { - // Short circuit if we have no children - boolean hasTaskViews = (mSv.getTaskViews().size() > 0); - if (!hasTaskViews) { - return false; - } - // Pass through to swipe helper if we are swiping if (mInterceptedBySwipeHelper && mSwipeHelper.onTouchEvent(ev)) { return true; } - // Update the velocity tracker - initVelocityTrackerIfNotExists(); + handleTouchEvent(ev); + return true; + } + + private boolean handleTouchEvent(MotionEvent ev) { + // Short circuit if we have no children + if (mSv.getTaskViews().size() == 0) { + return false; + } TaskStackViewLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm; int action = ev.getAction(); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { // Save the touch down info - mInitialMotionX = mLastMotionX = (int) ev.getX(); - mInitialMotionY = mLastMotionY = (int) ev.getY(); - mInitialP = mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, - layoutAlgorithm.mStackRect); + mDownX = (int) ev.getX(); + mDownY = (int) ev.getY(); + mDownScrollP = mScroller.getStackScroll(); mActivePointerId = ev.getPointerId(0); - mActiveTaskView = findViewAtPoint(mLastMotionX, mLastMotionY); + mActiveTaskView = findViewAtPoint(mDownX, mDownY); + // Stop the current scroll if it is still flinging mScroller.stopScroller(); mScroller.stopBoundScrollAnimation(); + // Initialize the velocity tracker initOrResetVelocityTracker(); - mVelocityTracker.addMovement(createMotionEventForStackScroll(ev)); - // Disallow parents from intercepting touch events - final ViewParent parent = mSv.getParent(); - if (parent != null) { - parent.requestDisallowInterceptTouchEvent(true); - } + mVelocityTracker.addMovement(ev); break; } case MotionEvent.ACTION_POINTER_DOWN: { final int index = ev.getActionIndex(); + mDownX = (int) ev.getX(); + mDownY = (int) ev.getY(); + mDownScrollP = mScroller.getStackScroll(); mActivePointerId = ev.getPointerId(index); - mLastMotionX = (int) ev.getX(index); - mLastMotionY = (int) ev.getY(index); - mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, - layoutAlgorithm.mStackRect); + mVelocityTracker.addMovement(ev); break; } case MotionEvent.ACTION_MOVE: { - if (mActivePointerId == INACTIVE_POINTER_ID) break; - - mVelocityTracker.addMovement(createMotionEventForStackScroll(ev)); - int activePointerIndex = ev.findPointerIndex(mActivePointerId); - int x = (int) ev.getX(activePointerIndex); int y = (int) ev.getY(activePointerIndex); - int yTotal = Math.abs(y - mInitialMotionY); - float curP = layoutAlgorithm.sCurve.xToP(y, layoutAlgorithm.mStackRect); - float deltaP = mLastP - curP; if (!mIsScrolling) { - if (yTotal > mScrollTouchSlop) { + if (Math.abs(y - mDownY) > mScrollTouchSlop) { mIsScrolling = true; + // Disallow parents from intercepting touch events final ViewParent parent = mSv.getParent(); if (parent != null) { @@ -292,54 +184,14 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { } } if (mIsScrolling) { - float curStackScroll = mScroller.getStackScroll(); - float overScrollAmount = mScroller.getScrollAmountOutOfBounds(curStackScroll + deltaP); - if (Float.compare(overScrollAmount, 0f) != 0) { - // Bound the overscroll to a fixed amount, and inversely scale the y-movement - // relative to how close we are to the max overscroll - float maxOverScroll = mContext.getResources().getFloat( - R.dimen.recents_stack_overscroll_percentage); - deltaP *= (1f - (Math.min(maxOverScroll, overScrollAmount) - / maxOverScroll)); - } - mScroller.setStackScroll(curStackScroll + deltaP); - } - mLastMotionX = x; - mLastMotionY = y; - mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect); - mTotalPMotion += Math.abs(deltaP); - break; - } - case MotionEvent.ACTION_UP: { - mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); - int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId); - if (mIsScrolling && (Math.abs(velocity) > mMinimumVelocity)) { - float overscrollRangePct = Math.abs((float) velocity / mMaximumVelocity); - int overscrollRange = (int) (Math.min(1f, overscrollRangePct) * - (Constants.Values.TaskStackView.TaskStackMaxOverscrollRange - - Constants.Values.TaskStackView.TaskStackMinOverscrollRange)); - mScroller.mScroller.fling(0, - mScroller.progressToScrollRange(mScroller.getStackScroll()), - 0, velocity, - 0, 0, - mScroller.progressToScrollRange(mSv.mLayoutAlgorithm.mMinScrollP), - mScroller.progressToScrollRange(mSv.mLayoutAlgorithm.mMaxScrollP), - 0, Constants.Values.TaskStackView.TaskStackMinOverscrollRange + - overscrollRange); - // Invalidate to kick off computeScroll - mSv.invalidate(); - } else if (mIsScrolling && mScroller.isScrollOutOfBounds()) { - // Animate the scroll back into bounds - mScroller.animateBoundScroll(); - } else if (mActiveTaskView == null) { - // This tap didn't start on a task. - maybeHideRecentsFromBackgroundTap((int) ev.getX(), (int) ev.getY()); + // If we just move linearly on the screen, then that would map to 1/arclength + // of the curve, so just move the scroll proportional to that + float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y); + float curScrollP = mDownScrollP + deltaP; + mScroller.setStackScroll(curScrollP); } - mActivePointerId = INACTIVE_POINTER_ID; - mIsScrolling = false; - mTotalPMotion = 0; - recycleVelocityTracker(); + mVelocityTracker.addMovement(ev); break; } case MotionEvent.ACTION_POINTER_UP: { @@ -349,11 +201,41 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { // Select a new active pointer id and reset the motion state final int newPointerIndex = (pointerIndex == 0) ? 1 : 0; mActivePointerId = ev.getPointerId(newPointerIndex); - mLastMotionX = (int) ev.getX(newPointerIndex); - mLastMotionY = (int) ev.getY(newPointerIndex); - mLastP = layoutAlgorithm.sCurve.xToP(mLastMotionY, layoutAlgorithm.mStackRect); - mVelocityTracker.clear(); } + mVelocityTracker.addMovement(ev); + break; + } + case MotionEvent.ACTION_UP: { + mVelocityTracker.addMovement(ev); + mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + int activePointerIndex = ev.findPointerIndex(mActivePointerId); + int y = (int) ev.getY(activePointerIndex); + int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId); + if (mIsScrolling) { + if (mScroller.isScrollOutOfBounds()) { + // Animate the scroll back into bounds + mScroller.animateBoundScroll(); + } else if (Math.abs(velocity) > mMinimumVelocity) { + float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y); + float curScrollP = mDownScrollP + deltaP; + float downToCurY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP, + curScrollP); + float downToMinY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP, + layoutAlgorithm.mMaxScrollP); + float downToMaxY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP, + layoutAlgorithm.mMinScrollP); + mScroller.fling(mDownScrollP, mDownY, (int) downToCurY, velocity, + (int) downToMinY, (int) downToMaxY, mOverscrollSize); + mSv.invalidate(); + } + } else if (mActiveTaskView == null) { + // This tap didn't start on a task. + maybeHideRecentsFromBackgroundTap((int) ev.getX(), (int) ev.getY()); + } + + mActivePointerId = INACTIVE_POINTER_ID; + mIsScrolling = false; + recycleVelocityTracker(); break; } case MotionEvent.ACTION_CANCEL: { @@ -363,20 +245,19 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { } mActivePointerId = INACTIVE_POINTER_ID; mIsScrolling = false; - mTotalPMotion = 0; recycleVelocityTracker(); break; } } - return true; + return mIsScrolling; } /** Hides recents if the up event at (x, y) is a tap on the background area. */ void maybeHideRecentsFromBackgroundTap(int x, int y) { // Ignore the up event if it's too far from its start position. The user might have been // trying to scroll or swipe. - int dx = Math.abs(mInitialMotionX - x); - int dy = Math.abs(mInitialMotionY - y); + int dx = Math.abs(mDownX - x); + int dy = Math.abs(mDownY - y); if (dx > mScrollTouchSlop || dy > mScrollTouchSlop) { return; } @@ -427,12 +308,16 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { @Override public boolean canChildBeDismissed(View v) { + if (v instanceof TaskView) { + return !((TaskView) v).getTask().isFreeformTask(); + } return true; } @Override public void onBeginDrag(View v) { TaskView tv = (TaskView) v; + mSwipeHelper.setSnapBackTranslationX(tv.getTranslationX()); // Disable clipping with the stack while we are swiping tv.setClipViewInStack(false); // Disallow touch events from this task view diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java index ec50eb61b5585..174ff33e96814 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java @@ -26,6 +26,7 @@ import android.view.animation.Interpolator; /* The transform state for a task view */ public class TaskViewTransform { public int startDelay = 0; + public int translationX = 0; public int translationY = 0; public float translationZ = 0; public float scale = 1f; @@ -43,6 +44,7 @@ public class TaskViewTransform { public TaskViewTransform(TaskViewTransform o) { startDelay = o.startDelay; + translationX = o.translationX; translationY = o.translationY; translationZ = o.translationZ; scale = o.scale; @@ -52,9 +54,12 @@ public class TaskViewTransform { p = o.p; } - /** Resets the current transform */ + /** + * Resets the current transform. + */ public void reset() { startDelay = 0; + translationX = 0; translationY = 0; translationZ = 0; scale = 1f; @@ -71,6 +76,9 @@ public class TaskViewTransform { public boolean hasScaleChangedFrom(float v) { return (Float.compare(scale, v) != 0); } + public boolean hasTranslationXChangedFrom(float v) { + return (Float.compare(translationX, v) != 0); + } public boolean hasTranslationYChangedFrom(float v) { return (Float.compare(translationY, v) != 0); } @@ -87,6 +95,9 @@ public class TaskViewTransform { boolean requiresLayers = false; // Animate to the final state + if (hasTranslationXChangedFrom(v.getTranslationX())) { + anim.translationX(translationX); + } if (hasTranslationYChangedFrom(v.getTranslationY())) { anim.translationY(translationY); } @@ -117,6 +128,9 @@ public class TaskViewTransform { .start(); } else { // Set the changed properties + if (hasTranslationXChangedFrom(v.getTranslationX())) { + v.setTranslationX(translationX); + } if (hasTranslationYChangedFrom(v.getTranslationY())) { v.setTranslationY(translationY); } @@ -147,7 +161,8 @@ public class TaskViewTransform { @Override public String toString() { - return "TaskViewTransform delay: " + startDelay + " y: " + translationY + " z: " + translationZ + + return "TaskViewTransform delay: " + startDelay + + " x: " + translationX + " y: " + translationY + " z: " + translationZ + " scale: " + scale + " alpha: " + alpha + " visible: " + visible + " rect: " + rect + " p: " + p; }