Merge "Unifying swipe handling for fallback launcher" into ub-launcher3-rvc-dev

This commit is contained in:
TreeHugger Robot
2020-05-20 05:32:36 +00:00
committed by Android (Google) Code Review
19 changed files with 603 additions and 919 deletions
@@ -18,6 +18,7 @@
android:id="@+id/drag_layer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:fitsSystemWindows="true">
<com.android.quickstep.fallback.FallbackRecentsView
@@ -49,64 +49,62 @@ import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_S
import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.view.View;
import android.view.animation.Interpolator;
import com.android.launcher3.CellLayout;
import com.android.launcher3.Hotseat;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherState.ScaleAndTranslation;
import com.android.launcher3.Workspace;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.quickstep.SysUINavigationMode;
import com.android.quickstep.util.RecentsAtomicAnimationFactory;
import com.android.quickstep.views.RecentsView;
/**
* Animation factory for quickstep specific transitions
*/
public class QuickstepAtomicAnimationFactory extends AtomicAnimationFactory<LauncherState> {
public class QuickstepAtomicAnimationFactory extends
RecentsAtomicAnimationFactory<Launcher, LauncherState> {
// Scale recents takes before animating in
private static final float RECENTS_PREPARE_SCALE = 1.33f;
public static final int INDEX_SHELF_ANIM = 0;
public static final int INDEX_RECENTS_FADE_ANIM = 1;
public static final int INDEX_RECENTS_TRANSLATE_X_ANIM = 2;
public static final int INDEX_PAUSE_TO_OVERVIEW_ANIM = 3;
private static final int ANIM_COUNT = 4;
public static final int INDEX_SHELF_ANIM = RecentsAtomicAnimationFactory.NEXT_INDEX + 0;
public static final int INDEX_PAUSE_TO_OVERVIEW_ANIM =
RecentsAtomicAnimationFactory.NEXT_INDEX + 1;
private static final int MY_ANIM_COUNT = 2;
protected static final int NEXT_INDEX = RecentsAtomicAnimationFactory.NEXT_INDEX
+ MY_ANIM_COUNT;
public static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300;
private final QuickstepLauncher mLauncher;
public QuickstepAtomicAnimationFactory(QuickstepLauncher launcher) {
super(ANIM_COUNT);
mLauncher = launcher;
public QuickstepAtomicAnimationFactory(QuickstepLauncher activity) {
super(activity, MY_ANIM_COUNT);
}
@Override
public Animator createStateElementAnimation(int index, float... values) {
switch (index) {
case INDEX_SHELF_ANIM: {
AllAppsTransitionController aatc = mLauncher.getAllAppsController();
AllAppsTransitionController aatc = mActivity.getAllAppsController();
Animator springAnim = aatc.createSpringAnimation(values);
if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
if ((OVERVIEW.getVisibleElements(mActivity) & HOTSEAT_ICONS) != 0) {
// Translate hotseat with the shelf until reaching overview.
float overviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
ScaleAndTranslation sat = OVERVIEW.getHotseatScaleAndTranslation(mLauncher);
float overviewProgress = OVERVIEW.getVerticalProgress(mActivity);
ScaleAndTranslation sat = OVERVIEW.getHotseatScaleAndTranslation(mActivity);
float shiftRange = aatc.getShiftRange();
if (values.length == 1) {
values = new float[] {aatc.getProgress(), values[0]};
@@ -114,9 +112,9 @@ public class QuickstepAtomicAnimationFactory extends AtomicAnimationFactory<Laun
ValueAnimator hotseatAnim = ValueAnimator.ofFloat(values);
hotseatAnim.addUpdateListener(anim -> {
float progress = (Float) anim.getAnimatedValue();
if (progress >= overviewProgress || mLauncher.isInState(BACKGROUND_APP)) {
if (progress >= overviewProgress || mActivity.isInState(BACKGROUND_APP)) {
float hotseatShift = (progress - overviewProgress) * shiftRange;
mLauncher.getHotseat().setTranslationY(hotseatShift + sat.translationY);
mActivity.getHotseat().setTranslationY(hotseatShift + sat.translationY);
}
});
hotseatAnim.setInterpolator(LINEAR);
@@ -130,34 +128,21 @@ public class QuickstepAtomicAnimationFactory extends AtomicAnimationFactory<Laun
return springAnim;
}
case INDEX_RECENTS_FADE_ANIM:
return ObjectAnimator.ofFloat(mLauncher.getOverviewPanel(),
RecentsView.CONTENT_ALPHA, values);
case INDEX_RECENTS_TRANSLATE_X_ANIM: {
RecentsView rv = mLauncher.getOverviewPanel();
return new SpringAnimationBuilder(mLauncher)
.setMinimumVisibleChange(1f / rv.getPageOffsetScale())
.setDampingRatio(0.8f)
.setStiffness(250)
.setValues(values)
.build(rv, ADJACENT_PAGE_OFFSET);
}
case INDEX_PAUSE_TO_OVERVIEW_ANIM: {
StateAnimationConfig config = new StateAnimationConfig();
config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
config.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
config.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3);
if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
if ((OVERVIEW.getVisibleElements(mActivity) & HOTSEAT_ICONS) != 0) {
config.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2);
config.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2);
}
StateManager<LauncherState> stateManager = mLauncher.getStateManager();
StateManager<LauncherState> stateManager = mActivity.getStateManager();
return stateManager.createAtomicAnimation(
stateManager.getCurrentStableState(), OVERVIEW, config);
}
default:
return super.createStateElementAnimation(index, values);
}
@@ -172,7 +157,7 @@ public class QuickstepAtomicAnimationFactory extends AtomicAnimationFactory<Laun
config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL);
config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
Workspace workspace = mLauncher.getWorkspace();
Workspace workspace = mActivity.getWorkspace();
// Start from a higher workspace scale, but only if we're invisible so we don't jump.
boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE;
@@ -186,13 +171,13 @@ public class QuickstepAtomicAnimationFactory extends AtomicAnimationFactory<Laun
workspace.setScaleX(0.92f);
workspace.setScaleY(0.92f);
}
Hotseat hotseat = mLauncher.getHotseat();
Hotseat hotseat = mActivity.getHotseat();
boolean isHotseatVisible = hotseat.getVisibility() == VISIBLE && hotseat.getAlpha() > 0;
if (!isHotseatVisible) {
hotseat.setScaleX(0.92f);
hotseat.setScaleY(0.92f);
if (ENABLE_OVERVIEW_ACTIONS.get()) {
AllAppsContainerView qsbContainer = mLauncher.getAppsView();
AllAppsContainerView qsbContainer = mActivity.getAppsView();
View qsb = qsbContainer.getSearchView();
boolean qsbVisible = qsb.getVisibility() == VISIBLE && qsb.getAlpha() > 0;
if (!qsbVisible) {
@@ -209,7 +194,7 @@ public class QuickstepAtomicAnimationFactory extends AtomicAnimationFactory<Laun
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
config.setInterpolator(ANIM_OVERVIEW_SCRIM_FADE, FAST_OUT_SLOW_IN);
} else if ((fromState == NORMAL || fromState == HINT_STATE) && toState == OVERVIEW) {
if (SysUINavigationMode.getMode(mLauncher) == NO_BUTTON) {
if (SysUINavigationMode.getMode(mActivity) == NO_BUTTON) {
config.setInterpolator(ANIM_WORKSPACE_SCALE,
fromState == NORMAL ? ACCEL : OVERSHOOT_1_2);
config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
@@ -217,7 +202,7 @@ public class QuickstepAtomicAnimationFactory extends AtomicAnimationFactory<Laun
config.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
// Scale up the recents, if it is not coming from the side
RecentsView overview = mLauncher.getOverviewPanel();
RecentsView overview = mActivity.getOverviewPanel();
if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) {
SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
}
@@ -225,7 +210,7 @@ public class QuickstepAtomicAnimationFactory extends AtomicAnimationFactory<Laun
config.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
config.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
Interpolator translationInterpolator = ENABLE_OVERVIEW_ACTIONS.get()
&& removeShelfFromOverview(mLauncher)
&& removeShelfFromOverview(mActivity)
? OVERSHOOT_1_2
: OVERSHOOT_1_7;
config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, translationInterpolator);
@@ -77,7 +77,6 @@ final class AppToOverviewAnimationProvider<T extends StatefulActivity<?>> extend
controller.dispatchOnStart();
controller.getAnimationPlayer().end();
});
factory.onRemoteAnimationReceived(null);
factory.createActivityInterface(RECENTS_LAUNCH_DURATION);
factory.setRecentsAttachedToAppWindow(true, false);
mActivity = activity;
@@ -55,7 +55,6 @@ import com.android.launcher3.views.FloatingIconView;
import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.AppWindowAnimationHelper;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
@@ -511,8 +510,8 @@ public abstract class BaseSwipeUpHandler<T extends StatefulActivity<?>, Q extend
public interface Factory {
BaseSwipeUpHandler newHandler(GestureState gestureState, long touchTimeMs,
boolean continuingLastGesture, boolean isLikelyToStartNewTask);
BaseSwipeUpHandler newHandler(
GestureState gestureState, long touchTimeMs, boolean continuingLastGesture);
}
protected interface RunningWindowAnim {
@@ -17,7 +17,6 @@ package com.android.quickstep;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
@@ -38,29 +37,24 @@ import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Build;
import android.os.SystemClock;
import android.os.UserHandle;
import android.view.View;
import android.view.View.OnApplyWindowInsetsListener;
import android.view.ViewTreeObserver.OnDrawListener;
import android.view.WindowInsets;
import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimationSuccessListener;
@@ -72,7 +66,6 @@ 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.TraceHelper;
import com.android.launcher3.views.FloatingIconView;
import com.android.quickstep.BaseActivityInterface.AnimationFactory;
import com.android.quickstep.GestureState.GestureEndTarget;
import com.android.quickstep.inputconsumers.OverviewInputConsumer;
@@ -80,7 +73,6 @@ import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.ShelfPeekAnim;
import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
import com.android.quickstep.util.TransformParams.TargetAlphaProvider;
import com.android.quickstep.views.LiveTileOverlay;
import com.android.quickstep.views.RecentsView;
@@ -92,11 +84,12 @@ import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
/**
* Handles the navigation gestures when Launcher is the default home activity.
* TODO: Merge this with BaseSwipeUpHandler
*/
@TargetApi(Build.VERSION_CODES.O)
public class LauncherSwipeHandler extends BaseSwipeUpHandler<Launcher, RecentsView>
implements OnApplyWindowInsetsListener {
private static final String TAG = LauncherSwipeHandler.class.getSimpleName();
public abstract class BaseSwipeUpHandlerV2<T extends StatefulActivity<?>, Q extends RecentsView>
extends BaseSwipeUpHandler<T, Q> implements OnApplyWindowInsetsListener {
private static final String TAG = BaseSwipeUpHandlerV2.class.getSimpleName();
private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
@@ -108,9 +101,11 @@ public class LauncherSwipeHandler extends BaseSwipeUpHandler<Launcher, RecentsVi
}
// Launcher UI related states
private static final int STATE_LAUNCHER_PRESENT = getFlagForIndex(0, "STATE_LAUNCHER_PRESENT");
private static final int STATE_LAUNCHER_STARTED = getFlagForIndex(1, "STATE_LAUNCHER_STARTED");
private static final int STATE_LAUNCHER_DRAWN = getFlagForIndex(2, "STATE_LAUNCHER_DRAWN");
protected static final int STATE_LAUNCHER_PRESENT =
getFlagForIndex(0, "STATE_LAUNCHER_PRESENT");
protected static final int STATE_LAUNCHER_STARTED =
getFlagForIndex(1, "STATE_LAUNCHER_STARTED");
protected static final int STATE_LAUNCHER_DRAWN = getFlagForIndex(2, "STATE_LAUNCHER_DRAWN");
// Internal initialization states
private static final int STATE_APP_CONTROLLER_RECEIVED =
@@ -122,7 +117,7 @@ public class LauncherSwipeHandler extends BaseSwipeUpHandler<Launcher, RecentsVi
private static final int STATE_SCALED_CONTROLLER_RECENTS =
getFlagForIndex(5, "STATE_SCALED_CONTROLLER_RECENTS");
private static final int STATE_HANDLER_INVALIDATED =
protected static final int STATE_HANDLER_INVALIDATED =
getFlagForIndex(6, "STATE_HANDLER_INVALIDATED");
private static final int STATE_GESTURE_STARTED =
getFlagForIndex(7, "STATE_GESTURE_STARTED");
@@ -163,7 +158,7 @@ public class LauncherSwipeHandler extends BaseSwipeUpHandler<Launcher, RecentsVi
*/
private static final int LOG_NO_OP_PAGE_INDEX = -1;
private final TaskAnimationManager mTaskAnimationManager;
protected final TaskAnimationManager mTaskAnimationManager;
// Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
private RunningWindowAnim mRunningWindowAnim;
@@ -193,7 +188,7 @@ public class LauncherSwipeHandler extends BaseSwipeUpHandler<Launcher, RecentsVi
private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
public LauncherSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
public BaseSwipeUpHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
TaskAnimationManager taskAnimationManager, GestureState gestureState,
long touchTimeMs, boolean continuingLastGesture,
InputConsumerController inputConsumer) {
@@ -222,9 +217,6 @@ public class LauncherSwipeHandler extends BaseSwipeUpHandler<Launcher, RecentsVi
| STATE_GESTURE_CANCELLED,
this::resetStateForAnimationCancel);
mStateCallback.runOnceAtState(STATE_LAUNCHER_STARTED | STATE_APP_CONTROLLER_RECEIVED,
this::sendRemoteAnimationsToAnimationFactory);
mStateCallback.runOnceAtState(STATE_RESUME_LAST_TASK | STATE_APP_CONTROLLER_RECEIVED,
this::resumeLastTask);
mStateCallback.runOnceAtState(STATE_START_NEW_TASK | STATE_SCREENSHOT_CAPTURED,
@@ -272,7 +264,7 @@ public class LauncherSwipeHandler extends BaseSwipeUpHandler<Launcher, RecentsVi
@Override
protected boolean onActivityInit(Boolean alreadyOnHome) {
super.onActivityInit(alreadyOnHome);
final Launcher activity = mActivityInterface.getCreatedActivity();
final T activity = mActivityInterface.getCreatedActivity();
if (mActivity == activity) {
return true;
}
@@ -323,7 +315,7 @@ public class LauncherSwipeHandler extends BaseSwipeUpHandler<Launcher, RecentsVi
}
private void onLauncherStart() {
final Launcher activity = mActivityInterface.getCreatedActivity();
final T activity = mActivityInterface.getCreatedActivity();
if (mActivity != activity) {
return;
}
@@ -411,6 +403,10 @@ public class LauncherSwipeHandler extends BaseSwipeUpHandler<Launcher, RecentsVi
updateSysUiFlags(mCurrentShift.value);
return;
}
notifyGestureAnimationStartToRecents();
}
protected void notifyGestureAnimationStartToRecents() {
mRecentsView.onGestureAnimationStart(mGestureState.getRunningTaskId());
}
@@ -418,10 +414,6 @@ public class LauncherSwipeHandler extends BaseSwipeUpHandler<Launcher, RecentsVi
mLauncherFrameDrawnTime = SystemClock.uptimeMillis();
}
private void sendRemoteAnimationsToAnimationFactory() {
mAnimationFactory.onRemoteAnimationReceived(mRecentsAnimationTargets);
}
private void initializeLauncherAnimationController() {
buildAnimationController();
@@ -633,7 +625,7 @@ public class LauncherSwipeHandler extends BaseSwipeUpHandler<Launcher, RecentsVi
*/
@UiThread
private void notifyGestureStartedAsync() {
final Launcher curActivity = mActivity;
final T curActivity = mActivity;
if (curActivity != null) {
// Once the gesture starts, we can no longer transition home through the button, so
// reset the force override of the activity visibility
@@ -681,7 +673,7 @@ public class LauncherSwipeHandler extends BaseSwipeUpHandler<Launcher, RecentsVi
endLauncherTransitionController();
if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
// Hide the task view, if not already hidden
setTargetAlphaProvider(LauncherSwipeHandler::getHiddenTargetAlpha);
setTargetAlphaProvider(BaseSwipeUpHandlerV2::getHiddenTargetAlpha);
}
StatefulActivity activity = mActivityInterface.getCreatedActivity();
@@ -911,6 +903,8 @@ public class LauncherSwipeHandler extends BaseSwipeUpHandler<Launcher, RecentsVi
interpolator, target, velocityPxPerMs));
}
protected abstract HomeAnimationFactory createHomeAnimationFactory(long duration);
@UiThread
private void animateToProgressInternal(float start, float end, long duration,
Interpolator interpolator, GestureEndTarget target, PointF velocityPxPerMs) {
@@ -919,67 +913,7 @@ public class LauncherSwipeHandler extends BaseSwipeUpHandler<Launcher, RecentsVi
maybeUpdateRecentsAttachedState();
if (mGestureState.getEndTarget() == HOME) {
HomeAnimationFactory homeAnimFactory;
if (mActivity != null) {
final TaskView runningTaskView = mRecentsView.getRunningTaskView();
final View workspaceView;
if (runningTaskView != null
&& runningTaskView.getTask().key.getComponent() != null) {
workspaceView = mActivity.getWorkspace().getFirstMatchForAppClose(
runningTaskView.getTask().key.getComponent().getPackageName(),
UserHandle.of(runningTaskView.getTask().key.userId));
} else {
workspaceView = null;
}
final RectF iconLocation = new RectF();
boolean canUseWorkspaceView =
workspaceView != null && workspaceView.isAttachedToWindow();
FloatingIconView floatingIconView = canUseWorkspaceView
? FloatingIconView.getFloatingIconView(mActivity, workspaceView,
true /* hideOriginal */, iconLocation, false /* isOpening */)
: null;
mActivity.getRootView().setForceHideBackArrow(true);
mActivityInterface.setHintUserWillBeActive();
homeAnimFactory = new HomeAnimationFactory(floatingIconView) {
@Override
public RectF getWindowTargetRect() {
if (canUseWorkspaceView) {
return iconLocation;
} else {
return super.getWindowTargetRect();
}
}
@NonNull
@Override
public AnimatorPlaybackController createActivityAnimationToHome() {
// Return an empty APC here since we have an non-user controlled animation
// to home.
long accuracy = 2 * Math.max(mDp.widthPx, mDp.heightPx);
return mActivity.getStateManager().createAnimationToNewWorkspace(
NORMAL, accuracy, 0 /* animComponents */);
}
@Override
public void playAtomicAnimation(float velocity) {
new StaggeredWorkspaceAnim(mActivity, velocity,
true /* animateOverviewScrim */).start();
}
};
} else {
homeAnimFactory = new HomeAnimationFactory(null) {
@Override
public AnimatorPlaybackController createActivityAnimationToHome() {
return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
}
};
mStateCallback.addChangeListener(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
isPresent -> mRecentsView.startHome());
}
HomeAnimationFactory homeAnimFactory = createHomeAnimationFactory(duration);
RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
windowAnim.addAnimatorListener(new AnimationSuccessListener() {
@Override
@@ -1303,14 +1237,15 @@ public class LauncherSwipeHandler extends BaseSwipeUpHandler<Launcher, RecentsVi
// If there are no targets or the animation not started, then there is nothing to finish
mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
} else {
mRecentsAnimationController.finish(true /* toRecents */,
() -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED),
true /* sendUserLeaveHint */);
finishRecentsControllerToHome(
() -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
}
ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", true);
doLogGesture(HOME);
}
protected abstract void finishRecentsControllerToHome(Runnable callback);
private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
endLauncherTransitionController();
mActivityInterface.onSwipeUpToRecentsComplete();
@@ -15,27 +15,21 @@
*/
package com.android.quickstep;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
import static com.android.quickstep.fallback.RecentsState.BACKGROUND_APP;
import static com.android.quickstep.fallback.RecentsState.DEFAULT;
import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import android.content.Context;
import android.graphics.PointF;
import android.graphics.Rect;
import android.view.MotionEvent;
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.quickstep.fallback.FallbackRecentsView;
import com.android.quickstep.fallback.RecentsState;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.views.RecentsView;
@@ -55,9 +49,10 @@ public final class FallbackActivityInterface extends
public static final FallbackActivityInterface INSTANCE = new FallbackActivityInterface();
private FallbackActivityInterface() {
super(false);
super(false, DEFAULT, BACKGROUND_APP);
}
/** 2 */
@Override
public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect,
PagedOrientationHandler orientationHandler) {
@@ -72,6 +67,13 @@ public final class FallbackActivityInterface extends
}
}
/** 4 */
@Override
public void onSwipeUpToHomeComplete() {
onSwipeUpToRecentsComplete();
}
/** 5 */
@Override
public void onAssistantVisibilityChanged(float visibility) {
// This class becomes active when the screen is locked.
@@ -79,51 +81,13 @@ public final class FallbackActivityInterface extends
// set to zero prior to this class becoming active.
}
/** 6 */
@Override
public AnimationFactory prepareRecentsUI(
boolean activityVisible, Consumer<AnimatorPlaybackController> callback) {
RecentsActivity activity = getCreatedActivity();
if (activity == null) {
return (transitionLength) -> { };
}
activity.getStateManager().goToState(BACKGROUND_APP);
FallbackRecentsView rv = activity.getOverviewPanel();
rv.setContentAlpha(0);
return new AnimationFactory() {
boolean isAnimatingToRecents = false;
@Override
public void onRemoteAnimationReceived(RemoteAnimationTargets targets) {
isAnimatingToRecents = targets != null && targets.isAnimatingHome();
if (!isAnimatingToRecents) {
rv.setContentAlpha(1);
}
createActivityInterface(getSwipeUpDestinationAndLength(
activity.getDeviceProfile(), activity, new Rect(),
rv.getPagedOrientationHandler()));
}
@Override
public void createActivityInterface(long transitionLength) {
PendingAnimation pa = new PendingAnimation(transitionLength * 2);
if (isAnimatingToRecents) {
pa.addFloat(rv, CONTENT_ALPHA, 0, 1, LINEAR);
}
pa.addFloat(rv, SCALE_PROPERTY, rv.getMaxScaleForFullScreen(), 1, LINEAR);
pa.addFloat(rv, FULLSCREEN_PROGRESS, 1, 0, LINEAR);
AnimatorPlaybackController controller = pa.createPlaybackController();
// Since we are changing the start position of the UI, reapply the state, at the end
controller.setEndAction(() -> activity.getStateManager().goToState(
controller.getInterpolatedProgress() > 0.5 ? DEFAULT : BACKGROUND_APP));
callback.accept(controller);
}
};
DefaultAnimationFactory factory = new DefaultAnimationFactory(callback);
factory.initUI();
return factory;
}
@Override
@@ -166,6 +130,15 @@ public final class FallbackActivityInterface extends
return false;
}
@Override
public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
// In non-gesture mode, user might be clicking on the home button which would directly
// start the home activity instead of going through recents. In that case, defer starting
// recents until we are sure it is a gesture.
return !deviceState.isFullyGesturalNavMode()
|| super.deferStartingActivity(deviceState, ev);
}
@Override
public int getContainerType() {
RecentsActivity activity = getCreatedActivity();
@@ -190,11 +163,6 @@ public final class FallbackActivityInterface extends
activity.<RecentsView>getOverviewPanel().startHome();
}
@Override
public void getMultiWindowSize(Context context, DeviceProfile dp, PointF out) {
out.set(dp.widthPx, dp.heightPx);
}
@Override
protected float getExtraSpace(Context context, DeviceProfile dp,
PagedOrientationHandler orientationHandler) {
@@ -15,537 +15,117 @@
*/
package com.android.quickstep;
import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
import static com.android.quickstep.RecentsActivity.EXTRA_TASK_ID;
import static com.android.quickstep.RecentsActivity.EXTRA_THUMBNAIL;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
import android.graphics.PointF;
import android.os.Bundle;
import android.util.ArrayMap;
import android.view.MotionEvent;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimationSuccessListener;
import androidx.annotation.NonNull;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.util.ObjectWrapper;
import com.android.quickstep.BaseActivityInterface.AnimationFactory;
import com.android.quickstep.GestureState.GestureEndTarget;
import com.android.launcher3.anim.PendingAnimation;
import com.android.quickstep.fallback.FallbackRecentsView;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.quickstep.util.TransformParams;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.ActivityOptionsCompat;
import com.android.systemui.shared.system.InputConsumerController;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder;
/**
* Handles the navigation gestures when a 3rd party launcher is the default home activity.
*/
public class FallbackSwipeHandler extends BaseSwipeUpHandler<RecentsActivity, FallbackRecentsView> {
public class FallbackSwipeHandler extends
BaseSwipeUpHandlerV2<RecentsActivity, FallbackRecentsView> {
private static final String[] STATE_NAMES = DEBUG_STATES ? new String[5] : null;
private static int getFlagForIndex(int index, String name) {
if (DEBUG_STATES) {
STATE_NAMES[index] = name;
}
return 1 << index;
}
private static final int STATE_RECENTS_PRESENT =
getFlagForIndex(0, "STATE_RECENTS_PRESENT");
private static final int STATE_HANDLER_INVALIDATED =
getFlagForIndex(1, "STATE_HANDLER_INVALIDATED");
private static final int STATE_GESTURE_CANCELLED =
getFlagForIndex(2, "STATE_GESTURE_CANCELLED");
private static final int STATE_GESTURE_COMPLETED =
getFlagForIndex(3, "STATE_GESTURE_COMPLETED");
private static final int STATE_APP_CONTROLLER_RECEIVED =
getFlagForIndex(4, "STATE_APP_CONTROLLER_RECEIVED");
public static class EndTargetAnimationParams {
private final float mEndProgress;
private final long mDurationMultiplier;
private final float mLauncherAlpha;
EndTargetAnimationParams(float endProgress, long durationMultiplier, float launcherAlpha) {
mEndProgress = endProgress;
mDurationMultiplier = durationMultiplier;
mLauncherAlpha = launcherAlpha;
}
}
private final ArrayMap<GestureEndTarget, EndTargetAnimationParams>
mEndTargetAnimationParams = new ArrayMap();
private final AnimatedFloat mLauncherAlpha = new AnimatedFloat(this::onLauncherAlphaChanged);
private boolean mOverviewThresholdPassed = false;
private final boolean mInQuickSwitchMode;
private final boolean mContinuingLastGesture;
private FallbackHomeAnimationFactory mActiveAnimationFactory;
private final boolean mRunningOverHome;
private final boolean mSwipeUpOverHome;
private boolean mTouchedHomeDuringTransition;
private final PointF mEndVelocityPxPerMs = new PointF(0, 0.5f);
private RunningWindowAnim mFinishAnimation;
// Used to control Recents components throughout the swipe gesture.
private AnimatorPlaybackController mLauncherTransitionController;
private boolean mHasLauncherTransitionControllerStarted;
private AnimationFactory mAnimationFactory = (t) -> { };
public FallbackSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
GestureState gestureState, InputConsumerController inputConsumer,
boolean isLikelyToStartNewTask, boolean continuingLastGesture) {
super(context, deviceState, gestureState, inputConsumer);
TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
boolean continuingLastGesture, InputConsumerController inputConsumer) {
super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
continuingLastGesture, inputConsumer);
mInQuickSwitchMode = isLikelyToStartNewTask || continuingLastGesture;
mContinuingLastGesture = continuingLastGesture;
mRunningOverHome = ActivityManagerWrapper.isHomeTask(mGestureState.getRunningTask());
mSwipeUpOverHome = mRunningOverHome && !mInQuickSwitchMode;
// Keep the home launcher invisible until we decide to land there.
mLauncherAlpha.value = mRunningOverHome ? 1 : 0;
if (mSwipeUpOverHome) {
mTransformParams.setBaseAlphaCallback((t, a) -> 1 - mLauncherAlpha.value);
} else {
mTransformParams.setBaseAlphaCallback((t, a) -> mLauncherAlpha.value);
}
// Going home has an extra long progress to ensure that it animates into the screen
mEndTargetAnimationParams.put(HOME, new EndTargetAnimationParams(3, 100, 1));
mEndTargetAnimationParams.put(RECENTS, new EndTargetAnimationParams(1, 300, 0));
mEndTargetAnimationParams.put(LAST_TASK, new EndTargetAnimationParams(0, 150, 1));
mEndTargetAnimationParams.put(NEW_TASK, new EndTargetAnimationParams(0, 150, 1));
initAfterSubclassConstructor();
initStateCallbacks();
}
private void initStateCallbacks() {
mStateCallback = new MultiStateCallback(STATE_NAMES);
mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED,
this::onHandlerInvalidated);
mStateCallback.runOnceAtState(STATE_RECENTS_PRESENT | STATE_HANDLER_INVALIDATED,
this::onHandlerInvalidatedWithRecents);
mStateCallback.runOnceAtState(STATE_GESTURE_CANCELLED | STATE_APP_CONTROLLER_RECEIVED,
this::finishAnimationTargetSetAnimationComplete);
if (mInQuickSwitchMode) {
mStateCallback.runOnceAtState(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED
| STATE_RECENTS_PRESENT,
this::finishAnimationTargetSet);
} else {
mStateCallback.runOnceAtState(STATE_GESTURE_COMPLETED | STATE_APP_CONTROLLER_RECEIVED,
this::finishAnimationTargetSet);
}
}
private void onLauncherAlphaChanged() {
if (mRecentsAnimationTargets != null && mGestureState.getEndTarget() == null) {
applyWindowTransform();
}
}
@Override
protected boolean onActivityInit(Boolean alreadyOnHome) {
super.onActivityInit(alreadyOnHome);
mActivity = mActivityInterface.getCreatedActivity();
mRecentsView = mActivity.getOverviewPanel();
mRecentsView.setOnPageTransitionEndCallback(null);
linkRecentsViewScroll();
if (!mContinuingLastGesture) {
if (mRunningOverHome) {
mRecentsView.onGestureAnimationStart(mGestureState.getRunningTask());
} else {
mRecentsView.onGestureAnimationStart(mGestureState.getRunningTaskId());
}
}
mStateCallback.setStateOnUiThread(STATE_RECENTS_PRESENT);
mDeviceState.enableMultipleRegions(false);
mAnimationFactory = mActivityInterface.prepareRecentsUI(alreadyOnHome,
this::onAnimatorPlaybackControllerCreated);
mAnimationFactory.createActivityInterface(mTransitionDragLength);
return true;
}
@Override
protected void initTransitionEndpoints(DeviceProfile dp) {
super.initTransitionEndpoints(dp);
if (canCreateNewOrUpdateExistingLauncherTransitionController()) {
mAnimationFactory.createActivityInterface(mTransitionDragLength);
}
}
private void onAnimatorPlaybackControllerCreated(AnimatorPlaybackController anim) {
mLauncherTransitionController = anim;
mLauncherTransitionController.dispatchSetInterpolator(t -> t * mDragLengthFactor);
mLauncherTransitionController.dispatchOnStart();
updateLauncherTransitionProgress();
}
private void updateLauncherTransitionProgress() {
if (mLauncherTransitionController == null
|| !canCreateNewOrUpdateExistingLauncherTransitionController()) {
return;
}
// Normalize the progress to 0 to 1, as the animation controller will clamp it to that
// anyway. The controller mimics the drag length factor by applying it to its interpolators.
float progress = mCurrentShift.value / mDragLengthFactor;
mLauncherTransitionController.setPlayFraction(progress);
}
/**
* We don't want to change mLauncherTransitionController if mGestureState.getEndTarget() == HOME
* (it has its own animation) or if we're already animating the current controller.
* @return Whether we can create the launcher controller or update its progress.
*/
private boolean canCreateNewOrUpdateExistingLauncherTransitionController() {
return mGestureState.getEndTarget() != HOME && !mHasLauncherTransitionControllerStarted;
}
@Override
protected boolean moveWindowWithRecentsScroll() {
return mInQuickSwitchMode;
}
@Override
public void initWhenReady(Intent intent) {
if (mInQuickSwitchMode) {
// Only init if we are in quickswitch mode
super.initWhenReady(intent);
}
}
@Override
public void updateDisplacement(float displacement) {
if (!mInQuickSwitchMode) {
super.updateDisplacement(displacement);
}
}
@Override
protected InputConsumer createNewInputProxyHandler() {
// Just consume all input on the active task
return new InputConsumer() {
@Override
public int getType() {
return InputConsumer.TYPE_NO_OP;
}
@Override
public void onMotionEvent(MotionEvent ev) {
mTouchedHomeDuringTransition = true;
}
};
}
@Override
public void onMotionPauseChanged(boolean isPaused) {
if (!mInQuickSwitchMode && mDeviceState.isFullyGesturalNavMode()) {
updateOverviewThresholdPassed(isPaused);
}
}
private void updateOverviewThresholdPassed(boolean passed) {
if (passed != mOverviewThresholdPassed) {
mOverviewThresholdPassed = passed;
if (mSwipeUpOverHome) {
mLauncherAlpha.animateToValue(mLauncherAlpha.value, passed ? 0 : 1)
.setDuration(150).start();
}
performHapticFeedback();
}
}
@Override
public Intent getLaunchIntent() {
if (mInQuickSwitchMode || mSwipeUpOverHome || !mDeviceState.isFullyGesturalNavMode()) {
return mGestureState.getOverviewIntent();
} else {
return mGestureState.getHomeIntent();
}
}
@Override
public void updateFinalShift() {
mTransformParams.setProgress(mCurrentShift.value);
if (mRecentsAnimationController != null) {
boolean swipeUpThresholdPassed = mCurrentShift.value > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
mRecentsAnimationController.setUseLauncherSystemBarFlags(mInQuickSwitchMode
|| swipeUpThresholdPassed);
mRecentsAnimationController.setSplitScreenMinimized(!mInQuickSwitchMode
&& swipeUpThresholdPassed);
}
if (!mInQuickSwitchMode && !mDeviceState.isFullyGesturalNavMode()) {
updateOverviewThresholdPassed(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW);
}
applyWindowTransform();
updateLauncherTransitionProgress();
}
@Override
public void onGestureCancelled() {
updateDisplacement(0);
mGestureState.setEndTarget(LAST_TASK);
mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED);
}
@Override
public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
mEndVelocityPxPerMs.set(0, velocity.y / 1000);
if (mInQuickSwitchMode) {
// For now set it to non-null, it will be reset before starting the animation
mGestureState.setEndTarget(LAST_TASK);
} else {
float flingThreshold = mContext.getResources()
.getDimension(R.dimen.quickstep_fling_threshold_velocity);
boolean isFling = Math.abs(endVelocity) > flingThreshold;
if (mDeviceState.isFullyGesturalNavMode()) {
if (isFling) {
mGestureState.setEndTarget(endVelocity < 0 ? HOME : LAST_TASK);
} else if (mOverviewThresholdPassed) {
mGestureState.setEndTarget(RECENTS);
} else {
mGestureState.setEndTarget(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW
? HOME
: LAST_TASK);
}
} else {
GestureEndTarget startState = mSwipeUpOverHome ? HOME : LAST_TASK;
if (isFling) {
mGestureState.setEndTarget(endVelocity < 0 ? RECENTS : startState);
} else {
mGestureState.setEndTarget(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW
? RECENTS
: startState);
}
}
}
mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
}
@Override
public void onConsumerAboutToBeSwitched() {
if (mInQuickSwitchMode && mGestureState.getEndTarget() != null) {
mGestureState.setEndTarget(NEW_TASK);
mCanceled = true;
mCurrentShift.cancelAnimation();
if (mFinishAnimation != null) {
mFinishAnimation.cancel();
}
if (mRecentsView != null) {
mRecentsView.setOnScrollChangeListener(null);
}
} else {
mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
}
}
private void onHandlerInvalidated() {
mActivityInitListener.unregister();
if (mGestureEndCallback != null) {
mGestureEndCallback.run();
}
if (mFinishAnimation != null) {
mFinishAnimation.end();
}
}
private void onHandlerInvalidatedWithRecents() {
mRecentsView.onGestureAnimationEnd();
mRecentsView.setDisallowScrollToClearAll(false);
mRecentsView.getClearAllButton().setVisibilityAlpha(1);
}
private void finishAnimationTargetSetAnimationComplete() {
switch (mGestureState.getEndTarget()) {
case HOME: {
if (mSwipeUpOverHome) {
mRecentsAnimationController.finish(false, null, false);
// Send a home intent to clear the task stack
mContext.startActivity(mGestureState.getHomeIntent());
} else {
// Notify swipe-to-home (recents animation) is finished
SystemUiProxy.INSTANCE.get(mContext).notifySwipeToHomeFinished();
mRecentsAnimationController.finish(true, () -> {
if (!mTouchedHomeDuringTransition) {
// If the user hasn't interacted with the screen during the transition,
// send a home intent so launcher can go to the default home screen.
// (If they are trying to touch something, we don't want to interfere.)
mContext.startActivity(mGestureState.getHomeIntent());
}
}, true);
}
break;
}
case LAST_TASK:
mRecentsAnimationController.finish(false, null, false);
break;
case RECENTS: {
if (mSwipeUpOverHome || !mDeviceState.isFullyGesturalNavMode()) {
mRecentsAnimationController.finish(true, null, true);
break;
}
final int runningTaskId = mGestureState.getRunningTaskId();
ThumbnailData thumbnail = mRecentsAnimationController.screenshotTask(runningTaskId);
mRecentsAnimationController.setDeferCancelUntilNextTransition(true /* defer */,
false /* screenshot */);
ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
ActivityOptionsCompat.setFreezeRecentTasksList(options);
Bundle extras = new Bundle();
extras.putBinder(EXTRA_THUMBNAIL, new ObjectWrapper<>(thumbnail));
extras.putInt(EXTRA_TASK_ID, runningTaskId);
Intent intent = new Intent(mGestureState.getOverviewIntent())
.putExtras(extras);
mContext.startActivity(intent, options.toBundle());
mRecentsAnimationController.cleanupScreenshot();
break;
}
case NEW_TASK: {
startNewTask(success -> { });
break;
}
}
mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
}
private void finishAnimationTargetSet() {
if (mInQuickSwitchMode) {
// Recalculate the end target, some views might have been initialized after
// gesture has ended.
if (mRecentsView == null || !hasTargets()) {
mGestureState.setEndTarget(LAST_TASK);
} else {
final int runningTaskIndex = getLastAppearedTaskIndex();
final int taskToLaunch = mRecentsView.getNextPage();
mGestureState.setEndTarget(
(runningTaskIndex >= 0 && taskToLaunch != runningTaskIndex)
? NEW_TASK
: LAST_TASK);
}
}
EndTargetAnimationParams params = mEndTargetAnimationParams.get(mGestureState.getEndTarget());
float endProgress = params.mEndProgress;
long duration = (long) (params.mDurationMultiplier *
Math.abs(endProgress - mCurrentShift.value));
if (mRecentsView != null) {
duration = Math.max(duration, mRecentsView.getScroller().getDuration());
}
if (mCurrentShift.value != endProgress || mInQuickSwitchMode) {
AnimationSuccessListener endListener = new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
if (mRecentsView != null) {
mRecentsView.setOnPageTransitionEndCallback(FallbackSwipeHandler.this
::finishAnimationTargetSetAnimationComplete);
} else {
finishAnimationTargetSetAnimationComplete();
}
mFinishAnimation = null;
}
};
if (mGestureState.getEndTarget() == HOME && !mRunningOverHome) {
mRecentsAnimationController.enableInputProxy(mInputConsumer,
this::createNewInputProxyHandler);
RectFSpringAnim anim = createWindowAnimationToHome(mCurrentShift.value, duration);
anim.addAnimatorListener(endListener);
anim.start(mContext, mEndVelocityPxPerMs);
mFinishAnimation = RunningWindowAnim.wrap(anim);
} else {
AnimatorSet anim = new AnimatorSet();
anim.play(mLauncherAlpha.animateToValue(
mLauncherAlpha.value, params.mLauncherAlpha));
anim.play(mCurrentShift.animateToValue(mCurrentShift.value, endProgress));
anim.setDuration(duration);
anim.addListener(endListener);
anim.start();
mFinishAnimation = RunningWindowAnim.wrap(anim);
}
} else {
finishAnimationTargetSetAnimationComplete();
}
}
@Override
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
super.onRecentsAnimationStart(controller, targets);
mRecentsAnimationController.enableInputConsumer();
applyWindowTransform();
mStateCallback.setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED);
}
@Override
public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
mStateCallback.setStateOnUiThread(STATE_HANDLER_INVALIDATED);
// Defer clearing the controller and the targets until after we've updated the state
super.onRecentsAnimationCanceled(thumbnailData);
protected HomeAnimationFactory createHomeAnimationFactory(long duration) {
mActiveAnimationFactory = new FallbackHomeAnimationFactory(duration);
ActivityOptions options = ActivityOptions.makeCustomAnimation(mContext, 0, 0);
mContext.startActivity(new Intent(mGestureState.getHomeIntent()), options.toBundle());
return mActiveAnimationFactory;
}
@Override
protected boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
return true;
}
if (mActiveAnimationFactory != null
&& mActiveAnimationFactory.handleHomeTaskAppeared(appearedTaskTarget)) {
mActiveAnimationFactory = null;
return false;
}
/**
* Creates an animation that transforms the current app window into the home app.
* @param startProgress The progress of {@link #mCurrentShift} to start the window from.
*/
private RectFSpringAnim createWindowAnimationToHome(float startProgress, long duration) {
HomeAnimationFactory factory = new HomeAnimationFactory(null) {
@Override
public AnimatorPlaybackController createActivityAnimationToHome() {
AnimatorSet anim = new AnimatorSet();
Animator fadeInLauncher = mLauncherAlpha.animateToValue(mLauncherAlpha.value, 1);
fadeInLauncher.setInterpolator(ACCEL_2);
anim.play(fadeInLauncher);
anim.setDuration(duration);
return AnimatorPlaybackController.wrap(anim, duration);
}
};
return createWindowAnimationToHome(startProgress, factory);
return super.handleTaskAppeared(appearedTaskTarget);
}
@Override
protected float getWindowAlpha(float progress) {
return 1 - ACCEL_1_5.getInterpolation(progress);
protected void finishRecentsControllerToHome(Runnable callback) {
mRecentsAnimationController.finish(
false /* toRecents */, callback, true /* sendUserLeaveHint */);
}
@Override
protected void notifyGestureAnimationStartToRecents() {
if (mRunningOverHome) {
mRecentsView.onGestureAnimationStartOnHome(mGestureState.getRunningTask());
} else {
super.notifyGestureAnimationStartToRecents();
}
}
private class FallbackHomeAnimationFactory extends HomeAnimationFactory
implements TransformParams.BuilderProxy {
private final TransformParams mHomeAlphaParams = new TransformParams();
private final AnimatedFloat mHomeAlpha = new AnimatedFloat(this::updateHomeAlpha);
private final long mDuration;
FallbackHomeAnimationFactory(long duration) {
super(null);
mDuration = duration;
}
@NonNull
@Override
public AnimatorPlaybackController createActivityAnimationToHome() {
PendingAnimation pa = new PendingAnimation(mDuration);
pa.setFloat(mHomeAlpha, AnimatedFloat.VALUE, 1, LINEAR);
return pa.createPlaybackController();
}
private void updateHomeAlpha() {
mHomeAlphaParams.setProgress(mHomeAlpha.value);
if (mHomeAlphaParams.getTargetSet() != null) {
mHomeAlphaParams.applySurfaceParams(mHomeAlphaParams.createSurfaceParams(this));
}
}
public boolean handleHomeTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
if (appearedTaskTarget.activityType == ACTIVITY_TYPE_HOME) {
RemoteAnimationTargets targets = new RemoteAnimationTargets(
new RemoteAnimationTargetCompat[] {appearedTaskTarget},
new RemoteAnimationTargetCompat[0], appearedTaskTarget.mode);
mHomeAlphaParams.setTargetSet(targets);
updateHomeAlpha();
return true;
}
return false;
}
@Override
public void onBuildParams(Builder builder, RemoteAnimationTargetCompat app, int targetMode,
TransformParams params) { }
}
}
@@ -15,29 +15,18 @@
*/
package com.android.quickstep;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM;
import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM;
import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_SHELF_ANIM;
import static com.android.quickstep.LauncherSwipeHandler.RECENTS_ATTACH_DURATION;
import static com.android.quickstep.SysUINavigationMode.getMode;
import static com.android.quickstep.SysUINavigationMode.hideShelfInTwoButtonLandscape;
import static com.android.quickstep.util.LayoutUtils.getDefaultSwipeHeight;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import android.animation.Animator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.Log;
import android.view.MotionEvent;
import android.view.animation.Interpolator;
import androidx.annotation.Nullable;
@@ -61,9 +50,7 @@ import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.quickstep.SysUINavigationMode.Mode;
import com.android.quickstep.util.ActivityInitListener;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.ShelfPeekAnim;
import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
import com.android.quickstep.views.LauncherRecentsView;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.plugins.shared.LauncherOverlayManager;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -75,12 +62,12 @@ import java.util.function.Predicate;
* {@link BaseActivityInterface} for the in-launcher recents.
*/
public final class LauncherActivityInterface extends
BaseActivityInterface<LauncherState, Launcher> {
BaseActivityInterface<LauncherState, BaseQuickstepLauncher> {
public static final LauncherActivityInterface INSTANCE = new LauncherActivityInterface();
private LauncherActivityInterface() {
super(true);
super(true, OVERVIEW, BACKGROUND_APP);
}
@Override
@@ -131,119 +118,43 @@ public final class LauncherActivityInterface extends
@Override
public AnimationFactory prepareRecentsUI(
boolean activityVisible, Consumer<AnimatorPlaybackController> callback) {
BaseQuickstepLauncher launcher = getCreatedActivity();
final LauncherState startState = launcher.getStateManager().getState();
LauncherState resetState = startState;
if (startState.shouldDisableRestore()) {
resetState = launcher.getStateManager().getRestState();
}
launcher.getStateManager().setRestState(resetState);
launcher.getStateManager().goToState(BACKGROUND_APP, false);
// Since all apps is not visible, we can safely reset the scroll position.
// This ensures then the next swipe up to all-apps starts from scroll 0.
launcher.getAppsView().reset(false /* animate */);
return new AnimationFactory() {
private final ShelfPeekAnim mShelfAnim = launcher.getShelfPeekAnim();
private boolean mIsAttachedToWindow;
@Override
public void createActivityInterface(long transitionLength) {
callback.accept(createBackgroundToOverviewAnim(launcher, transitionLength));
// Creating the activity controller animation sometimes reapplies the launcher state
// (because we set the animation as the current state animation), so we reapply the
// attached state here as well to ensure recents is shown/hidden appropriately.
if (SysUINavigationMode.getMode(launcher) == Mode.NO_BUTTON) {
setRecentsAttachedToAppWindow(mIsAttachedToWindow, false);
}
}
@Override
public void onTransitionCancelled() {
launcher.getStateManager().goToState(startState, false /* animate */);
}
DefaultAnimationFactory factory = new DefaultAnimationFactory(callback) {
@Override
public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator,
long duration) {
mShelfAnim.setShelfState(shelfState, interpolator, duration);
mActivity.getShelfPeekAnim().setShelfState(shelfState, interpolator, duration);
}
@Override
public void setRecentsAttachedToAppWindow(boolean attached, boolean animate) {
if (mIsAttachedToWindow == attached && animate) {
return;
}
mIsAttachedToWindow = attached;
LauncherRecentsView recentsView = launcher.getOverviewPanel();
Animator fadeAnim = launcher.getStateManager()
.createStateElementAnimation(
INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0);
protected void createBackgroundToOverviewAnim(BaseQuickstepLauncher activity,
PendingAnimation pa) {
super.createBackgroundToOverviewAnim(activity, pa);
float fromTranslation = attached ? 1 : 0;
float toTranslation = attached ? 0 : 1;
launcher.getStateManager()
.cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM);
if (!recentsView.isShown() && animate) {
ADJACENT_PAGE_OFFSET.set(recentsView, fromTranslation);
} else {
fromTranslation = ADJACENT_PAGE_OFFSET.get(recentsView);
}
if (!animate) {
ADJACENT_PAGE_OFFSET.set(recentsView, toTranslation);
} else {
launcher.getStateManager().createStateElementAnimation(
INDEX_RECENTS_TRANSLATE_X_ANIM,
fromTranslation, toTranslation).start();
if (!activity.getDeviceProfile().isVerticalBarLayout()
&& SysUINavigationMode.getMode(activity) != Mode.NO_BUTTON) {
// Don't animate the shelf when the mode is NO_BUTTON, because we
// update it atomically.
pa.add(activity.getStateManager().createStateElementAnimation(
INDEX_SHELF_ANIM,
BACKGROUND_APP.getVerticalProgress(activity),
OVERVIEW.getVerticalProgress(activity)));
}
fadeAnim.setInterpolator(attached ? INSTANT : ACCEL_2);
fadeAnim.setDuration(animate ? RECENTS_ATTACH_DURATION : 0).start();
// Animate the blur and wallpaper zoom
float fromDepthRatio = BACKGROUND_APP.getDepth(activity);
float toDepthRatio = OVERVIEW.getDepth(activity);
pa.addFloat(getDepthController(),
new ClampedDepthProperty(fromDepthRatio, toDepthRatio),
fromDepthRatio, toDepthRatio, LINEAR);
}
};
}
private AnimatorPlaybackController createBackgroundToOverviewAnim(
Launcher activity, long transitionLength) {
PendingAnimation pa = new PendingAnimation(transitionLength * 2);
if (!activity.getDeviceProfile().isVerticalBarLayout()
&& SysUINavigationMode.getMode(activity) != Mode.NO_BUTTON) {
// Don't animate the shelf when the mode is NO_BUTTON, because we update it atomically.
pa.add(activity.getStateManager().createStateElementAnimation(
INDEX_SHELF_ANIM,
BACKGROUND_APP.getVerticalProgress(activity),
OVERVIEW.getVerticalProgress(activity)));
}
// Animate the blur and wallpaper zoom
float fromDepthRatio = BACKGROUND_APP.getDepth(activity);
float toDepthRatio = OVERVIEW.getDepth(activity);
pa.addFloat(getDepthController(), new ClampedDepthProperty(fromDepthRatio, toDepthRatio),
fromDepthRatio, toDepthRatio, LINEAR);
// Scale down recents from being full screen to being in overview.
RecentsView recentsView = activity.getOverviewPanel();
pa.addFloat(recentsView, SCALE_PROPERTY,
BACKGROUND_APP.getOverviewScaleAndOffset(activity)[0],
OVERVIEW.getOverviewScaleAndOffset(activity)[0],
LINEAR);
pa.addFloat(recentsView, FULLSCREEN_PROGRESS,
BACKGROUND_APP.getOverviewFullscreenProgress(),
OVERVIEW.getOverviewFullscreenProgress(),
LINEAR);
AnimatorPlaybackController controller = pa.createPlaybackController();
activity.getStateManager().setCurrentUserControlledAnimation(controller);
// Since we are changing the start position of the UI, reapply the state, at the end
controller.setEndAction(() -> activity.getStateManager().goToState(
controller.getInterpolatedProgress() > 0.5 ? OVERVIEW : BACKGROUND_APP, false));
return controller;
BaseQuickstepLauncher launcher = factory.initUI();
// Since all apps is not visible, we can safely reset the scroll position.
// This ensures then the next swipe up to all-apps starts from scroll 0.
launcher.getAppsView().reset(false /* animate */);
return factory;
}
@Override
@@ -252,6 +163,15 @@ public final class LauncherActivityInterface extends
onInitListener.test(alreadyOnHome));
}
@Override
public void setOnDeferredActivityLaunchCallback(Runnable r) {
Launcher launcher = getCreatedActivity();
if (launcher == null) {
return;
}
launcher.setOnDeferredActivityLaunchCallback(r);
}
@Nullable
@Override
public BaseQuickstepLauncher getCreatedActivity() {
@@ -259,11 +179,13 @@ public final class LauncherActivityInterface extends
}
@Nullable
@UiThread
private Launcher getVisibleLauncher() {
Launcher launcher = getCreatedActivity();
return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus() ?
launcher : null;
@Override
public DepthController getDepthController() {
BaseQuickstepLauncher launcher = getCreatedActivity();
if (launcher == null) {
return null;
}
return launcher.getDepthController();
}
@Nullable
@@ -274,6 +196,14 @@ public final class LauncherActivityInterface extends
? launcher.getOverviewPanel() : null;
}
@Nullable
@UiThread
private Launcher getVisibleLauncher() {
Launcher launcher = getCreatedActivity();
return (launcher != null) && launcher.isStarted() && launcher.hasWindowFocus()
? launcher : null;
}
@Override
public boolean switchToRecentsIfVisible(Runnable onCompleteCallback) {
if (TestProtocol.sDebugTracing) {
@@ -293,15 +223,6 @@ public final class LauncherActivityInterface extends
return true;
}
@Override
public void setHintUserWillBeActive() {
getCreatedActivity().setHintUserWillBeActive();
}
@Override
public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
return deviceState.isInDeferredGestureRegion(ev);
}
@Override
public Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target) {
@@ -313,6 +234,16 @@ public final class LauncherActivityInterface extends
return true;
}
@Override
public void updateOverviewPredictionState() {
Launcher launcher = getCreatedActivity();
if (launcher == null) {
return;
}
PredictionUiStateManager.INSTANCE.get(launcher).switchClient(
PredictionUiStateManager.Client.OVERVIEW);
}
@Override
public int getContainerType() {
final Launcher launcher = getVisibleLauncher();
@@ -350,51 +281,6 @@ public final class LauncherActivityInterface extends
}
}
@Override
public void setOnDeferredActivityLaunchCallback(Runnable r) {
Launcher launcher = getCreatedActivity();
if (launcher == null) {
return;
}
launcher.setOnDeferredActivityLaunchCallback(r);
}
@Override
public void updateOverviewPredictionState() {
Launcher launcher = getCreatedActivity();
if (launcher == null) {
return;
}
PredictionUiStateManager.INSTANCE.get(launcher).switchClient(
PredictionUiStateManager.Client.OVERVIEW);
}
@Nullable
@Override
public DepthController getDepthController() {
BaseQuickstepLauncher launcher = getCreatedActivity();
if (launcher == null) {
return null;
}
return launcher.getDepthController();
}
@Override
public void getMultiWindowSize(Context context, DeviceProfile dp, PointF out) {
DeviceProfile fullDp = dp.getFullScreenProfile();
// Use availableWidthPx and availableHeightPx instead of widthPx and heightPx to
// account for system insets
out.set(fullDp.availableWidthPx, fullDp.availableHeightPx);
float halfDividerSize = context.getResources()
.getDimension(R.dimen.multi_window_task_divider_size) / 2;
if (fullDp.isLandscape) {
out.x = out.x / 2 - halfDividerSize;
} else {
out.y = out.y / 2 - halfDividerSize;
}
}
@Override
protected float getExtraSpace(Context context, DeviceProfile dp,
PagedOrientationHandler orientationHandler) {
@@ -426,4 +312,5 @@ public final class LauncherActivityInterface extends
}
}
}
}
@@ -0,0 +1,121 @@
/*
* Copyright (C) 2020 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.LauncherState.NORMAL;
import android.animation.AnimatorSet;
import android.content.Context;
import android.graphics.RectF;
import android.os.UserHandle;
import android.view.View;
import androidx.annotation.NonNull;
import com.android.launcher3.BaseQuickstepLauncher;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.views.FloatingIconView;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.InputConsumerController;
/**
* Temporary class to allow easier refactoring
*/
public class LauncherSwipeHandlerV2 extends
BaseSwipeUpHandlerV2<BaseQuickstepLauncher, RecentsView> {
public LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
boolean continuingLastGesture, InputConsumerController inputConsumer) {
super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
continuingLastGesture, inputConsumer);
}
@Override
protected HomeAnimationFactory createHomeAnimationFactory(long duration) {
HomeAnimationFactory homeAnimFactory;
if (mActivity != null) {
final TaskView runningTaskView = mRecentsView.getRunningTaskView();
final View workspaceView;
if (runningTaskView != null
&& runningTaskView.getTask().key.getComponent() != null) {
workspaceView = mActivity.getWorkspace().getFirstMatchForAppClose(
runningTaskView.getTask().key.getComponent().getPackageName(),
UserHandle.of(runningTaskView.getTask().key.userId));
} else {
workspaceView = null;
}
final RectF iconLocation = new RectF();
boolean canUseWorkspaceView =
workspaceView != null && workspaceView.isAttachedToWindow();
FloatingIconView floatingIconView = canUseWorkspaceView
? FloatingIconView.getFloatingIconView(mActivity, workspaceView,
true /* hideOriginal */, iconLocation, false /* isOpening */)
: null;
mActivity.getRootView().setForceHideBackArrow(true);
mActivity.setHintUserWillBeActive();
homeAnimFactory = new HomeAnimationFactory(floatingIconView) {
@Override
public RectF getWindowTargetRect() {
if (canUseWorkspaceView) {
return iconLocation;
} else {
return super.getWindowTargetRect();
}
}
@NonNull
@Override
public AnimatorPlaybackController createActivityAnimationToHome() {
// Return an empty APC here since we have an non-user controlled animation
// to home.
long accuracy = 2 * Math.max(mDp.widthPx, mDp.heightPx);
return mActivity.getStateManager().createAnimationToNewWorkspace(
NORMAL, accuracy, 0 /* animComponents */);
}
@Override
public void playAtomicAnimation(float velocity) {
new StaggeredWorkspaceAnim(mActivity, velocity,
true /* animateOverviewScrim */).start();
}
};
} else {
homeAnimFactory = new HomeAnimationFactory(null) {
@Override
public AnimatorPlaybackController createActivityAnimationToHome() {
return AnimatorPlaybackController.wrap(new AnimatorSet(), duration);
}
};
mStateCallback.addChangeListener(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
isPresent -> mRecentsView.startHome());
}
return homeAnimFactory;
}
@Override
protected void finishRecentsControllerToHome(Runnable callback) {
mRecentsAnimationController.finish(
true /* toRecents */, callback, true /* sendUserLeaveHint */);
}
}
@@ -46,6 +46,7 @@ import com.android.launcher3.R;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.launcher3.util.ActivityTracker;
@@ -57,6 +58,7 @@ import com.android.quickstep.fallback.FallbackRecentsStateController;
import com.android.quickstep.fallback.FallbackRecentsView;
import com.android.quickstep.fallback.RecentsRootView;
import com.android.quickstep.fallback.RecentsState;
import com.android.quickstep.util.RecentsAtomicAnimationFactory;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -345,6 +347,11 @@ public final class RecentsActivity extends StatefulActivity<RecentsState> {
dumpMisc(prefix + "\t", writer);
}
@Override
public AtomicAnimationFactory<RecentsState> createAtomicAnimationFactory() {
return new RecentsAtomicAnimationFactory<>(this, 0);
}
private AnimatorListenerAdapter resetStateListener() {
return new AnimatorListenerAdapter() {
@Override
@@ -85,7 +85,6 @@ import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.AssistantUtilities;
import com.android.quickstep.util.ProtoTracer;
import com.android.quickstep.util.SplitScreenBounds;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.plugins.OverscrollPlugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.shared.recents.IOverviewProxy;
@@ -833,16 +832,16 @@ public class TouchInteractionService extends Service implements PluginListener<O
}
}
private BaseSwipeUpHandler createLauncherSwipeHandler(GestureState gestureState,
long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
return new LauncherSwipeHandler(this, mDeviceState, mTaskAnimationManager,
private BaseSwipeUpHandler createLauncherSwipeHandler(
GestureState gestureState, long touchTimeMs, boolean continuingLastGesture) {
return new LauncherSwipeHandlerV2(this, mDeviceState, mTaskAnimationManager,
gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
}
private BaseSwipeUpHandler createFallbackSwipeHandler(GestureState gestureState,
long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
return new FallbackSwipeHandler(this, mDeviceState, gestureState,
mInputConsumer, isLikelyToStartNewTask, continuingLastGesture);
private BaseSwipeUpHandler createFallbackSwipeHandler(
GestureState gestureState, long touchTimeMs, boolean continuingLastGesture) {
return new FallbackSwipeHandler(this, mDeviceState, mTaskAnimationManager,
gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
}
protected boolean shouldNotifyBackGesture() {
@@ -24,11 +24,13 @@ import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.statemanager.StateManager.StateListener;
import com.android.quickstep.FallbackActivityInterface;
import com.android.quickstep.RecentsActivity;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
@@ -38,7 +40,7 @@ import java.util.ArrayList;
public class FallbackRecentsView extends RecentsView<RecentsActivity>
implements StateListener<RecentsState> {
private RunningTaskInfo mRunningTaskInfo;
private RunningTaskInfo mHomeTaskInfo;
public FallbackRecentsView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -67,16 +69,40 @@ public class FallbackRecentsView extends RecentsView<RecentsActivity>
return false;
}
public void onGestureAnimationStart(RunningTaskInfo runningTaskInfo) {
mRunningTaskInfo = runningTaskInfo;
onGestureAnimationStart(runningTaskInfo == null ? -1 : runningTaskInfo.taskId);
/**
* When starting gesture interaction from home, we add a temporary invisible tile corresponding
* to the home task. This allows us to handle quick-switch similarly to a quick-switching
* from a foreground task.
*/
public void onGestureAnimationStartOnHome(RunningTaskInfo homeTaskInfo) {
mHomeTaskInfo = homeTaskInfo;
onGestureAnimationStart(homeTaskInfo == null ? -1 : homeTaskInfo.taskId);
}
/**
* When the gesture ends and recents view become interactive, we also remove the temporary
* invisible tile added for the home task. This also pushes the remaining tiles back
* to the center.
*/
@Override
public void onGestureAnimationEnd() {
super.onGestureAnimationEnd();
if (mHomeTaskInfo != null) {
TaskView tv = getTaskView(mHomeTaskInfo.taskId);
if (tv != null) {
PendingAnimation pa = createTaskDismissAnimation(tv, true, false, 150);
pa.addEndListener(e -> setCurrentTask(-1));
runDismissAnimation(pa);
}
}
}
@Override
public void setCurrentTask(int runningTaskId) {
super.setCurrentTask(runningTaskId);
if (mRunningTaskInfo != null && mRunningTaskInfo.taskId != runningTaskId) {
mRunningTaskInfo = null;
if (mHomeTaskInfo != null && mHomeTaskInfo.taskId != runningTaskId) {
mHomeTaskInfo = null;
setRunningTaskHidden(false);
}
}
@@ -85,7 +111,7 @@ public class FallbackRecentsView extends RecentsView<RecentsActivity>
// When quick-switching on 3p-launcher, we add a "dummy" tile corresponding to Launcher
// as well. This tile is never shown as we have setCurrentTaskHidden, but allows use to
// track the index of the next task appropriately, as if we are switching on any other app.
if (mRunningTaskInfo != null && mRunningTaskInfo.taskId == mRunningTaskId) {
if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == mRunningTaskId) {
// Check if the task list has running task
boolean found = false;
for (Task t : tasks) {
@@ -97,13 +123,22 @@ public class FallbackRecentsView extends RecentsView<RecentsActivity>
if (!found) {
ArrayList<Task> newList = new ArrayList<>(tasks.size() + 1);
newList.addAll(tasks);
newList.add(Task.from(new TaskKey(mRunningTaskInfo), mRunningTaskInfo, false));
newList.add(Task.from(new TaskKey(mHomeTaskInfo), mHomeTaskInfo, false));
tasks = newList;
}
}
super.applyLoadPlan(tasks);
}
@Override
public void setRunningTaskHidden(boolean isHidden) {
if (mHomeTaskInfo != null) {
// Always keep the home task hidden
isHidden = true;
}
super.setRunningTaskHidden(isHidden);
}
@Override
public void setModalStateEnabled(boolean isModalState) {
super.setModalStateEnabled(isModalState);
@@ -21,7 +21,7 @@ import static android.view.MotionEvent.ACTION_UP;
import static com.android.launcher3.Utilities.squaredHypot;
import static com.android.launcher3.Utilities.squaredTouchSlop;
import static com.android.quickstep.LauncherSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
import static com.android.quickstep.BaseSwipeUpHandlerV2.MIN_PROGRESS_FOR_OVERVIEW;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
@@ -207,7 +207,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
// Start the window animation on down to give more time for launcher to draw if the
// user didn't start the gesture over the back button
if (!mIsDeferredDownTarget) {
startTouchTrackingForWindowAnimation(ev.getEventTime(), false);
startTouchTrackingForWindowAnimation(ev.getEventTime());
}
TraceHelper.INSTANCE.endSection(traceToken);
@@ -275,8 +275,7 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
if (mIsDeferredDownTarget) {
// Deferred gesture, start the animation and gesture tracking once
// we pass the actual touch slop
startTouchTrackingForWindowAnimation(
ev.getEventTime(), isLikelyToStartNewTask);
startTouchTrackingForWindowAnimation(ev.getEventTime());
}
if (!mPassedWindowMoveSlop) {
mPassedWindowMoveSlop = true;
@@ -326,12 +325,11 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC
mInteractionHandler.onGestureStarted();
}
private void startTouchTrackingForWindowAnimation(
long touchTimeMs, boolean isLikelyToStartNewTask) {
private void startTouchTrackingForWindowAnimation(long touchTimeMs) {
ActiveGestureLog.INSTANCE.addLog("startRecentsAnimation");
mInteractionHandler = mHandlerFactory.newHandler(mGestureState, touchTimeMs,
mTaskAnimationManager.isRecentsAnimationRunning(), isLikelyToStartNewTask);
mTaskAnimationManager.isRecentsAnimationRunning());
mInteractionHandler.setGestureEndCallback(this::onInteractionGestureFinished);
mMotionPauseDetector.setOnMotionPauseListener(mInteractionHandler::onMotionPauseChanged);
Intent intent = new Intent(mInteractionHandler.getLaunchIntent());
@@ -0,0 +1,67 @@
/*
* Copyright (C) 2020 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.util;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
import android.animation.Animator;
import android.animation.ObjectAnimator;
import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
import com.android.launcher3.statemanager.StatefulActivity;
import com.android.quickstep.views.RecentsView;
public class RecentsAtomicAnimationFactory<ACTIVITY_TYPE extends StatefulActivity, STATE_TYPE>
extends AtomicAnimationFactory<STATE_TYPE> {
public static final int INDEX_RECENTS_FADE_ANIM = AtomicAnimationFactory.NEXT_INDEX + 0;
public static final int INDEX_RECENTS_TRANSLATE_X_ANIM = AtomicAnimationFactory.NEXT_INDEX + 1;
private static final int MY_ANIM_COUNT = 2;
protected static final int NEXT_INDEX = AtomicAnimationFactory.NEXT_INDEX + MY_ANIM_COUNT;
protected final ACTIVITY_TYPE mActivity;
/**
* @param extraAnims number of animations supported by the subclass. This should not include
* the 2 animations supported by this class.
*/
public RecentsAtomicAnimationFactory(ACTIVITY_TYPE activity, int extraAnims) {
super(MY_ANIM_COUNT + extraAnims);
mActivity = activity;
}
@Override
public Animator createStateElementAnimation(int index, float... values) {
switch (index) {
case INDEX_RECENTS_FADE_ANIM:
return ObjectAnimator.ofFloat(mActivity.getOverviewPanel(),
RecentsView.CONTENT_ALPHA, values);
case INDEX_RECENTS_TRANSLATE_X_ANIM: {
RecentsView rv = mActivity.getOverviewPanel();
return new SpringAnimationBuilder(mActivity)
.setMinimumVisibleChange(1f / rv.getPageOffsetScale())
.setDampingRatio(0.8f)
.setStiffness(250)
.setValues(values)
.build(rv, ADJACENT_PAGE_OFFSET);
}
default:
return super.createStateElementAnimation(index, values);
}
}
}
@@ -47,24 +47,21 @@ public class ClearAllButton extends Button implements PageCallbacks {
private boolean mIsRtl;
private int mScrollOffset;
private RecentsView mParent;
public ClearAllButton(Context context, AttributeSet attrs) {
super(context, attrs);
mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
PagedOrientationHandler orientationHandler = mParent.getPagedOrientationHandler();
mScrollOffset = orientationHandler.getClearAllScrollOffset(mParent, mIsRtl);
PagedOrientationHandler orientationHandler = getRecentsView().getPagedOrientationHandler();
mScrollOffset = orientationHandler.getClearAllScrollOffset(getRecentsView(), mIsRtl);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mParent = (RecentsView) getParent();
mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
private RecentsView getRecentsView() {
return (RecentsView) getParent();
}
@Override
@@ -94,7 +91,7 @@ public class ClearAllButton extends Button implements PageCallbacks {
@Override
public void onPageScroll(ScrollState scrollState) {
PagedOrientationHandler orientationHandler = mParent.getPagedOrientationHandler();
PagedOrientationHandler orientationHandler = getRecentsView().getPagedOrientationHandler();
float orientationSize = orientationHandler.getPrimaryValue(getWidth(), getHeight());
if (orientationSize == 0) {
return;
@@ -17,6 +17,8 @@
package com.android.quickstep.views;
import static android.view.Surface.ROTATION_0;
import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.makeMeasureSpec;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
@@ -1080,9 +1082,9 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
* Called when a gesture from an app has finished.
*/
public void onGestureAnimationEnd() {
setOnScrollChangeListener(null);
setEnableFreeScroll(true);
setEnableDrawingLiveTile(true);
setOnScrollChangeListener(null);
if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
setRunningTaskViewShowScreenshot(true);
}
@@ -1113,6 +1115,12 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
false, true, false, false, new ActivityManager.TaskDescription(), 0,
new ComponentName("", ""), false);
taskView.bind(mTmpRunningTask, mOrientationState);
// Measure and layout immediately so that the scroll values is updated instantly
// as the user might be quick-switching
measure(makeMeasureSpec(getMeasuredWidth(), EXACTLY),
makeMeasureSpec(getMeasuredHeight(), EXACTLY));
layout(getLeft(), getTop(), getRight(), getBottom());
}
boolean runningTaskTileHidden = mRunningTaskTileHidden;
@@ -1489,7 +1497,7 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
return true;
}
private void runDismissAnimation(PendingAnimation pendingAnim) {
protected void runDismissAnimation(PendingAnimation pendingAnim) {
AnimatorPlaybackController controller = pendingAnim.createPlaybackController();
controller.dispatchOnStart();
controller.setEndAction(() -> pendingAnim.finish(true, Touch.SWIPE));
@@ -15,15 +15,24 @@
*/
package com.android.quickstep;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.anim.Interpolators.ACCEL_2;
import static com.android.launcher3.anim.Interpolators.INSTANT;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
import static com.android.quickstep.BaseSwipeUpHandlerV2.RECENTS_ATTACH_DURATION;
import static com.android.quickstep.SysUINavigationMode.getMode;
import static com.android.quickstep.SysUINavigationMode.hideShelfInTwoButtonLandscape;
import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM;
import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import android.animation.Animator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Build;
import android.view.MotionEvent;
@@ -35,6 +44,7 @@ import androidx.annotation.UiThread;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StatefulActivity;
@@ -58,11 +68,15 @@ import java.util.function.Predicate;
public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_TYPE>,
ACTIVITY_TYPE extends StatefulActivity<STATE_TYPE>> {
private final PointF mTempPoint = new PointF();
public final boolean rotationSupportedByActivity;
protected BaseActivityInterface(boolean rotationSupportedByActivity) {
private final STATE_TYPE mOverviewState, mBackgroundState;
protected BaseActivityInterface(boolean rotationSupportedByActivity,
STATE_TYPE overviewState, STATE_TYPE backgroundState) {
this.rotationSupportedByActivity = rotationSupportedByActivity;
mOverviewState = overviewState;
mBackgroundState = backgroundState;
}
public void onTransitionCancelled(boolean activityVisible) {
@@ -87,7 +101,7 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
activity.getStateManager().reapplyState();
}
public void onSwipeUpToHomeComplete() { }
public abstract void onSwipeUpToHomeComplete();
public abstract void onAssistantVisibilityChanged(float visibility);
@@ -133,7 +147,7 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
public abstract boolean allowMinimizeSplitScreen();
public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
return true;
return deviceState.isInDeferredGestureRegion(ev);
}
/**
@@ -177,13 +191,6 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
recentsView.switchToScreenshot(thumbnailData, runnable);
}
public void setHintUserWillBeActive() {}
/**
* Sets the expected window size in multi-window mode
*/
public abstract void getMultiWindowSize(Context context, DeviceProfile dp, PointF out);
/**
* Calculates the taskView size for the provided device configuration
*/
@@ -259,7 +266,7 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
/**
* Calculates the modal taskView size for the provided device configuration
*/
public void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect) {
public final void calculateModalTaskSize(Context context, DeviceProfile dp, Rect outRect) {
float paddingHorz = context.getResources().getDimension(dp.isMultiWindowMode
? R.dimen.multi_window_task_card_horz_space
: dp.isVerticalBarLayout()
@@ -273,7 +280,7 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
}
/** Gets the space that the overview actions will take, including margins. */
public float getOverviewActionsHeight(Context context) {
public final float getOverviewActionsHeight(Context context) {
Resources res = context.getResources();
float actionsBottomMargin = 0;
if (getMode(context) == Mode.THREE_BUTTONS) {
@@ -290,8 +297,6 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
public interface AnimationFactory {
default void onRemoteAnimationReceived(RemoteAnimationTargets targets) { }
void createActivityInterface(long transitionLength);
default void onTransitionCancelled() { }
@@ -307,6 +312,97 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
default void setRecentsAttachedToAppWindow(boolean attached, boolean animate) { }
}
class DefaultAnimationFactory implements AnimationFactory {
protected final ACTIVITY_TYPE mActivity;
private final STATE_TYPE mStartState;
private final Consumer<AnimatorPlaybackController> mCallback;
private boolean mIsAttachedToWindow;
DefaultAnimationFactory(Consumer<AnimatorPlaybackController> callback) {
mCallback = callback;
mActivity = getCreatedActivity();
mStartState = mActivity.getStateManager().getState();
}
protected ACTIVITY_TYPE initUI() {
STATE_TYPE resetState = mStartState;
if (mStartState.shouldDisableRestore()) {
resetState = mActivity.getStateManager().getRestState();
}
mActivity.getStateManager().setRestState(resetState);
mActivity.getStateManager().goToState(mBackgroundState, false);
return mActivity;
}
@Override
public void createActivityInterface(long transitionLength) {
PendingAnimation pa = new PendingAnimation(transitionLength * 2);
createBackgroundToOverviewAnim(mActivity, pa);
AnimatorPlaybackController controller = pa.createPlaybackController();
mActivity.getStateManager().setCurrentUserControlledAnimation(controller);
// Since we are changing the start position of the UI, reapply the state, at the end
controller.setEndAction(() -> mActivity.getStateManager().goToState(
controller.getInterpolatedProgress() > 0.5 ? mOverviewState : mBackgroundState,
false));
mCallback.accept(controller);
// Creating the activity controller animation sometimes reapplies the launcher state
// (because we set the animation as the current state animation), so we reapply the
// attached state here as well to ensure recents is shown/hidden appropriately.
if (SysUINavigationMode.getMode(mActivity) == Mode.NO_BUTTON) {
setRecentsAttachedToAppWindow(mIsAttachedToWindow, false);
}
}
@Override
public void onTransitionCancelled() {
mActivity.getStateManager().goToState(mStartState, false /* animate */);
}
@Override
public void setRecentsAttachedToAppWindow(boolean attached, boolean animate) {
if (mIsAttachedToWindow == attached && animate) {
return;
}
mIsAttachedToWindow = attached;
RecentsView recentsView = mActivity.getOverviewPanel();
Animator fadeAnim = mActivity.getStateManager()
.createStateElementAnimation(INDEX_RECENTS_FADE_ANIM, attached ? 1 : 0);
float fromTranslation = attached ? 1 : 0;
float toTranslation = attached ? 0 : 1;
mActivity.getStateManager()
.cancelStateElementAnimation(INDEX_RECENTS_TRANSLATE_X_ANIM);
if (!recentsView.isShown() && animate) {
ADJACENT_PAGE_OFFSET.set(recentsView, fromTranslation);
} else {
fromTranslation = ADJACENT_PAGE_OFFSET.get(recentsView);
}
if (!animate) {
ADJACENT_PAGE_OFFSET.set(recentsView, toTranslation);
} else {
mActivity.getStateManager().createStateElementAnimation(
INDEX_RECENTS_TRANSLATE_X_ANIM,
fromTranslation, toTranslation).start();
}
fadeAnim.setInterpolator(attached ? INSTANT : ACCEL_2);
fadeAnim.setDuration(animate ? RECENTS_ATTACH_DURATION : 0).start();
}
protected void createBackgroundToOverviewAnim(ACTIVITY_TYPE activity, PendingAnimation pa) {
// Scale down recents from being full screen to being in overview.
RecentsView recentsView = activity.getOverviewPanel();
pa.addFloat(recentsView, SCALE_PROPERTY,
recentsView.getMaxScaleForFullScreen(), 1, LINEAR);
pa.addFloat(recentsView, FULLSCREEN_PROGRESS, 1, 0, LINEAR);
}
}
protected static boolean showOverviewActions(Context context) {
return ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context);
}
@@ -554,6 +554,8 @@ public class StateManager<STATE_TYPE extends BaseState<STATE_TYPE>> {
*/
public static class AtomicAnimationFactory<STATE_TYPE> {
protected static final int NEXT_INDEX = 0;
private final Animator[] mStateElementAnimators;
/**