1 /* 2 * Copyright (C) 2018 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; 17 18 import static com.android.launcher3.LauncherAnimUtils.MIN_PROGRESS_TO_ALL_APPS; 19 import static com.android.launcher3.LauncherState.ALL_APPS; 20 import static com.android.launcher3.LauncherState.OVERVIEW; 21 import static com.android.launcher3.anim.Interpolators.DEACCEL; 22 import static com.android.quickstep.WindowTransformSwipeHandler.MAX_SWIPE_DURATION; 23 import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber; 24 import static com.android.systemui.shared.recents.utilities.Utilities.getSurface; 25 26 import android.animation.ValueAnimator; 27 import android.view.Surface; 28 29 import com.android.launcher3.Launcher; 30 import com.android.launcher3.LauncherAnimUtils; 31 import com.android.launcher3.R; 32 import com.android.launcher3.allapps.AllAppsTransitionController; 33 import com.android.launcher3.allapps.DiscoveryBounce; 34 import com.android.launcher3.anim.AnimatorPlaybackController; 35 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; 36 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; 37 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; 38 import com.android.launcher3.util.FlingBlockCheck; 39 import com.android.quickstep.util.RemoteAnimationTargetSet; 40 import com.android.quickstep.views.RecentsView; 41 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 42 import com.android.systemui.shared.system.TransactionCompat; 43 44 /** 45 * Utility class to handle long swipe from an app. 46 * This assumes the presence of Launcher activity as long swipe is not supported on the 47 * fallback activity. 48 */ 49 public class LongSwipeHelper { 50 51 private static final float SWIPE_DURATION_MULTIPLIER = 52 Math.min(1 / MIN_PROGRESS_TO_ALL_APPS, 1 / (1 - MIN_PROGRESS_TO_ALL_APPS)); 53 54 private final Launcher mLauncher; 55 private final RemoteAnimationTargetSet mTargetSet; 56 57 private float mMaxSwipeDistance = 1; 58 private AnimatorPlaybackController mAnimator; 59 private FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck(); 60 61 LongSwipeHelper(Launcher launcher, RemoteAnimationTargetSet targetSet) { 62 mLauncher = launcher; 63 mTargetSet = targetSet; 64 init(); 65 } 66 67 private void init() { 68 setTargetAlpha(0, true); 69 mFlingBlockCheck.blockFling(); 70 71 // Init animations 72 AllAppsTransitionController controller = mLauncher.getAllAppsController(); 73 // TODO: Scale it down so that we can reach all-apps in screen space 74 mMaxSwipeDistance = Math.max(1, controller.getProgress() * controller.getShiftRange()); 75 mAnimator = mLauncher.getStateManager() 76 .createAnimationToNewWorkspace(ALL_APPS, Math.round(2 * mMaxSwipeDistance)); 77 mAnimator.dispatchOnStart(); 78 } 79 80 public void onMove(float displacement) { 81 mAnimator.setPlayFraction(displacement / mMaxSwipeDistance); 82 mFlingBlockCheck.onEvent(); 83 } 84 85 public void destroy() { 86 // TODO: We can probably also hide the task view 87 setTargetAlpha(1, false); 88 89 mLauncher.getStateManager().goToState(OVERVIEW, false); 90 } 91 92 public void end(float velocity, boolean isFling, Runnable callback) { 93 long duration = MAX_SWIPE_DURATION; 94 95 final float currentFraction = mAnimator.getProgressFraction(); 96 final boolean toAllApps; 97 float endProgress; 98 99 boolean blockedFling = isFling && mFlingBlockCheck.isBlocked(); 100 if (blockedFling) { 101 isFling = false; 102 } 103 104 if (!isFling) { 105 toAllApps = currentFraction > MIN_PROGRESS_TO_ALL_APPS; 106 endProgress = toAllApps ? 1 : 0; 107 108 long expectedDuration = Math.abs(Math.round((endProgress - currentFraction) 109 * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER)); 110 duration = Math.min(MAX_SWIPE_DURATION, expectedDuration); 111 } else { 112 toAllApps = velocity < 0; 113 endProgress = toAllApps ? 1 : 0; 114 115 float minFlingVelocity = mLauncher.getResources() 116 .getDimension(R.dimen.quickstep_fling_min_velocity); 117 if (Math.abs(velocity) > minFlingVelocity && mMaxSwipeDistance > 0) { 118 float distanceToTravel = (endProgress - currentFraction) * mMaxSwipeDistance; 119 120 // we want the page's snap velocity to approximately match the velocity at 121 // which the user flings, so we scale the duration by a value near to the 122 // derivative of the scroll interpolator at zero, ie. 2. 123 long baseDuration = Math.round(1000 * Math.abs(distanceToTravel / velocity)); 124 duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration); 125 } 126 } 127 128 if (blockedFling && !toAllApps) { 129 duration *= LauncherAnimUtils.blockedFlingDurationFactor(0); 130 } 131 final boolean finalIsFling = isFling; 132 mAnimator.setEndAction(() -> onSwipeAnimationComplete(toAllApps, finalIsFling, callback)); 133 ValueAnimator animator = mAnimator.getAnimationPlayer(); 134 animator.setDuration(duration).setInterpolator(DEACCEL); 135 animator.setFloatValues(currentFraction, endProgress); 136 animator.start(); 137 } 138 139 private void setTargetAlpha(float alpha, boolean defer) { 140 final Surface surface = getSurface(mLauncher.getDragLayer()); 141 final long frameNumber = defer && surface != null ? getNextFrameNumber(surface) : -1; 142 if (defer) { 143 if (frameNumber == -1) { 144 defer = false; 145 } else { 146 mLauncher.getDragLayer().invalidate(); 147 } 148 } 149 150 TransactionCompat transaction = new TransactionCompat(); 151 for (RemoteAnimationTargetCompat app : mTargetSet.apps) { 152 if (!(app.isNotInRecents 153 || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME)) { 154 transaction.setAlpha(app.leash, alpha); 155 if (defer) { 156 transaction.deferTransactionUntil(app.leash, surface, frameNumber); 157 } 158 } 159 } 160 transaction.setEarlyWakeup(); 161 transaction.apply(); 162 } 163 164 private void onSwipeAnimationComplete(boolean toAllApps, boolean isFling, Runnable callback) { 165 mLauncher.getStateManager().goToState(toAllApps ? ALL_APPS : OVERVIEW, false); 166 if (!toAllApps) { 167 DiscoveryBounce.showForOverviewIfNeeded(mLauncher); 168 mLauncher.<RecentsView>getOverviewPanel().setSwipeDownShouldLaunchApp(true); 169 } 170 171 mLauncher.getUserEventDispatcher().logStateChangeAction( 172 isFling ? Touch.FLING : Touch.SWIPE, Direction.UP, 173 ContainerType.NAVBAR, ContainerType.APP, 174 toAllApps ? ContainerType.ALLAPPS : ContainerType.TASKSWITCHER, 175 0); 176 177 callback.run(); 178 } 179 } 180