| /* |
| * Copyright (C) 2018 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; |
| |
| import static com.android.launcher3.LauncherAnimUtils.MIN_PROGRESS_TO_ALL_APPS; |
| import static com.android.launcher3.LauncherState.ALL_APPS; |
| import static com.android.launcher3.LauncherState.OVERVIEW; |
| import static com.android.launcher3.anim.Interpolators.DEACCEL; |
| import static com.android.quickstep.WindowTransformSwipeHandler.MAX_SWIPE_DURATION; |
| import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber; |
| import static com.android.systemui.shared.recents.utilities.Utilities.getSurface; |
| |
| import android.animation.ValueAnimator; |
| import android.view.Surface; |
| |
| import com.android.launcher3.Launcher; |
| import com.android.launcher3.LauncherAnimUtils; |
| import com.android.launcher3.R; |
| import com.android.launcher3.allapps.AllAppsTransitionController; |
| import com.android.launcher3.allapps.DiscoveryBounce; |
| import com.android.launcher3.anim.AnimatorPlaybackController; |
| import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; |
| import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; |
| import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; |
| import com.android.launcher3.util.FlingBlockCheck; |
| import com.android.quickstep.util.RemoteAnimationTargetSet; |
| import com.android.quickstep.views.RecentsView; |
| import com.android.systemui.shared.system.RemoteAnimationTargetCompat; |
| import com.android.systemui.shared.system.TransactionCompat; |
| |
| /** |
| * Utility class to handle long swipe from an app. |
| * This assumes the presence of Launcher activity as long swipe is not supported on the |
| * fallback activity. |
| */ |
| public class LongSwipeHelper { |
| |
| private static final float SWIPE_DURATION_MULTIPLIER = |
| Math.min(1 / MIN_PROGRESS_TO_ALL_APPS, 1 / (1 - MIN_PROGRESS_TO_ALL_APPS)); |
| |
| private final Launcher mLauncher; |
| private final RemoteAnimationTargetSet mTargetSet; |
| |
| private float mMaxSwipeDistance = 1; |
| private AnimatorPlaybackController mAnimator; |
| private FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck(); |
| |
| LongSwipeHelper(Launcher launcher, RemoteAnimationTargetSet targetSet) { |
| mLauncher = launcher; |
| mTargetSet = targetSet; |
| init(); |
| } |
| |
| private void init() { |
| setTargetAlpha(0, true); |
| mFlingBlockCheck.blockFling(); |
| |
| // Init animations |
| AllAppsTransitionController controller = mLauncher.getAllAppsController(); |
| // TODO: Scale it down so that we can reach all-apps in screen space |
| mMaxSwipeDistance = Math.max(1, controller.getProgress() * controller.getShiftRange()); |
| mAnimator = mLauncher.getStateManager() |
| .createAnimationToNewWorkspace(ALL_APPS, Math.round(2 * mMaxSwipeDistance)); |
| mAnimator.dispatchOnStart(); |
| } |
| |
| public void onMove(float displacement) { |
| mAnimator.setPlayFraction(displacement / mMaxSwipeDistance); |
| mFlingBlockCheck.onEvent(); |
| } |
| |
| public void destroy() { |
| // TODO: We can probably also hide the task view |
| setTargetAlpha(1, false); |
| |
| mLauncher.getStateManager().goToState(OVERVIEW, false); |
| } |
| |
| public void end(float velocity, boolean isFling, Runnable callback) { |
| long duration = MAX_SWIPE_DURATION; |
| |
| final float currentFraction = mAnimator.getProgressFraction(); |
| final boolean toAllApps; |
| float endProgress; |
| |
| boolean blockedFling = isFling && mFlingBlockCheck.isBlocked(); |
| if (blockedFling) { |
| isFling = false; |
| } |
| |
| if (!isFling) { |
| toAllApps = currentFraction > MIN_PROGRESS_TO_ALL_APPS; |
| endProgress = toAllApps ? 1 : 0; |
| |
| long expectedDuration = Math.abs(Math.round((endProgress - currentFraction) |
| * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER)); |
| duration = Math.min(MAX_SWIPE_DURATION, expectedDuration); |
| } else { |
| toAllApps = velocity < 0; |
| endProgress = toAllApps ? 1 : 0; |
| |
| float minFlingVelocity = mLauncher.getResources() |
| .getDimension(R.dimen.quickstep_fling_min_velocity); |
| if (Math.abs(velocity) > minFlingVelocity && mMaxSwipeDistance > 0) { |
| float distanceToTravel = (endProgress - currentFraction) * mMaxSwipeDistance; |
| |
| // we want the page's snap velocity to approximately match the velocity at |
| // which the user flings, so we scale the duration by a value near to the |
| // derivative of the scroll interpolator at zero, ie. 2. |
| long baseDuration = Math.round(1000 * Math.abs(distanceToTravel / velocity)); |
| duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration); |
| } |
| } |
| |
| if (blockedFling && !toAllApps) { |
| duration *= LauncherAnimUtils.blockedFlingDurationFactor(0); |
| } |
| final boolean finalIsFling = isFling; |
| mAnimator.setEndAction(() -> onSwipeAnimationComplete(toAllApps, finalIsFling, callback)); |
| ValueAnimator animator = mAnimator.getAnimationPlayer(); |
| animator.setDuration(duration).setInterpolator(DEACCEL); |
| animator.setFloatValues(currentFraction, endProgress); |
| animator.start(); |
| } |
| |
| private void setTargetAlpha(float alpha, boolean defer) { |
| final Surface surface = getSurface(mLauncher.getDragLayer()); |
| final long frameNumber = defer && surface != null ? getNextFrameNumber(surface) : -1; |
| if (defer) { |
| if (frameNumber == -1) { |
| defer = false; |
| } else { |
| mLauncher.getDragLayer().invalidate(); |
| } |
| } |
| |
| TransactionCompat transaction = new TransactionCompat(); |
| for (RemoteAnimationTargetCompat app : mTargetSet.apps) { |
| if (!(app.isNotInRecents |
| || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME)) { |
| transaction.setAlpha(app.leash, alpha); |
| if (defer) { |
| transaction.deferTransactionUntil(app.leash, surface, frameNumber); |
| } |
| } |
| } |
| transaction.setEarlyWakeup(); |
| transaction.apply(); |
| } |
| |
| private void onSwipeAnimationComplete(boolean toAllApps, boolean isFling, Runnable callback) { |
| mLauncher.getStateManager().goToState(toAllApps ? ALL_APPS : OVERVIEW, false); |
| if (!toAllApps) { |
| DiscoveryBounce.showForOverviewIfNeeded(mLauncher); |
| mLauncher.<RecentsView>getOverviewPanel().setSwipeDownShouldLaunchApp(true); |
| } |
| |
| mLauncher.getUserEventDispatcher().logStateChangeAction( |
| isFling ? Touch.FLING : Touch.SWIPE, Direction.UP, |
| ContainerType.NAVBAR, ContainerType.APP, |
| toAllApps ? ContainerType.ALLAPPS : ContainerType.TASKSWITCHER, |
| 0); |
| |
| callback.run(); |
| } |
| } |