/* * Copyright (C) 2019 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.touchcontrollers; import static android.view.View.TRANSLATION_X; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS; import static com.android.launcher3.anim.Interpolators.DEACCEL_3; import static com.android.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS; import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; import android.animation.Animator; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.view.MotionEvent; import android.view.animation.Interpolator; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherStateManager.AnimationConfig; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.anim.AnimationSuccessListener; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.config.BaseFlags; import com.android.launcher3.touch.SingleAxisSwipeDetector; import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch; import com.android.launcher3.util.TouchController; import com.android.quickstep.util.AssistantUtilities; import com.android.quickstep.views.RecentsView; import com.android.systemui.shared.system.ActivityManagerWrapper; /** * Handles swiping up on the nav bar to go home from launcher, e.g. overview or all apps. */ public class NavBarToHomeTouchController implements TouchController, SingleAxisSwipeDetector.Listener { private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL_3; private final Launcher mLauncher; private final SingleAxisSwipeDetector mSwipeDetector; private final float mPullbackDistance; private boolean mNoIntercept; private LauncherState mStartState; private LauncherState mEndState = NORMAL; private AnimatorPlaybackController mCurrentAnimation; public NavBarToHomeTouchController(Launcher launcher) { mLauncher = launcher; mSwipeDetector = new SingleAxisSwipeDetector(mLauncher, this, SingleAxisSwipeDetector.VERTICAL); mPullbackDistance = mLauncher.getResources().getDimension(R.dimen.home_pullback_distance); } @Override public final boolean onControllerInterceptTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { mStartState = mLauncher.getStateManager().getState(); mNoIntercept = !canInterceptTouch(ev); if (mNoIntercept) { return false; } mSwipeDetector.setDetectableScrollConditions(SingleAxisSwipeDetector.DIRECTION_POSITIVE, false /* ignoreSlop */); } if (mNoIntercept) { return false; } onControllerTouchEvent(ev); return mSwipeDetector.isDraggingOrSettling(); } private boolean canInterceptTouch(MotionEvent ev) { boolean cameFromNavBar = (ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0; if (!cameFromNavBar) { return false; } if (mStartState == OVERVIEW || mStartState == ALL_APPS) { return true; } if (AbstractFloatingView.getTopOpenView(mLauncher) != null) { return true; } if (BaseFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get() && AssistantUtilities.isExcludedAssistantRunning()) { return true; } return false; } @Override public final boolean onControllerTouchEvent(MotionEvent ev) { return mSwipeDetector.onTouchEvent(ev); } private float getShiftRange() { return mLauncher.getDeviceProfile().heightPx; } @Override public void onDragStart(boolean start) { initCurrentAnimation(); } private void initCurrentAnimation() { long accuracy = (long) (getShiftRange() * 2); final AnimatorSet anim = new AnimatorSet(); if (mStartState == OVERVIEW) { RecentsView recentsView = mLauncher.getOverviewPanel(); float pullbackDist = mPullbackDistance; if (!recentsView.isRtl()) { pullbackDist = -pullbackDist; } Animator pullback = ObjectAnimator.ofFloat(recentsView, TRANSLATION_X, pullbackDist); pullback.setInterpolator(PULLBACK_INTERPOLATOR); anim.play(pullback); } else if (mStartState == ALL_APPS) { AnimatorSetBuilder builder = new AnimatorSetBuilder(); AllAppsTransitionController allAppsController = mLauncher.getAllAppsController(); Animator allAppsProgress = ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS, -mPullbackDistance / allAppsController.getShiftRange()); allAppsProgress.setInterpolator(PULLBACK_INTERPOLATOR); builder.play(allAppsProgress); // Slightly fade out all apps content to further distinguish from scrolling. builder.setInterpolator(AnimatorSetBuilder.ANIM_ALL_APPS_FADE, Interpolators .mapToProgress(PULLBACK_INTERPOLATOR, 0, 0.5f)); AnimationConfig config = new AnimationConfig(); config.duration = accuracy; allAppsController.setAlphas(mEndState.getVisibleElements(mLauncher), config, builder); anim.play(builder.build()); } AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher); if (topView != null) { Animator hintCloseAnim = topView.createHintCloseAnim(mPullbackDistance); if (hintCloseAnim != null) { hintCloseAnim.setInterpolator(PULLBACK_INTERPOLATOR); anim.play(hintCloseAnim); } } anim.setDuration(accuracy); mCurrentAnimation = AnimatorPlaybackController.wrap(anim, accuracy, this::clearState); } private void clearState() { mCurrentAnimation = null; mSwipeDetector.finishedScrolling(); mSwipeDetector.setDetectableScrollConditions(0, false); } @Override public boolean onDrag(float displacement) { // Only allow swipe up. displacement = Math.min(0, displacement); float progress = Utilities.getProgress(displacement, 0, getShiftRange()); mCurrentAnimation.setPlayFraction(progress); return true; } @Override public void onDragEnd(float velocity) { boolean fling = mSwipeDetector.isFling(velocity); final int logAction = fling ? Touch.FLING : Touch.SWIPE; float progress = mCurrentAnimation.getProgressFraction(); float interpolatedProgress = PULLBACK_INTERPOLATOR.getInterpolation(progress); boolean success = interpolatedProgress >= SUCCESS_TRANSITION_PROGRESS || (velocity < 0 && fling); if (success) { mLauncher.getStateManager().goToState(mEndState, true, () -> onSwipeInteractionCompleted(mEndState)); if (mStartState != mEndState) { logStateChange(mStartState.containerType, logAction); } AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(mLauncher); if (topOpenView != null) { AbstractFloatingView.closeAllOpenViews(mLauncher); logStateChange(topOpenView.getLogContainerType(), logAction); } ActivityManagerWrapper.getInstance() .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); } else { // Quickly return to the state we came from (we didn't move far). ValueAnimator anim = mCurrentAnimation.getAnimationPlayer(); anim.setFloatValues(progress, 0); anim.addListener(new AnimationSuccessListener() { @Override public void onAnimationSuccess(Animator animator) { onSwipeInteractionCompleted(mStartState); } }); anim.setDuration(80).start(); } } private void onSwipeInteractionCompleted(LauncherState targetState) { clearState(); mLauncher.getStateManager().goToState(targetState, false /* animated */); AccessibilityManagerCompat.sendStateEventToTest(mLauncher, targetState.ordinal); } private void logStateChange(int startContainerType, int logAction) { mLauncher.getUserEventDispatcher().logStateChangeAction(logAction, LauncherLogProto.Action.Direction.UP, mSwipeDetector.getDownX(), mSwipeDetector.getDownY(), LauncherLogProto.ContainerType.NAVBAR, startContainerType, mEndState.containerType, mLauncher.getWorkspace().getCurrentPage()); } }