blob: 40bf0572861edc7969525121e8f262ff1161050f [file] [log] [blame]
/*
* Copyright (C) 2016 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.launcher3.uioverrides;
import static com.android.launcher3.LauncherAnimUtils.OVERVIEW_TRANSITION_MS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.Utilities.isAccessibilityEnabled;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.Workspace;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.util.TouchController;
/**
* Detects pinches and animates the Workspace to/from overview mode.
*/
public class PinchToOverviewListener extends AnimatorListenerAdapter
implements TouchController, OnScaleGestureListener {
private static final float ACCEPT_THRESHOLD = 0.65f;
/**
* The velocity threshold at which a pinch will be completed instead of canceled,
* even if the first threshold has not been passed. Measured in scale / millisecond
*/
private static final float FLING_VELOCITY = 0.001f;
private final ScaleGestureDetector mPinchDetector;
private Launcher mLauncher;
private Workspace mWorkspace = null;
private boolean mPinchStarted = false;
private AnimatorPlaybackController mCurrentAnimation;
private float mCurrentScale;
private boolean mShouldGoToFinalState;
private LauncherState mToState;
public PinchToOverviewListener(Launcher launcher) {
mLauncher = launcher;
mPinchDetector = new ScaleGestureDetector(mLauncher, this);
}
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
mPinchDetector.onTouchEvent(ev);
return mPinchStarted;
}
public boolean onControllerTouchEvent(MotionEvent ev) {
return mPinchDetector.onTouchEvent(ev);
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
if (isAccessibilityEnabled(mLauncher)) {
return false;
}
if (!mLauncher.isInState(NORMAL) && !mLauncher.isInState(OVERVIEW)) {
// Don't listen for the pinch gesture if on all apps, widget picker, -1, etc.
return false;
}
if (mCurrentAnimation != null) {
// Don't listen for the pinch gesture if we are already animating from a previous one.
return false;
}
if (mLauncher.isWorkspaceLocked()) {
// Don't listen for the pinch gesture if the workspace isn't ready.
return false;
}
if (mWorkspace == null) {
mWorkspace = mLauncher.getWorkspace();
}
if (mWorkspace.isSwitchingState()) {
// Don't listen for the pinch gesture while switching state, as it will cause a jump
// once the state switching animation is complete.
return false;
}
if (AbstractFloatingView.getTopOpenView(mLauncher) != null) {
// Don't listen for the pinch gesture if a floating view is open.
return false;
}
if (mLauncher.getDragController().isDragging()) {
mLauncher.getDragController().cancelDrag();
}
mToState = mLauncher.isInState(OVERVIEW) ? NORMAL : OVERVIEW;
mCurrentAnimation = mLauncher.getStateManager()
.createAnimationToNewWorkspace(mToState, OVERVIEW_TRANSITION_MS);
mCurrentAnimation.getTarget().addListener(this);
mPinchStarted = true;
mCurrentScale = 1;
mShouldGoToFinalState = false;
mCurrentAnimation.dispatchOnStart();
return true;
}
@Override
public void onAnimationEnd(Animator animation) {
mCurrentAnimation = null;
mPinchStarted = false;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
if (mShouldGoToFinalState) {
mCurrentAnimation.start();
} else {
mCurrentAnimation.setEndAction(new Runnable() {
@Override
public void run() {
mLauncher.getStateManager().goToState(
mToState == OVERVIEW ? NORMAL : OVERVIEW, false);
}
});
mCurrentAnimation.reverse();
}
}
@Override
public boolean onScale(ScaleGestureDetector detector) {
mCurrentScale = detector.getScaleFactor() * mCurrentScale;
// If we are zooming out, inverse the mCurrentScale so that animationFraction = [0, 1]
// 0 => Animation complete
// 1=> Animation started
float animationFraction = mToState == OVERVIEW ? mCurrentScale : (1 / mCurrentScale);
float velocity = (1 - detector.getScaleFactor()) / detector.getTimeDelta();
if (Math.abs(velocity) >= FLING_VELOCITY) {
LauncherState toState = velocity > 0 ? OVERVIEW : NORMAL;
mShouldGoToFinalState = toState == mToState;
} else {
mShouldGoToFinalState = animationFraction <= ACCEPT_THRESHOLD;
}
// Move the transition animation to that duration.
mCurrentAnimation.setPlayFraction(1 - animationFraction);
return true;
}
}