Home | History | Annotate | Download | only in quickstep
      1 /*
      2  * Copyright (C) 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.quickstep;
     17 
     18 import static com.android.launcher3.LauncherAnimUtils.MIN_PROGRESS_TO_ALL_APPS;
     19 import static com.android.launcher3.LauncherState.ALL_APPS;
     20 import static com.android.launcher3.LauncherState.OVERVIEW;
     21 import static com.android.launcher3.anim.Interpolators.DEACCEL;
     22 import static com.android.quickstep.WindowTransformSwipeHandler.MAX_SWIPE_DURATION;
     23 import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber;
     24 import static com.android.systemui.shared.recents.utilities.Utilities.getSurface;
     25 
     26 import android.animation.ValueAnimator;
     27 import android.view.Surface;
     28 
     29 import com.android.launcher3.Launcher;
     30 import com.android.launcher3.LauncherAnimUtils;
     31 import com.android.launcher3.R;
     32 import com.android.launcher3.allapps.AllAppsTransitionController;
     33 import com.android.launcher3.allapps.DiscoveryBounce;
     34 import com.android.launcher3.anim.AnimatorPlaybackController;
     35 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
     36 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
     37 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
     38 import com.android.launcher3.util.FlingBlockCheck;
     39 import com.android.quickstep.util.RemoteAnimationTargetSet;
     40 import com.android.quickstep.views.RecentsView;
     41 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
     42 import com.android.systemui.shared.system.TransactionCompat;
     43 
     44 /**
     45  * Utility class to handle long swipe from an app.
     46  * This assumes the presence of Launcher activity as long swipe is not supported on the
     47  * fallback activity.
     48  */
     49 public class LongSwipeHelper {
     50 
     51     private static final float SWIPE_DURATION_MULTIPLIER =
     52             Math.min(1 / MIN_PROGRESS_TO_ALL_APPS, 1 / (1 - MIN_PROGRESS_TO_ALL_APPS));
     53 
     54     private final Launcher mLauncher;
     55     private final RemoteAnimationTargetSet mTargetSet;
     56 
     57     private float mMaxSwipeDistance = 1;
     58     private AnimatorPlaybackController mAnimator;
     59     private FlingBlockCheck mFlingBlockCheck = new FlingBlockCheck();
     60 
     61     LongSwipeHelper(Launcher launcher, RemoteAnimationTargetSet targetSet) {
     62         mLauncher = launcher;
     63         mTargetSet = targetSet;
     64         init();
     65     }
     66 
     67     private void init() {
     68         setTargetAlpha(0, true);
     69         mFlingBlockCheck.blockFling();
     70 
     71         // Init animations
     72         AllAppsTransitionController controller = mLauncher.getAllAppsController();
     73         // TODO: Scale it down so that we can reach all-apps in screen space
     74         mMaxSwipeDistance = Math.max(1, controller.getProgress() * controller.getShiftRange());
     75         mAnimator = mLauncher.getStateManager()
     76                 .createAnimationToNewWorkspace(ALL_APPS, Math.round(2 * mMaxSwipeDistance));
     77         mAnimator.dispatchOnStart();
     78     }
     79 
     80     public void onMove(float displacement) {
     81         mAnimator.setPlayFraction(displacement / mMaxSwipeDistance);
     82         mFlingBlockCheck.onEvent();
     83     }
     84 
     85     public void destroy() {
     86         // TODO: We can probably also hide the task view
     87         setTargetAlpha(1, false);
     88 
     89         mLauncher.getStateManager().goToState(OVERVIEW, false);
     90     }
     91 
     92     public void end(float velocity, boolean isFling, Runnable callback) {
     93         long duration = MAX_SWIPE_DURATION;
     94 
     95         final float currentFraction = mAnimator.getProgressFraction();
     96         final boolean toAllApps;
     97         float endProgress;
     98 
     99         boolean blockedFling = isFling && mFlingBlockCheck.isBlocked();
    100         if (blockedFling) {
    101             isFling = false;
    102         }
    103 
    104         if (!isFling) {
    105             toAllApps = currentFraction > MIN_PROGRESS_TO_ALL_APPS;
    106             endProgress = toAllApps ? 1 : 0;
    107 
    108             long expectedDuration = Math.abs(Math.round((endProgress - currentFraction)
    109                     * MAX_SWIPE_DURATION * SWIPE_DURATION_MULTIPLIER));
    110             duration = Math.min(MAX_SWIPE_DURATION, expectedDuration);
    111         } else {
    112             toAllApps = velocity < 0;
    113             endProgress = toAllApps ? 1 : 0;
    114 
    115             float minFlingVelocity = mLauncher.getResources()
    116                     .getDimension(R.dimen.quickstep_fling_min_velocity);
    117             if (Math.abs(velocity) > minFlingVelocity && mMaxSwipeDistance > 0) {
    118                 float distanceToTravel = (endProgress - currentFraction) * mMaxSwipeDistance;
    119 
    120                 // we want the page's snap velocity to approximately match the velocity at
    121                 // which the user flings, so we scale the duration by a value near to the
    122                 // derivative of the scroll interpolator at zero, ie. 2.
    123                 long baseDuration = Math.round(1000 * Math.abs(distanceToTravel / velocity));
    124                 duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
    125             }
    126         }
    127 
    128         if (blockedFling && !toAllApps) {
    129             duration *= LauncherAnimUtils.blockedFlingDurationFactor(0);
    130         }
    131         final boolean finalIsFling = isFling;
    132         mAnimator.setEndAction(() -> onSwipeAnimationComplete(toAllApps, finalIsFling, callback));
    133         ValueAnimator animator = mAnimator.getAnimationPlayer();
    134         animator.setDuration(duration).setInterpolator(DEACCEL);
    135         animator.setFloatValues(currentFraction, endProgress);
    136         animator.start();
    137     }
    138 
    139     private void setTargetAlpha(float alpha, boolean defer) {
    140         final Surface surface = getSurface(mLauncher.getDragLayer());
    141         final long frameNumber = defer && surface != null ? getNextFrameNumber(surface) : -1;
    142         if (defer) {
    143             if (frameNumber == -1) {
    144                 defer = false;
    145             } else {
    146                 mLauncher.getDragLayer().invalidate();
    147             }
    148         }
    149 
    150         TransactionCompat transaction = new TransactionCompat();
    151         for (RemoteAnimationTargetCompat app : mTargetSet.apps) {
    152             if (!(app.isNotInRecents
    153                     || app.activityType == RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME)) {
    154                 transaction.setAlpha(app.leash, alpha);
    155                 if (defer) {
    156                     transaction.deferTransactionUntil(app.leash, surface, frameNumber);
    157                 }
    158             }
    159         }
    160         transaction.setEarlyWakeup();
    161         transaction.apply();
    162     }
    163 
    164     private void onSwipeAnimationComplete(boolean toAllApps, boolean isFling, Runnable callback) {
    165         mLauncher.getStateManager().goToState(toAllApps ? ALL_APPS : OVERVIEW, false);
    166         if (!toAllApps) {
    167             DiscoveryBounce.showForOverviewIfNeeded(mLauncher);
    168             mLauncher.<RecentsView>getOverviewPanel().setSwipeDownShouldLaunchApp(true);
    169         }
    170 
    171         mLauncher.getUserEventDispatcher().logStateChangeAction(
    172                 isFling ? Touch.FLING : Touch.SWIPE, Direction.UP,
    173                 ContainerType.NAVBAR, ContainerType.APP,
    174                 toAllApps ? ContainerType.ALLAPPS : ContainerType.TASKSWITCHER,
    175                 0);
    176 
    177         callback.run();
    178     }
    179 }
    180