Add additional fake task views on overview gesture completion.
Added motion pause listening to animate an additional fake task view on overview gestures in the sandbox.
Test: manual
Demo: https://drive.google.com/file/d/1EpewbIAxiMUEz0Fqdfbok5q9xpWS6NWz/view?usp=sharing
Change-Id: Ifd0aed0a2bbb3204ae32c833b8466952679700e3
diff --git a/quickstep/res/drawable/default_sandbox_app_previous_task_thumbnail.xml b/quickstep/res/drawable/default_sandbox_app_previous_task_thumbnail.xml
new file mode 100644
index 0000000..9c95497
--- /dev/null
+++ b/quickstep/res/drawable/default_sandbox_app_previous_task_thumbnail.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2020 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/gesture_tutorial_fake_previous_task_view_color" />
+</shape>
diff --git a/quickstep/res/layout/gesture_tutorial_fragment.xml b/quickstep/res/layout/gesture_tutorial_fragment.xml
index 2ff3a5e..9d06dfb 100644
--- a/quickstep/res/layout/gesture_tutorial_fragment.xml
+++ b/quickstep/res/layout/gesture_tutorial_fragment.xml
@@ -31,6 +31,14 @@
android:visibility="invisible" />
<View
+ android:id="@+id/gesture_tutorial_fake_previous_task_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleX="0.98"
+ android:scaleY="0.98"
+ android:visibility="invisible" />
+
+ <View
android:id="@+id/gesture_tutorial_fake_task_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index d1b0a70..a9a9e2a 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -56,7 +56,7 @@
/** Utility class to handle Home and Assistant gestures. */
public class NavBarGestureHandler implements OnTouchListener,
- TriggerSwipeUpTouchTracker.OnSwipeUpListener {
+ TriggerSwipeUpTouchTracker.OnSwipeUpListener, MotionPauseDetector.OnMotionPauseListener {
private static final String LOG_TAG = "NavBarGestureHandler";
private static final long RETRACT_GESTURE_ANIMATION_DURATION_MS = 300;
@@ -181,7 +181,7 @@
mLaunchedAssistant = false;
mSwipeUpTouchTracker.init();
mMotionPauseDetector.clear();
- mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseDetected);
+ mMotionPauseDetector.setOnMotionPauseListener(this);
break;
case MotionEvent.ACTION_MOVE:
mLastPos.set(event.getX(), event.getY());
@@ -256,7 +256,13 @@
|| event.getY() >= mDisplaySize.y - mBottomGestureHeight;
}
- protected void onMotionPauseDetected() {
+ @Override
+ public void onMotionPauseChanged(boolean isPaused) {
+ mGestureCallback.onMotionPaused(isPaused);
+ }
+
+ @Override
+ public void onMotionPauseDetected() {
VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
}
@@ -311,6 +317,9 @@
/** Called whenever any touch is completed. */
void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity);
+ /** Called when a motion stops or resumes */
+ default void onMotionPaused(boolean isPaused) {}
+
/** Indicates how far a touch originating in the nav bar has moved from the nav bar. */
default void setNavBarGestureProgress(@Nullable Float displacement) {}
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index 865b66e..68c63bf 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -61,16 +61,21 @@
@TargetApi(Build.VERSION_CODES.R)
abstract class SwipeUpGestureTutorialController extends TutorialController {
- private final ViewSwipeUpAnimation mViewSwipeUpAnimation;
+
+ private static final int FAKE_PREVIOUS_TASK_MARGIN = Utilities.dpToPx(12);
+
+ private final ViewSwipeUpAnimation mTaskViewSwipeUpAnimation;
private float mFakeTaskViewRadius;
private Rect mFakeTaskViewRect = new Rect();
private RunningWindowAnim mRunningWindowAnim;
+ private boolean mShowTasks = false;
+ private boolean mShowPreviousTasks = false;
SwipeUpGestureTutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) {
super(tutorialFragment, tutorialType);
RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(mContext);
OverviewComponentObserver observer = new OverviewComponentObserver(mContext, deviceState);
- mViewSwipeUpAnimation = new ViewSwipeUpAnimation(mContext, deviceState,
+ mTaskViewSwipeUpAnimation = new ViewSwipeUpAnimation(mContext, deviceState,
new GestureState(observer, -1));
observer.onDestroy();
deviceState.destroy();
@@ -83,16 +88,22 @@
.getWindowInsets()
.getInsets(WindowInsets.Type.systemBars());
dp.updateInsets(new Rect(insets.left, insets.top, insets.right, insets.bottom));
- mViewSwipeUpAnimation.initDp(dp);
+ mTaskViewSwipeUpAnimation.initDp(dp);
mFakeTaskViewRadius = QuickStepContract.getWindowCornerRadius(mContext.getResources());
- mFakeTaskView.setClipToOutline(true);
- mFakeTaskView.setOutlineProvider(new ViewOutlineProvider() {
+
+ ViewOutlineProvider outlineProvider = new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setRoundRect(mFakeTaskViewRect, mFakeTaskViewRadius);
}
- });
+ };
+
+ mFakeTaskView.setClipToOutline(true);
+ mFakeTaskView.setOutlineProvider(outlineProvider);
+
+ mFakePreviousTaskView.setClipToOutline(true);
+ mFakePreviousTaskView.setOutlineProvider(outlineProvider);
}
private void cancelRunningAnimation() {
@@ -114,16 +125,22 @@
mFakeIconView.setVisibility(View.INVISIBLE);
mFakeTaskView.setVisibility(View.INVISIBLE);
mFakeTaskView.setAlpha(1);
+ mFakePreviousTaskView.setVisibility(View.INVISIBLE);
+ mFakePreviousTaskView.setAlpha(1);
+ mShowTasks = false;
+ mShowPreviousTasks = false;
mRunningWindowAnim = null;
}
};
if (toOverviewFirst) {
- anim.setFloat(mViewSwipeUpAnimation.getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL);
+ anim.setFloat(mTaskViewSwipeUpAnimation
+ .getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation, boolean isReverse) {
PendingAnimation fadeAnim = new PendingAnimation(300);
fadeAnim.setViewAlpha(mFakeTaskView, 0, ACCEL);
+ fadeAnim.setViewAlpha(mFakePreviousTaskView, 0, ACCEL);
fadeAnim.addListener(resetTaskView);
AnimatorSet animset = fadeAnim.buildAnim();
animset.setStartDelay(100);
@@ -133,6 +150,7 @@
});
} else {
anim.setViewAlpha(mFakeTaskView, 0, ACCEL);
+ anim.setViewAlpha(mFakePreviousTaskView, 0, ACCEL);
anim.setViewAlpha(mFakeIconView, 0, ACCEL);
anim.addListener(resetTaskView);
}
@@ -148,8 +166,10 @@
hideFeedback();
hideHandCoachingAnimation();
cancelRunningAnimation();
+ mFakePreviousTaskView.setVisibility(View.INVISIBLE);
+ mShowPreviousTasks = false;
RectFSpringAnim rectAnim =
- mViewSwipeUpAnimation.handleSwipeUpToHome(finalVelocity);
+ mTaskViewSwipeUpAnimation.handleSwipeUpToHome(finalVelocity);
// After home animation finishes, fade out and run onEndRunnable.
rectAnim.addAnimatorListener(AnimationSuccessListener.forRunnable(
() -> fadeOutFakeTaskView(false, onEndRunnable)));
@@ -161,11 +181,31 @@
if (displacement == null || mTutorialType == HOME_NAVIGATION_COMPLETE
|| mTutorialType == OVERVIEW_NAVIGATION_COMPLETE) {
mFakeTaskView.setVisibility(View.INVISIBLE);
+ mFakePreviousTaskView.setVisibility(View.INVISIBLE);
} else {
+ mShowTasks = true;
mFakeTaskView.setVisibility(View.VISIBLE);
- if (mRunningWindowAnim == null) {
- mViewSwipeUpAnimation.updateDisplacement(displacement);
+ if (mShowPreviousTasks) {
+ mFakePreviousTaskView.setVisibility(View.VISIBLE);
}
+ if (mRunningWindowAnim == null) {
+ mTaskViewSwipeUpAnimation.updateDisplacement(displacement);
+ }
+ }
+ }
+
+ @Override
+ public void onMotionPaused(boolean unused) {
+ if (mShowTasks) {
+ if (!mShowPreviousTasks) {
+ mFakePreviousTaskView.setTranslationX(
+ -(2 * mFakePreviousTaskView.getWidth() + FAKE_PREVIOUS_TASK_MARGIN));
+ mFakePreviousTaskView.animate()
+ .setDuration(300)
+ .translationX(-(mFakePreviousTaskView.getWidth() + FAKE_PREVIOUS_TASK_MARGIN))
+ .start();
+ }
+ mShowPreviousTasks = true;
}
}
@@ -232,6 +272,7 @@
false /* isVerticalBarLayout */);
mFakeIconView.setAlpha(1);
mFakeTaskView.setAlpha(getWindowAlpha(progress));
+ mFakePreviousTaskView.setAlpha(getWindowAlpha(progress));
}
@Override
@@ -258,9 +299,11 @@
public void applySurfaceParams(SurfaceParams[] params) {
SurfaceParams p = params[0];
mFakeTaskView.setAnimationMatrix(p.matrix);
+ mFakePreviousTaskView.setAnimationMatrix(p.matrix);
mFakeTaskViewRect.set(p.windowCrop);
mFakeTaskViewRadius = p.cornerRadius;
mFakeTaskView.invalidateOutline();
+ mFakePreviousTaskView.invalidateOutline();
}
}
}
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 0d5a110..12d2efc 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -52,6 +52,7 @@
final View mLauncherView;
final ClipIconView mFakeIconView;
final View mFakeTaskView;
+ final View mFakePreviousTaskView;
final View mRippleView;
final RippleDrawable mRippleDrawable;
@Nullable final TutorialHandAnimation mHandCoachingAnimation;
@@ -74,6 +75,8 @@
mLauncherView = getMockLauncherView();
mFakeIconView = rootView.findViewById(R.id.gesture_tutorial_fake_icon_view);
mFakeTaskView = rootView.findViewById(R.id.gesture_tutorial_fake_task_view);
+ mFakePreviousTaskView =
+ rootView.findViewById(R.id.gesture_tutorial_fake_previous_task_view);
mRippleView = rootView.findViewById(R.id.gesture_tutorial_ripple_view);
mRippleDrawable = (RippleDrawable) mRippleView.getBackground();
mHandCoachingAnimation = tutorialFragment.getHandAnimation();
@@ -93,6 +96,8 @@
if (mContext != null) {
rootView.setBackground(mContext.getDrawable(getMockWallpaperResId()));
mFakeTaskView.setBackground(mContext.getDrawable(getMockAppTaskThumbnailResId()));
+ mFakePreviousTaskView.setBackground(
+ mContext.getDrawable(getMockPreviousAppTaskThumbnailResId()));
mFakeIconView.setBackground(mContext.getDrawable(getMockAppIconResId()));
}
}
@@ -126,6 +131,11 @@
return R.drawable.default_sandbox_app_task_thumbnail;
}
+ @DrawableRes
+ protected int getMockPreviousAppTaskThumbnailResId() {
+ return R.drawable.default_sandbox_app_previous_task_thumbnail;
+ }
+
@Nullable
public View getMockLauncherView() {
InvariantDeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext);
diff --git a/res/values/colors.xml b/res/values/colors.xml
index f56fbaa..78c2df6 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -37,6 +37,7 @@
<color name="gesture_tutorial_ripple_color">#A0C2F9</color> <!-- Light Blue -->
<color name="gesture_tutorial_fake_task_view_color">#6DA1FF</color> <!-- Light Blue -->
+ <color name="gesture_tutorial_fake_previous_task_view_color">#9CCC65</color> <!-- Light Green -->
<color name="gesture_tutorial_action_button_label_color">#FFFFFFFF</color>
<color name="gesture_tutorial_primary_color">#1A73E8</color> <!-- Blue -->
</resources>
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 1e023df..5c2f35b 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -404,6 +404,11 @@
return (size / densityRatio);
}
+ /** Converts a dp value to pixels for the current device. */
+ public static int dpToPx(float dp) {
+ return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
+ }
+
public static int pxFromSp(float size, DisplayMetrics metrics) {
return (int) Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
size, metrics));