Merge "Fix anim done callback not being called sometimes" into ub-launcher3-qt-dev
diff --git a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index c228bb9..62e40d1 100644
--- a/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/go/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -15,30 +15,20 @@
*/
package com.android.quickstep;
-import static com.android.launcher3.anim.Interpolators.ACCEL_2;
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
-import static com.android.quickstep.util.RemoteAnimationProvider.getLayer;
+import static com.android.quickstep.views.IconRecentsView.REMOTE_APP_TO_OVERVIEW_DURATION;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
-import android.graphics.Matrix;
-import android.graphics.Rect;
import android.util.Log;
-import android.view.View;
-
-import androidx.annotation.NonNull;
import com.android.launcher3.BaseDraggingActivity;
-import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.quickstep.util.RemoteAnimationProvider;
import com.android.quickstep.util.RemoteAnimationTargetSet;
import com.android.quickstep.views.IconRecentsView;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
-import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
/**
* Provider for the atomic remote window animation from the app to the overview.
@@ -47,9 +37,6 @@
*/
final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> implements
RemoteAnimationProvider {
-
- private static final long APP_TO_THUMBNAIL_FADE_DURATION = 50;
- private static final long APP_SCALE_DOWN_DURATION = 400;
private static final String TAG = "AppToOverviewAnimationProvider";
private final ActivityControlHelper<T> mHelper;
@@ -131,106 +118,17 @@
return anim;
}
- View thumbnailView = mRecentsView.getBottomThumbnailView();
- if (thumbnailView == null) {
- // This can be null if there were previously 0 tasks and the recycler view has not had
- // enough time to take in the data change, bind a new view, and lay out the new view.
- // TODO: Have a fallback to animate to
- if (Log.isLoggable(TAG, Log.WARN)) {
- Log.w(TAG, "No thumbnail view for running task. Using stub animation.");
- }
- anim.play(ValueAnimator.ofInt(0, 1).setDuration(getRecentsLaunchDuration()));
- return anim;
- }
-
- playAppScaleDownAnim(anim, closingAppTarget, recentsTarget, thumbnailView);
+ mRecentsView.playRemoteAppToRecentsAnimation(anim, closingAppTarget, recentsTarget);
return anim;
}
/**
- * Animate a closing app to scale down to the location of the thumbnail view in recents.
- *
- * @param anim animator set
- * @param appTarget the app surface thats closing
- * @param recentsTarget the surface containing recents
- * @param thumbnailView the thumbnail view to animate to
- */
- private void playAppScaleDownAnim(@NonNull AnimatorSet anim,
- @NonNull RemoteAnimationTargetCompat appTarget,
- @NonNull RemoteAnimationTargetCompat recentsTarget, @NonNull View thumbnailView) {
-
- // Identify where the entering remote app should animate to.
- Rect endRect = new Rect();
- thumbnailView.getGlobalVisibleRect(endRect);
-
- Rect appBounds = appTarget.sourceContainerBounds;
-
- ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1);
- valueAnimator.setDuration(APP_SCALE_DOWN_DURATION);
-
- SyncRtSurfaceTransactionApplierCompat surfaceApplier =
- new SyncRtSurfaceTransactionApplierCompat(thumbnailView);
-
- // Keep recents visible throughout the animation.
- SurfaceParams[] params = new SurfaceParams[2];
- // Closing app should stay on top.
- int boostedMode = MODE_CLOSING;
- params[0] = new SurfaceParams(recentsTarget.leash, 1f, null /* matrix */,
- null /* windowCrop */, getLayer(recentsTarget, boostedMode), 0 /* cornerRadius */);
-
- valueAnimator.addUpdateListener(new MultiValueUpdateListener() {
- private final FloatProp mScaleX;
- private final FloatProp mScaleY;
- private final FloatProp mTranslationX;
- private final FloatProp mTranslationY;
- private final FloatProp mAlpha;
-
- {
- // Scale down and move to view location.
- float endScaleX = ((float) endRect.width()) / appBounds.width();
- mScaleX = new FloatProp(1f, endScaleX, 0, APP_SCALE_DOWN_DURATION,
- ACCEL_DEACCEL);
- float endScaleY = ((float) endRect.height()) / appBounds.height();
- mScaleY = new FloatProp(1f, endScaleY, 0, APP_SCALE_DOWN_DURATION,
- ACCEL_DEACCEL);
- float endTranslationX = endRect.left -
- (appBounds.width() - thumbnailView.getWidth()) / 2.0f;
- mTranslationX = new FloatProp(0, endTranslationX, 0, APP_SCALE_DOWN_DURATION,
- ACCEL_DEACCEL);
- float endTranslationY = endRect.top -
- (appBounds.height() - thumbnailView.getHeight()) / 2.0f;
- mTranslationY = new FloatProp(0, endTranslationY, 0, APP_SCALE_DOWN_DURATION,
- ACCEL_DEACCEL);
-
- // Fade out quietly near the end to be replaced by the real view.
- mAlpha = new FloatProp(1.0f, 0,
- APP_SCALE_DOWN_DURATION - APP_TO_THUMBNAIL_FADE_DURATION,
- APP_TO_THUMBNAIL_FADE_DURATION, ACCEL_2);
- }
-
- @Override
- public void onUpdate(float percent) {
- Matrix m = new Matrix();
- m.setScale(mScaleX.value, mScaleY.value,
- appBounds.width() / 2.0f, appBounds.height() / 2.0f);
- m.postTranslate(mTranslationX.value, mTranslationY.value);
-
- params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, m,
- null /* windowCrop */, getLayer(appTarget, boostedMode),
- 0 /* cornerRadius */);
- surfaceApplier.scheduleApply(params);
- }
- });
- anim.play(valueAnimator);
- }
-
- /**
* Get duration of animation from app to overview.
*
* @return duration of animation
*/
long getRecentsLaunchDuration() {
- return APP_SCALE_DOWN_DURATION;
+ return REMOTE_APP_TO_OVERVIEW_DURATION;
}
}
diff --git a/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java b/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java
index 9b4ab8e..808cd72 100644
--- a/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java
+++ b/go/quickstep/src/com/android/quickstep/ContentFillItemAnimator.java
@@ -180,6 +180,7 @@
@Override
public void onAnimationEnd(Animator animation) {
+ CONTENT_TRANSITION_PROGRESS.set(itemView, 1.0f);
dispatchChangeFinished(viewHolder, true /* oldItem */);
mRunningAnims.remove(anim);
dispatchFinishedWhenDone();
@@ -215,20 +216,8 @@
@Override
public void endAnimation(@NonNull ViewHolder item) {
for (int i = mPendingAnims.size() - 1; i >= 0; i--) {
- PendingAnimation pendAnim = mPendingAnims.get(i);
- if (pendAnim.viewHolder == item) {
- mPendingAnims.remove(i);
- switch (pendAnim.animType) {
- case ANIM_TYPE_REMOVE:
- dispatchRemoveFinished(item);
- break;
- case ANIM_TYPE_CHANGE:
- dispatchChangeFinished(item, true /* oldItem */);
- break;
- default:
- break;
- }
- }
+ endPendingAnimation(mPendingAnims.get(i));
+ mPendingAnims.remove(i);
}
dispatchFinishedWhenDone();
}
@@ -239,27 +228,33 @@
return;
}
for (int i = mPendingAnims.size() - 1; i >= 0; i--) {
- PendingAnimation pendAnim = mPendingAnims.get(i);
- ViewHolder item = pendAnim.viewHolder;
- switch (pendAnim.animType) {
- case ANIM_TYPE_REMOVE:
- dispatchRemoveFinished(item);
- break;
- case ANIM_TYPE_CHANGE:
- dispatchChangeFinished(item, true /* oldItem */);
- break;
- default:
- break;
- }
+ endPendingAnimation(mPendingAnims.get(i));
mPendingAnims.remove(i);
}
for (int i = mRunningAnims.size() - 1; i >= 0; i--) {
ObjectAnimator anim = mRunningAnims.get(i);
- anim.end();
+ // This calls the on end animation callback which will set values to their end target.
+ anim.cancel();
}
dispatchFinishedWhenDone();
}
+ private void endPendingAnimation(PendingAnimation pendAnim) {
+ ViewHolder item = pendAnim.viewHolder;
+ switch (pendAnim.animType) {
+ case ANIM_TYPE_REMOVE:
+ item.itemView.setAlpha(1.0f);
+ dispatchRemoveFinished(item);
+ break;
+ case ANIM_TYPE_CHANGE:
+ CONTENT_TRANSITION_PROGRESS.set(item.itemView, 1.0f);
+ dispatchChangeFinished(item, true /* oldItem */);
+ break;
+ default:
+ break;
+ }
+ }
+
@Override
public boolean isRunning() {
return !mPendingAnims.isEmpty() || !mRunningAnims.isEmpty();
diff --git a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
index bba08a4..d39a66c 100644
--- a/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
+++ b/go/quickstep/src/com/android/quickstep/FallbackActivityControllerHelper.java
@@ -45,6 +45,7 @@
@Override
public AnimationFactory prepareRecentsUI(RecentsActivity activity, boolean activityVisible,
boolean animateActivity, Consumer<AnimatorPlaybackController> callback) {
+ // TODO: Logic for setting remote animation
if (activityVisible) {
return (transitionLength) -> { };
}
diff --git a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
index 40db7dd..d595007 100644
--- a/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
+++ b/go/quickstep/src/com/android/quickstep/LauncherActivityControllerHelper.java
@@ -40,6 +40,7 @@
boolean activityVisible, boolean animateActivity,
Consumer<AnimatorPlaybackController> callback) {
LauncherState fromState = activity.getStateManager().getState();
+ activity.<IconRecentsView>getOverviewPanel().setUsingRemoteAnimation(true);
//TODO: Implement this based off where the recents view needs to be for app => recents anim.
return new AnimationFactory() {
@Override
@@ -87,6 +88,7 @@
if (launcher == null) {
return false;
}
+ launcher.<IconRecentsView>getOverviewPanel().setUsingRemoteAnimation(false);
launcher.getUserEventDispatcher().logActionCommand(
LauncherLogProto.Action.Command.RECENTS_BUTTON,
getContainerType(),
diff --git a/go/quickstep/src/com/android/quickstep/RecentsActivity.java b/go/quickstep/src/com/android/quickstep/RecentsActivity.java
index 078f3a5..7f813ce 100644
--- a/go/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/go/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -67,8 +67,8 @@
}
@Override
- protected void onStart() {
+ protected void onResume() {
mIconRecentsView.onBeginTransitionToOverview();
- super.onStart();
+ super.onResume();
}
}
diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
index 07faa4b..8276078 100644
--- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
+++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
@@ -19,10 +19,14 @@
import static androidx.recyclerview.widget.LinearLayoutManager.VERTICAL;
+import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.quickstep.TaskAdapter.CHANGE_EVENT_TYPE_EMPTY_TO_CONTENT;
import static com.android.quickstep.TaskAdapter.ITEM_TYPE_CLEAR_ALL;
import static com.android.quickstep.TaskAdapter.ITEM_TYPE_TASK;
+import static com.android.quickstep.TaskAdapter.MAX_TASKS_TO_DISPLAY;
import static com.android.quickstep.TaskAdapter.TASKS_START_POSITION;
+import static com.android.quickstep.util.RemoteAnimationProvider.getLayer;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -32,6 +36,7 @@
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.ArraySet;
@@ -40,10 +45,12 @@
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewTreeObserver;
+import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.interpolator.view.animation.LinearOutSlowInInterpolator;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
@@ -64,7 +71,11 @@
import com.android.quickstep.TaskHolder;
import com.android.quickstep.TaskListLoader;
import com.android.quickstep.TaskSwipeCallback;
+import com.android.quickstep.util.MultiValueUpdateListener;
import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
import java.util.ArrayList;
import java.util.List;
@@ -102,6 +113,22 @@
private static final float ITEM_ANIMATE_OUT_TRANSLATION_X_RATIO = .25f;
private static final long CLEAR_ALL_FADE_DELAY = 120;
+ private static final long REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION = 300;
+ private static final long REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION = 400;
+ private static final long REMOTE_TO_RECENTS_ITEM_FADE_START_DELAY = 200;
+ private static final long REMOTE_TO_RECENTS_ITEM_FADE_DURATION = 217;
+ private static final long REMOTE_TO_RECENTS_ITEM_FADE_BETWEEN_DELAY = 33;
+
+ private static final PathInterpolator FAST_OUT_SLOW_IN_1 =
+ new PathInterpolator(.4f, 0f, 0f, 1f);
+ private static final PathInterpolator FAST_OUT_SLOW_IN_2 =
+ new PathInterpolator(.5f, 0f, 0f, 1f);
+ private static final LinearOutSlowInInterpolator OUT_SLOW_IN =
+ new LinearOutSlowInInterpolator();
+
+ public static final long REMOTE_APP_TO_OVERVIEW_DURATION =
+ REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION;
+
/**
* A ratio representing the view's relative placement within its padded space. For example, 0
* is top aligned and 0.5 is centered vertically.
@@ -125,6 +152,7 @@
private View mEmptyView;
private View mContentView;
private boolean mTransitionedFromApp;
+ private boolean mUsingRemoteAnimation;
private AnimatorSet mLayoutAnimation;
private final ArraySet<View> mLayingOutViews = new ArraySet<>();
private Rect mInsets;
@@ -276,7 +304,9 @@
// not be scrollable.
mTaskLayoutManager.scrollToPositionWithOffset(TASKS_START_POSITION, 0 /* offset */);
}
- scheduleFadeInLayoutAnimation();
+ if (!mUsingRemoteAnimation) {
+ scheduleFadeInLayoutAnimation();
+ }
// Load any task changes
if (!mTaskLoader.needsToLoad()) {
return;
@@ -315,6 +345,17 @@
}
/**
+ * Set whether we're using a custom remote animation. If so, we will not do the default layout
+ * animation when entering recents and instead wait for the remote app surface to be ready to
+ * use.
+ *
+ * @param usingRemoteAnimation true if doing a remote animation, false o/w
+ */
+ public void setUsingRemoteAnimation(boolean usingRemoteAnimation) {
+ mUsingRemoteAnimation = usingRemoteAnimation;
+ }
+
+ /**
* Handles input from the overview button. Launch the most recent task unless we just came from
* the app. In that case, we launch the next most recent.
*/
@@ -365,17 +406,19 @@
}
/**
- * Get the bottom most thumbnail view to animate to.
+ * Get the bottom most task view to animate to.
*
- * @return the thumbnail view if laid out
+ * @return the task view
*/
- public @Nullable View getBottomThumbnailView() {
- ArrayList<TaskItemView> taskViews = getTaskViews();
- if (taskViews.isEmpty()) {
- return null;
+ private @Nullable TaskItemView getBottomTaskView() {
+ int childCount = mTaskRecyclerView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View view = mTaskRecyclerView.getChildAt(i);
+ if (mTaskRecyclerView.getChildViewHolder(view).getItemViewType() == ITEM_TYPE_TASK) {
+ return (TaskItemView) view;
+ }
}
- TaskItemView view = taskViews.get(0);
- return view.getThumbnailView();
+ return null;
}
/**
@@ -581,6 +624,205 @@
mLayoutAnimation.start();
}
+ /**
+ * Play remote animation from app to recents. This should scale the currently closing app down
+ * to the recents thumbnail.
+ *
+ * @param anim animator set
+ * @param appTarget the app surface thats closing
+ * @param recentsTarget the surface containing recents
+ */
+ public void playRemoteAppToRecentsAnimation(@NonNull AnimatorSet anim,
+ @NonNull RemoteAnimationTargetCompat appTarget,
+ @NonNull RemoteAnimationTargetCompat recentsTarget) {
+ TaskItemView bottomView = getBottomTaskView();
+ if (bottomView == null) {
+ // This can be null if there were previously 0 tasks and the recycler view has not had
+ // enough time to take in the data change, bind a new view, and lay out the new view.
+ // TODO: Have a fallback to animate to
+ anim.play(ValueAnimator.ofInt(0, 1).setDuration(REMOTE_APP_TO_OVERVIEW_DURATION));
+ }
+ final Matrix appMatrix = new Matrix();
+ playRemoteTransYAnim(anim, appMatrix);
+ playRemoteAppScaleDownAnim(anim, appMatrix, appTarget, recentsTarget,
+ bottomView.getThumbnailView());
+ playRemoteTaskListFadeIn(anim, bottomView);
+ }
+
+ /**
+ * Play translation Y animation for the remote app to recents animation. Animates over all task
+ * views as well as the closing app, easing them into their final vertical positions.
+ *
+ * @param anim animator set to play on
+ * @param appMatrix transformation matrix for the closing app surface
+ */
+ private void playRemoteTransYAnim(@NonNull AnimatorSet anim, @NonNull Matrix appMatrix) {
+ final ArrayList<TaskItemView> views = getTaskViews();
+
+ // Start Y translation from about halfway through the tasks list to the bottom thumbnail.
+ float taskHeight = getResources().getDimension(R.dimen.task_item_height);
+ float totalTransY = -(MAX_TASKS_TO_DISPLAY / 2.0f - 1) * taskHeight;
+ for (int i = 0, size = views.size(); i < size; i++) {
+ views.get(i).setTranslationY(totalTransY);
+ }
+
+ ValueAnimator transYAnim = ValueAnimator.ofFloat(totalTransY, 0);
+ transYAnim.setDuration(REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION);
+ transYAnim.setInterpolator(FAST_OUT_SLOW_IN_2);
+ transYAnim.addUpdateListener(valueAnimator -> {
+ float transY = (float) valueAnimator.getAnimatedValue();
+ for (int i = 0, size = views.size(); i < size; i++) {
+ views.get(i).setTranslationY(transY);
+ }
+ appMatrix.postTranslate(0, transY - totalTransY);
+ });
+ transYAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ for (int i = 0, size = views.size(); i < size; i++) {
+ views.get(i).setTranslationY(0);
+ }
+ }
+ });
+ anim.play(transYAnim);
+ }
+
+ /**
+ * Play the scale down animation for the remote app to recents animation where the app surface
+ * scales down to where the thumbnail is.
+ *
+ * @param anim animator set to play on
+ * @param appMatrix transformation matrix for the app surface
+ * @param appTarget closing app target
+ * @param recentsTarget opening recents target
+ * @param thumbnailView thumbnail view to animate to
+ */
+ private void playRemoteAppScaleDownAnim(@NonNull AnimatorSet anim, @NonNull Matrix appMatrix,
+ @NonNull RemoteAnimationTargetCompat appTarget,
+ @NonNull RemoteAnimationTargetCompat recentsTarget,
+ @NonNull View thumbnailView) {
+ // Identify where the entering remote app should animate to.
+ Rect endRect = new Rect();
+ thumbnailView.getGlobalVisibleRect(endRect);
+ Rect appBounds = appTarget.sourceContainerBounds;
+
+ SyncRtSurfaceTransactionApplierCompat surfaceApplier =
+ new SyncRtSurfaceTransactionApplierCompat(this);
+
+ // Keep recents visible throughout the animation.
+ SurfaceParams[] params = new SurfaceParams[2];
+ // Closing app should stay on top.
+ int boostedMode = MODE_CLOSING;
+ params[0] = new SurfaceParams(recentsTarget.leash, 1f, null /* matrix */,
+ null /* windowCrop */, getLayer(recentsTarget, boostedMode), 0 /* cornerRadius */);
+
+ ValueAnimator remoteAppAnim = ValueAnimator.ofInt(0, 1);
+ remoteAppAnim.setDuration(REMOTE_TO_RECENTS_VERTICAL_EASE_IN_DURATION);
+ remoteAppAnim.addUpdateListener(new MultiValueUpdateListener() {
+ private final FloatProp mScaleX;
+ private final FloatProp mScaleY;
+ private final FloatProp mTranslationX;
+ private final FloatProp mTranslationY;
+ private final FloatProp mAlpha;
+
+ {
+ // Scale down and move to view location.
+ float endScaleX = ((float) endRect.width()) / appBounds.width();
+ mScaleX = new FloatProp(1f, endScaleX, 0, REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION,
+ FAST_OUT_SLOW_IN_1);
+ float endScaleY = ((float) endRect.height()) / appBounds.height();
+ mScaleY = new FloatProp(1f, endScaleY, 0, REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION,
+ FAST_OUT_SLOW_IN_1);
+ float endTranslationX = endRect.left -
+ (appBounds.width() - thumbnailView.getWidth()) / 2.0f;
+ mTranslationX = new FloatProp(0, endTranslationX, 0,
+ REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION, FAST_OUT_SLOW_IN_1);
+ float endTranslationY = endRect.top -
+ (appBounds.height() - thumbnailView.getHeight()) / 2.0f;
+ mTranslationY = new FloatProp(0, endTranslationY, 0,
+ REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION, FAST_OUT_SLOW_IN_2);
+ mAlpha = new FloatProp(1.0f, 0, 0, REMOTE_TO_RECENTS_APP_SCALE_DOWN_DURATION,
+ ACCEL_2);
+ }
+
+ @Override
+ public void onUpdate(float percent) {
+ appMatrix.preScale(mScaleX.value, mScaleY.value,
+ appBounds.width() / 2.0f, appBounds.height() / 2.0f);
+ appMatrix.postTranslate(mTranslationX.value, mTranslationY.value);
+
+ params[1] = new SurfaceParams(appTarget.leash, mAlpha.value, appMatrix,
+ null /* windowCrop */, getLayer(appTarget, boostedMode),
+ 0 /* cornerRadius */);
+ surfaceApplier.scheduleApply(params);
+ appMatrix.reset();
+ }
+ });
+ anim.play(remoteAppAnim);
+ }
+
+ /**
+ * Play task list fade in animation as part of remote app to recents animation. This animation
+ * ensures that the task views in the recents list fade in from bottom to top.
+ *
+ * @param anim animator set to play on
+ * @param appTaskView the task view associated with the remote app closing
+ */
+ private void playRemoteTaskListFadeIn(@NonNull AnimatorSet anim,
+ @NonNull TaskItemView appTaskView) {
+ long delay = REMOTE_TO_RECENTS_ITEM_FADE_START_DELAY;
+ int childCount = mTaskRecyclerView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ ValueAnimator fadeAnim = ValueAnimator.ofFloat(0, 1.0f);
+ fadeAnim.setDuration(REMOTE_TO_RECENTS_ITEM_FADE_DURATION).setInterpolator(OUT_SLOW_IN);
+ fadeAnim.setStartDelay(delay);
+ View view = mTaskRecyclerView.getChildAt(i);
+ if (Objects.equals(view, appTaskView)) {
+ // Only animate icon and text for the view with snapshot animating in
+ final View icon = appTaskView.getIconView();
+ final View label = appTaskView.getLabelView();
+
+ icon.setAlpha(0.0f);
+ label.setAlpha(0.0f);
+
+ fadeAnim.addUpdateListener(alphaVal -> {
+ float val = alphaVal.getAnimatedFraction();
+
+ icon.setAlpha(val);
+ label.setAlpha(val);
+ });
+ fadeAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ icon.setAlpha(1.0f);
+ label.setAlpha(1.0f);
+ }
+ });
+ } else {
+ // Otherwise, fade in the entire view.
+ view.setAlpha(0.0f);
+ fadeAnim.addUpdateListener(alphaVal -> {
+ float val = alphaVal.getAnimatedFraction();
+ view.setAlpha(val);
+ });
+ fadeAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setAlpha(1.0f);
+ }
+ });
+ }
+ anim.play(fadeAnim);
+
+ int itemType = mTaskRecyclerView.getChildViewHolder(view).getItemViewType();
+ if (itemType == ITEM_TYPE_CLEAR_ALL) {
+ // Don't add delay. Clear all should animate at same time as next view.
+ continue;
+ }
+ delay += REMOTE_TO_RECENTS_ITEM_FADE_BETWEEN_DELAY;
+ }
+ }
+
@Override
public void setInsets(Rect insets) {
mInsets = insets;
diff --git a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
index 6db8013..f184ad0 100644
--- a/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
+++ b/go/quickstep/src/com/android/quickstep/views/TaskItemView.java
@@ -137,6 +137,14 @@
return mThumbnailView;
}
+ public View getIconView() {
+ return mIconView;
+ }
+
+ public View getLabelView() {
+ return mLabelView;
+ }
+
/**
* Start a new animation from the current task content to the specified new content. The caller
* is responsible for the actual animation control via the property
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
index 96d2dca..5eecf17 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsAnimationWrapper.java
@@ -24,6 +24,7 @@
import android.view.MotionEvent;
import com.android.launcher3.util.Preconditions;
+import com.android.quickstep.inputconsumers.InputConsumer;
import com.android.quickstep.util.SwipeAnimationTargetSet;
import com.android.systemui.shared.system.InputConsumerController;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
index e20ef52..2c919b3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskSystemShortcut.java
@@ -296,10 +296,6 @@
if (sysUiProxy == null) {
return null;
}
- if (SysUINavigationMode.getMode(activity) == SysUINavigationMode.Mode.NO_BUTTON) {
- // TODO(b/130225926): Temporarily disable pinning while gesture nav is enabled
- return null;
- }
if (!ActivityManagerWrapper.getInstance().isScreenPinningEnabled()) {
return null;
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index ee9876a..128fd45 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -66,6 +66,13 @@
import com.android.launcher3.util.UiThreadHelper;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
+import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
+import com.android.quickstep.inputconsumers.AssistantTouchConsumer;
+import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
+import com.android.quickstep.inputconsumers.InputConsumer;
+import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
+import com.android.quickstep.inputconsumers.OverviewInputConsumer;
+import com.android.quickstep.inputconsumers.ScreenPinnedInputConsumer;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -460,6 +467,12 @@
mInputMonitorCompat);
}
+ if (ActivityManagerWrapper.getInstance().isScreenPinningActive()) {
+ // Note: we only allow accessibility to wrap this, and it replaces the previous
+ // base input consumer (which should be NO_OP anyway since topTaskLocked == true).
+ base = new ScreenPinnedInputConsumer(this, mISystemUiProxy, activityControl);
+ }
+
if ((mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0) {
base = new AccessibilityInputConsumer(this, mISystemUiProxy,
(mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0, base,
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 58ae5eb..49c95f1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -67,6 +67,7 @@
import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.AbstractFloatingView;
@@ -91,6 +92,8 @@
import com.android.quickstep.ActivityControlHelper.AnimationFactory.ShelfAnimState;
import com.android.quickstep.ActivityControlHelper.HomeAnimationFactory;
import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.inputconsumers.InputConsumer;
+import com.android.quickstep.inputconsumers.OverviewInputConsumer;
import com.android.quickstep.util.ClipAnimationHelper;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.RemoteAnimationTargetSet;
@@ -166,7 +169,7 @@
private static final int LAUNCHER_UI_STATES =
STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
- enum GestureEndTarget {
+ public enum GestureEndTarget {
HOME(1, STATE_SCALED_CONTROLLER_HOME, true, false, ContainerType.WORKSPACE, false),
RECENTS(1, STATE_SCALED_CONTROLLER_RECENTS | STATE_CAPTURE_SCREENSHOT
@@ -220,8 +223,8 @@
private final ClipAnimationHelper mClipAnimationHelper;
private final ClipAnimationHelper.TransformParams mTransformParams;
- protected Runnable mGestureEndCallback;
- protected GestureEndTarget mGestureEndTarget;
+ private Runnable mGestureEndCallback;
+ private GestureEndTarget mGestureEndTarget;
// Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
private RunningWindowAnim mRunningWindowAnim;
private boolean mIsShelfPeeking;
@@ -273,7 +276,7 @@
private final long mTouchTimeMs;
private long mLauncherFrameDrawnTime;
- WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context,
+ public WindowTransformSwipeHandler(RunningTaskInfo runningTaskInfo, Context context,
long touchTimeMs, ActivityControlHelper<T> controller, boolean continuingLastGesture,
InputConsumerController inputConsumer) {
mContext = context;
@@ -1125,6 +1128,13 @@
return anim;
}
+ /**
+ * @return The GestureEndTarget if the gesture has ended, else null.
+ */
+ public @Nullable GestureEndTarget getGestureEndTarget() {
+ return mGestureEndTarget;
+ }
+
@UiThread
private void resumeLastTask() {
mRecentsAnimationWrapper.finish(false /* toRecents */, null);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AccessibilityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
similarity index 98%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/AccessibilityInputConsumer.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
index 8f8cd18..f8475ca 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AccessibilityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.quickstep;
+package com.android.quickstep.inputconsumers;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java
similarity index 98%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java
index 335e8b1..0448fd1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AssistantTouchConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantTouchConsumer.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.quickstep;
+package com.android.quickstep.inputconsumers;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
@@ -43,6 +43,7 @@
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.touch.SwipeDetector;
+import com.android.quickstep.ActivityControlHelper;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.QuickStepContract;
@@ -236,7 +237,7 @@
}
}
- static boolean withinTouchRegion(Context context, MotionEvent ev) {
+ public static boolean withinTouchRegion(Context context, MotionEvent ev) {
final Resources res = context.getResources();
final int width = res.getDisplayMetrics().widthPixels;
final int height = res.getDisplayMetrics().heightPixels;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/DelegateInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
similarity index 96%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/DelegateInputConsumer.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index d36162f..311ddd2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/DelegateInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -1,4 +1,4 @@
-package com.android.quickstep;
+package com.android.quickstep.inputconsumers;
import android.view.MotionEvent;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
similarity index 97%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index 7fd09f7..b1d175d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.quickstep;
+package com.android.quickstep.inputconsumers;
import android.content.Context;
import android.content.Intent;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
similarity index 95%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
index 37b7288..2e8880d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/InputConsumer.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.quickstep;
+package com.android.quickstep.inputconsumers;
import android.annotation.TargetApi;
import android.os.Build;
@@ -30,6 +30,7 @@
int TYPE_ASSISTANT = 1 << 3;
int TYPE_DEVICE_LOCKED = 1 << 4;
int TYPE_ACCESSIBILITY = 1 << 5;
+ int TYPE_SCREEN_PINNED = 1 << 6;
InputConsumer NO_OP = () -> TYPE_NO_OP;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
similarity index 97%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index db377b0..e862fa6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.quickstep;
+package com.android.quickstep.inputconsumers;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
@@ -50,7 +50,13 @@
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RaceConditionTracker;
import com.android.launcher3.util.TraceHelper;
+import com.android.quickstep.ActivityControlHelper;
+import com.android.quickstep.OverviewCallbacks;
+import com.android.quickstep.RecentsModel;
+import com.android.quickstep.SwipeSharedState;
+import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.WindowTransformSwipeHandler;
import com.android.quickstep.WindowTransformSwipeHandler.GestureEndTarget;
import com.android.quickstep.util.CachedEventDispatcher;
import com.android.quickstep.util.MotionPauseDetector;
@@ -376,7 +382,7 @@
// The consumer is being switched while we are active. Set up the shared state to be
// used by the next animation
removeListener();
- GestureEndTarget endTarget = mInteractionHandler.mGestureEndTarget;
+ GestureEndTarget endTarget = mInteractionHandler.getGestureEndTarget();
mSwipeSharedState.canGestureBeContinued = endTarget != null && endTarget.canBeContinued;
mSwipeSharedState.goingToLauncher = endTarget != null && endTarget.isLauncher;
if (mSwipeSharedState.canGestureBeContinued) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
similarity index 96%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
rename to quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index bafc367..bab3c71 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.quickstep;
+package com.android.quickstep.inputconsumers;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
import static com.android.quickstep.TouchInteractionService.TOUCH_INTERACTION_LOG;
@@ -27,6 +27,8 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.Utilities;
import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.ActivityControlHelper;
+import com.android.quickstep.OverviewCallbacks;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.InputMonitorCompat;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
new file mode 100644
index 0000000..a0e20f2
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.inputconsumers;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.HapticFeedbackConstants;
+import android.view.MotionEvent;
+
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.R;
+import com.android.quickstep.ActivityControlHelper;
+import com.android.quickstep.util.MotionPauseDetector;
+import com.android.systemui.shared.recents.ISystemUiProxy;
+
+/**
+ * An input consumer that detects swipe up and hold to exit screen pinning mode.
+ */
+public class ScreenPinnedInputConsumer implements InputConsumer {
+
+ private static final String TAG = "ScreenPinnedConsumer";
+
+ private final float mMotionPauseMinDisplacement;
+ private final MotionPauseDetector mMotionPauseDetector;
+
+ private float mTouchDownY;
+
+ public ScreenPinnedInputConsumer(Context context, ISystemUiProxy sysuiProxy,
+ ActivityControlHelper activityControl) {
+ mMotionPauseMinDisplacement = context.getResources().getDimension(
+ R.dimen.motion_pause_detector_min_displacement_from_app);
+ mMotionPauseDetector = new MotionPauseDetector(context, true /* makePauseHarderToTrigger*/);
+ mMotionPauseDetector.setOnMotionPauseListener(isPaused -> {
+ if (isPaused) {
+ try {
+ sysuiProxy.stopScreenPinning();
+ BaseDraggingActivity launcherActivity = activityControl.getCreatedActivity();
+ if (launcherActivity != null) {
+ launcherActivity.getRootView().performHapticFeedback(
+ HapticFeedbackConstants.LONG_PRESS,
+ HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
+ }
+ mMotionPauseDetector.clear();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to stop screen pinning ", e);
+ }
+ }
+ });
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_SCREEN_PINNED;
+ }
+
+ @Override
+ public void onMotionEvent(MotionEvent ev) {
+ float y = ev.getY();
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mTouchDownY = y;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ float displacement = mTouchDownY - y;
+ mMotionPauseDetector.setDisallowPause(displacement < mMotionPauseMinDisplacement);
+ mMotionPauseDetector.addPosition(y, ev.getEventTime());
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ mMotionPauseDetector.clear();
+ break;
+ }
+ }
+}
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 32f312f..82d1aa6 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -36,6 +36,7 @@
<!-- These speeds are in dp / ms -->
<dimen name="motion_pause_detector_speed_very_slow">0.0285dp</dimen>
+ <dimen name="motion_pause_detector_speed_slow">0.15dp</dimen>
<dimen name="motion_pause_detector_speed_somewhat_fast">0.285dp</dimen>
<dimen name="motion_pause_detector_speed_fast">0.5dp</dimen>
<dimen name="motion_pause_detector_min_displacement_from_app">36dp</dimen>
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index f58f0d4..893c053 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -35,10 +35,18 @@
/** If no motion is added for this amount of time, assume the motion has paused. */
private static final long FORCE_PAUSE_TIMEOUT = 300;
+ /**
+ * After {@link #makePauseHarderToTrigger()}, must
+ * move slowly for this long to trigger a pause.
+ */
+ private static final long HARDER_TRIGGER_TIMEOUT = 400;
+
private final float mSpeedVerySlow;
+ private final float mSpeedSlow;
private final float mSpeedSomewhatFast;
private final float mSpeedFast;
private final Alarm mForcePauseTimeout;
+ private final boolean mMakePauseHarderToTrigger;
private Long mPreviousTime = null;
private Float mPreviousPosition = null;
@@ -52,19 +60,29 @@
private boolean mHasEverBeenPaused;
/** @see #setDisallowPause(boolean) */
private boolean mDisallowPause;
+ // Time at which speed became < mSpeedSlow (only used if mMakePauseHarderToTrigger == true).
+ private long mSlowStartTime;
public MotionPauseDetector(Context context) {
+ this(context, false);
+ }
+
+ /**
+ * @param makePauseHarderToTrigger Used for gestures that require a more explicit pause.
+ */
+ public MotionPauseDetector(Context context, boolean makePauseHarderToTrigger) {
Resources res = context.getResources();
mSpeedVerySlow = res.getDimension(R.dimen.motion_pause_detector_speed_very_slow);
+ mSpeedSlow = res.getDimension(R.dimen.motion_pause_detector_speed_slow);
mSpeedSomewhatFast = res.getDimension(R.dimen.motion_pause_detector_speed_somewhat_fast);
mSpeedFast = res.getDimension(R.dimen.motion_pause_detector_speed_fast);
mForcePauseTimeout = new Alarm();
mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */));
+ mMakePauseHarderToTrigger = makePauseHarderToTrigger;
}
/**
- * Get callbacks for when motion pauses and resumes, including an
- * immediate callback with the current pause state.
+ * Get callbacks for when motion pauses and resumes.
*/
public void setOnMotionPauseListener(OnMotionPauseListener listener) {
mOnMotionPauseListener = listener;
@@ -88,13 +106,15 @@
if (mFirstPosition == null) {
mFirstPosition = position;
}
- mForcePauseTimeout.setAlarm(FORCE_PAUSE_TIMEOUT);
+ mForcePauseTimeout.setAlarm(mMakePauseHarderToTrigger
+ ? HARDER_TRIGGER_TIMEOUT
+ : FORCE_PAUSE_TIMEOUT);
if (mPreviousTime != null && mPreviousPosition != null) {
long changeInTime = Math.max(1, time - mPreviousTime);
float changeInPosition = position - mPreviousPosition;
float velocity = changeInPosition / changeInTime;
if (mPreviousVelocity != null) {
- checkMotionPaused(velocity, mPreviousVelocity);
+ checkMotionPaused(velocity, mPreviousVelocity, time);
}
mPreviousVelocity = velocity;
}
@@ -102,7 +122,7 @@
mPreviousPosition = position;
}
- private void checkMotionPaused(float velocity, float prevVelocity) {
+ private void checkMotionPaused(float velocity, float prevVelocity, long time) {
float speed = Math.abs(velocity);
float previousSpeed = Math.abs(prevVelocity);
boolean isPaused;
@@ -122,6 +142,17 @@
boolean isRapidDeceleration = speed < previousSpeed * RAPID_DECELERATION_FACTOR;
isPaused = isRapidDeceleration && speed < mSpeedSomewhatFast;
}
+ if (mMakePauseHarderToTrigger) {
+ if (speed < mSpeedSlow) {
+ if (mSlowStartTime == 0) {
+ mSlowStartTime = time;
+ }
+ isPaused = time - mSlowStartTime >= HARDER_TRIGGER_TIMEOUT;
+ } else {
+ mSlowStartTime = 0;
+ isPaused = false;
+ }
+ }
}
}
updatePaused(isPaused);
@@ -149,6 +180,7 @@
mFirstPosition = null;
setOnMotionPauseListener(null);
mIsPaused = mHasEverBeenPaused = false;
+ mSlowStartTime = 0;
mForcePauseTimeout.cancelAlarm();
}
diff --git a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
index 8bdb90d..2111e2c 100644
--- a/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
+++ b/quickstep/tests/src/com/android/quickstep/StartLauncherViaGestureTests.java
@@ -28,6 +28,7 @@
import com.android.launcher3.util.RaceConditionReproducer;
import com.android.quickstep.NavigationModeSwitchRule.Mode;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
+import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
import org.junit.Before;
import org.junit.Ignore;
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index e2a5160..41252aa 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -604,4 +604,18 @@
return super.performAccessibilityAction(action, arguments);
}
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ switch (ev.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mAllAppsStore.setDeferUpdates(true);
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mAllAppsStore.setDeferUpdates(false);
+ break;
+ }
+ return super.dispatchTouchEvent(ev);
+ }
}