blob: 6b66ec8db53e0f0b602e8b6759192b386bce7a52 [file] [log] [blame]
/*
* 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.quickstep.WindowTransformSwipeHandler.MIN_OVERSHOOT_DURATION;
import android.animation.ValueAnimator;
import android.view.animation.Interpolator;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.Interpolators.OvershootParams;
import com.android.launcher3.uioverrides.PortraitStatesTouchController;
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;
/**
* 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() {
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());
AnimatorSetBuilder builder = PortraitStatesTouchController.getOverviewToAllAppsAnimation();
mAnimator = mLauncher.getStateManager().createAnimationToNewWorkspace(ALL_APPS, builder,
Math.round(2 * mMaxSwipeDistance), null, LauncherStateManager.ANIM_ALL);
mAnimator.dispatchOnStart();
}
public void onMove(float displacement) {
mAnimator.setPlayFraction(displacement / mMaxSwipeDistance);
mFlingBlockCheck.onEvent();
}
public void destroy() {
// TODO: We can probably also show the task view
mLauncher.getStateManager().goToState(OVERVIEW, false);
}
public void end(float velocity, boolean isFling, Runnable callback) {
float velocityPxPerMs = velocity / 1000;
long duration = MAX_SWIPE_DURATION;
Interpolator interpolator = DEACCEL;
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);
if (blockedFling && !toAllApps) {
Interpolators.OvershootParams overshoot = new OvershootParams(currentFraction,
currentFraction, endProgress, velocityPxPerMs, (int) mMaxSwipeDistance);
duration = (overshoot.duration + duration);
duration = Utilities.boundToRange(duration, MIN_OVERSHOOT_DURATION,
MAX_SWIPE_DURATION);
interpolator = overshoot.interpolator;
endProgress = overshoot.end;
}
} 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(Math.abs(distanceToTravel / velocityPxPerMs));
duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
}
}
final boolean finalIsFling = isFling;
mAnimator.setEndAction(() -> onSwipeAnimationComplete(toAllApps, finalIsFling, callback));
ValueAnimator animator = mAnimator.getAnimationPlayer();
animator.setDuration(duration).setInterpolator(interpolator);
animator.setFloatValues(currentFraction, endProgress);
animator.start();
}
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();
}
public float getTargetAlpha(RemoteAnimationTargetCompat app, Float expectedAlpha) {
if (!(app.isNotInRecents
|| app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME)) {
return 0;
}
return expectedAlpha;
}
}