blob: fbcde8bba2abf4243bcf4a57878900c49ab21ad4 [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.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();
}
}