Adding support for swipe and hold to overview from home screen to all-apps

- After the atomic animation ends, overview jumps slightly because the
  normal -> all apps transition puts it at a different position than
  normal -> overview

Bug: 111926330
Change-Id: I6ca359b3ef2fc4d0b6b96229d8bf118bd0db9649
This commit is contained in:
Sunny Goyal 2019-01-28 15:11:36 -08:00
parent 989732be57
commit 8b2b4e24ee
8 changed files with 197 additions and 96 deletions

View File

@ -24,27 +24,39 @@ import android.view.View;
import com.android.launcher3.Launcher; import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherStateManager.StateHandler; import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.TouchController; import com.android.launcher3.util.TouchController;
import com.android.quickstep.OverviewInteractionState;
import java.util.ArrayList;
/** /**
* Provides recents-related {@link UiFactory} logic and classes. * Provides recents-related {@link UiFactory} logic and classes.
*/ */
public final class RecentsUiFactory { public abstract class RecentsUiFactory {
// Scale recents takes before animating in // Scale recents takes before animating in
private static final float RECENTS_PREPARE_SCALE = 1.33f; private static final float RECENTS_PREPARE_SCALE = 1.33f;
private RecentsUiFactory() {} public static TouchController[] createTouchControllers(Launcher launcher) {
ArrayList<TouchController> list = new ArrayList<>();
list.add(launcher.getDragController());
/** if (launcher.getDeviceProfile().isVerticalBarLayout()) {
* Creates and returns a touch controller for swiping recents tasks. list.add(new OverviewToAllAppsTouchController(launcher));
* list.add(new LandscapeEdgeSwipeController(launcher));
* @param launcher the launcher activity } else {
* @return the touch controller for recents tasks boolean allowDragToOverview = OverviewInteractionState.INSTANCE.get(launcher)
*/ .isSwipeUpGestureEnabled();
public static TouchController createTaskSwipeController(Launcher launcher) { list.add(new PortraitStatesTouchController(launcher, allowDragToOverview));
// We leave all input handling to the view itself. }
return null; if (FeatureFlags.PULL_DOWN_STATUS_BAR && Utilities.IS_DEBUG_DEVICE
&& !launcher.getDeviceProfile().isMultiWindowMode
&& !launcher.getDeviceProfile().isVerticalBarLayout()) {
list.add(new StatusBarTouchController(launcher));
}
return list.toArray(new TouchController[list.size()]);
} }
/** /**
@ -62,7 +74,7 @@ public final class RecentsUiFactory {
* *
* @param launcher the launcher activity * @param launcher the launcher activity
*/ */
public static void prepareToShowRecents(Launcher launcher) { public static void prepareToShowOverview(Launcher launcher) {
View overview = launcher.getOverviewPanel(); View overview = launcher.getOverviewPanel();
if (overview.getVisibility() != VISIBLE) { if (overview.getVisibility() != VISIBLE) {
SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE); SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
@ -74,7 +86,7 @@ public final class RecentsUiFactory {
* *
* @param launcher the launcher activity * @param launcher the launcher activity
*/ */
public static void resetRecents(Launcher launcher) {} public static void resetOverview(Launcher launcher) {}
/** /**
* Recents logic that triggers when launcher state changes or launcher activity stops/resumes. * Recents logic that triggers when launcher state changes or launcher activity stops/resumes.

View File

@ -0,0 +1,95 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.uioverrides;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.LauncherStateManager.NON_ATOMIC_COMPONENT;
import android.animation.ValueAnimator;
import com.android.launcher3.Launcher;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
import com.android.quickstep.util.MotionPauseDetector;
import com.android.quickstep.views.RecentsView;
/**
* Touch controller which handles swipe and hold to go to Overview
*/
public class FlingAndHoldTouchController extends PortraitStatesTouchController {
private final MotionPauseDetector mMotionPauseDetector;
public FlingAndHoldTouchController(Launcher l) {
super(l, false /* allowDragToOverview */);
mMotionPauseDetector = new MotionPauseDetector(l);
}
@Override
public void onDragStart(boolean start) {
mMotionPauseDetector.clear();
super.onDragStart(start);
if (mStartState == NORMAL) {
mMotionPauseDetector.setOnMotionPauseListener(isPaused -> {
RecentsView recentsView = mLauncher.getOverviewPanel();
recentsView.setOverviewStateEnabled(isPaused);
maybeUpdateAtomicAnim(NORMAL, OVERVIEW, isPaused ? 1 : 0);
});
}
}
@Override
public boolean onDrag(float displacement) {
mMotionPauseDetector.addPosition(displacement);
return super.onDrag(displacement);
}
@Override
public void onDragEnd(float velocity, boolean fling) {
if (mMotionPauseDetector.isPaused() && mStartState == NORMAL) {
float range = getShiftRange();
long maxAccuracy = (long) (2 * range);
// Let the state manager know that the animation didn't go to the target state,
// but don't cancel ourselves (we already clean up when the animation completes).
Runnable onCancel = mCurrentAnimation.getOnCancelRunnable();
mCurrentAnimation.setOnCancelRunnable(null);
mCurrentAnimation.dispatchOnCancel();
mCurrentAnimation = mLauncher.getStateManager()
.createAnimationToNewWorkspace(OVERVIEW, new AnimatorSetBuilder(), maxAccuracy,
onCancel, NON_ATOMIC_COMPONENT);
final int logAction = fling ? Touch.FLING : Touch.SWIPE;
mCurrentAnimation.setEndAction(() -> onSwipeInteractionCompleted(OVERVIEW, logAction));
ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
maybeUpdateAtomicAnim(NORMAL, OVERVIEW, 1f);
mCurrentAnimation.dispatchOnStartWithVelocity(1, velocity);
// TODO: Find a better duration
anim.setDuration(100);
anim.start();
settleAtomicAnimation(1f, anim.getDuration());
} else {
super.onDragEnd(velocity, fling);
}
}
}

View File

@ -21,32 +21,65 @@ import static android.view.View.VISIBLE;
import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.config.FeatureFlags.SWIPE_HOME;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher; import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager.StateHandler; import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.TouchController; import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.UiThreadHelper;
import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
import com.android.quickstep.OverviewInteractionState;
import com.android.quickstep.views.RecentsView; import com.android.quickstep.views.RecentsView;
import com.android.systemui.shared.system.WindowManagerWrapper;
import java.util.ArrayList;
/** /**
* Provides recents-related {@link UiFactory} logic and classes. * Provides recents-related {@link UiFactory} logic and classes.
*/ */
public final class RecentsUiFactory { public abstract class RecentsUiFactory {
private static final AsyncCommand SET_SHELF_HEIGHT_CMD = (visible, height) ->
WindowManagerWrapper.getInstance().setShelfHeight(visible != 0, height);
// Scale recents takes before animating in // Scale recents takes before animating in
private static final float RECENTS_PREPARE_SCALE = 1.33f; private static final float RECENTS_PREPARE_SCALE = 1.33f;
private RecentsUiFactory() {} public static TouchController[] createTouchControllers(Launcher launcher) {
boolean swipeUpEnabled = OverviewInteractionState.INSTANCE.get(launcher)
.isSwipeUpGestureEnabled();
boolean swipeUpToHome = swipeUpEnabled && SWIPE_HOME.get();
/**
* Creates and returns a touch controller for swiping recents tasks. ArrayList<TouchController> list = new ArrayList<>();
* list.add(launcher.getDragController());
* @param launcher the launcher activity
* @return the touch controller for recents tasks if (swipeUpToHome) {
*/ list.add(new FlingAndHoldTouchController(launcher));
public static TouchController createTaskSwipeController(Launcher launcher) { list.add(new OverviewToAllAppsTouchController(launcher));
return new LauncherTaskViewController(launcher); } else {
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
list.add(new OverviewToAllAppsTouchController(launcher));
list.add(new LandscapeEdgeSwipeController(launcher));
} else {
list.add(new PortraitStatesTouchController(launcher,
swipeUpEnabled /* allowDragToOverview */));
}
}
if (FeatureFlags.PULL_DOWN_STATUS_BAR && Utilities.IS_DEBUG_DEVICE
&& !launcher.getDeviceProfile().isMultiWindowMode
&& !launcher.getDeviceProfile().isVerticalBarLayout()) {
list.add(new StatusBarTouchController(launcher));
}
list.add(new LauncherTaskViewController(launcher));
return list.toArray(new TouchController[list.size()]);
} }
/** /**
@ -64,7 +97,7 @@ public final class RecentsUiFactory {
* *
* @param launcher the launcher activity * @param launcher the launcher activity
*/ */
public static void prepareToShowRecents(Launcher launcher) { public static void prepareToShowOverview(Launcher launcher) {
RecentsView overview = launcher.getOverviewPanel(); RecentsView overview = launcher.getOverviewPanel();
if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) { if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) {
SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE); SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
@ -76,9 +109,8 @@ public final class RecentsUiFactory {
* *
* @param launcher the launcher activity * @param launcher the launcher activity
*/ */
public static void resetRecents(Launcher launcher) { public static void resetOverview(Launcher launcher) {
RecentsView recents = launcher.getOverviewPanel(); launcher.<RecentsView>getOverviewPanel().reset();
recents.reset();
} }
/** /**
@ -88,6 +120,14 @@ public final class RecentsUiFactory {
*/ */
public static void onLauncherStateOrResumeChanged(Launcher launcher) { public static void onLauncherStateOrResumeChanged(Launcher launcher) {
LauncherState state = launcher.getStateManager().getState(); LauncherState state = launcher.getStateManager().getState();
if (!OverviewInteractionState.INSTANCE.get(launcher).swipeGestureInitializing()) {
DeviceProfile profile = launcher.getDeviceProfile();
boolean visible = (state == NORMAL || state == OVERVIEW) && launcher.isUserActive()
&& !profile.isVerticalBarLayout();
UiThreadHelper.runAsyncCommand(launcher, SET_SHELF_HEIGHT_CMD,
visible ? 1 : 0, profile.hotseatBarSizePx);
}
if (state == NORMAL) { if (state == NORMAL) {
launcher.<RecentsView>getOverviewPanel().setSwipeDownShouldLaunchApp(false); launcher.<RecentsView>getOverviewPanel().setSwipeDownShouldLaunchApp(false);
} }

View File

@ -36,7 +36,7 @@ import com.android.quickstep.views.RecentsView;
public class OverviewToAllAppsTouchController extends PortraitStatesTouchController { public class OverviewToAllAppsTouchController extends PortraitStatesTouchController {
public OverviewToAllAppsTouchController(Launcher l) { public OverviewToAllAppsTouchController(Launcher l) {
super(l); super(l, true /* allowDragToOverview */);
} }
@Override @Override

View File

@ -67,14 +67,17 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
private final PortraitOverviewStateTouchHelper mOverviewPortraitStateTouchHelper; private final PortraitOverviewStateTouchHelper mOverviewPortraitStateTouchHelper;
private InterpolatorWrapper mAllAppsInterpolatorWrapper = new InterpolatorWrapper(); private final InterpolatorWrapper mAllAppsInterpolatorWrapper = new InterpolatorWrapper();
private final boolean mAllowDragToOverview;
// If true, we will finish the current animation instantly on second touch. // If true, we will finish the current animation instantly on second touch.
private boolean mFinishFastOnSecondTouch; private boolean mFinishFastOnSecondTouch;
public PortraitStatesTouchController(Launcher l) { public PortraitStatesTouchController(Launcher l, boolean allowDragToOverview) {
super(l, SwipeDetector.VERTICAL); super(l, SwipeDetector.VERTICAL);
mOverviewPortraitStateTouchHelper = new PortraitOverviewStateTouchHelper(l); mOverviewPortraitStateTouchHelper = new PortraitOverviewStateTouchHelper(l);
mAllowDragToOverview = allowDragToOverview;
} }
@Override @Override
@ -128,7 +131,8 @@ public class PortraitStatesTouchController extends AbstractStateChangeTouchContr
} else if (fromState == OVERVIEW) { } else if (fromState == OVERVIEW) {
return isDragTowardPositive ? ALL_APPS : NORMAL; return isDragTowardPositive ? ALL_APPS : NORMAL;
} else if (fromState == NORMAL && isDragTowardPositive) { } else if (fromState == NORMAL && isDragTowardPositive) {
return TouchInteractionService.isConnected() ? OVERVIEW : ALL_APPS; return mAllowDragToOverview && TouchInteractionService.isConnected()
? OVERVIEW : ALL_APPS;
} }
return fromState; return fromState;
} }

View File

@ -35,68 +35,32 @@ import android.os.CancellationSignal;
import android.util.Base64; import android.util.Base64;
import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher; import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState; import com.android.launcher3.LauncherState;
import com.android.launcher3.LauncherStateManager; import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.LauncherStateManager.StateHandler; import com.android.launcher3.LauncherStateManager.StateHandler;
import com.android.launcher3.QuickstepAppTransitionManagerImpl; import com.android.launcher3.QuickstepAppTransitionManagerImpl;
import com.android.launcher3.Utilities; import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.UiThreadHelper;
import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
import com.android.quickstep.OverviewInteractionState; import com.android.quickstep.OverviewInteractionState;
import com.android.quickstep.RecentsModel; import com.android.quickstep.RecentsModel;
import com.android.quickstep.util.RemoteFadeOutAnimationListener; import com.android.quickstep.util.RemoteFadeOutAnimationListener;
import com.android.systemui.shared.system.ActivityCompat; import com.android.systemui.shared.system.ActivityCompat;
import com.android.systemui.shared.system.WindowManagerWrapper;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.zip.Deflater; import java.util.zip.Deflater;
public class UiFactory { public class UiFactory extends RecentsUiFactory {
private static final AsyncCommand SET_SHELF_HEIGHT_CMD = (visible, height) ->
WindowManagerWrapper.getInstance().setShelfHeight(visible != 0, height);
public static TouchController[] createTouchControllers(Launcher launcher) {
boolean swipeUpEnabled = OverviewInteractionState.INSTANCE.get(launcher)
.isSwipeUpGestureEnabled();
ArrayList<TouchController> list = new ArrayList<>();
list.add(launcher.getDragController());
if (!swipeUpEnabled || launcher.getDeviceProfile().isVerticalBarLayout()) {
list.add(new OverviewToAllAppsTouchController(launcher));
}
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
list.add(new LandscapeEdgeSwipeController(launcher));
} else {
list.add(new PortraitStatesTouchController(launcher));
}
if (FeatureFlags.PULL_DOWN_STATUS_BAR && Utilities.IS_DEBUG_DEVICE
&& !launcher.getDeviceProfile().isMultiWindowMode
&& !launcher.getDeviceProfile().isVerticalBarLayout()) {
list.add(new StatusBarTouchController(launcher));
}
TouchController taskSwipeController =
RecentsUiFactory.createTaskSwipeController(launcher);
if (taskSwipeController != null) {
list.add(taskSwipeController);
}
return list.toArray(new TouchController[list.size()]);
}
public static void setOnTouchControllersChangedListener(Context context, Runnable listener) { public static void setOnTouchControllersChangedListener(Context context, Runnable listener) {
OverviewInteractionState.INSTANCE.get(context).setOnSwipeUpSettingChangedListener(listener); OverviewInteractionState.INSTANCE.get(context).setOnSwipeUpSettingChangedListener(listener);
} }
public static StateHandler[] getStateHandler(Launcher launcher) { public static StateHandler[] getStateHandler(Launcher launcher) {
return new StateHandler[] {launcher.getAllAppsController(), launcher.getWorkspace(), return new StateHandler[] {
RecentsUiFactory.createRecentsViewStateController(launcher), launcher.getAllAppsController(),
launcher.getWorkspace(),
createRecentsViewStateController(launcher),
new BackButtonAlphaHandler(launcher)}; new BackButtonAlphaHandler(launcher)};
} }
@ -116,10 +80,6 @@ public class UiFactory {
.setBackButtonAlpha(shouldBackButtonBeHidden ? 0 : 1, true /* animate */); .setBackButtonAlpha(shouldBackButtonBeHidden ? 0 : 1, true /* animate */);
} }
public static void resetOverview(Launcher launcher) {
RecentsUiFactory.resetRecents(launcher);
}
public static void onCreate(Launcher launcher) { public static void onCreate(Launcher launcher) {
if (!launcher.getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)) { if (!launcher.getSharedPrefs().getBoolean(HOME_BOUNCE_SEEN, false)) {
launcher.getStateManager().addStateListener(new LauncherStateManager.StateListener() { launcher.getStateManager().addStateListener(new LauncherStateManager.StateListener() {
@ -171,19 +131,6 @@ public class UiFactory {
.getHighResLoadingState().setVisible(true); .getHighResLoadingState().setVisible(true);
} }
public static void onLauncherStateOrResumeChanged(Launcher launcher) {
LauncherState state = launcher.getStateManager().getState();
if (!OverviewInteractionState.INSTANCE.get(launcher).swipeGestureInitializing()) {
DeviceProfile profile = launcher.getDeviceProfile();
boolean visible = (state == NORMAL || state == OVERVIEW) && launcher.isUserActive()
&& !profile.isVerticalBarLayout();
UiThreadHelper.runAsyncCommand(launcher, SET_SHELF_HEIGHT_CMD,
visible ? 1 : 0, profile.hotseatBarSizePx);
}
RecentsUiFactory.onLauncherStateOrResumeChanged(launcher);
}
public static void onTrimMemory(Context context, int level) { public static void onTrimMemory(Context context, int level) {
RecentsModel model = RecentsModel.INSTANCE.get(context); RecentsModel model = RecentsModel.INSTANCE.get(context);
if (model != null) { if (model != null) {
@ -233,8 +180,4 @@ public class UiFactory {
out.toByteArray(), Base64.NO_WRAP | Base64.NO_PADDING)); out.toByteArray(), Base64.NO_WRAP | Base64.NO_PADDING));
return true; return true;
} }
public static void prepareToShowOverview(Launcher launcher) {
RecentsUiFactory.prepareToShowRecents(launcher);
}
} }

View File

@ -135,6 +135,10 @@ public class MotionPauseDetector {
mIsPaused = mHasEverBeenPaused = false; mIsPaused = mHasEverBeenPaused = false;
} }
public boolean isPaused() {
return mIsPaused;
}
public interface OnMotionPauseListener { public interface OnMotionPauseListener {
void onMotionPauseChanged(boolean isPaused); void onMotionPauseChanged(boolean isPaused);
} }

View File

@ -46,7 +46,6 @@ import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder; import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.compat.AccessibilityManagerCompat; import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.userevent.nano.LauncherLogProto; import com.android.launcher3.userevent.nano.LauncherLogProto;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction; 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.Action.Touch;
@ -298,7 +297,7 @@ public abstract class AbstractStateChangeTouchController
* When going between normal and overview states, see if we passed the overview threshold and * When going between normal and overview states, see if we passed the overview threshold and
* play the appropriate atomic animation if so. * play the appropriate atomic animation if so.
*/ */
private void maybeUpdateAtomicAnim(LauncherState fromState, LauncherState toState, protected void maybeUpdateAtomicAnim(LauncherState fromState, LauncherState toState,
float progress) { float progress) {
if (!goingBetweenNormalAndOverview(fromState, toState)) { if (!goingBetweenNormalAndOverview(fromState, toState)) {
return; return;
@ -435,7 +434,11 @@ public abstract class AbstractStateChangeTouchController
mLauncher.getAppsView().addSpringFromFlingUpdateListener(anim, velocity); mLauncher.getAppsView().addSpringFromFlingUpdateListener(anim, velocity);
} }
anim.start(); anim.start();
mAtomicAnimAutoPlayInfo = new AutoPlayAtomicAnimationInfo(endProgress, anim.getDuration()); settleAtomicAnimation(endProgress, anim.getDuration());
}
protected void settleAtomicAnimation(float endProgress, long duration) {
mAtomicAnimAutoPlayInfo = new AutoPlayAtomicAnimationInfo(endProgress, duration);
maybeAutoPlayAtomicComponentsAnim(); maybeAutoPlayAtomicComponentsAnim();
} }