1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.quickstep.interaction; 17 18 import static com.android.launcher3.anim.Interpolators.ACCEL; 19 import static com.android.launcher3.util.DisplayController.getSingleFrameMs; 20 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; 21 import static com.android.quickstep.AbsSwipeUpHandler.MAX_SWIPE_DURATION; 22 import static com.android.quickstep.interaction.TutorialController.TutorialType.HOME_NAVIGATION_COMPLETE; 23 import static com.android.quickstep.interaction.TutorialController.TutorialType.OVERVIEW_NAVIGATION_COMPLETE; 24 25 import android.animation.Animator; 26 import android.animation.AnimatorListenerAdapter; 27 import android.animation.AnimatorSet; 28 import android.animation.ValueAnimator; 29 import android.annotation.TargetApi; 30 import android.content.Context; 31 import android.graphics.Outline; 32 import android.graphics.PointF; 33 import android.graphics.Rect; 34 import android.graphics.RectF; 35 import android.os.Build; 36 import android.view.SurfaceControl; 37 import android.view.View; 38 import android.view.ViewOutlineProvider; 39 40 import androidx.annotation.NonNull; 41 import androidx.annotation.Nullable; 42 43 import com.android.launcher3.DeviceProfile; 44 import com.android.launcher3.InvariantDeviceProfile; 45 import com.android.launcher3.Utilities; 46 import com.android.launcher3.anim.AnimatorListeners; 47 import com.android.launcher3.anim.AnimatorPlaybackController; 48 import com.android.launcher3.anim.PendingAnimation; 49 import com.android.quickstep.AnimatedFloat; 50 import com.android.quickstep.GestureState; 51 import com.android.quickstep.OverviewComponentObserver; 52 import com.android.quickstep.RecentsAnimationDeviceState; 53 import com.android.quickstep.RemoteTargetGluer; 54 import com.android.quickstep.SwipeUpAnimationLogic; 55 import com.android.quickstep.SwipeUpAnimationLogic.RunningWindowAnim; 56 import com.android.quickstep.util.RectFSpringAnim; 57 import com.android.quickstep.util.TransformParams; 58 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; 59 60 @TargetApi(Build.VERSION_CODES.R) 61 abstract class SwipeUpGestureTutorialController extends TutorialController { 62 63 private static final int FAKE_PREVIOUS_TASK_MARGIN = Utilities.dpToPx(12); 64 65 protected static final long TASK_VIEW_END_ANIMATION_DURATION_MILLIS = 300; 66 private static final long HOME_SWIPE_ANIMATION_DURATION_MILLIS = 625; 67 private static final long OVERVIEW_SWIPE_ANIMATION_DURATION_MILLIS = 1000; 68 69 final ViewSwipeUpAnimation mTaskViewSwipeUpAnimation; 70 private float mFakeTaskViewRadius; 71 private final Rect mFakeTaskViewRect = new Rect(); 72 RunningWindowAnim mRunningWindowAnim; 73 private boolean mShowTasks = false; 74 private boolean mShowPreviousTasks = false; 75 76 private final AnimatorListenerAdapter mResetTaskView = new AnimatorListenerAdapter() { 77 @Override 78 public void onAnimationEnd(Animator animation) { 79 mFakeHotseatView.setVisibility(View.INVISIBLE); 80 mFakeIconView.setVisibility(View.INVISIBLE); 81 if (mTutorialFragment.getActivity() != null) { 82 int height = mTutorialFragment.getRootView().getFullscreenHeight(); 83 int width = mTutorialFragment.getRootView().getWidth(); 84 mFakeTaskViewRect.set(0, 0, width, height); 85 } 86 mFakeTaskViewRadius = 0; 87 mFakeTaskView.invalidateOutline(); 88 mFakeTaskView.setVisibility(View.VISIBLE); 89 mFakeTaskView.setAlpha(1); 90 mFakePreviousTaskView.setVisibility(View.INVISIBLE); 91 mFakePreviousTaskView.setAlpha(1); 92 mFakePreviousTaskView.setToSingleRowLayout(false); 93 mShowTasks = false; 94 mShowPreviousTasks = false; 95 mRunningWindowAnim = null; 96 } 97 }; 98 SwipeUpGestureTutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType)99 SwipeUpGestureTutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) { 100 super(tutorialFragment, tutorialType); 101 RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(mContext); 102 OverviewComponentObserver observer = new OverviewComponentObserver(mContext, deviceState); 103 mTaskViewSwipeUpAnimation = new ViewSwipeUpAnimation(mContext, deviceState, 104 new GestureState(observer, -1)); 105 observer.onDestroy(); 106 deviceState.destroy(); 107 108 DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext) 109 .getDeviceProfile(mContext) 110 .copy(mContext); 111 mTaskViewSwipeUpAnimation.initDp(dp); 112 113 int height = mTutorialFragment.getRootView().getFullscreenHeight(); 114 int width = mTutorialFragment.getRootView().getWidth(); 115 mFakeTaskViewRect.set(0, 0, width, height); 116 mFakeTaskViewRadius = 0; 117 118 ViewOutlineProvider outlineProvider = new ViewOutlineProvider() { 119 @Override 120 public void getOutline(View view, Outline outline) { 121 outline.setRoundRect(mFakeTaskViewRect, mFakeTaskViewRadius); 122 } 123 }; 124 125 mFakeTaskView.setClipToOutline(true); 126 mFakeTaskView.setOutlineProvider(outlineProvider); 127 128 mFakePreviousTaskView.setClipToOutline(true); 129 mFakePreviousTaskView.setOutlineProvider(outlineProvider); 130 } 131 cancelRunningAnimation()132 private void cancelRunningAnimation() { 133 if (mRunningWindowAnim != null) { 134 mRunningWindowAnim.cancel(); 135 } 136 mRunningWindowAnim = null; 137 } 138 139 /** Fades the task view, optionally after animating to a fake Overview. */ fadeOutFakeTaskView(boolean toOverviewFirst, boolean reset, @Nullable Runnable onEndRunnable)140 void fadeOutFakeTaskView(boolean toOverviewFirst, boolean reset, 141 @Nullable Runnable onEndRunnable) { 142 cancelRunningAnimation(); 143 PendingAnimation anim = new PendingAnimation(300); 144 if (toOverviewFirst) { 145 anim.setFloat(mTaskViewSwipeUpAnimation 146 .getCurrentShift(), AnimatedFloat.VALUE, 1, ACCEL); 147 anim.addListener(new AnimatorListenerAdapter() { 148 @Override 149 public void onAnimationEnd(Animator animation, boolean isReverse) { 150 PendingAnimation fadeAnim = 151 new PendingAnimation(TASK_VIEW_END_ANIMATION_DURATION_MILLIS); 152 if (reset) { 153 fadeAnim.setFloat(mTaskViewSwipeUpAnimation 154 .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCEL); 155 fadeAnim.addListener(mResetTaskView); 156 } else { 157 fadeAnim.setViewAlpha(mFakeTaskView, 0, ACCEL); 158 fadeAnim.setViewAlpha(mFakePreviousTaskView, 0, ACCEL); 159 } 160 if (onEndRunnable != null) { 161 fadeAnim.addListener(AnimatorListeners.forSuccessCallback(onEndRunnable)); 162 } 163 AnimatorSet animset = fadeAnim.buildAnim(); 164 165 if (reset && mTutorialFragment.isLargeScreen()) { 166 animset.addListener(new AnimatorListenerAdapter() { 167 @Override 168 public void onAnimationStart(Animator animation) { 169 super.onAnimationStart(animation); 170 Animator multiRowAnimation = 171 mFakePreviousTaskView.createAnimationToMultiRowLayout(); 172 173 if (multiRowAnimation != null) { 174 multiRowAnimation.setDuration( 175 TASK_VIEW_END_ANIMATION_DURATION_MILLIS).start(); 176 } 177 } 178 }); 179 } 180 181 animset.setStartDelay(100); 182 animset.start(); 183 mRunningWindowAnim = RunningWindowAnim.wrap(animset); 184 } 185 }); 186 } else { 187 if (reset) { 188 anim.setFloat(mTaskViewSwipeUpAnimation 189 .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCEL); 190 anim.addListener(mResetTaskView); 191 } else { 192 anim.setViewAlpha(mFakeTaskView, 0, ACCEL); 193 anim.setViewAlpha(mFakePreviousTaskView, 0, ACCEL); 194 } 195 if (onEndRunnable != null) { 196 anim.addListener(AnimatorListeners.forSuccessCallback(onEndRunnable)); 197 } 198 } 199 AnimatorSet animset = anim.buildAnim(); 200 hideFakeTaskbar(/* animateToHotseat= */ false); 201 animset.start(); 202 mRunningWindowAnim = RunningWindowAnim.wrap(animset); 203 } 204 resetFakeTaskView(boolean animateFromHome)205 void resetFakeTaskView(boolean animateFromHome) { 206 mFakeTaskView.setVisibility(View.VISIBLE); 207 PendingAnimation anim = new PendingAnimation(300); 208 anim.setFloat(mTaskViewSwipeUpAnimation 209 .getCurrentShift(), AnimatedFloat.VALUE, 0, ACCEL); 210 anim.setViewAlpha(mFakeTaskView, 1, ACCEL); 211 anim.addListener(mResetTaskView); 212 AnimatorSet animset = anim.buildAnim(); 213 showFakeTaskbar(animateFromHome); 214 animset.start(); 215 mRunningWindowAnim = RunningWindowAnim.wrap(animset); 216 } 217 animateFakeTaskViewHome(PointF finalVelocity, @Nullable Runnable onEndRunnable)218 void animateFakeTaskViewHome(PointF finalVelocity, @Nullable Runnable onEndRunnable) { 219 cancelRunningAnimation(); 220 hideFakeTaskbar(/* animateToHotseat= */ true); 221 mFakePreviousTaskView.setVisibility(View.INVISIBLE); 222 mFakeHotseatView.setVisibility(View.VISIBLE); 223 mShowPreviousTasks = false; 224 RectFSpringAnim rectAnim = 225 mTaskViewSwipeUpAnimation.handleSwipeUpToHome(finalVelocity); 226 // After home animation finishes, fade out and run onEndRunnable. 227 PendingAnimation fadeAnim = new PendingAnimation(300); 228 fadeAnim.setViewAlpha(mFakeIconView, 0, ACCEL); 229 if (onEndRunnable != null) { 230 fadeAnim.addListener(AnimatorListeners.forSuccessCallback(onEndRunnable)); 231 } 232 AnimatorSet animset = fadeAnim.buildAnim(); 233 rectAnim.addAnimatorListener(AnimatorListeners.forSuccessCallback(animset::start)); 234 mRunningWindowAnim = RunningWindowAnim.wrap(rectAnim); 235 } 236 237 @Override setNavBarGestureProgress(@ullable Float displacement)238 public void setNavBarGestureProgress(@Nullable Float displacement) { 239 if (isGestureCompleted()) { 240 return; 241 } 242 if (mTutorialType == HOME_NAVIGATION_COMPLETE 243 || mTutorialType == OVERVIEW_NAVIGATION_COMPLETE) { 244 mFakeTaskView.setVisibility(View.INVISIBLE); 245 mFakePreviousTaskView.setVisibility(View.INVISIBLE); 246 } else { 247 mShowTasks = true; 248 mFakeTaskView.setVisibility(View.VISIBLE); 249 if (mShowPreviousTasks) { 250 mFakePreviousTaskView.setVisibility(View.VISIBLE); 251 } 252 if (mRunningWindowAnim == null && displacement != null) { 253 mTaskViewSwipeUpAnimation.updateDisplacement(displacement); 254 } 255 } 256 } 257 258 @Override onMotionPaused(boolean unused)259 public void onMotionPaused(boolean unused) { 260 if (isGestureCompleted()) { 261 return; 262 } 263 if (mShowTasks) { 264 if (!mShowPreviousTasks) { 265 mFakePreviousTaskView.setTranslationX( 266 -(2 * mFakePreviousTaskView.getWidth() + FAKE_PREVIOUS_TASK_MARGIN)); 267 mFakePreviousTaskView.animate() 268 .setDuration(300) 269 .translationX(-(mFakePreviousTaskView.getWidth() + FAKE_PREVIOUS_TASK_MARGIN)) 270 .start(); 271 } 272 mShowPreviousTasks = true; 273 } 274 } 275 276 class ViewSwipeUpAnimation extends SwipeUpAnimationLogic { 277 ViewSwipeUpAnimation(Context context, RecentsAnimationDeviceState deviceState, GestureState gestureState)278 ViewSwipeUpAnimation(Context context, RecentsAnimationDeviceState deviceState, 279 GestureState gestureState) { 280 super(context, deviceState, gestureState); 281 mRemoteTargetHandles[0] = new RemoteTargetGluer.RemoteTargetHandle( 282 mRemoteTargetHandles[0].getTaskViewSimulator(), new FakeTransformParams()); 283 } 284 initDp(DeviceProfile dp)285 void initDp(DeviceProfile dp) { 286 initTransitionEndpoints(dp); 287 mRemoteTargetHandles[0].getTaskViewSimulator().setPreviewBounds( 288 new Rect(0, 0, dp.widthPx, dp.heightPx), dp.getInsets()); 289 } 290 291 @Override updateFinalShift()292 public void updateFinalShift() { 293 mRemoteTargetHandles[0].getPlaybackController() 294 .setProgress(mCurrentShift.value, mDragLengthFactor); 295 mRemoteTargetHandles[0].getTaskViewSimulator().apply( 296 mRemoteTargetHandles[0].getTransformParams()); 297 } 298 getCurrentShift()299 AnimatedFloat getCurrentShift() { 300 return mCurrentShift; 301 } 302 handleSwipeUpToHome(PointF velocity)303 RectFSpringAnim handleSwipeUpToHome(PointF velocity) { 304 PointF velocityPxPerMs = new PointF(velocity.x, velocity.y); 305 float currentShift = mCurrentShift.value; 306 final float startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y 307 * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor); 308 float distanceToTravel = (1 - currentShift) * mTransitionDragLength; 309 310 // we want the page's snap velocity to approximately match the velocity at 311 // which the user flings, so we scale the duration by a value near to the 312 // derivative of the scroll interpolator at zero, ie. 2. 313 long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y)); 314 long duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration); 315 HomeAnimationFactory homeAnimFactory = new HomeAnimationFactory() { 316 @Override 317 public AnimatorPlaybackController createActivityAnimationToHome() { 318 return AnimatorPlaybackController.wrap(new AnimatorSet(), duration); 319 } 320 321 @NonNull 322 @Override 323 public RectF getWindowTargetRect() { 324 int fakeHomeIconSizePx = Utilities.dpToPx(60); 325 int fakeHomeIconLeft = getHotseatIconLeft(); 326 int fakeHomeIconTop = getHotseatIconTop(); 327 return new RectF(fakeHomeIconLeft, fakeHomeIconTop, 328 fakeHomeIconLeft + fakeHomeIconSizePx, 329 fakeHomeIconTop + fakeHomeIconSizePx); 330 } 331 332 @Override 333 public void update(RectF rect, float progress, float radius) { 334 mFakeIconView.setVisibility(View.VISIBLE); 335 mFakeIconView.update(rect, progress, 336 1f - SHAPE_PROGRESS_DURATION /* shapeProgressStart */, 337 radius, 255, 338 false, /* isOpening */ 339 mFakeIconView, mDp, 340 false /* isVerticalBarLayout */); 341 mFakeIconView.setAlpha(1); 342 mFakeTaskView.setAlpha(getWindowAlpha(progress)); 343 mFakePreviousTaskView.setAlpha(getWindowAlpha(progress)); 344 } 345 346 @Override 347 public void onCancel() { 348 mFakeIconView.setVisibility(View.INVISIBLE); 349 } 350 }; 351 RectFSpringAnim windowAnim = createWindowAnimationToHome(startShift, 352 homeAnimFactory)[0]; 353 windowAnim.start(mContext, velocityPxPerMs); 354 return windowAnim; 355 } 356 } 357 createFingerDotHomeSwipeAnimator(float fingerDotStartTranslationY)358 protected Animator createFingerDotHomeSwipeAnimator(float fingerDotStartTranslationY) { 359 Animator homeSwipeAnimator = createFingerDotSwipeUpAnimator(fingerDotStartTranslationY) 360 .setDuration(HOME_SWIPE_ANIMATION_DURATION_MILLIS); 361 362 homeSwipeAnimator.addListener(new AnimatorListenerAdapter() { 363 @Override 364 public void onAnimationEnd(Animator animation) { 365 super.onAnimationEnd(animation); 366 animateFakeTaskViewHome( 367 new PointF( 368 0f, 369 fingerDotStartTranslationY / HOME_SWIPE_ANIMATION_DURATION_MILLIS), 370 null); 371 } 372 }); 373 374 return homeSwipeAnimator; 375 } 376 createFingerDotOverviewSwipeAnimator(float fingerDotStartTranslationY)377 protected Animator createFingerDotOverviewSwipeAnimator(float fingerDotStartTranslationY) { 378 Animator overviewSwipeAnimator = createFingerDotSwipeUpAnimator(fingerDotStartTranslationY) 379 .setDuration(OVERVIEW_SWIPE_ANIMATION_DURATION_MILLIS); 380 381 overviewSwipeAnimator.addListener(new AnimatorListenerAdapter() { 382 @Override 383 public void onAnimationEnd(Animator animation) { 384 super.onAnimationEnd(animation); 385 mFakePreviousTaskView.setVisibility(View.VISIBLE); 386 onMotionPaused(true /*arbitrary value*/); 387 } 388 }); 389 390 return overviewSwipeAnimator; 391 } 392 393 createFingerDotSwipeUpAnimator(float fingerDotStartTranslationY)394 private Animator createFingerDotSwipeUpAnimator(float fingerDotStartTranslationY) { 395 ValueAnimator swipeAnimator = ValueAnimator.ofFloat(0f, 1f); 396 397 swipeAnimator.addUpdateListener(valueAnimator -> { 398 float gestureProgress = 399 -fingerDotStartTranslationY * valueAnimator.getAnimatedFraction(); 400 setNavBarGestureProgress(gestureProgress); 401 mFingerDotView.setTranslationY(fingerDotStartTranslationY + gestureProgress); 402 }); 403 404 return swipeAnimator; 405 } 406 407 private class FakeTransformParams extends TransformParams { 408 409 @Override createSurfaceParams(BuilderProxy proxy)410 public SurfaceParams[] createSurfaceParams(BuilderProxy proxy) { 411 SurfaceParams.Builder builder = new SurfaceParams.Builder((SurfaceControl) null); 412 proxy.onBuildTargetParams(builder, null, this); 413 return new SurfaceParams[] {builder.build()}; 414 } 415 416 @Override applySurfaceParams(SurfaceParams[] params)417 public void applySurfaceParams(SurfaceParams[] params) { 418 SurfaceParams p = params[0]; 419 mFakeTaskView.setAnimationMatrix(p.matrix); 420 mFakePreviousTaskView.setAnimationMatrix(p.matrix); 421 mFakeTaskViewRect.set(p.windowCrop); 422 mFakeTaskViewRadius = p.cornerRadius; 423 mFakeTaskView.invalidateOutline(); 424 mFakePreviousTaskView.invalidateOutline(); 425 } 426 } 427 } 428